Does Clean Code Matter?

In his book Clean Code, Uncle Bob quotes Kent Beck about how fragile a premise it is that good code at all matters. Both authors write their respective books on the faith that it is although I suspect it is perhaps more accurate to call it conviction than faith.

It is not uncommon to come across code bases that are undoubtably dirty, messy, or unclean. It is also not entirely uncommon to come across successful projects with a terribly unclean code base. You can’t help but think whether clean code is plain vanity after all.

However, I think clean code ultimately matters because messy code is hard to read and hard to change. When necessary care is not taken writing code, you get messy code and mess tends to multiply and escalate. Messiness can initially be cosmetic but over time (and not a long time is required at all) more material aspects become messy including design and architecture. Small messes don’t stay small for long.

I think an excellent analogy is the Broken Window Theory. The idea is that visible signs of disorder encourages more of it. One story talks about a building which was well maintained for many years. It never got intruded or vandalized. Then one day, an accident happened and one window was broken. The rest of the building was still pristine but, for some reason, the broken window was not immediately repaired. In the following weeks, the building, that for years never got intruded or vandalized, got intruded and vandalized resulting in more broken windows.

Code that looks neglected tends to encourage neglect. Messy code tends to become the foundation for even more messy code. The mess bleeds into design and even escalates into architecture. The mess snowballs.

Between clean code and messy code, we know clean code is better. Yet, we also know projects succeed despite having messy code. Perhaps projects even initially succeed precisely because of quick messy coding. Clean code is practiced on the conviction that it is the right way to succeed. While it may ruffle some feathers, clean code reflects our professionalism and a low barrier to entry is not an excuse for low professional standards.

Dependency Inversion

One of the most useful techniques I’ve found when building software is dependency inversion. It seems to be largely misunderstood by most programmers and confused with dependency injection. Yet, it is a cornerstone of Clean Architecture and is the main principle behind decoupling a system from its lower level dependencies.

The central idea in DI is that high level policies must not depend upon lower level details. Typically, the flow of dependency follows the same direction as the flow of control as a program executes. The inversion in DI refers to the inversion of dependency flow against control flow.

The inversion of dependency flow happens on the source code and not on runtime. This seems to be a common point of confusion when trying to grok DI. In the source code, dependency is expressed by the presence/utilization of a module (dependency) in another (dependent). To perform the inversion, an indirection of the dependency must be done and this is typically done using interfaces. Although, having written applications in JS which does not natively support interfaces, I think the interface language construct is not strictly necessary to achieve DI.

Rather than depending on a specific module, we depend on an interface. An interface consist of public functions, arguments, returns, and exceptions thrown. An interface must express the purpose of a module using these elements. Put differently, an interface is a precise and formal specification of purpose and structure.

To be usable for its purpose, modules must fulfill i.e. implement the interface. Without interfaces, the higher level module must “know how to use” the dependency. With interfaces, the lower level modules must “know how they will be used”. This is a big shift of responsibilities. Now, the dependencies must “look up” at the interface and they must be implemented to its specification. In essence, they now depend on the interface.

In a very fundamental way, an interface is a boundary. It is the point where dependency flow reverses and it is a boundary that separates higher level modules from lower level ones. Crossing an interface is crossing an abstraction layer. On one side, we have high level policies embodying the high value business logic while, on the other, we have lower level details serving as a business-agnostic foundation of the system.

When dependency flow is reversed, lower level modules can now be freely changed without affecting the structure of high level ones. Hence, we refer to lower modules as details. We expect them to change and vary in implementation while still providing the same “service” e.g. calculation, storage, retrieval, etc. This gives us a more flexible system, highly decoupled, and robust to changes in lower level dependencies.