Reusable building blocks in software
Sometimes we design a round form, however that becomes way more complex to build. Still, we try to stick with simple forms like circle arcs.
The Sydney Opera House was architected with unusual round forms at the roof. That project "was completed ten years late and 1,357% over budget" (source). The project really gained speed when they reduced the complexity of sail-like round forms to reusable (smaller) building blocks.
In software you have the same problem. If you don't find reusable building blocks - as reusable code or reusable patterns - the complexity of the project grows exponentially, and you find yourself over budget (time and cost).
In software, you rarely need something as sophisticated as Sydney Opera House. Therefore, try to reduce complexity as much as possible if you want to finish your project in realistic time.
Simplicity is the ultimate sophistication (Leonardo da Vinci)
The Grug Brained Developer teaches us that "complexity very, very bad". Then what is the antidote to complexity?
Simplicity should be the obvious answer. However, simplicity is tricky to define and even harder to achieve.
Is simplicity to express your intent with 50% less characters or words? Not if it reduces readability. The code should be simple to read, because you spend more time debugging a line of code than writing it.
Sometimes is preferrable to write longer code to achieve a different type of simplicity: the simplicity for the human brain. It is preferable to write 10 lines of verbose brain-dead code, and not compress it in 15 lines that are so "clever" that it takes 30 minutes to understand it - even for you, months later.
Now we are approaching the building blocks paradigm. It is not only about code reuse - sometimes code reuse introduces incidental complexity. It is about using reusable patterns that are simple to implement, understand and (re)use.
When higher level patterns are built on other simple to understand patterns, then you are approaching simplicity. But each pattern should be simple enough in itself, considering you understand the underlying patterns that are made of. Think about "code should read like good prose". This means that each word is easy to understand, and the phrase structure is simple enough.
Sometimes those patterns are methods and classes. Some other times they can be patterns of organizing code, like MVC. If code reuse makes you to write harder to understand code, maybe you are over-using it.
A brick is something simple: it has right angles and plane facets. A random rock is nothing like simple as shape. It is way easier to build something from bricks than random rocks.
Compression
This simplicity can be expressed in the terms of the ability to compress something complex (requirements) to something simple (implementation).
A brick is a highly compressible pattern, everyone can picture it. The shape of a random rock is almost impossible to be expressed concisely in words or code. Try to describe a random rock's shape in details...
A simple10-floors building block can be compressed by multiplying the plan of a floor by 10 times. This is a highly compressible design. It is not necessarily pretty, but easy to understand and execute. Most important, it is less likely to make serious mistakes in implementation. Often you want this in software - not something golden plated that don't pay off the extra implementation time. You should allow extra complexity only when the extra customization brings a really high business value.
Actually, pretty is somehow related to compression. A pretty face is often symmetrical left-to-right, so more compressible patterns feel pretty. A pretty fractal image is the absolute compressible form - it emerges from a simple rule applied recursively. You want something like this in your software - if possible.
I prefer the work "elegant" to describe the analog of pretty in software. Sometimes elegant is only a feeling that "it looks right". Sometimes an elegant implementation even looks perfect. Sometimes you need to relax a bit the requirements to achieve such elegance and efficiency in implementation.
If we look deeper, an elegant implementation is a combination of simplicity with compressibility. It expressed requirements that look complex and messy in a concise way, that is also easy to understand. It solves a complex enough problem of the user in a simple and concise code. Once you read it, it gives you that Evrika feeling, however it was not obvious before finding the solution.
The "perfection" feeling that some code inspire must be related to compression. While simple code can be confused with short but unmaintainable code, the compression has a higher-level dimension of optimization. An elegant code should optimize higher level, including the effort of maintaining and extending that code later. Elegance normally provides a way to implement new requirements faster and with less code - and that is very related to compression.
An elegantly designed project normally achieves an economy of scale - as new features are implemented, it gets easier to implement a new one - because you can reuse some building blocks implemented before. Unfortunately, many projects achieve the opposite: as project gets bigger, it gets more costly to implement a relatively simple new feature. This is a sign that the architecture departed a long time ago from elegancy.
For a more abstract concept of compression see: Kolmogorov complexity - short, informal introduction
An iterative process
There is no up-front elegant or simple architecture for a project. You might have a good intuition about a fundamental structure that would simplify the project; however, you cannot have all the architecture details determined before starting to code.
Unlike a building, the software code is... soft - it can be refactored with relatively low cost. It is highly inefficient in software to determine the perfect architecture/blueprint of the project before starting to code. That was the waterfall paradigm, when refactoring code was expensive. Now we have great tools for refactoring.
Most often you get the requirements wrong and you need to throw away your blueprint later, anyway. Even worse, some people stick to the plan and continue to use an architecture that is highly inadequate for the actual business requirements. I call this an "impedance mismatch" between requirements and architecture.
The best way is to grow a software project organically. Start with a simple structure based on initial knowledge, then evolve that structure as new requirements arrive. The architecture should start to emerge a little later, when you implemented most patterns that your application need - ideally with reusable building blocks that are also simple to use.
Software building blocks
If you feel like you are re-implementing that same logic over and over for new requirements, this is a sign that you need some reusable building blocks in code. If the programming language does not allow to reuse the code, you can automatically generate the code for the same pattern - with a script.
If you allow a complex pattern to be re-implemented multiple times in your project, you are really far from compressibility and elegance. The clear effect will be that: when you find a bug in one of the implementations, you don't know where you need to fix it additionally.
A word of caution
We were thought to use the DRY principle (don't repeat yourself). This does not mean to implement common code for anything that resembles a bit. It is not good to compress 2 methods of 10 lines of code in a common method of 15 lines of code - if those 15 lines have many if(case1){}, if(case2){}... Such 15 lines of code are harder to read than the 20 lines you started with. In that case, it is better to keep the two methods of 10 lines.
I say, duplicate code first if you need to change the code, and refactor after the third use. I have a theory about this based on skier's problem, but this is another subject.
Sometimes it is good to have some code duplication to achieve simplicity and compressibility for the human brain (think WET). This is the fine grained situation of software decomposition. However, most software projects greatly benefit from identifying reusable building blocks that are elegantly designed and simple to use.
.jpg)
Comments
Post a Comment
Comments?