In this book, Bob Martin teaches us how to create a good architecture for our systems. The idea of architecture is to minimize the effort to change and maintain the system. In other words, good architecture makes a system easy to change, easy to develop, and easy to maintain. If you don’t have a good architecture, then changing the system would be hard and it will take a considerable amount of time and resources.
Thesis of the Book
Systems with bad software architecture are very costly to maintain, costly to develop, and hard to change. Hence it is important to get the architecture right.
Review
I think the book provides very interesting topics that can be very valuable to all software practitioners. I will provide a brief overview of the most important topics included in the book.
For starters, Martin covers the three main programming paradigms (Procedural, Functional, and Object-Oriented). In particular, it is very useful to understand the relationship between polymorphism and Object-Oriented Programming. Afterwards, Martin discusses the SOLID principles. The SOLID principles are the architectural principles that apply at the source file/class level. This is very interesting because SOLID teaches you how to program, how to extend the functionality of a program, how to design source files so that they are easy to extend, why you should use abstract classes and interfaces, when you should split a source file/class and when not, etc. All the SOLID principles are very useful, but for me, the most useful is the Dependency Inversion principle because it shows you how to control all the source code dependencies in your system by using polymorphism.
Bob Martin then discusses the Component Cohesion and Component Coupling Principles, which are the principles that guide the agglomeration of source files into components/packages. With these, you will learn what are the trade-offs of gathering all your source files in a single big component instead of dividing them between components, and how to do it right. Also, you will learn how to organize the dependencies between components. Though, perhaps the most surprising lesson of these chapters is that components are not necessarily grouped because of functionality. Instead, they are grouped to allow development between different teams and to facilitate maintenance.
In the last chapters, Bob Martin discusses several topics around Architecture. The biggest lesson in this chapter is that you need to always differentiate the policy from the details. The policy is the most important part of your code, it is also known as the business rules or the entities. The business rules or procedures that make or save the company money. The rest of the code is composed of details and has a secondary nature. One lesson from this discussion is that the business rules should never depend on the details, it should be the other way around. Another lesson (and quite shocking) is that for Martin, Databases, Web Frameworks, UIs, should not determine the architecture of your application. They are only details! Don’t marry a DB, framework or UI, these should be plugins of your system.
Finally, I recommend this book for intermediate programmers or software engineers. Despite everyone who works with code will learn valuable lessons from the book, I believe beginners could profit more by starting with Clean Code by Bob Martin.
Key take-aways
In my opinion, the most important parts of the books are the discussion of Object-Oriented Programming, the SOLID principles, and the Component Principles. Which I will describe below:
Object-Oriented Programming
Object-Oriented Programming is one of those terms that we hear often, Uncle Bob expanded on one of the common definitions usually used. Object-Oriented Programming is the proper mix of Encapsulation, Inheritance, and Polymorphism. Uncle Bob compares these features with what could be previously achieved with C to see what specific features are intrinsically Object-Oriented Programming.
Encapsulation
Encapsulation means that you can hide data and functions so that they can only be accessed through specific exposed functions. This is achieved for example by using Objects. Each object has its hidden state/variables, and this state/variables can only be modified through specific functions (public functions). The idea is that if you modify the state of an object, you are not modifying the state of other objects. This is very useful because in some programming languages all the data and functions belong to the global namespace, hence running a function (or declaring a function) can have side effects on other functions or variables.
Martin says that C had already perfect encapsulation, while not all OO languages enforce encapsulation. Think for example Python, in Python by default all the state variables of an object are public as well as the functions, you have to use tricks (like dunderscore) to create private methods. Hence, encapsulation is not intrinsically Object-Oriented Programming, though encapsulation is easier in OO languages.
Inheritance
Inheritance is “simply the re-declaration of a group of variables and function within an enclosing scope”. In other words, it is the ability to extend another class. Martin commented that this was already possible in C, but OO language makes this feature easier, especially in cases of multiple inheritance. Hence this is indeed a trait of OO programming.
Polymorphism
Polymorphism is the substitutability of different entities when they adhere to a common interface. Polymorphism already existed before OO programming, but OO made it safer and easier. For Martin, the most important feature of OO languages is polymorphism, because, through it, the architect can have control over every source code dependency of its system. In other words, the code is not tied to a specific database, UI or a specific web framework, all these details become plugins to the core of your system (the high-level policies or the business rules).
Hence, OO programming is not about using objects per se, but instead, it is the proper use of encapsulation, inheritance, and polymorphism, (specially polymorphism and inheritance) to achieve your objectives.
SOLID
The first is the introduction and the discussion of the SOLID principles. Bob Martin started gathering the SOLID principles in 1980 and by 2000 the grouping stabilized (p.58).
The following quotes express the purpose of the SOLID principles:
“The SOLID principles tell us how to arrange our functions and data structure into classes, and how those classes should be interconnected. (…) The goal of the principles is the creation of mid-level software structures that: Tolerate change; Are easy to understand; Are the basis of components that can be used in many software systems.”(p. 58)
In simple terms, SOLID are the principles of a clean architecture at the module/source file level.
Single Responsibility Principle
“A module should be responsible to one and only one actor”/ “A software module should have one and only one reason to change”.
What this principle states is that a module/source file should not have content (functions, classes, data structures, etc.) that two (or more) people (or actors or clients) of the company would want eventually to change. In other words, you don’t want that a change in the source file requested by Actor A affects/breaks the functionality of Actor B. Hence, you want that all the content of the source file responds to a single actor.
Open-Closed principle
“A software artifact should be open for extension but closed for modification”/ “The behavior of a software artifact ought to be extendible without having to modify the artifact”.
In other words, to add new functionality to a class/module don’t modify the code directly but instead, look for ways to extend it rather than modifying it (e.g. use inheritance). This means that your classes/modules/source files should have a certain level of abstractness to allow a different implementation of the same class. Ideally, this class should be left alone and further development should be based on inheritance or implementations. In this sense, the class would be open for extension but closed to modification.
LSP Liskov Substitution Principle
This principle says that “to build software systems from interchangeable parts, those parts must adhere to a contract that allows those parts to be substituted one for another”.
In other words, LSP requires us to do good implementations of inheritance and interfaces. More clearly, all the ‘children’ of a class should be substitutable for the parent from which they inherit. The same in the case of interfaces. A bad inheritance (or implementation of an interface) occurs when the class that uses the parent class (or the interface) has to add ‘if statements’ to handle badly implemented inheritance relationships or interfaces.
Interface Segregation principle
“Do not depend on things that you don’t need”/ “Many client-specific interfaces are better than one general-purpose interface”
What this principle says is that if several client classes depend on another class’ interface, you should split that interface and give each client its own interface. The idea is not to couple together all the clients, and give yourself the flexibility to change one of those interfaces without affecting the others.
Dependency inversion principle
“The most flexible systems are those in which source code dependencies refer only to abstractions, not to concretions”/ “Depend upon Abstractions. Do not depend upon concretions”.
The code that implements high-level policy should not depend on the code that implements low-level details. Rather, details should depend on policies.
This principle tells us that our modules (and components) should depend on abstracts classes or interfaces. The idea mainly is that if you use abstract classes and interfaces correctly, you can turn upside down the dependencies of your application. For example, in a regular procedural program the top-level business rules depend on details (e.g. if you return text, or email a report, or render a pdf, etc.), hence when the details change, the business rules should change as well. Instead, with the dependency inversion principle, you switch the order of the dependencies, and now the details depend on the business rules, and the business rules are now independent (have no dependencies). This allows that changes in the details do not affect the business rules, it makes the reuse of the business rules easier, and it makes easier new implementations of the details since they just need to implement an interface or inherit from an abstract class.
Component cohesion principles
In this section, the author discussed when the source files should be grouped, and when not.
Reuse/Release equivalence principle
“Classes and modules that are formed into a component must belong to a cohesive group”/ “Classes and modules that are grouped together into a component should be releasable together”.
This principle states that a group of source files should be grouped together to be able to be reused. And to be reused, these source files must have a common theme and a system its releases.
The Common Closure Principle
“Gather into components those classes that change for the same reasons and at the same times. Separate into different components those classes that change at different times and for different reasons”.
This principle states that classes that change at the same time should go together. The reason is that software is built upon several different packages. The higher the number of components/packages that change, the higher the difficulties to rebuild, test, and deploy the final release of the project. Hence, you would like that if a change must be made, the change ideally would only be contained in a single component/package rather than across several components.
Common Reuse Principle
“Classes and modules that tend to be reused together belong in the same component”/ “Classes that are not tightly bound to each other should not be in the same component”.
The principle states that you should not force re-users to reuse things that they don’t need. Hence, the classes that you put inside a component should be close to inseparable.
Component coupling principles
Acyclic dependencies principle
“Allow no cycles in the component dependency graph”
The component dependency graph is a graph that shows the dependency relationships between components. This principle shows that in the component principle graph, you should not see or permit not allow cycles. If there is a cycle, the components involved become a big component. And it is very hard to develop without messing with other people’s code at the same time.
On top of that building, the system becomes very complicated.
Top down design
The component structure cannot be designed from the top down.
This is the most counter-intuitive of the principles. It simply states that the component structure of an application usually is not designed but rather it evolves as the system grows and changes. The component dependency diagrams have very little to do with describing the function of the application. Instead, they are a map to the buildability and maintainability of the application.
The Stable dependencies principle
Depend on the direction of stability.
Components should depend on more stable components. Otherwise, you will force a volatiles component (like details) to be stable.
The stable abstractions Principle
A component should be as abstract as it is stable.
This principle states that if a component should be stable, then it should be composed of abstract classes and interfaces so that the component can be extended.