The Robert C. Martin Clean Code Collection (Collection) (Robert C. Martin Series)
Rate it:
Open Preview
Kindle Notes & Highlights
13%
Flag icon
structured programming.14 Dijkstra said that every function, and every block within a function, should have one entry and one exit. Following these rules means that there should only be one return statement in a function, no break or continue statements in a loop, and never, ever, any goto statements.
13%
Flag icon
So if you keep your functions small, then the occasional multiple return, break, or continue statement does no harm and can sometimes even be more expressive than the single-entry, single-exit rule.
13%
Flag icon
Writing software is like any other kind of writing. When you write a paper or an article, you get your thoughts down first, then you massage it until it reads well. The first draft might be clumsy and disorganized, so you wordsmith it and restructure it and refine it until it reads the way you want it to read. When I write functions, they come out long and complicated. They have lots of indenting and nested loops. They have long argument lists. The names are arbitrary, and there is duplicated code. But I also have a suite of unit tests that cover every one of those clumsy lines of code. So ...more
13%
Flag icon
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.
13%
Flag icon
The art of programming is, and has always been, the art of language design.
13%
Flag icon
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.
13%
Flag icon
Indeed, comments are, at best, a necessary evil.
13%
Flag icon
The proper use of comments is to compensate for our failure to express ourself in code.
13%
Flag icon
Why am I so down on comments? Because they lie. Not always, and not intentionally, but too often. The older a comment is, and the farther away it is from the code it describes, the more likely it is to be just plain wrong. The reason is simple. Programmers can’t realistically maintain them.
13%
Flag icon
Truth can only be found in one place: the code. 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 minimize them.
14%
Flag icon
There is nothing quite so helpful and satisfying as a well-described public API.
15%
Flag icon
Don’t Use a Comment When You Can Use a Function or a Variable Consider the following stretch of code:    // does the module from the global list <mod> depend on the    // subsystem we are part of?    if (smodule.getDependSubsystems().contains(subSysMod.getSubSystem())) This could be rephrased without the comment as    ArrayList moduleDependees = smodule.getDependSubsystems();    String ourSubSystem = subSysMod.getSubSystem();    if (moduleDependees.contains(ourSubSystem))
17%
Flag icon
Nearly all code is read left to right and top to bottom. Each line represents an expression or a clause, and each group of lines represents a complete thought. Those thoughts should be separated from each other with blank lines.
18%
Flag icon
Variable Declarations. 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,
18%
Flag icon
Dependent Functions. If one function calls another, they should be vertically close, and the caller should be above the callee, if at all possible. This gives the program a natural flow. If the convention is followed reliably, readers will be able to trust that function definitions will follow shortly after their use.
Nick
Not possible in most C-like languages, which require functions to be defined before use.
18%
Flag icon
As in newspaper articles, we expect the most important concepts to come first, and we expect them to be expressed with the least amount of polluting detail. We expect the low-level details to come last. This allows us to skim source files, getting the gist from the first few functions, without having to immerse ourselves in the details.
19%
Flag icon
The old Hollerith limit of 80 is a bit arbitrary, and I’m not opposed to lines edging out to 100 or even 120. But beyond that is probably just careless.
19%
Flag icon
I personally set my limit at 120.
19%
Flag icon
Another use for white space is to accentuate the precedence of operators.    public class Quadratic {      public static double root1(double a, double b, double c) {        double determinant = determinant(a, b, c);        return (-b + Math.sqrt(determinant)) / (2*a);      }      public static double root2(int a, int b, int c) {        double determinant = determinant(a, b, c);        return (-b - Math.sqrt(determinant)) / (2*a);      }      private static double determinant(double a, double b, double c) {        return b*b - 4*a*c;      }    } Notice how nicely the equations read. The factors ...more
19%
Flag icon
A team of developers should agree upon a single formatting style, and then every member of that team should use that style. We want the software to have a consistent style. We don’t want it to appear to have been written by a bunch of disagreeing individuals.
20%
Flag icon
There is a reason that we keep our variables private. We don’t want anyone else to depend on them. We want to keep the freedom to change their type or implementation on a whim or an impulse.
20%
Flag icon
Listing 6-2 Abstract Point    public interface Point {      double getX();      double getY();      void setCartesian(double x, double y);      double getR();      double getTheta();      void setPolar(double r, double theta);    } The beautiful thing about Listing 6-2 is that there is no way you can tell whether the implementation is in rectangular or polar coordinates. It might be neither! And yet the interface still unmistakably represents a data structure. But it represents more than just a data structure. The methods enforce an access policy. You can read the individual coordinates ...more
20%
Flag icon
Hiding implementation is not just a matter of putting a layer of functions between the variables. Hiding implementation is about abstractions! A class does not simply push its variables out through getters and setters. Rather it exposes abstract interfaces that allow its users to manipulate the essence of the data, without having to know its implementation.
20%
Flag icon
Consider Listing 6-3 and Listing 6-4. The first uses concrete terms to communicate the fuel level of a vehicle, whereas the second does so with the abstraction of percentage. In the concrete case you can be pretty sure that these are just accessors of variables. In the abstract case you have no clue at all about the form of the data. Listing 6-3 Concrete Vehicle    public interface Vehicle {      double getFuelTankCapacityInGallons();      double getGallonsOfGasoline();    } Listing 6-4 Abstract Vehicle    public interface Vehicle {      double getPercentFuelRemaining();    } In both of the ...more
20%
Flag icon
These two examples show the difference between objects and data structures. Objects hide their data behind abstractions and expose functions that operate on that data. Data structure expose their data and have no meaningful functions.
20%
Flag icon
public class Square {      public Point topLeft;      public double side;    }    public class Rectangle {      public Point topLeft;      public double height;      public double width;    }    public class Circle {      public Point center;      public double radius;    }    public class Geometry {      public final double PI = 3.141592653589793;      public double area(Object shape) throws NoSuchShapeException      {        if (shape instanceof Square) {          Square s = (Square)shape;          return s.side * s.side;        }        else if (shape instanceof Rectangle) { ...more
20%
Flag icon
Now consider the object-oriented solution in Listing 6-6. Here the area() method is polymorphic. No Geometry class is necessary. So if I add a new shape, none of the existing functions are affected, but if I add a new function all of the shapes must be changed!1 Listing 6-6 Polymorphic Shapes    public class Square implements Shape {      private Point topLeft;      private double side;      public double area() {        return side*side;      }    }    public class Rectangle implements Shape {      private Point topLeft;      private double height;      private double width;         public ...more
20%
Flag icon
Mature programmers know that the idea that everything is an object is a myth. Sometimes you really do want simple data structures with procedures operating on them.
20%
Flag icon
the Law of Demeter2 that says a module should not know about the innards of the objects it manipulates. As we saw in the last section, objects hide their data and expose operations. This means that an object should not expose its internal structure through accessors because to do so is to expose, rather than to hide, its internal structure. More precisely, the Law of Demeter says that a method f of a class C should only call the methods of these: • C • An object created by f • An object passed as an argument to f • An object held in an instance variable of C The method should not invoke ...more
Nick
Worth considering.
20%
Flag icon
The following code3 appears to violate the Law of Demeter (among other things) because it calls the getScratchDir() function on the return value of getOptions() and then calls getAbsolutePath() on the return value of getScratchDir().    final String outputDir = ctxt.getOptions().getScratchDir().getAbsolutePath(); Train Wrecks This kind of code is often called a train wreck because it look like a bunch of coupled train cars. Chains of calls like this are generally considered to be sloppy style and should be avoided [G36]. It is usually best to split them up as follows:    Options opts = ...more
20%
Flag icon
unfortunate hybrid structures that are half object and half data structure. They have functions that do significant things, and they also have either public variables or public accessors and mutators that, for all intents and purposes, make the private variables public, tempting other external functions to use those variables the way a procedural program would use a data structure.4 Such hybrids make it hard to add new functions but also make it hard to add new data structures. They are the worst of both worlds. Avoid creating them. They are indicative of a muddled design whose authors are ...more
21%
Flag icon
So, what if we told the ctxt object to do this?    BufferedOutputStream bos = ctxt.createScratchFileStream(classFileName); That seems like a reasonable thing for an object to do! This allows ctxt to hide its internals and prevents the current function from having to violate the Law of Demeter by navigating through objects it shouldn’t know about.
21%
Flag icon
The quintessential form of a data structure is a class with public variables and no functions. This is sometimes called a data transfer object, or DTO.
21%
Flag icon
Somewhat more common is the “bean” form shown in Listing 6-7. Beans have private variables manipulated by getters and setters. The quasi-encapsulation of beans seems to make some OO purists feel better but usually provides no other benefit.
21%
Flag icon
Active Records are special forms of DTOs. They are data structures with public (or bean-accessed) variables; but they typically have navigational methods like save and find. Typically these Active Records are direct translations from database tables, or other data sources. Unfortunately we often find that developers try to treat these data structures as though they were objects by putting business rule methods in them. This is awkward because it creates a hybrid between a data structure and an object. The solution, of course, is to treat the Active Record as a data structure and to create ...more
21%
Flag icon
Objects expose behavior and hide data. This makes it easy to add new kinds of objects without changing existing behaviors. It also makes it hard to add new behaviors to existing objects. Data structures expose data and have no significant behavior. This makes it easy to add new behaviors to existing data structures but makes it hard to add new data structures to existing functions. In any given system we will sometimes want the flexibility to add new data types, and so we prefer objects for that part of the system. Other times we will want the flexibility to add new behaviors, and so in that ...more
22%
Flag icon
we thought that checked exceptions were a great idea; and yes, they can yield some benefit. However, it is clear now that they aren’t necessary for the production of robust software. C# doesn’t have checked exceptions, and despite valiant attempts, C++ doesn’t either. Neither do Python or Ruby. Yet it is possible to write robust software in all of these languages. Because that is the case, we have to decide—really—whether checked exceptions are worth their price. What price? The price of checked exceptions is an Open/Closed Principle1 violation. If you throw a checked exception from a method ...more
22%
Flag icon
Each exception that you throw should provide enough context to determine the source and location of an error. In Java, you can get a stack trace from any exception; however, a stack trace can’t tell you the intent of the operation that failed.
22%
Flag icon
Define Exception Classes in Terms of a Caller’s Needs There are many ways to classify errors. We can classify them by their source: Did they come from one component or another? Or their type: Are they device failures, network failures, or programming errors? However, when we define exception classes in an application, our most important concern should be how they are caught.
22%
Flag icon
Wrappers like the one we defined for ACMEPort can be very useful. In fact, wrapping third-party APIs is a best practice. When you wrap a third-party API, you minimize your dependencies upon it: You can choose to move to a different library in the future without much penalty. Wrapping also makes it easier to mock out third-party calls when you are testing your own code.
22%
Flag icon
If you follow the advice in the preceding sections, you’ll end up with a good amount of separation between your business logic and your error handling. The bulk of your code will start to look like a clean unadorned algorithm. However, the process of doing this pushes error detection to the edges of your program. You wrap external APIs so that you can throw your own exceptions, and you define a handler above your code so that you can deal with any aborted computation. Most of the time this is a great approach, but there are some times when you may not want to abort. Let’s take a look at an ...more
This highlight has been truncated due to consecutive passage length restrictions.
22%
Flag icon
If you are tempted to return null from a method, consider throwing an exception or returning a SPECIAL CASE object instead. If you are calling a null-returning method from a third-party API, consider wrapping that method with a method that either throws an exception or returns a special case object.
23%
Flag icon
In most programming languages there is no good way to deal with a null that is passed by a caller accidentally. Because this is the case, the rational approach is to forbid passing null by default. When you do, you can code with the knowledge that a null in an argument list is an indication of a problem, and end up with far fewer careless mistakes.
23%
Flag icon
Clean code is readable, but it must also be robust. These are not conflicting goals. We can write robust clean code if we see error handling as a separate concern, something that is viewable independently of our main logic.
23%
Flag icon
There is a natural tension between the provider of an interface and the user of an interface. Providers of third-party packages and frameworks strive for broad applicability so they can work in many environments and appeal to a wide audience. Users, on the other hand, want an interface that is focused on their particular needs. This tension can cause problems at the boundaries of our systems.
23%
Flag icon
If our application needs a Map of Sensors, you might find the sensors set up like this:    Map sensors = new HashMap(); Then, when some other part of the code needs to access the sensor, you see this code:    Sensor s = (Sensor)sensors.get(sensorId ); We don’t just see it once, but over and over again throughout the code. The client of this code carries the responsibility of getting an Object from the Map and casting it to the right type. This works, but it’s not clean code. Also, this code does not tell its story as well as it could. The readability of this code can be greatly improved by ...more
This highlight has been truncated due to consecutive passage length restrictions.
23%
Flag icon
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.1 In learning tests we call the third-party API, as we expect to use it in our application. We’re essentially doing controlled experiments that check our understanding of that API. The tests focus on what we want out of the API.
23%
Flag icon
A number of years back I was part of a team developing software for a radio communications system. There was a subsystem, the “Transmitter,” that we knew little about, and the people responsible for the subsystem had not gotten to the point of defining their interface. We did not want to be blocked, so we started our work far away from the unknown part of the code. We had a pretty good idea of where our world ended and the new world began. As we worked, we sometimes bumped up against this boundary. Though mists and clouds of ignorance obscured our view beyond the boundary, our work made us ...more
This highlight has been truncated due to consecutive passage length restrictions.
24%
Flag icon
Code at the boundaries needs clear separation and tests that define expectations. 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. We manage third-party boundaries by having very few places in the code that refer to them. We may wrap them as we did with Map, or we may use an ADAPTER to convert from our perfect interface to the provided interface. Either way our code speaks to us better, promotes internally consistent usage across the boundary, ...more
24%
Flag icon
First Law You may not write production code until you have written a failing unit test. Second Law You may not write more of a unit test than is sufficient to fail, and not compiling is failing. Third Law You may not write more production code than is sufficient to pass the currently failing test. These three laws lock you into a cycle that is perhaps thirty seconds long. The tests and the production code are written together, with the tests just a few seconds ahead of the production code.