Shearing layers is a principle originally expressed for buildings: things that change at a different pace should be kept separate. Service like electrical wiring needs to be changed more often than the load-bearing structure, and load-bearing structure is expensive to change. If wiring is nested in the structure in such a way that changing a cable requires breaking a load-bearing wall, we end up with frequent expensive changes.
The same idea applies to software, as discussed by Brian Foote and Joseph Yoder in their essay Big Ball of Mud.
Layers in a particular piece of software could be ordered as follows, from fast to slow-changing:
- Application data such as the address of a contact for a business application, in memory, in files or in database.
- Configuration data, in the database or configuration files.
- Configuration data written in code
- Business logic
- In-house framework
- Programming language
In a well-designed application, the effort required to change one of these items is inversely proportional to the frequency they have to be changed:
Changing business data is constantly done by the end-users themselves through the application. The ability for users to perform actions at this level is the whole reason for the application’s existence. Even in a “passive” application like a media player, a user will change the currently played item, change the volume, and so on. It is what happens most frequently and should be easiest to do.
Changing configuration data is done less frequently and may be restricted to application administrators. It may require restarting the application to take effect.
Hardcoded configuration is already out of reach for end-users as it requires changing source code and recompiling. If done well it won’t require knowledge of the programming language, however.
Business logic changes more frequently and is typically (hopefully) kept separate from technical code. Even if business experts are not programmers, that part of code should be a direct translation of business specifications, so that changes in business specifications may quickly be reflected in code.
Lower level frameworks change whenever business logic has new requirements that can’t be expressed in the existing framework. Code in frameworks tends to be more complex and technical than business logic, and therefore requires more expertise and care during maintenance.
Libraries are frameworks for frameworks. Especially in the case of third party libraries, getting a new version brings the risk of regressions and may require adapting existing code, which means we avoid changing libraries too frequently.
Finally, the programming language is the hardest item to change. It may happen in extreme cases that a compiler is changed to accomodate a need in a project, but most of the time the language is chosen in the beginning and won’t change for the lifetime of a software project, except maybe incremental upgrades.
Zen in Layers?
How does the above observation work with zen levels? Which layers should be most robust?
An natural answer would be to say that the deepest the layer is, the more robust it should be. As I will show now, we need exactly the opposite!
If a piece of code needs to be changed very frequently:
- Every time someone changes it is an opportunity to make a mistake and introduce a bug
- Frequent changes often means repetitive work, and repetitive work often implies less attention from the programmer
- It is a common trend to give fast-changing parts to newcomers in the project: being closer from the business they are assumed to be easier to understand and easier to test. But we can’t expect newcomers to know all subtleties and hidden requirements in the frameworks
These are all reasons why it is crucial that making a change in a high layer:
In other words:
Higher, fast moving layers should have deep zen levels, even if it implies bringing low-level layers to a shallow zen level.
Of course low-level layers should strive to reach deep zen, but never at the expense of higher layer.
The Importance of Keeping Layers Separate
Now that we have established that, in a project we will have components with different zen levels working together, it becomes very important to keep the more fragile components free of fast-moving parts, and fast-moving components free of fragile parts.
One reason for that is reusability. Consider two interacting components A1 and B1. As requirements change, B1 evolves into B2 while A1 stays unchanged. A1 being a slow-moving component it was able to cater to the needs of both B1 and B2, and everything in between! In other words, the slower an A component is, the largest the set of B× components able to interact with it gets.
Slow-moving components that are free of fast-moving islands are more reusable
I recently wrote a framework component of a shallow zen level (a code generation tool) that contained a block of code configuring it (the list of classes containing high level specifications of the code to generate). As that component has a shallow zen level, I carefully watch any change happening in it, which should be fine because it moves very slowly. However, as it contains an island of fast-moving configuration, I found myself far too often reviewing changes in that file, to discover it is just the (Level One) configuration block.
Inversely, if an island of fragile code is nested in a fast-moving component, not only it becomes tempting to change the fragile component to satisfy one of those frequently-occurring requirement changes, but that change may be overlooked by a code reviewer because the file is assumed to be a high-level layer containing business requirements, not so technical stuff.
Each component of an application should be homogeneous in pace and in zen level.