This is a follow-up on the post Software Design – The Big Picture (Intro) and Software-Design – Part 2: Structuring For Control Flow, as well as Software Design Part 3 – Working With Persistent Data.
One of the fundamental problems of software development (and not only that) is that a) humans are really bad at managing complexity and that b) that code becomes really complex quickly.
The top one reason behind code going bad is that we stop being able to grasp how it actually works so much so that we start being afraid to change it structurally (i.e. refactor it). Possibly contrary to intuition, code that reached that state is essentially a car that lost its steering and – if at all – still moves out of inertia. Not good!
Obviously there must be a way around our limited intellectual capacity – after all we see enormously complex systems at work around us. But are they?
The trick to managing complexity is to avoid it. And the trick to avoid complexity is to build abstractions. Finally something we are quite good at.
Building abstractions happens everywhere from science to accounting. In software, the abstraction is a means to structure code and to decouple the code relying on an abstraction (such as a mobile app wanting to take a picture) from the details of the implementation of the abstraction (such as the hardware driver for the camera).
The same is true when creating an interface or a generic type in our favorite programming language so that we make effective use polymorphism to better structure some code.
You can look at modularization from different levels. For example as a way of structuring code of a library or application that is developed, packaged, and distributed as a whole from an (essentially) single source code folder. For example by arranging packages and folders in ways that help explain and understand responsibilities.
While maintaining a clear and instructive code structure is really important, it only carries so far. The reason is simple: As there is many, many more ways to screw up than there are to improve, any sufficiently large and non-usage-constraint code is prone to rot by violations of abstractions and complexity creep.
This kind of local (if you will) modularization is not what I am considering in this post. Instead, I am talking about moving whole slews of implementation (modules) away from your code, so that at any time the complexity you actually need to deal with is manageable.
The means of modularization are abstraction, encapsulation, and information hiding. However that is actually really the outcome of:
- Coming up with an API (contract)
- Separating implementation details from its API (hiding details)
- Making sure implementation is neither visible nor impacting other parts of the system (encapsulate)
How to Do It
I wrote a few posts on techniques and aspects of modularization. I will just enumerate the basics:
Re-Using and Extending
The two most notable patterns in contract building between modules are providing an API to be used by another module to invoke some function and, in contrast to that, providing API that is to be implemented by another module so that it can be invoked. The latter is in many ways how Object-Oriented-Programming factors into this story.
See Extend me maybe…
Sharing and Isolating
Exposing a contract means to share capabilities to be used by others. What is needed to do so however should not only be visible (so that it may not be accidentally used), it should not affect other parts of the system by its presence either.
Looking at a large non-modularized code base can easily be overwhelming. I tried to come up with an algorithm to reduce complexity iteratively:
This post looked at modularization as if it only applies to code. A good modular system should provide modularization capabilities to essentially all aspects it is used for though. If managing configuration is an important aspect, it just means that configuration can be part of modules as well. So do Web application resources or whatever else is part of what your platform of choice is typically used for. That is a core feature of the z2-environment.
Modularization is a rich topic. Doing it right, keeping a sustainable complexity level over time regardless of solution size by finding appropriate contracts and management of contracts is skillful craftmanship.
Not paying attention, a lack of willingness to invest into structural maintenance easily leads to frustrating, endless “too little, too late” activities. A growing code base built on weak isolation requires development disciplin that is unrealistic to expect in most commercial circumstances.
Getting it right, seeing it work out however is a great experience of collaborative creation that I am fortunate to have been part of!