Clean Code Chapter 10: Classes
These posts are an effort to get the word out about this awesome book, but also to help me solidify some of the concepts in my mind so I won’t forget them. Where possible I included direct quotes from the book in an effort to spread the wisdom far and wide.
Hopefully this is helpful and feel free to recommend any other helpful software publications!
Classes represent higher levels of organization. They can also be a somewhat confusing and intimidating topic for those who are new to Object Oriented Programming. But this chapter of Clean Code gave me some excellent new insights on how to make the most of classes in an OO program. The following are a few rules and takeaways from this awesomely helpful chapter.
Classes should be small! For anyone who has read this author’s take on Functions, this should come as no surprise. But programmers should think about classes in a slightly different fashion than they do about functions when it comes to writing and refactoring.
“With functions we measure size by counting physical lines. With classes, we should measure size by counting responsibilities.”
When all is said and done, the programmer should be able to write a description of the class in ~25 words without using words like ‘if’, ‘and’, ‘or’, or ’but’.
As with functions, effective naming comes into play. The name of a class should describe the responsibilities fulfilled by that class. Naming should help determine class size, not be something arbitrary done just to fill the void.
Some names that hint that a class might be too big include: ‘Processor’ or ‘Manager’ or ‘Super’ hint at too many responsibilities. These don’t clarify what the class is all about. In fact, they indicate that the class is all about several things.
It all comes back to the Single Responsibility Principle.
The Single Responsibility Principle
The SRP states that every class or module should have one and only one reason to change. It’s one of the most important concepts in OO design. It’s not too hard to grasp either, but according to the author of Clean Code, this principle often gets squashed when teams are working fiercely to generate thousands of lines of new code.
“Getting software to work and making software clean are two very different activities…. The problem is too many of us think we are done as soon as the program works.”
Do a large number of smaller classes make it too difficult to understand the bigger picture? Martin frames this decision in an interesting analogy:
“Do you want your tools organized into toolboxes with many small drawers each containing well-defined and well-labeled components? Or do you want a few drawers that you just toss everything into?”
According to the author, many of us probably would prefer the first option, but too often due to speed and technical debt, classes slip into the latter category.
In general, the more variables a method manipulates the more cohesive that method is to its class…. A class in which each variable is used by each method is maximally cohesive, although it’s not entirely possible that this will ever exist. It’s more of an ideal.
This lofty ideal means that everything is co-dependent and hangs together as a logical whole. It’s a giant green light that your class is functioning precisely the way it should be and it’s not too bloated, as everything contained therein is necessary.
Maintaining Cohesion Results in Many Small Classes
This chapter brings up an interesting paradox when coding and accumulating lots of instance variables.
While instance variables make it easier to work without passing variables at all, they often cut down on cohesion, because, by elevating these variables above one single class and using them throughout the program, they are no longer so tied to one particular function or reason for one class to change.
The author’s advice for this paradox?
“When classes loose cohesion, split them!”
According to Martin, there is a 5 step process for doing this that is organic and can lead to completely fresh code when it is fully implemented:
- Get the program functional
- Write a test suite to verify precise behavior of the program
- Refactor: Make a myriad of tiny improvements, one at a time
- Check to see if the program is functional after each change.
- Clean up and transform the program
By splitting up the classes and redividing them, the Program gets a lot longer. Programmers may also want to add longer, more descriptive variable names and use formatting and spacing to their advantage as well.
But just because a program is longer doesn’t necessarily mean it’s any less readable or less efficient.
This is an insight that I can really appreciate as someone learning to program effectively. I originally had thought that fewer lines means a better program, but now I realize that’s not always the case if other people on your team are unable to figure out the intention of the code in the first place.
Oftentimes clarity can be more efficient than brevity, at least for other people who have to read the code you wrote!
Organizing for Change, Isolating from Change
The two big reasons programmers do any of this reorganization are: organizing for change and isolating from change.
For most systems, change is continual. Every change subjects us to the risk that the remainder of the system no longer works as intended.
Organizing for change means that ideally, we’re incorporating new features by extending the system, not by constantly making modifications to existing code. We’re anticipating a future need for a lot of change, and programming defensively to make sure those transitions are smooth.
On the other hand, the classes that do exist should be isolated from that change as often as possible. Lack of coupling means that various elements of the system are isolated from each other and from change.
Many small classes with singular reasons for changing means classes should very seldom need to change when you add new functionality
In other words, it’s natural that you will have to refactor and rework your code as it expands and the functionality changes. That’s the name of the game. But as soon as we find ourselves opening up an entire class to fix it, we should stop to think about the big picture of our program and maybe consider adjusting our approach to how we write classes altogether.
The Dependency Inversion Principle
This reflects another important programming ideal, the Dependency Inversion Principle (DIP). This basically means to limit your software’s dependencies through careful consideration of design.
Our classes should depend on abstractions, not on concrete details, because concrete details will change.
For example, the author uses the example of writing a program that checks stocks whose particular prices could change every 5 minutes.
But If we build a solid foundation of isolated classes that depend on other classes that abstract that information, it can prove very effective.
Instead of being dependent on the
StockExchange class that provides a given price, why not write a class that is a
StockExchangeInterface that provides a fluid variable that can be plugged in throughout the program, but which itself could be constantly changing?
This second strategy isolates the action of asking for a price, as opposed to relying on the actual price being received.
In this way, the program is sheltered from unwanted dependencies and the more effective functional testing can be developed.