Bob asks, “Have you ever waded through a mess so grave that it took weeks to do what should have taken hours?” He follows up with nothing that with problem software, “Every change they make to the code breaks two or three other parts of the code. No change is trivial.”
He doesn’t pull any punches in his response: “The fault is ours. Not the requirements, not the schedule, not the stupid managers and useless marketing types.”
Bob attributes part of this as a problem with refinement. “Getting software to work and making software clean are two very different activities. The problem is that too many of us think that we are done once the program works. 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.” As someone who has written both software and articles, Bob’s analogy really resonated with me!
Bob provides excellent guidance on what software craftsmanship looks like, with the objective of producing a system that acts as intended – with that intent being verifiable – as well as creating a system that is well-understood and maintainable over time, preventing the problem of every change literally being non-trivial. “Clarity is king,” Bob states.
I’ll run down a very condensed version of his advice to give you a flavor of the content:
- Use intention-revealing names. Int d; is not informative. Int elapsedTimeInDays; is much better.
- Avoid disinformation. Don’t refer to a grouping of accounts as an accountList unless it’s actually a list. Use accountGroup or plain-old accounts.
- Classes and objects should have noun or noun phrase names like Customer, WikiPage, Account, and AddressParser.
- The first rule of functions is that they should be small. The second rule is that they should be smaller than that.
- Do one thing. Functions should do one thing. They should do it well. They should do it only.
- We want the code to read like a top-down narrative. We want every function to be followed by those at the next level of abstraction so that we can read the program, descending one level of abstraction at a time as we read down the list of functions. I call this the Step-down Rule. The ideal number of arguments for a function is zero. Next comes one. Then two. Three arguments should be avoided where possible.
- Flag arguments are ugly. Passing a Boolean into a function is truly a terrible practice. It immediately complicates the signature of the method, loudly proclaiming that this function does more than one thing!
- Comments do not make up for bad code.
- Some comments are necessary. Legal comments, like copyright notices, or explaining the intent behind a decision.
- Code formatting is about communication. A team of developers should agree upon a single formatting style, and then every member of that team should use that style.
- The Single Responsibility Principle states that a class or module should have one, and only one, reason to change.
- Classes should have a small number of instance variables. Each of the methods of a class should manipulate one or more of these variables.
- Maintaining cohesion results in many small classes. This gives our program a much better organization and a more transparent structure.
- Concrete classes contain implementation details (code) and abstract classes represent concepts only. A client class depending upon concrete details is at risk when those details change. We can introduce interfaces and abstract classes to help isolate the impact of those details.
- 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.
- A powerful mechanism for separating construction from use is Dependency Injection (DI), the application of Inversion of Control (IoC) to dependency management. An object should not take responsibility for instantiating dependencies itself, it should pass this responsibility to another “authoritative” mechanism, thereby inverting control.
- Keep the tests clean, just like the code being tested. If you don’t keep your tests clean, you will lose the very thing that keeps your production code flexible. Readability is perhaps even more important in unit tests than it is in production code.
- We want to test a single concept in each test function.