From my experience, the by far most frequent cause of over-engineered and overly complex software is engineers trying to anticipate requirements and potential future use cases. Everything has to be abstract in order to accommodate any possible use case business might come up with. Repetition is generally regarded as waste. Engineers hate repetition. They also hate having to adapt elegantly designed code to new business requirements in ways that make that code less elegant. Usually, when development on a particular piece of software starts the requirements tend to be quite clear and the use cases simple enough to design an API that accommodates all of these use cases in an abstract fashion.
However, the nature of software and business is change and it’s change where things get, well, complicated. The most common use cases often can be dealt with using general-purposes algorithms and business processes. Every business has to deal with a plethora of special situations and edge cases though. Some businesses and industries like logistics for instance are inherently so complex that generalised use cases can be hard to identify and everything appears to be an edge case. Even something as basic as bookkeeping and accounting – while simple in principle – can become very complex once you delve deeper into it. This complexity often is as much about legacy systems and long-running traditions as it is about reality and human interaction being naturally complex and not as elegant in a mathematical sense as we as engineers would like it to be.
This is why I think these are the most important aspects of writing software that can accommodate new requirements and easily adapt to change:
- Less is more: Write less code in the first place! Every line of code can be thought of as an expense – a line spent if you will. Each new code segment needs to be maintained. Each new piece of code is a potential future source of errors.
- Code means communication. Whenever you write code you’re communicating with someone, quite likely your future self! Make your message easy to understand.
- Make refactoring a primary requirement of each and every task / user story. Refactoring must not be a mere afterthought. The Boy Scout Rule can be applied to software quite nicely. Known as Opportunistic Refactoring this is a design (or rather behavioural) pattern, a good practice that helps with gradually improving code quality by leaving the code you’re working on in a better state than before you started your work.
- Last, not least: Don’t write reusable code, write disposable code instead! Developers and software architects have become so infatuated with the idea of reusability as a primary principle of software development that we often tend to not see the forest for the trees anymore. Software development is not mechanical or civil engineering. The output we produce doesn’t have to be built to last years or decades without change. The ability to rapidly adapt to change likely is the most salient difference between software engineering and other engineering disciplines. So, in lieu of fighting it we should draw upon and make good use of it!