There's a tone in much of the writing that I have read about software development lately, a tone that insists that we mustn't be too smart and we shouldn't do clever things in case the creations we produce from applying our intellect become too complex and thus too difficult to maintain.
I don't like it.
There are indeed merits to reducing complexity where possible - simpler things do tend to be well…simpler. However, I think this tendency to push away from complexity and away from doing something that is arbitrarily decreed as too smart is harmful both to the process of creating software and to the software that is produced.
Complexity is inherent to software. Software should aid us in performing real world tasks, those tasks have a level of complexity. So in order to manage those tasks, the law of requisite complexity asserts that the software must be at least as complex. It is not unreasonable to design software such that it can only manage a subset of operations within a task - but this is an explicit choice that should have ramifications across the software making it clear where it's limits lie, and providing necessary feedback and information to allow humans to take over.
This attitude of avoiding being clever extends to users as well. If we do not believe that the genius developer can handle complexity, our estimation of an end user is lower still. We do not expect that a lowly user could take over where the machine reaches its limit, and thus do not equip them to do so.
When we build software, we must consider what it is intended to do. A deeper understanding of the problem space allows us to create software that is correspondingly more able to provide assistance in solving that problem. Building software that is effective in a problem space also requires an understanding of the complexities in the software itself.
To reduce this overhead and to enable greater focus on the problem domain, we can learn development techniques that allow for more expressive code, or for more succinct handling of the required logic.
A quest for simplicity often stalls progress on both of these fronts. Problem spaces are not thoroughly explored, instead preferring to cleave to the happy path with the minimum amount of disruption from beginning to end. Software development is viewed in a similarly restricted fashion: more advanced techniques are never explored (despite their potential suitability for a problem space) and a lowest common denominator set of techniques is seen as an outer boundary.
The Happy Path
The happy path is a seductive proposition when starting on a new piece of software functionality. If we can focus just on what should happen when everything goes right then we can show speedy progress and everyone will be happy. But software has to operate in a world where everything doesn't always go as expected. Errors and outliers must be considered at some point, but continuing to work on them after showing a functioning system can easily be considered an unnecessary waste.
Avoiding errors at the start has more insidious ramifications than the obvious risk of the work to handle them being delayed. Defining a model in software that doesn't consider such outcomes can lead to a model that is incapable of handling them and difficult to modify such that it would be capable of dealing with them. If such a model becomes the source of truth for how a system should operate, it also hides any information about error states from any future developers. This causes a significant increase in the amount of work needed to even see the potential problems, never mind that needed to address them.
Using this hammer has really simplified all my problems
Code is the most malleable substance that we have available to work with. With it we can create and recreate systems of all sorts of shapes and sizes. This means that there are a great many tools and techniques that can be used in a many different programming paradigms from simple procedural scripts to total functional programming and everything else.
This wealth of knowledge ensures that for any given problem that software should help with, there is likely a technique that allows it to be represented clearly and worked with gracefully.
But in a quest for simplicity at all costs many tools and techniques are ignored or forbidden. We cannot adopt a new language/tool/framework/technique because we do not all understand it. This greatly limits the suitability of code that is produced to the problem at hand, and increases the chances that the complexities inherent in the problem will not properly be accommodated.
I could use a hammer to bang screws into place, but it would be more effective to use a screwdriver.
Whether or not software complexity is considered during the building of software, it is inevitably encountered during its use. Software that is unsuited to the task for which it was intended harms those who rely on using it.
Even software that works as intended can still embody a lack of faith in any users that disempowers them, removing tools that would enable them to work more effectively in favour of more simplicity.
Your Name is Invalid
A frustrating flaw in many software systems is an encoding of rules about data that do not reflect the realities of that data. Software routinely tells users that their names are incorrect, their addresses cannot be found, their gender could only be one of two rigid options, or that they are performing a feat that is clearly impossible and could they just stop lying about it.
These deficiencies reflect a poor understanding of the data model on the part of the developers that seek to eschew the complexities of reality in favour of a simpler model. In many cases, they also show a missed opportunity for an even simpler solution - not collecting the data in the first place. But where data relating to humans is essential to the function of a system, it should be collected in a form that deals with the myriad complexities of human life. To do any less is to exclude the lived experiences of real people.
When software is the only method by which a task can be accomplished, it must always be possible to accomplish the task using the software. A poor model of the task and the data involved can lead to dead ends, where no further steps can be taken because data collected excludes all options to continue.
It is not unreasonable to allow software to fail in complex tasks, but allowing that failing software to become a barrier to completion of the task is unacceptable. Software should be capable of the tasks for which it is intended, and where it fails it must provide an alternative path for a user.
Even where software is capable of performing in its intended role, it may still lack flexibility to offer users alternative ways of solving a problem.
Making software easy to use is a noble goal. The mantra that "you can't hold it wrong" is a good one. However someone approaches software, it should seek to be accommodating to them and guide them through performing the tasks that they need to complete with it. But people learn and grow in capabilities. What is suitable for a novice who must learn how to operate a system, is often limiting and slow for someone who has performed the same task many times before.
Refusing to offer more advanced ways of working with a system robs them of the potential to learn more about it. It operates as a sort of lock in that prevents them from learning more general skills that could be transferred from task to task.
In these cases, simplicity can be good until it is no longer appropriate. Humans are capable of handling complexities, and software ought to allow them to do so.
Software can be complex, but pretending that people are incapable of handling complexity leads us to shy away from dealing with it and that causes problems down the line. Instead, we must address that complexity head-on (even if that requires us to learn new things) in order to build software that is fit for purpose and beneficial to those who use it.