Code Craft Preface Study cards

Code Craft Preface


Good programming stems from your {{c1::attitude}}.

If you don’t write clear, understandable, defensive, easily testable, easily maintainable software, then you’ll be distracted by tedious code-related problems when you should be preparing for what the software factory will throw at you next. 
According to Code Craft, good programming stems from what?
According to Code Craft, what will happen if you do not write clear, defensive, easily testable, and easily maintainable software?
Good programming stems from your [...].
Code Craft 1. Defensive Programming Study cards

Defensive Programming


{{c1::Legacy code}} is archaic programs written by code monkeys that are now long gone.

Defensive programming is an informal set of guidelines that can prevent many different coding problems. Defensive programming techniques are easier to apply when writing code the first time but should be used all the time.

The first thing to do to program defensively is do not make any assumptions. Do not assume that something will not happen. Remember Murphy's Law. If you make assumptions, as the code base evolves and time passes, you will forget what assumptions you had made. Assume nothing. Smash unwritten assumptions by explicitly checking for them in the code. Do not get into a cycle of tinker -> run -> crash -> tinker -> run until it works.

Three things that some people might believe are {{c1::defensive programming}}, but are really not, are {{c2::error handling}}, {{c3::testing}}, and {{c4::debugging}}. Error checking is a part of writing correct code. Testing is also a normal part of development. Defensive programming prevents problems. Debugging is the act of removing bugs after they have already been introduced.

Defensive programming also avoids a number of security problems. Sloppy code can be exploited to gain control of an application which can have huge implications.

Adopt a good coding style. Choose meaningful variable names, use parentheses judiciously. These increase clarity and reduce the probability of faults slipping through. Also consider the larger-scale design before rushing into the code.

Always think carefully about what you're typing as you type it. A common mistake is mistyping == as =.

Always do all of the tasks involved in completing a code section before moving on. Every time you do not do the right thing now, you become more likely to continue not doing the right thing in the future. Do it now!

Treat all inputs with suspicion.

Write code for clarity, not brevity. It may be up to a junior to maintain your code one day. Unusual language tricks may prove your knowledge but do not help maintainability. Keep it simple. Code that cannot be maintained is not safe. Never make code more complex than necessary.

Prevent access to internal class data by keeping it private in object-oriented languages (encapsulation). Keep variables in the tightest scope possible.

Compile with all warnings switched on. When warnings are generated, fix them immediately. Never be satisfied with code that doesn't compile quietly when warnings are enabled. They are there for a reason.

Use static analysis tools. Static analysis will prevent the compiler warnings before the code is run and pick up many more errors than your compiler alone.

Use safe data structures. The most common security vulnerability may result from buffer overrun. This is caused by the careless use of fixed-size data structures. Code that writes into a buffer without checking its size first allows for the possibility of writing past the end of the buffer. This is frighteningly easy to do with a small snippet of C code. This can lead to data corruption. It can also allow a malicious to put executable code on the program stack and run an arbitrary program.

Handle memory and other precious resources carefully.
Release any resources that you acquire during execution.

Initialize variables at their points of declaration. Declare variables as late as possible.

Use standard language facilities.
Be clear about what language version you are using. Do not rely on compiler weirdness or any nonstandard extensions to the language. Do not rely on undefined behavior. This will lead to very subtle bugs when the compiler is updated.

Be careful when casting data from one type to another.
Consider what may happen when casting a 64-bit integer into a 8-bit type. What happens to 56 of the bits? Be extremely careful with C and C++ here because they are vague about the precision of data types--you cannot make assumptions about data type interchangeability.

When using switch statements, if reaching the default case is an error, make that clear. If it is allowed and nothing is supposed to happen, make that clear.

Make everything as const as you can.
This acts as code documentation and allows the compiler to spot certain mistakes.

Physically incorporate any assumptions you make into the program. This codifies the constraints on program functionality and behavior. Preconditions are conditions that must hold true before a section of code is entered. Postconditions must hold true after a code block is left. Invariants are conditions that hold true every time the program's execution reaches a particular point. Assertions are any other statement about a program's state at a given point in time. Good constraints make your program clearer and more maintainable. This technique is also known as design by contract.

Summary


Craft code that is correct and also good. Document any assumptions made. It will be easier to maintain and have less bugs. 

Defensive programming is about expecting the worst and being prepared for it. Codified constraints can make your software far more robust. Like other good practices, by spending a little extra time early on, more time can be saved later. This can save the entire project.

Good programmers care (attitude is important). They make sure any assumption is explicitly captured. They ensure well-defined behavior for garbage input. They think carefully about the code as they write it. They write code that protects itself.

Bad programmers ignore what can go wrong. They are careless and hope others will figure it out. They do not document assumptions and then forget them themselves. They do not apply much thought to the code they are writing. This results in unpredictable and unreliable software.
What is the definition of legacy code given by Code Craft?
What does Code Craft call an informal set of guidelines that is a practical way to prevent many coding problems?
What summarizes the Code Craft stance on making assumptions in programming?
When is the easiest time to use defensive programming techniques?
When should defensive programming techniques be deployed?
What is the first rule of defensive programming according to Code Craft?
What are two reasons you shouldn't make assumptions according to Code Craft?
How does Code Craft recommend you smash unwritten assumptions?
What do good programmers also do when they make assumptions according to Code Craft?
What are three things that some people mistakenly believe are defensive programming techniques?
How is defensive programming different from debugging?
What happens when you do not do the right thing right away according to Code Craft?
How should all inputs be treated according to Code Craft?
What do good programmers do with garbage input according to Code Craft?
What scope should variables be held in according to Code Craft?
Where should variables be declared according to Code Craft?
When should variables be initialized according to Code Craft?
What does Code Craft claim causes the most common security vulnerability?
What does Code Craft say to do when you see warnings being generated?
Why does Code Craft recommend using parentheses judiciously?
Defensive programming is a practice where time invested in the beginning saves time later, and it can even do what?
What does Code Craft give as a common mistake that happens when you do not think carefully about the code you are typing as you are typing it?
What happens when programmers are careless, do not apply much thought to the code they write, and do not document assumptions?
What question does Code Craft state regarding casting a 64-bit integer into an 8-bit type?
Code Craft describes what kind of programming as ensuring well-defined behavior for garbage input, making sure any assumptions are codified or explicitly captured, and writing code that protects itself against the worst that could happen?
[...] is archaic programs written by code monkeys that are now long gone.
Three things that some people might believe are [...], but are really not, are [...], [...], and [...].
Code Craft 2. The Layout and Presentation of Source Code Study cards

Layout of source code


The most obvious and perhaps contentious issue of code presentation concerns brace positioning. Brace is a common name for the curly bracket.

The compiler may completely ignore the code presentation format but that does not mean it is not important. Presentation can illuminate and support your code's structure and help the reader understand what is going on. It can also do the opposite and make it more difficult for the reader. It can also hide bugs.

The three audiences for source code are ourselves, the compiler, and others. The most important audience to consider is others.

The indentation strategy should be consistent across the project. Individual presentation rules such as positioning of braces and number of spaces of indent should always be the same.

Use an indentation strategy that is conventional and can be described concisely.

K&R style of brace positioning was established in The C Programming Language and may be the dominant style for Java code.

int k_and_r() {
    int a = 0;
    return a;
}

Pick a good coding style and stick to it. Unless your team already has a coding standard, in which case use it. There are advantages to the company adopting the same style--less noise in version control where one person is changing the style, less time spent understanding the layout when making a change, etc.

Summary

Presentation is one of the things that differentiates good code from bad code. It is an important skill to lay out code for maximum clarity within the guidelines of the company coding style being followed. It is a reasonable thing to assume that carefully laid out code has been carefully designed. It is even more reasonable to assume that sloppily laid out code was not carefully designed.

Good programmers avoid pointless arguments, are sensitive to others' opinions, and are humble enough to know they are not always right. They understand that code layout can impact readability and try to make the code as clear as possible. They adopt the house style even if it contradicts their preferences, for the greater good.

Bad programmers are closed-minded and argue over the most trivial things. They have no consistent personal coding style and trample over others' code in their own style.
According to Code Craft, what is the most obvious, and contentious, issue of code presentation?
What is a common name for the curly bracket?
Is the format of code not important since the compiler completely ignores it?
What are the three audiences for source code?
Who is the most important audience for the source code, according to Code Craft?
What is the Code Craft recommendation for what indentation strategy to use?
What is the name for the style of brace positioning that was established in The C Programming Language and may be the dominant style for Java code?
What is a reasonable thing to assume about sloppily laid out code?
Code Craft 3. Meaningful Names Study cards

What's in a name?


It is not true that names will never hurt you. Writing source code is all about clear communication. Good names convey information about the structure of the code. They are an essential tool to aid comprehensibility and maintainability. It is crucial to name things well. Name things well the first time, all the time.

You can only reason about 7 pieces of information concurrently (the Miller number). An object's name should describe it clearly.

Keep in mind that bad names may be due to a poor understanding. The key to good naming may be to understand exactly what you are naming.

If you can't come up with a good name, it may be worth considering if the design needs to be changed.

Clear names should also follow the language's idioms.

Favor clarity over brevity when naming. One letter variable names may be used as loop counters.

Do not use silly names. They make the reader doubt the ability of the original author. Do not use variable names like blah, wibble, foo, or bar. They may seem amusing at first but can create confusion later. Foo and bar should never be used in production code.

Hungarian notation is a controversial naming convention (with many subtly different and slightly incompatible dialects) that was popularized by Microsoft's Win32 APIs.

A variable is like something that you could hold in your hand--it should probably have a noun name. A function is more akin to an action and should probably have a name which is a verb.

Meaningful function names avoid the words be, do, and perform. These are traps that add noise and not value. Functions should be named from the viewpoint of the user and hide the implementation details. They should be named from an external viewpoint. Describe the logical operation, not the implementation.

Classes may have less rigid naming heuristics. Some may have noun names and other may be verbs. If the class seems like it should have a combination of a noun name and verb name, then it may be designed badly.

Avoid redundant words in names. Do not put the word class, data, object, or type in the name of a class.

In C and C++, macros should always be capitalized and nothing else should ever be capitalized.

File naming


Filenames are also important (some languages have restrictions on these, and others have none at all). The code should be split into the maximum number of files that you can. This will make them easier to name and reduce coupling.

The easiest way to avoid problems with how different operating systems treat capitalization differently in filenames is to mandate that all filenames be lowercase. Never exploit the fact that your filesystem considers foo.h and Foo.h to be different.

Bad file naming will make finding particular bits of code a nightmare.

The main principles when it comes to naming:
  • be consistent
  • exploit context
  • use names to your advantage.

Being consistent may be the most important naming principle.

Exploiting context means deleting the superfluous bits that duplicate contextual information. The name will only ever be read in the context it's in. Put things in the smallest scope that you can. A name need not restate the type (this is why Hungarian Notation is an often derided convention).

Do not name cryptically. Avoid terse names but also do not be overly verbose. Make your names accurate. Do not misspell words as this can open a minefield of confusion. Do not name things in a way that is ambiguous or vague. Sexy little abbreviations like i18n should be avoided.

Summary

 
Good programmers know the importance of names. They think about naming and choose appropriate names. They consider all of the forces that must be balanced: length, clarity, context, etc. They see the bigger picture so that the names hold together across the project.

Bad programmers care little about clarity. They produce write-once code that is quick to write and poorly thought out. They ignore idioms, are inconsistent in naming, and do not think holistically about how their piece of code fits into the whole.
Is it true that names will never hurt you?
Without uncertainty, what is Miller's number?
What are two groups of six words that should be avoided in naming, three of them specifically because they are redundant?
What are two groups of six words that should be avoided in naming, three of them specifically because they add noise and do not add value?
What is the most important naming principle according to Code Craft?
What does Code Craft say about splitting the code into files?
What kind of word should the name of a function be?
What kind of word should the name of a variable be?
What was one of the things that made Hungarian notation popular?
What might you need to do if you cannot come up with a good name for something in code?
What does Code Craft call i18n?
What is a common cause of bad names, as pointed out by Code Craft?
When should you name things well in programming?
Code Craft 4. "Self-Documenting" Code Study cards

Self-documenting code


Good code is well-documented.

Code must communicate clear sets of instructions not only to the computer but to the poor fools who have to maintain it later.

Supporting documents take time to write and maintain and must be kept up to date with code changes. They can become inaccurate and misleading over time. It can be hard to find the relevant thing you need. Therefore you should not write code that needs to be propped up by external documentation. The code should read clearly on its own.

The only document that describes the code completely and correctly is the code itself. It may be the only documentation available. You should do everything you can to make it good documentation. Self-documenting code is code that is easy to read. Therefore, write your code to be read by humans.

Clear code that does not need comments is best. Avoid comments by writing clear code that does not need comments.

Single Entry, Single Exit (SESE) code is a commonly known idea that a function should have one and only one exit point. This is too restrictive for readable code and leads to deep levels of nesting.

Be wary of optimizing code so that it's no longer a clear expression of a basic algorithm. Only do this if you have proved it is a bottleneck to acceptable program function.

A function should only have one action. Minimize surprising side effects which require extra documentation. Keep the functions short. Avoid magic numbers. Use well-named constants instead. Public information in a class should come first. Hide nonessential information. Do not hide important code. Limit the number of nested conditional statement to avoid hiding important conditions.

Present related information in the same place. Use file headers (a comment block at the top of each source file).

Clear code also contains an appropriate amount of commenting. Only add comments if you can't improve the clarity of the code in any other way.

Literate Programming


Donald Knuth conceived an extreme self-documenting code technique called literate programming and he also wrote a book by this name to describe it. The idea is that you do not write a program. Instead, you write a document. The document compiles into the program. Some people were really against this idea and thought it was an unfortunate turn in his career, but there are some good ideas from it to consider. It places emphasis on writing documentation. It makes you think about the code in a different way. You are more likely to update the documentation since it's situated nearby. You will always see the correct version of the code you're working on. It encourages the inclusion of items not normally found in source code comments such as algorithm descriptions, proofs of correctness, and justification for design decisions.

It has drawbacks: it is hard to write. Extra compilation steps are required and good tool support is lacking. You may document code that does not need it and add noise, making missing what is important more likely. Altering the documentation is not possible without changing the source code which can be an issue when you need to update the documentation of already released software.

Javadoc and similar tools


There are many programming tools that are about halfway between literate programming and external specifications. These are tools which generate documentation from the source code by pulling out specifall formatted comments. This has become popular since Sun introduced Javadoc which generates all of the Java API documentation.

This is an excellent approach to documentation. It is easy to keep documentation up to date. It's natural and does not require much extra effort. However, it is really only useful for API documentation, not internal code documentation.

Remember that you can still write bad documentation with this. Do not document every last detail if it adds no value such as variables with names that it is already obvious what they are. Also document any function pre- or post- conditions, any exceptions that may be thrown, and any of a function's side effects.

Summary


The main thing to remember is we code primarily to communicate. The code is the only documentation that will always be there so making it self-documenting. Literate programming is an extreme way to write self-documenting code, and a less extreme way is to use documentation tools that generate API documentation.

Good programmers seek to write clear, self-documenting code with the least amount of extra documentation necessary. They are thinking of the needs of the programmers who will maintain their code.

Bad programmers write unfathomable sphagetti and are proud of it. They avoid writing any documentation and don't care about updating it. They want the code that was hard for them to write to be hard for anyone else to understand.
What is the only document that describes a program completely and correctly?
What does Code Craft say is the main quality of code that can make it self-documenting?
When does Code Craft recommend adding comments to code?
What was the self-documenting code technique conceived by Donald Knuth that involves writing a document that compiles into the program?
What is the tool that popularized generating documentation from source code (and now there are many similar tools that sit about halfway between literate programming and external specifications)?
What is the Code Craft stance on documentation extracted from specifically formatted comments in the source code?
What idea which was not popular (but has some good points to consider) essentially took self-documenting code to the extreme?
What is the main reason to make code as self-documenting as possible according to Code Craft?
Code Craft 5. Comments Study cards

Comments


With truly good code, comments are icing on the cake.

A comment is a block of source that the compiler will ignore.

When it comes to comments, quality is more important than quantity. Too much commenting adds noise that hides important information. Document as much as possible in the code itself. Well written code may not need comments at all. The fewer comments you write, the less chance you have of writing bad comments.

Bad comments are worse than no comments because they can misinform and mislead the reader.

Comments should not describe how the program works. That is the code. Focus on the why something is written the way it is.

Do not add comments which describe the code. It violates DRY to do so. Don't duplicate code in a comment.

Do not add comments that express something that could be enforced by the language itself.

Comments can be good to document a bit of code that is unusual, unexpected, or surprising. Do not put lies in the comments. Do not put cryptic comments in or jokes. Make the comments clear and readable. Don't type without using your brain.

Do not have comments that document how things used to be. We have version control for that. Do not knock out code by enclosing it in comments. Do not be clever and add ASCII art with comments.

Commenting should be done in a consistent way.

Comments are usually written above the code that they describe.

Be aware of comment rot-inane comments indicate you must be suspicious of the quality of the design you are looking at so unless you are refactoring everything, leave them as a warning. 

Summary


Good programmers try to write a few good comments explaining why and helpful comments that make sense. They concentrate on writing good code rather than a bunch of comments.

Bad programmers cannot tell the difference between good and bad comments. They write comments explaining how. They don't mind if comments only make sense to themselves. They bolster bad code with many comments. They fill their source files with redundant information.
What are icing on the cake when you have truly good code?
A [...] is a block of source that the [...] will ignore.
What can be good to document unusual, unexpected, or surprising bits of code?
Should you ever use comments to express something that could be enforced by the language itself?
What type of programmers cannot tell the difference between good and bad comments, write comments explaining how stuff works, bolster code with many bad comments, and fill their source files with redundant code information?
What type of programmers write a few good comments explaining why and helpful comments that make sense (and concentrate writing good code rather than a bunch of comments)?
What to do with inane comments you find?
Where should a comment be written?
What principle is violated when comments are written that simply describe the code (describing how, not why)?
Which is worse: bad comments or no comments?
What should comments describe according to Code Craft?
Does well written code always need comments?
What does too much commenting do?
What is a comment?
Code Craft 6. Error Conditions in Code Study cards

Error Conditions in Code


You must handle all error conditions in your code. Error handling must be taken seriously.

Bugs occur when the code does not handle the error condition. Error conditions are distinct from bugs because they can and always will occur. The database file you want to write to could have been deleted, the disk could fill up, or a web service may not be available.

An error may fall into three categories: user error, programmer error, and exceptional circumstance.

A good program will handle a user error by pointing out the mistake and helping the user rectify it.

Programmer errors should ideally never occur. The user can do nothing about this. Defensive programming is important to stop the cycle of unhandled errors causing further error conditions.

Exceptional circumstances include things like the network connection failing or the hard disk running out of space.

Errors are raised by subordinate components and communicated upward to be dealt with by the caller. In general, to take control of program execution, we need to have the subordinate components raise an error when something goes wrong. We need the caller to detect all possible errors and handle them appropriately, and if it cannot handle them, propagate the error upward to be handled by a different caller.

Locality of error


An error is local in time if it is discovered soon after it is created. An error is local in space if it is identified very close to the site where it manifests.

Never simply ignore an error condition. If you do not know how to handle the problem, then signal a failure back up to the calling code.

A simple mechanism is to return a success/failure value from your function. A more advanced approach would be to enumerate all of the possible statuses and return a corresponding reason code. One value means success and the others are abortive cases. This can get messy for functions that need to return data. A different approach may be set a global variable which can be inspected to see if things worked. This can cause confusion and be a source of bugs.

Exceptions are a language facility for managing errors. When your code encounters a program that it cannot handle, it throws an exception. The run time then steps back up the call stack until it finds some exception-handling code. What happens after the exception is handled may vary and there are two operational models: the termination model and the resumption model.

Resilient code must be exception safe. There are different levels of exception safety: basic guarantee (if an exception occurs, it will not leak resources), strong guarantee (no object is altered), nothrow guarantee (an operation can never throw an exception).

Signals are a more extreme reporting mechanism usually for errors sent by the execution environment. The operating system traps a number of exceptional events and delivers them to the application. The program could receive a signal at any time and must be able to cope with it.

Never ignore any errors that might be reported to you.

Two schools of thought for handling errors are to handle them ASAP or as late as possible. The ASAP approach is a self-documenting code technique.

The best way may to handle any error in the most appropriate context: as soon as you know enough about it to deal with it correctly.

Logging is one reaction to any error. Any reasonably large project should be employing a logging facility. It allows collecting important trace information and investigation of nasty problems. The log exists to record interesting events. All errors should be detailed because they are some of the most interesting and telling events.

For problems that only the user can fix, the problem should be reported immediately to the user so that they can resolve the situation. This does not apply to a deeply embedded system, e.g. a dialog box is not going to pop up on a washing machine.

Ignoring errors does not save time. This may be the major cause of bugs in software packages. Far longer will be spent working out the cause of bad program behavior later.

When propagating an error upward, there are two ways to do it. You might export the same information you were fed, or reinterpret the information and send a more meaningful message to the next level up.

Summary


Good programmers are thorough and write the error-handling code as they write the main code. Bad programmers take a haphazard approach to writing code, don't think about or review what they are doing, and ignore errors that arise. Bad programmers end up conducting length debugging sessions to track down crashes due to not considering error conditions in the first place.
What are the three categories of errors according to Code Craft?
What will a good program do in response to a user error?
What programming technique helps stop the cycle of unhandled errors causing further error conditions?
What kind of errors should ideally never occur?
When something goes wrong in a subordinate component, what should it do?
What should a caller do if it cannot handle an error propagated upward by a subordinate component?
What does it mean for an error to be local in time?
What does it mean for an error to be local in space?
What is an effect on the code by handling errors ASAP?
When a caller cannot handle an error propagated to it and needs to propagate it up, what are the two ways it can do that?
What is the part of the code that handles errors?
What do bad programmers end up doing when they do not consider error conditions in the first place?
Code Craft 9. Debugging Study cards

Debugging


Programs are written by humans, and humans make mistakes. Software errors are not entirely inevitable. The main tools we have to prevent them are defensive programming and testing.

Software bugs fall into a few broad categories, and understanding these helps us reason about them. The difficulty of finding a bug is related to its category. The three classes of bugs are failure to compile, run-time crash, and unexpected behavior. The longer it takes to detect a fault, the more cost it is to fix them.

Failures to compile are the best type of error you can get and usually is due to a syntactic mistake or simple oversight.

Run-time crashes occur when the executable is run. These are much harder to deal with than compilation errors but still relatively simple.

Unexpected behavior is the nastiest type of bug. It will usually be a minute logic problem deep in sode that executed half an hour ago.

Run-time errors can be sub-grouped into syntactic errors, build errors, basic semantic bugs, and semantic bugs.

Syntactic errors are usually caught by the compiler at build time, but sometimes language grammar errors can get through undetected. This may be mistaking a == for a =, a && for a &, forgetting a semicolon, etc. The best way to avoid these is compile with all warnings switched on.

Build errors may only manifest themselves at run time. Always distrust your build system, no matter how good you think it is. This can take time to figure out, so if you are in doubt, do a total cleanout of your project and rebuild it from scratch.

Basic semantic bugs are the majority of run-time faults and are simple errors causing incorrect behavior. Using an uninitialized variable will cause the program's behavior to depend on the garbage value in the memory location used by the variable. Other common basic semantic faults are comparing floating-point variables for equality, calculations that do not handle numerical overflow, rounding errors, and other type errors. Often this kind of semantic fault can be caught with static code analysis.

Semantic bugs are insidious errors that won't be caught by inspection tools. These can be low-levelerrors like using the wrong variable, not validating input, or an incorrect loop, and they can be high-level like calling an API incorrectly. The best of these are the repeatable ones. The worse are ones that cause memory corruptions.

The semantic bugs can then be further divided into subcategories: segmentation faults, memory overruns, memory leaks, running out of memory, math errors, and program hangs.

Segmentation faults (also known as protection faults) are caused when memory locations not allocated for the program's use are accessed. This is far too easy to do in C. A common C typo causing a segfault is:

scanf("%d", number);

The missing & before number causes scanf to try to write into the memory location referenced by the (garbage) contents of number.

Memory overruns are caused by writing past memory that has been allocated for your data structure. In an unprotected operating system, this may even tamper with data from another process or the OS itself. Use safe data structures whenever possible to avoid these errors.

Memory leaks are constant threats in languages that do not have garbage collection. Anything that you manually require must be manually released.

Program hangs are usually caused by bad program logic. Infinite loops are the most common. Deadlock and race conditions can also occur in threaded code, and event-driven code can end up waiting on events that will never occur.

It has been found that some programmers introduce far fewer faults (60 percent less), find and fix faults quicker (in 35 percent of the time), and introduce fewer faults as they do. The key to doing so is paying attention on a microscopic level of the code you write while also keeping the bigger picture in mind. The single most important rule (the golden rule of debugging) when debugging is this: use your brain.

If you have to debug some code, the first thing you should do is learn about it first. You can't expect to find errors in code that you don't understand.

Compile-time errors result in the compiler spitting out a lot of error messages: look at the first one, which should be trusted far more than the subsequent messages. Sometimes the syntax error is on the preceding line that the compiler reports.

Run-time errors must be found methodically and finding the bug is a process of confirming what you think is correct until you find the place where that condition doesn't hold. Figuring out how to reproduce it reliably is the first step. When locating the fault, a good place to start is where the error manifests itself. Divide and conquer is a good strategy to locate it. A dry run is another technique, where you would play the role of the computer, trace program execution and compare your result with reality. Once you think you find the cause, investigate it thoroughly to prove that you are right. Next, write a test case to demonstrate the failure. Now, the easy part: fix the bug.

If when trying all of this doesn't work, try explaining the problem to someone else.

Convince yourself that you have really found the root cause of the problem and are not just hiding a symptom. When you fix the bug, check to see if the same mistake is lurking in related sections of code. Think about the lessons learned from each fault. How could it have been prevented? How could it have been discovered more quickly?

The most important rule to follow to not introduce bugs: use you brain.
What are the two main tools we have to prevent software errors according to Code Craft?
What are the three classes of bugs according to Code Craft?
What is the nastiest type of bug according to Code Craft?
What is the most common type of program hang according to Code Craft?
What type of variable, if you use it in a program, will cause the program's behavior to depend on the garbage value in the variable's memory location?
What is the first step when debugging some code?
What error subtype of run-time crashes are usually caught by the compiler, but if they are not, are usually language grammar errors?
What is a common syntactic error that is a language grammar error that might happen in a conditional?
What error subtype of run-time crash would include a Rails app using a version of a gem that was installed from local development rather than the actual released version of the gem?
An example of a basic semantic bug is using an uninitialized variable; what will the behavior of the program depend on in this case?
A common basic semantic fault related to floating point numbers is?
What subtype of semantic bug is caused when memory locations not allocated for the program's use are accessed?
What type of semantic bug is when memory is written past what has been allocated for a data structure?
What type of semantic bug includes infinite loops, deadlock and race conditions, and event-driven code waiting for an event that will never occur?
What is the key to find and fix faults quicker and also introduce less new faults as you do?
What is the golden rule of debugging according to Code Craft?
When a compile-time error results in the compiler spitting out a lot of error messages, usually the most important one to look at is:
What technique to investigate a run-time error has you play the role of the computer, trace program execution and compare your result with reality?
Code Craft 13. Grand Designs Study cards
 C.A.R. Hoare wrote, “There are two ways of constructing a software design: One way is to make it so simple that there are obviously no deficiencies, and the other way is to make it so complicated that there are no obvious deficiencies. The first method is far more difficult.” 

It is a popular belief that "design" is a stage you complete before moving on to writing code. The truth is that the act of writing code is a design activity. Practitioners of Extreme Programming advocate that design is the code. Writing code tests before any code as a design verification tool is a wise idea (test-driven design). This does not mean you shouldn't think (design) before you start typing.

The most important design act is the architectural design. This looks at the system as a whole, identifies the main subsystems, and works out how they communicate. This design has the most influence on the performance and characteristics of the system as a whole and the least impact on specific lines of code.

The architectural subsystems usually need to be broken down into comprehensible modules. It is very easy to be vague about design at the module level. Module may mean many different things depending on the design approach. It could be a class hierarchy or a free-standing executable. This design stage often produces published interfaces. These may be difficult to change later on as they form strict contracts between code modules and teams of programmers.

Interface design tends to be less formal and easier to change behind the module. Resist the urge to do this micro design at the keyboard. Do not write the first code that comes into your head.

Functions are the lowest design level but are still important.

Sloppy design may be due to inexperienced programmers but may more often be caused by commercial pressures of the software factory. The irony is that in almost every case, a lack of a good design costs more than properly doing it would have.

A sound design has many benefits. It makes the code easier to write because there is a well-defined plan of attack and it's clear how things will fit together. It is easier to understand. It is easier to fix as you can identify the locations of problems more easily. It is less likely to harbor bugs/ It is more resilient to change.

The best design approaches are iterative, cautious, realistic, and informed. Avoid too many nasty surprises by doing a small amount of design, implementing it, assessing the implications, and feeding it to the next design round. Don't try to do too much design at once. Small, sure design steps are more likely to succeed than large, clumsy ones. Be realistic. The outcome of a prescriptive design process may depend on the quality of the established requirements, the team's experience, and the rigor that the process is applied. You must fully understand all requirements and motivating principles to be clear about the problem you are solving. You must fully understand the important qualities of the right solution. If you don't, you will solve the wrong problem.

Remember that there is no right or wrong design. At best, there are good designs and bad designs.

Good designs have a number of attractive characteristics.

The single most important characteristic of well-designed code is simplicity. A simple design is simple to understand, easy to implement, coherent, and consistent.

Simple code is as small as possible but no smaller.

This takes some doing, as the mathematician Blaise Pascal appreciated: “I am sorry for the length of my letter, but I had not the time to write a short one.” 

Laziness can pay off. Work your design to concentrate on the immediate problems. Strive for simple code that does a lot with a little (less is more). Well-designed code may look obvious but it may take an awful lot of thought (and refactoring) to make it that simple. Do not assume that an obvious-looking code structure was easy to design.

Elegance is another characteristic of well-designed code. Code that is confusingly clever or overly complex is not elegant. The design should not be riddled with special cases. A single, small change in one place should not lead to modifications in many other places.

It is natural to divide a design problem into parts, each of which is called a {{c1::module}} (or component). The quality of this decomposition is paramount. Key qualities of modularity are cohesion and coupling. What we want are modules with strong cohesion and low coupling.

{{c1::Cohesion}} is a measure of how related functionality is gathered together and how well the parts inside the module work as a whole. Weakly cohesive modules are a sign of bad decomposition. Each module must have a clearly defined role and not a grab bag of unrelated functionality.

{{c1::Coupling}} is a measure of the interdependency between modules. In the simplest designs, modules have little coupling and so are less reliant on one another. Modules of course cannot be totally decoupled because they would be unable to work together. Still, good design limits the lines of communication to only those absolutely necessary.

{{c1::Modular design}} helps split tasks between programmers, but take care to not decompose based on team organization ({{c2::Conway's law}}). A {{c1::module}} should be designed to have high {{c2::cohesion}} and minimal {{c3::coupling}}.

Modularity also helps splitting tasks between programmers. Take care that you do not decompose modules based on team organization (Conway's law). Design modules to be cohesive with minimal coupling. The decomposition must represent a valid partition of the problem space.

Each {{c1::module}} defines an {{c2::interface}} which is the public facade that hides an internal {{c3::implementation}}; the set of available operations is often called an {{c4::API}}.

Bad design that puts operations in the wrong place make it a nightmare to follow the application logic and difficult to extend the design.

Well-designed code clearly defines roles and responsibilities. Each main actor in the system that have clear responsibilities can ensure they have crisp interfaces.

When designing an interface, you create an abstraction. Choose carefully what is important for the user and what can be hidden from them.

Abstractions can form a hierarchy. View the different levels of abstraction.

Compression is the ability of an interface to represent a large operation with something simpler and is often the result of making good abstractions. Bad abstractions can lead to more verbose code.

Substitutability is possible when interfaces meet the same contract. A soft interface can have different sorting algorithms sitting behind it. This is possible with class inheritance hierarchies where objects can be substituted for its supertype.

Well-designed code is extensible: it allows extra functionality to be slotted in at appropriate places when necessary. The danger is of over-engineered code that tries to cope with any potential future modification.

Extensibility can be accommodated through software scaffolding. This includes dynamically loaded plug-ins, carefully chosen class hierarchies with abstract interfaces at the top, the provision of useful callback functions, and a fundamentally logical and malleable code structure.

Well-designed code contains no duplication. It never has to repeat itself. {{c1::Duplication}} is the enemy of elegant and simple design and usually is due to {{c2::cut-and-paste programming}} or more subtly when programmers do not understand the whole system.

A good design is not necessarily portable. A lot can be done to prevent platform dependence, but compromising code for unnecessary portability is bad design. A good design is appropriately portable. Think about it early to avoid expensive rework to fix old assumptions. A common approach is to create a platform abstraction layer.

A good design naturally employs best practices involving both the design methodology and the language's idioms. Given an implementation language, you must understand how to use it will.

A good design should be documented. Do not leave readers to infer the structure by themselves. This is even more important at higher levels of design. The documentation should be small because the design should be simple. At one end of the spectrum, architectural designs are documented in a specification, and on the other end, functions can be as self-documenting as possible, and in the middle, you will probably want to use literate programming for API documentation.

How to Design Code


 To be a good designer, you must understand what constitutes a good design and learn to avoid the characteristics of bad design. Then practice. For a long time. 

Modern design methods fall into two main families of fundamental design philosophies: structured design and object-oriented design.

Structured design is fundamentally about functional decompression: breaking up the functionality of the system into a series of smaller operations. Routines are the main structuring devices and the design is composed of a hierarchy of routines. Structured design is characterized by the divide-and-conquer approach. The two main lines of attack are top-down and bottom-up.

A top-down approach starts with the entire problem and breaks it into smaller units until no more division is necessary. A bottom-up design starts with the smallest units of functionality or the simple things you know the system must do. In practice, both can occur at the same time and the design process ends where they meet.

Object-oriented design focuses instead on the data within the system (rather than the operations a system must perform). {{c1::Object-oriented design}} models the software as an interacting set of individual units called objects (How to Design Code).

An object-oriented design identifies the primary objects in the problem domain and determines their characteristics. The behavior of the objects and operations they provide are established and then the objects are weaved into the design along with any implementation domain objects needed.

{{c1::Object-oriented programming}} was hailed as the savior of the software design world, largely lived up to the hype, and allowed software to manage the {{c2::complexity}} of far larger problems.

Design Tools


The Unified Modeling Language is currently the most popular and well-specified notation. {{c1::UML}} provides a standard way to model and document practically every artifact generated in software development. {{c1::UML}} has grown to the point that it models more than just software: it can model hardware, business processes, and even organizational structures.

{{c1::Design patterns}} provide a vocabulary of proven design techniques and are worthy of study.

Flowcharts are good to visualize algorithms and high-level overview. They should be used sparingly because they are less precise than code and become another thing to be kept in sync with code changes.

Pseudocode can help draft function implementations. Program Design Language is a formalized pseudocode.

I guess it made sense to somebody at the time. I'd love to have seen their pseudocode compiler.

Design in code: you can capture interfaces in code without implementing them (just stub values and put comments describing what should be done).

Computer-aided software engineering tools can be used to generate code from pictures and modify pictures when you modify the code, which is known as round-trip engineering.

In a Nutshell


Good code is well designed. It has a certain aesthetic appeal that makes it feel good. You must plan a design before beginning to write code, or you’ll end up with an unpleasant mess. Consider things like clean structure, possible future extensions, correct interfaces, appropriate abstractions, and portability requirements. Aim for simplicity and elegance. Design involves a strong element of craftsmanship. The best designs come from experienced and skilled hands. Ultimately, a good designer is what makes a good design. Mediocre programmers do not produce excellent designs. 
What is the single most important characteristic of well-designed code according to Code Craft?
What does Code Craft note you should not assume about obvious-looking code?
What does Code Craft call a measure of how related functionality is gathered together and how well the parts inside a module work as a whole?
What does Code Craft call a measure of the interdependency between modules?
What idea does Code Craft mention when saying not to decompose modules based on team organization?
Code Craft: sloppy design is most often due to the commercial pressures of the software factory. The irony is that in almost every case:
What must you do in order to have a clear design for the problem you are solving?
What characteristic of well-designed code is not present if it is confusingly clever, overly complex, or riddled with special cases?
Each [...] defines an [...] which is the public facade that hides an internal [...]; the set of available operations is often called an [...].
[...] is a measure of how related functionality is gathered together and how well the parts inside the module work as a whole.
[...] provide a vocabulary of proven design techniques and are worthy of study.
[...] provides a standard way to model and document practically every artifact generated in software development.
[...] helps split tasks between programmers, but take care to not decompose based on team organization ([...]).
It is natural to divide a design problem into parts, each of which is called a [...] (or component).
[...] has grown to the point that it models more than just software: it can model hardware, business processes, and even organizational structures.
[...] was hailed as the savior of the software design world, largely lived up to the hype, and allowed software to manage the [...] of far larger problems.
[...] models the software as an interacting set of individual units called objects (How to Design Code).
[...] is a measure of the interdependency between modules.
[...] is the enemy of elegant and simple design and usually is due to [...] or more subtly when programmers do not understand the whole system.
A [...] should be designed to have high [...] and minimal [...].
Code Craft 14. Software Architecture Study cards

Software Architecture


What is Software Architecture?


Software architecture is high-level design, but the term invokes the building metaphor. It is a high-level view that hides implementation details, but still identifies the key software modules, which components communicate with each other, and clarifies the roles and responsibilities of the various subsystems after determination of the important interfaces. A unified vision of how the software should be adapted allows a large team to develop the program more elegantly. The architecture is the single largest influence on the design and future growth of the software system, and it is essential to get it right in the early stages of development.

It may be important to specify particular hardware components, or the number of machines and processors in a distributed system. It may also describe specific algorithms or data structures if they are fundamental to the overall design.

There are four points of view commonly recognized in the architectural process. The conceptual view, also sometimes called the logical view, shows all the major parts of the systems and their interconnections. The implementation view is seen in terms of the real implementation models. The process view is designed to show the dynamic structure in terms of tasks, processes, and communication (best used when a high degree of concurrency is involved). The deployment view shows the allocation of tasks to physical nodes in a distributed system. Here we concentrate on the conceptual view which is the main result of the initial architectural phase.

The architecture is captured in a high-level document called something like the architecture specification. It is the initial system design and the first development step after the requirements have been agreed upon.

Architecture mostly concerns itself with components and connections. A component could be an object, process, library, database, or third party product. Each component is a clear and logical unit. Connections may be simple function calls, data flow through a pipe, an event handler, or a message. It can be synchronous (blocking the caller until the implementation has completed the request) or asynchronous (returning control to the caller immediately and arranging for a reply to be posted back later).

What is good architecture?


The key is simplicity. There should be neither too few nor too many components. Too many fine-grained components lead to an architecture that is hard to work with. Too few components mean that each is doing too much work and the structure is unclear. A good architecture leave space for maneuverability, extension, and modification, but it is also not hopelessly general.

Architectural styles


It is key to recognize the architectural style to work with the existing software sympathetically.

No architecture as pasta is the pasta ball.

Layered architecture is the most common style in conceptual views. This describes the system as a hierarchy of layers. A famous example is the OSI seven-layer reference model for network communication systems. The lowest level is usually a hardware interface. The highest level is the interface that the user interacts with. Layered architecture as pasta is lasagna.

Pipe and filter architecture models the logical flow of data through the system. It requires a well-defined data structure between each filter. Pipe and filter architecture as pasta is the cannelloni.

Client/server architecture divides functionality itno the client and server. Sometimes this approach is known as a two-tier architecture. As pasta, it is gemelli.

Component-based architecture splits control into a number of separate collaborating components rather than a single monolithic structure. This kind of design arrived with the lure of assembling applications quickly out of prefabricated components. The core is a communication infrastructure, or middleware, that allows components to be plugged in. Common middleware platforms include CORBA, JavaBeans, and COM. As pasta, conchiglie.

Frameworks basically give you an architecture. A traditional library is used by your thread of control making explicit calls into the library components. A framework turns this around: it manages the thread of control and makes calls into your supplied code. As pasta, canned ravioli.
What term refers to the high-level design of a software system and also invokes the building metaphor?
What is the single largest influence on the design and future growth of a software system according to Code Craft?
What are the two things that Code Craft says architecture mostly concerns itself with?
Code Craft divides the connections in software architecture into what two categories?
What describes a function call that blocks the caller until the implementation has completed the call?
What describes a function call where control is immediately returned to the caller with an arrangement for something to happen later?
What type of pasta is a metaphor for basically having no architecture?
What type of pasta is a metaphor for layered architecture?
What type of pasta would describe the architecture of the OSI seven-layer reference model for network communication systems?
What type of pasta is a metaphor for the architecture given to you out of the box by a framework like Ruby on Rails?
What does Code Craft say is the key to good software architecture?
What is usually seen as the lowest level in a layered architecture?
What is usually seen as the highest level in a layered architecture?
What turn around the pattern of a traditional library where you have the thread of control and explicit calls are made into the library according to Code Craft?
Code Craft 15. Software Evolution Study cards

Software Evolution


Software is a thing that has its own kind of life: it is conceived, developed, reaches maturity, is sent into the world, continues to develop, and eventually grows tired and old and is retired.

"Growing software" metaphor


Bug fixing isn't growth; it is tending to diseased parts of the code. Code grows slowly by the progressive accretion of small extra parts, similar to how an oyster makes a pearl.

"Evolving software" metaphor


Software might seem to evolve like a single-celled organism into a large, complex beast. It is an incremental process and develops through a number of stages. Some differences are that the changes are deliberate and do not happen through natural selection. We can use experience gained from previous release to adapt the code to its habitat and ensure its long term survival.

Software Rot


 Bad things happen to good code. No matter how well you start, no matter how honorable your intentions, no matter how pure your design and how clean the first release’s implementation, time will warp and twist your masterpiece. Never underestimate the ability of code to acquire warts and blemishes during its life.

The longest phase of software development is always maintenance. It is where most of the overall effort goes. B.W. Boehm observed that 40-80 precent of total development time is spent in maintenance.

 Software is never expected to stand still after a release. There will always be odd faults to fix, no matter how much testing went on. Customers demand new features. Requirements change under the development team’s feet. Assumptions that were made during development prove to be incorrect in the Real World and require adjustments. The upshot: More code is written after the project is considered complete. 

After software has been released, you become more restricted with what you can do. Users are dependent on published APIs and are familiar with the current UI. There are also psychological restrictions such as not being able to see a different version than how the code has always worked or not believing the product will be around for long enough to make the effort to modify it properly.'

Software rot happens whether you modify the code or not. Fixing one fault may cause the addition of other faults. Brooks found that as many as 40 percent of fixes introduced new faults. Quick-and-dirty fixes pile atop one another and act as nails in the original design's coffin. Be aware of how easily code degrades as it is modified. Do not be satisfied with changes that leave the system in a worse state.

There are many forms of rot that are easy to notice. Large classes, large functions, cryptic names, surprising side effects, lack of structure, duplication, high coupling, blurry APIs, implementation details leaking, work-arounds, functions with long parameter lists, code that you can't even consider trying to improve, out of date documentation, warnings, comments saying "Don't touch this."

There are also many more subtle and invisible degradations that manifest at a higher level than that syntactic gunk. Modifications that fudge the original code architecture or subtly circumvent program conventions are much harder to spot until you are deeply immersed in the system.

Why do we make such a big mess of code? The answer is simple: complexity. There is a lot to understand and there isn't enough time with the deadlines we are given.

How does code grow?


By luck: this is code that never had any design, it was modified without thought, its structure is down to happenstance. Maintenance modifications to well designed code can follow this approach.

Accretion: Get something working quickly rather than do it properly which there is not time for.

Rewrite: This takes courage and vision. It gets riskier as you do more at the same time. Good modularity and separation of concerns will make this easier.

Refactor: Make small changes in order to improve the internal structure without changing the external behavior. It is design enhancement.

Design for growth: Careful design allows room for growth. Do not try to guess the future and do not over-design. Over-design is especially likely when design occurs by committee. There is a school of thought exemplified in Extreme Programming that insists on using the absolute simplest design that can possibly work in any given situation.

Believe the impossible


Perhaps the problem is the mistaken belief that it takes longer to do the job properly. This is a false assumption when you factor in the time spent debugging and the ease of making later modifications.

What can we do about this?


First, recognize the problem. Before we think about how to work with existing code, consider the interconnection of modules, and reduce coupling as much as possible. Remember that modularity and information hiding are the cornerstones of modern software engineering. Think carefully about your system interfaces as you create them. Write neat, clear code that can be easily understood. Do not overcomplicate things. Simplicity is nearly always more desirable than performance.

Maintenance of existing code


Maintaining good code requires a different battle plan that maintaining bad code. With good code, you must carefully preserve the integrity of the design and do not introduce anything out of place. Only change what is necessary. Do not make many modifications at the same time (yourself). Do one thing at a time carefully.

Before making a change, you must be informed about the code you are working on. Understand where it sits within the whole system, which components will be affected by the change, what assumptions are present, and the history of modifications already made. Inspect the code's quality. Adopt the correct attitude. Be prepared to do some redesign work. Try not to introduce extra dependencies. Retain the programming style of the source files you are working with. Maintain any comments. Carefully test any modification you make.

 It’s important to maintain software well and expand it correctly, preserving the code design and making sympathetic modifications. Don’t expect maintenance to be easy. You may need to invest a lot of time to rewrite, redesign, or refactor. 
Thinking of software as similar to how an oyster makes a pearl or bug fixing as tending to diseased parts of the code is what metaphor?
If you were using "growing" as a metaphor for how software changes, what would be an example of something from nature that is similar to how software grows (a progressive accretion of small parts)?
Thinking of software as starting as a single-celled organism and incrementally changing into a large, complex beast is what metaphor?
Using experiences of previous software releases to adapt the code to ensure its long term survival is thinking consistent with what metaphor?
What is the longest phase of software development?
What phase does most of the overall effort in software development go to?
B.W. Boehm observed what percentage of total software development time is spent in maintenance?
Brooks found what percentage of code changes fixing faults also introduced new faults?
What unfortunately happens to code whether you modify it or not?
What is the simple answer given by Code Craft as to why we make such a big mess of code?
In software development, what commonly pile atop one another and act as nails in the original design's coffin?
Large classes, large functions, cryptic names, surprising side effects, lack of structure, duplication, high coupling, blurry APIs, implementation details leaking, work-arounds, functions with long parameter lists, code that you can't even consider trying to improve, out of date documentation, warnings, comments saying "Don't touch this" are all forms of code rot that together might be considered?
What form of what Code Craft calls "syntactic gunk" could taking its elimination too far lead to what APoSD calls "classitis"?
What two forms of higher-level code rot are harder to see than typical syntactic gunk (that you may only notice once you are deeply immersed in the system)?
To dispel the belief that doing software development properly always takes more time, what must you factor in?
What does Code Craft say are the cornerstones of modern software engineering?
Code Craft says that simplicity or performance is almost always more desirable?
In software design according to Code Craft, you should not be satisfied with any changes that do what?