Clean Code - A Handbook of Agile Software Craftsmanship: Book Summary

Clean Code teaches you the principles, patterns, and practices of writing clean code. It also contains several real world exercises in cleaning up code.

Even bad code can function. But if code isn’t clean, it can bring a development organization to its knees. Every year, countless hours and significant resources are lost because of poorly written code. But it doesn’t have to be that way.

Foreword

  • A piece of code should be where you expect to find it and, if not, you should re-factor to get it there.
  • Making your code readable is as important as making it executable.
  • Clean code honours the deep roots of wisdom beneath our broader culture, or our culture as it once was, or should be, and can be with attentiveness to detail.
  • Abstraction is evil. Code is anti-evil, and clean code is perhaps divine.

Introduction

  • There are two parts of learning craftsmanship: Knowledge and work.
  • Learning to write clean code is hard work. It requires more than just the knowledge of principles and patterns. You must sweat over it. You must practice it yourself, and watch yourself fail.

Chapter 1: Clean Code

  • "Later equals never." -- LeBlanc’s
  • Keeping your code clean is not just cost effective; it’s a matter of professional survival.
  • It is unprofessional for programmers to bend to the will of managers who don’t understand the risks of making messes.
  • The only way to make the deadline — the only way to go fast—is to keep the code as clean as possible at all times.
  • It’s no good trying to write clean code if you don’t know what it means for code to be clean!
  • In short, a programmer who writes clean code is an artist who can take a blank screen through a series of transformations until it is an elegantly coded system.
  • Clean code is code that has been taken care of. Someone has taken the time to keep it simple and orderly. They have paid appropriate attention to details.
  • If you want to go fast, if you want to get done quickly, if you want your code to be easy to write, make it easy to read.

Chapter 2: Meaningful Names

  • Use intention revealing names for variables, functions, arguments etc
  • The name of a variable, function, or class, should answer all the big questions. It should tell you why it exists, what it does, and how it is used.
  • Avoid leaving false clues that obscure the meaning of code.
  • Make meaningful distinctions. If variable names must be different, then they should also mean different things.
  • Noise words are redundant. The word variable should never appear in a variable name.
  • Distinguish variable names in such a way that the reader knows what the differences offer.
  • Use searchable variable names.
  • Single letter names can ONLY be used as local variables inside short methods. The length of a name should correspond to the size of its scope.
  • Avoid encoding variable names
  • Avoid Mental Mapping: People should not translate variable names to understand their use.
  • One difference between a smart programmer and a professional programmer is that the professional understands that clarity is king.
  • Classes and objects should have noun or noun phrase names. A class name should not be a verb.
  • Methods should have verb or verb phrase names like postPayment, deletePage, or save.
  • Say what you mean, mean what you say.
  • A consistent lexicon is a great boon to the programmers who must use your code. don't use fetch and get for the same methods in different files.
  • Avoid using the same word for two purposes. Using the same term for to different ideas is essentially a pun.
  • Choosing technical names is better than business specific names since the people reading your code are programmers.
  • The code that has more to do with problem domain concepts should have names drawn from the problem domain.
  • At times its good to add prefixes to variable names to give it context eg $state and $addressState
  • Shorter variable names are generally better than longer ones so long as they are clear. Add no more context to a name than is necessary.
  • The hardest thing about choosing good names is that it requires good descriptive skills and a shared cultural background. This is a teaching issue rather than a technical, business, or management issue.

Chapter 3: Functions

  • The first rule of functions is that they should be small. The second rule of functions is that they should be smaller than that.
  • The blocks within if statements, else statements, while statements etc should be one line long.
  • Functions should do one thing. They should do it well. They should do it only.
  • To make sure our functions are doing “one thing,” we need to make sure that the statements within our function are all at the same level of abstraction
  • It’s also hard to make a switch statement that does one thing. By their nature, switch statements always do N things.
  • “You know you are working on clean code when each routine turns out to be pretty much what you expected.” -- Ward’s principle
  • Don’t be afraid to make a name long. A long descriptive name is better than a short enigmatic name. A long descriptive name is better than a long descriptive comment.
  • Don’t be afraid to spend time choosing a name. Indeed, you should try several different names and read the code with each in place.
  • Output arguments are harder to understand than input arguments.
  • One input argument is the next best thing to no arguments.
  • The ideal number of arguments for a function is zero. Next comes one, followed closely by two. Three arguments should be avoided where possible. More than three requires very special justification and then shouldn’t be used anyway.
  • Flag arguments are ugly. Passing a boolean into a function is a truly terrible practice.
  • We should never ignore any part of code. The parts we ignore are where the bugs will hide.
  • When a function seems to need more than two or three arguments, it is likely that some of those arguments ought to be wrapped into a class of their own.
  • Functions that take variable arguments can be monads, dyads, or even triads. But it would be a mistake to give them more arguments than that.
  • Choosing good names for a function can go a long way toward explaining the intent of the function and the order and intent of the arguments.
  • Side effects are lies. Your function promises to do one thing, but it also does other hidden things.
  • If you must have a temporal coupling, you should make it clear in the name of the function.
  • In general output arguments should be avoided. If your function must change the state of something, have it change the state of its owning object.
  • Functions should either do something or answer something, but not both. Either your function should change the state of an object, or it should return some information about that object.
  • Prefer Exceptions to Returning Error Codes. When you return an error code, you create the problem that the caller must deal with the error immediately.
  • It is better to extract the bodies of the try and catch blocks out into functions of their own.
  • Functions should do one thing. Error handing is one thing. Thus, a function that handles errors should do nothing else.
  • Duplication may be the root of all evil in software. Many principles and practices have been created for the purpose of controlling or eliminating it.
  • "Every function, and every block within a function, should have one entry and one exit." -- Edsger Dijkstra
  • Every system is built from a domain-specific language designed by the programmers to describe that system. Functions are the verbs of that language, and classes are the nouns.
  • Master programmers think of systems as stories to be told rather than programs to be written. They use the facilities of their chosen programming language to construct a much richer and more expressive language that can be used to tell that story.

Chapter 4: Comments

  • "Don’t comment bad code—rewrite it.” —Brian W. Kernighan and P. J. Plaugher
  • Nothing can be quite so helpful as a well-placed comment. Nothing can clutter up a module more than frivolous dogmatic comments. Nothing can be quite so damaging as an old crufty comment that propagates lies and misinformation.
  • Comments are not “pure good.” Indeed, comments are, at best, a necessary evil.
  • When you find yourself in a position where you need to write a comment, think it through and see whether there isn’t some way to turn the tables and express yourself in code.
  • Inaccurate comments are far worse than no comments at all. They delude and mislead. They set expectations that will never be fulfilled. They lay down old rules that need not, or should not, be followed any longer.
  • Only the code can truly tell you what it does. It is the only source of truly accurate information. Therefore, though comments are sometimes necessary, we will expend significant energy to minimise them.
  • Rather than spend your time writing the comments that explain the mess you’ve made, spend it cleaning that mess.
  • The only truly good comment is the comment you found a way not to write.
  • Don't use a comment when you can use a function of a variable.
  • If you find yourself wanting to mark your closing braces with comments, try to shorten your functions instead.
  • Don't comment out code! Commented-out code gathers like dregs at the bottom of a bad bottle of wine.
  • If you must write a comment, then make sure it describes the code it appears near. Don’t offer system-wide information in the context of a local comment.
  • Don’t put interesting historical discussions or irrelevant descriptions of details into your comments.
  • Short functions don’t need much description. A well-chosen name for a small function that does one thing is usually better than a comment header.

Chapter 5: Formatting

  • You should take care that your code is nicely formatted. You should choose a set of simple rules that govern the format of your code, and then you should consistently apply those rules.
  • It appears to be possible to build significant systems out of files that are typically 200 lines long, with an upper limit of 500.
  • We would like a source file to be like a newspaper article. The name should be simple but explanatory. The name, by itself, should be sufficient to tell us whether we are in the right module or not.
  • Concepts that are closely related should be kept vertically close to each other. We want to avoid forcing our readers to hop around through our source files and classes.
  • Variables should be declared as close to their usage as possible. Because our functions are very short, local variables should appear a the top of each function,
  • Instance variables, on the other hand, should be declared at the top of the class.
  • If one function calls another, they should be vertically close, and the caller should be above the callee, if at all possible.
  • Certain bits of code want to be near other bits. They have a certain conceptual affinity. The stronger that affinity, the less vertical distance there should be between them
  • A source file is a hierarchy rather like an outline.
  • It is sometimes tempting to break the indentation rule for short if statements, short while loops, or short functions: Don't do it
  • I can’t dummy statements, Make sure that the dummy body is properly indented and surrounded by braces.
  • A team of developers should agree upon a single formatting style, and then every member of that team should use that style
  • Remember, a good software system is composed of a set of documents that read nicely. They need to have a consistent and smooth style.
  • The last thing we want to do is add more complexity to the source code by writing it in a jumble of different individual formatting styles.

Chapter 6: Objects and Data Structures

  • Why, then, do so many programmers automatically add getters and setters to their objects, exposing their private variables as if they were public?
  • There is a reason that we keep our variables private. We don’t want anyone else to depend on them.
  • Objects hide their data behind abstractions and expose functions that operate on that data. Data structure expose their data and have no meaningful functions.
  • Procedural code (code using data structures) makes it easy to add new functions without changing the existing data structures. OO code, on the other hand, makes it easy to add new classes without changing existing functions.
  • "A module should not know about the innards of the objects it manipulates" -- Law of Demeters
  • The quintessential form of a data structure is a class with public variables and no functions. This is sometimes called a data transfer object
  • Objects expose behaviour and hide data. This makes it easy to add new kinds of objects without changing existing behaviours. It also makes it hard to add new behaviours to existing objects.
  • Data structures expose data and have no significant behaviour. This makes it easy to add new behaviours to existing data structures but makes it hard to add new data structures to existing functions.

Chapter 7: Error Handling

  • Things can go wrong, and when they do, we as programmers are responsible for making sure that our code does what it needs to do.
  • Error handling is important, but if it obscures logic, it’s wrong.
  • When you execute code in the try portion of a try-catch-finally statement, you are stating that execution can abort at any point and then resume at the catch.
  • In a way, try blocks are like transactions. Your catch has to leave your program in a consistent state, no matter what happens in the try.
  • Write Your Try-Catch-Finally Statement First
  • Each exception that you throw should provide enough context to determine the source and location of an error.
  • Create informative error messages and pass them along with your exceptions. Mention the operation that failed and the type of failure.
  • Wrapping third-party APIs is a best practice. When you wrap a third-party API, you minimise your dependencies upon it.
  • SPECIAL CASE PATTERN [Fowler]: You create a class or configure an object so that it handles a special case for you.
  • Returning null from methods is bad, but passing null into methods is worse.
  • We can write robust clean code if we see error handling as a separate concern, something that is viewable independently of our main logic.
  • Clean code is readable, but it must also be robust. These are not conflicting goals.

Chapter 8: Boundaries

  • There is a natural tension between the provider of an interface and the user of an interface.
  • Instead of experimenting and trying out the new stuff in our production code, we could write some tests to explore our understanding of the third-party code. Jim Newkirk calls such tests learning tests.
  • Interesting things happen at boundaries. Change is one of those things. Good software designs accommodate change without huge investments and rework.
  • We should avoid letting too much of our code know about the third-party particulars.
  • It’s better to depend on something you control than on something you don’t control, lest it end up controlling you.

Chapter 9: Unit Tests

  • Three Laws of TDD
    1. You may not write production code until you have written a failing unit test.
    2. You may not write more of a unit test than is sufficient to fail, and not compiling is failing.
    3. You may not write more production code than is sufficient to pass the currently failing test.
  • Test code is just as important as production code. It is not a second-class citizen. It requires thought, design, and care. It must be kept as clean as production code.
  • What makes a clean test? Three things. Readability, readability, and readability.
  • One Assert per test? The best thing we can say is that the number of asserts in a test ought to be minimised.
  • Test a single concept in each test function
  • Clean tests follow the FIRST rule
    • Fast
      • Test should run quickly
    • Independent
      • Tests should not depend on each other
    • Repeatable
      • Test should be repeatable in any environment
    • Self-Validating
      • Test should have a boolean output.
    • Timely
      • Test need to be written in a timely fashion.

Chapter 10: Classes

  • A class should begin with a list of variables. Pubic static constants, if any, should come first. Then private static variables, followed by private instance variables. There is seldom a good reason to have a public variable.
  • Public functions should follow the list of variables. We like to put the private utilities called by a public function right after the public function itself.
  • The first rule of classes is that they should be small. The second rule of classes is that they should be smaller than that.
  • The name of a class should describe what responsibilities it fulfils. In fact, naming is probably the first way of helping determine class size.
  • We should also be able to write a brief description of the class in about 25 words, without using the words “if,” “and,” “or,” or “but.”
  • Getting software to work and making software clean are two very different activities.
  • We want our systems to be composed of many small classes, not a few large ones. Each small class encapsulates a single responsibility, has a single reason to change, and collaborates with a few others to achieve the desired system behaviours.
  • Classes should have a small number of instance variables. Each of the methods of a class should manipulate one or more of those variables. (high cohesion)
  • "Classes should be open for extension but closed for modification." -- Open-Closed Principle
  • In essence, the Dependency Inversion Principle says that our classes should depend upon abstractions, not on concrete details.

Chapter 11: Systems

  • Complexity kills. It sucks the life out of developers, it makes products difficult to plan, build, and test.
  • Separate constructing a system from using it.
  • Software systems should separate the startup process, when the application objects are constructed and the dependencies are “wired” together, from the runtime logic that takes over after startup.
  • One way to separate construction from use is simply to move all aspects of construction to main, or modules called by main, and to design the rest of the system assuming that all objects have been constructed and wired up appropriately.
  • A powerful mechanism for separating construction from use is Dependency Injection (DI), the application of Inversion of Control (IoC) to dependency management.
  • Inversion of Control moves secondary responsibilities from an object to other objects that are dedicated to the purpose, thereby supporting the Single Responsibility Principle.
  • It is a myth that we can get systems “right the first time.” Instead, we should implement only today’s stories, then refactor and expand the system to implement new stories tomorrow.
  • Modularity and separation of concerns make decentralised management and decision making possible.
  • In a sufficiently large system, whether it is a city or a software project, no one person can make all the decisions.
  • We often forget that it is also best to postpone decisions until the last possible moment. This isn’t lazy or irresponsible; it lets us make informed choices with the best possible information. A premature decision is a decision made with suboptimal knowledge.
  • Whether you are designing systems or individual modules, never forget to use the simplest thing that can possibly work.

Chapter 12: Emergence

  • According to Kent, a design is simple if it follows these rules in order of importance:
    • Runs all the tests
      • A system that is comprehensively tested and passes all of its tests all of the time is a testable system.
    • Contains no duplication
    • Expresses the intent of the programmer
      • You can express yourself by using standard nomenclature eg adding "Command" to the name of your class.
    • Minimises the number of classes and methods
  • The presence of tests eliminate the fear that cleaning up the code will break it!
  • The majority of the cost of a software project is in long-term maintenance. In order to minimise the potential for defects as we introduce change, it’s critical for us to be able to understand what a system does.

Chapter 13: Concurrency

  • “Objects are abstractions of processing. Threads are abstractions of schedule.” -- James O. Coplien
  • Concurrency is a decoupling strategy. It helps us decouple what gets done from when it gets done.
  • Keep your concurrency related code separate from your other code.
  • Take data encapsulation to heart. Severely limit the access of any data that may be shared.
  • Copy objects and treat them as read only instead of using shared objects.
  • Threads should be as independent as possible with no synchronisation requirements.
  • Attempt to partition data into independent subsets that can be operated on my independent threads, possibly in different processors.
  • Avoid using more than one method on a shared object.
  • Keep synchronised sections as small as possible.
  • Think about shut down early and get it working early. It's going to take longer than you expect. Review existing algorithms because this is probably harder than you think.
  • Write tests that have the potential to expose problems and then run them frequently with different programatic configurations and system configurations and load. If tests ever fail, track down the failure. Don't ignore a failure just because tests pass on a subsequent run.
  • Do not try to chase down non-threading bugs and threading bugs at the same time. Make sure your code works outside of tests.
  • Run your threaded code on all target platforms early and often
  • Instrument your threaded code to try and force failures.
  • Break your concurrent system into thread-aware code and thread-ignorant code.

Chapter 14: Successive Refinement.

  • Specific to Java and I don't do Java

Chapter 15: JUnit Internals

  • Specific to Java
  • No module is immune from improvement, and each of us has the responsibility to leave the code a little better than we found it.

Chapter 16: Refactoring SerialDate

  • Specific to Java.

Chapter 17: Smells and Heuristics.

  • Comments

    • Inappropriate information
      • Comments should be reserved for technical notes about the code and design
    • Obsolete Comments
      • Comments which have gotten old, irrelevant and incorrect are Obsolete
    • Redundant Comment
      • A comment is Redundant if it describes something that adequately describes itself.
      • Comments should say things which code cannot say for itself.
    • Poorly written comment
      • A comment worth writing is worth writing well. Don't state the obvious. Be brief.
    • Commented out code
      • Commented out code is an abomination. When you see commented-out code, delete it!
  • Environment

    • Build Requires more than one step.
      • Building a project should be a single trivial operation.
    • Tests Require more than one step.
      • You should be able to run all unit tests in just one command
  • Functions

    • Too many arguments
      • Functions should have a small number of arguments.
    • Output arguments
      • If your function must change the state of something, have it change the state of the object it is called on.
    • Flag arguments
      • Boolean arguments loudly declare that the function does more than one thing. They are confusing and should be eliminated.
    • Dead Function
      • Methods that are never called should be discarded.
  • General

    • Multiple Languages in One Source File.
      • The ideal is for a source file to contain one and only one language.
    • Obvious Behavior is Unimplemented
      • When an obvious behavior is not implemented, readers and users of the code can no longer depend on their intuition about the function names.
    • Incorrect behaviour at the boundaries
      • Don't rely on your intuition. Look for every boundary condition and write a test for it.
    • Overridden Safeties.
      • It is risky to override safeties.
      • Turning off certain compiler warnings may bet the build to succeed but at the risk of endless debugging sessions.
    • Duplication
      • Don't repeat yourself.
      • Find and eliminate duplication wherever you can.
    • Code at wrong level of abstraction.
      • Isolating abstractions is one of the hardest thing for software developers to do. There's no quick fix when you get it wrong.
    • Base Classes Depending on their Derivatives
      • Base classes should know nothing about their derivatives.
    • Too Much information
      • Well-defined modules have very small interfaces that allow you to do a lot with a little.
      • The fewer methods a class has, the better. The fewer variables a function knows about, the better. The fewer instance variables a class has, the better.
      • Hide your data. Hide your utility functions. Hide your constants and your temporaries.
    • Dead code
      • Dead code is code that isn't executed. When you find dead code, do the right thing and give it a decent burial. Delete it from the system.
    • Vertical Separation
      • Variables and functions should be defined close to where they are used.
      • Local variables should be declared just above their first usage and should have a small vertical scope.
      • Private functions should be defined just below their first usage.
    • Inconsistency
      • If you do something a certain way, do all similar things in the same way.
    • Clutter
      • Keep your source files clean, well organized and free of clutter eg (unused variables)
    • Artificial coupling
      • Things that don't depend on each other should not be artificially coupled.
      • Take the time to figure out where functions, constants, and variables ought to be declared. Don’t just toss them in the most convenient place at hand and then leave them there.
    • Feature Envy
      • The methods of a class should be interested in the variables and functions of the class they belong to and not the variables and functions of other classes.
    • Selector Arguments
      • It is better to have many functions than to pass some code into a function to select the behavior
    • Obscured Intent
      • Run-on Expressions, Hungarian notation, and magic numbers all obscure the author's intent.
    • Misplaced Responsibility
      • One of the most important decisions a software developer can make is where to put code.
      • Code should be placed where a reader would naturally expect it to be. -- Principle of least Surprise
    • Inappropriate static
      • You should prefer non-static methods to static methods. When in doubt, make the function non-static.
      • If you really want a function to be static, make sure that there is no chance that you'll want it to behave polymorphically.
    • Use Explanatory Variables
      • One of the more powerful ways to make a program readable is to break the calculations up into intermediate values that are held in variables with meaningful names.
    • Function Names Should Say what they do.
      • If you have to look at the implementation of the function to know what it does, then you should work to find a better name or rearrange the functionality so that it can be placed in functions with better names.
    • Understand the algorithms
      • Before you consider yourself to be done with a function, make sure you understand how it works. It is not good enough that it passes all tests. You must know that the solution is correct.
      • Often the best way to gain this knowledge and understanding is to refactor the function into something that is so clean and expressive that it is obvious how it works.
      • There is a difference between knowing how the code works and knowing whether the algorithm will do the job required of it.
    • Make Logical dependencies Physical.
      • If one module depends on another, that dependency should be physical and not just logical.The dependency should not make assumptions about the module it depends on.
    • Prefer Polymorphism to if/else or switch case
      • Most people use switch statements because it's the obvious brute force solution not because it's the right solution for the situation.
      • "There may be no more than one switch statement for a given type of selection. The cases in that switch statement must create polymorphic objects that take the place of other such switch statements in the rest of the system"
    • Follow Standard Conventions.
      • Every team should follow a coding standard based on common industry norms. The team should not need a document to describe these conventions because their code provides the examples.
    • Replace Magic Numbers with Named Constants
      • It's a bad idea to have raw numbers in your code. You should hide them behind well-named constants
    • Be Precise
      • Expecting the first match to be the only match to a query is probably naive.
      • Using floating point numbers to represent currency is almost criminal.
      • When you make a decision in your code, make sure you make it precisely. You know why you've made it and how you will deal with any exceptions
      • If you need to deal with currency, use integers and deal with rounding appropriately.
      • Ambiguities and imprecision in code are a result of disagreements or laziness. In either case, they should be eliminated.
    • Structure over convention
      • Enforce design decisions with structure over convention.
      • Naming conventions are good but they are inferior to structures that force compliance.
    • Encapsulate Conditionals
      • Extract functions that explain the intent of a conditional.
    • Avoid Negative Conditionals.
      • Negatives are just a bit harder to understand than positives so when possible, conditionals should be expressed as positives.
    • Functions should do one thing.
      • Functions that do more than one thing should be converted into many smaller functions, each of which does one thing.
    • Hidden Temporal Couplings
      • Structure the arguments of your functions such that the order in which they should be called is obvious.
    • Don't be Arbitrary
      • Have a reason for the way you structure your code and make sure that the reason is communicated by the structure of the code.
    • Encapsulate Boundary Conditions
      • Boundary conditions are hard to keep track of. Put the processing of them in one place.
    • Functions should descend only one level of abstraction
      • The statements within a function should all be written at the same level of abstraction, which should be one level below the operation described by the name of the function.
      • When you break a function along lines of abstraction, you often uncover new lines of abstraction that were obscured by the previous structure.
    • Keep Configurable Data at High Levels.
      • If you have a constant such as a default or configuration value that is known and expected at high level of abstraction, do not burry it in a low-level function. Expose it as an argument to that low level function called from the high level function
    • Avoid Transitive Navigation.
      • If A collaborates with B and B collaborates with C, we don't want modules that use A to know about C. Avoid a.getB().getC().doSomething()
  • Java
    • Avoid long import lists by using wildcards. -If you use two or more classes from a package, then import the whole package.
    • Don't inherit constants
      • Use a static import if you want to import a class of constants.
    • Constants vs Enums
      • Now that enums have been added to the java language, Use them! don't keep using the old trick of public static final int
  • Names

    • Chose Descriptive Names.
      • Don't be too quick to chose a name. Make sure the name is descriptive.
    • Chose Names at the appropriate level of abstraction.
      • Don't pick names that communicate implementation; chose names that reflect the level of abstraction of the class or function your working in.
    • Use Standard Nomenclature where possible.
      • Names are easier to understand if they are based on existing convention of usage.
      • For example, if you are using the DECORATOR pattern, you should use the word Decorator in the names of the decorating classes.
    • Unambiguous Names
      • Chose names that make the workings of a function or variable unambiguous.
    • Use long names for long scopes.
      • The length of a name should be related to the length of the scope.
    • Avoid Encodings
      • Names should not be encoded with type or scope information.
    • Names should describe side-effects
      • Names should describe everything that a function, variable, or class is or does.
  • Tests

    • Insufficient Tests
      • A test suite should test everything that could possibly break.
    • User a coverage tool
      • Coverage tools reports gaps in your testing strategy, making it easy to find modules, classes and functions that are insufficiently tested.
    • Don't Skip Trivial Tests
      • They are easy to write and their documentary value is higher than the cost to produce them.
    • An ignored test is a question about ambiguity
      • We can express our question about the requirements as a test that is commented out or as a test that is annotated with @ignore
    • Test Boundary Conditions
      • Take special care to test boundary conditions. We often get the middle of an algorithm right but misjudge the boundaries.
    • Exhaustively Test Near Bugs
      • Bugs then to congregate
      • When you find a bug in a function, it is wise to do an exhaustive test of that function.
    • Patterns of Failure are revealing
      • Sometimes, you can diagnose a problem by finding patterns in the way the test cases fail.
    • Test Coverage Patterns Can Be Revealing
      • Looking at the code that is or is not executed by the passing tests gives clues to why the tests fail.
    • Tests Should be Fast
      • A slow test is a test that won't get run. When things get tight, it's the slow test that will be dropped from the suite.
      • Do what you must to keep your tests fast.