The Woes of Modern Software
I don't see myself as an expert developer but I have written enough software over the last 10 years both for my own projects and those of others to understand that simplicity is the best approach to software design. Every problem in computer science can be solved by adding another layer of indirection. This is a rule that has been well understood in our modern age of computing. The software that we build today are based on many layers of indirection from device to OS to languages to API to frameworks to products. Needless to say, many products today are also build on top of other products.
But it is in my opinion that these layering of indirection is starting to produce cracks in our morden software architecture especially in the hands of inexperienced developers. We have taken for granted that because we don't see these layers, we have assumed that they are not there. So as a natural result, we keep building more and more layers of indirection one on top of the other. The more layers we build the more computing power we need. The more layers we build, the more complex our software becomes. Our pains and woes as developers are usually directly proportionally to the layers of indirection we stack up on our products over time. It is not my intention to suggest that we should do away with all the layers but rather to control the amount of layers we put into our software.
Here is a list of 4 rules that I stick to when designing and architecturing software:
- Do the speed test.
- Do the drawing test.
- Do the overkill test.
- Communication
Do the speed test
The fastest way is usually the least layered way. Simplicity often manifest itself through speed. The fastest way is not always the best solution but it will always be a good measure that we can use to guide our development. If we know that this task can be done in 5 seconds using the fastest approach, then developing a solution that is scalable and does not deviate too far from those timings will help us do the right thing.
Do the drawing test
Visualize and drawout on a piece of standard size paper all the key components of your design and their relationship, can they all fit into the paper? Even if they can fit into the paper, can they be simply understood. If you end up with a mess of relationship connections and it is hard to tell if the drawing was done by you or your 3 year old, then you need to rethink your design. It is true that complexity is unavoidable, however, our aim is not to avoid it but to properly manage it. Do remember that when you are doing this, you should not be over simplifying your black boxes. The more detailed your drawing, the better it will reflect the complexity of your design.
Do the overkill test
Always ask yourself this question, "Am I building a tank to kill an ant?". Don't build features that you don't need. Don't design your software to handle everything possible, this is when you over build. Software will evolve as the needs change. The designs you build in today may not be what is required in the future. Solve the current problems elegantly and let your design evolve with the needs as they come. This will keep your code small, layers thin and provides you will all the advantages of simplicity. One of the tell tale signs of over build are code features that are in the coming release but will never be in use. Another common sign are the cascading of 3 or more method or function calls just to do a simple task. If they exists, you should reconsider your design.
Communication
If you work in a team, communication is key to good and simple software. The evil of poor software are often the results of teams that don't communicate. They work in silos only to appear during integration. Remember that your code may be designed to be limited to 3 layers deep but when you start calling other methods that belong to other team members, that layering may become twice as deep. Knowing the overall architecture and design of your team member's software is important because it helps to eliminate unnecessary layering.
These concepts can be adjusted to different levels of design from code within a product to frameworks that you use to product to product layering or dependencies. The more layers you have, the greater chance of producing poor software. There are probably many other guidelines that you use but these are the common ones that I reuse often.