Strict vs. Flexible Code
TLDR;
Strict code, which is rigid about inputs and crashes on errors, helps identify and fix issues faster, ultimately leading to better software. Flexible code, which adapts to errors, often results in hidden bugs and maintenance challenges. While strictness is ideal, finding a balance that fits the scenario is key, particularly when it comes to web development.
Introduction
While learning to code, I was taught to think of all scenarios and to handle all potential outcomes elegantly; to foresee all the ways in which the code may veer from the happy path, and to code defensively to prevent unforeseen issues. I used to think that it was better to write code that was flexible and permissive about its inputs. I thought that this would make it easier to build software that could handle a wide variety of inputs and use cases.
As I began working with enterprise software, I carried that ethos with me, but it slowed me down during the initial development phase. Defensive programming often demands more thought and time, and in many scenarios, spending those extra resources is not always appropriate or even possible at all. I have come to realize that it is better to write code that is strict and inflexible about its inputs. This makes it easier to diagnose and fix errors, and ultimately build better software.
However, I have also come to realize that there is a fine balance of flexibility vs. inflexibility.
The Parable of Mike and the Login Shell
This parable stuck with me ever since I read it:
Once, a student named Mike wondered whether it was better for programs to be written so that:
- Each function would be strict about its preconditions, checking its inputs and crashing immediately with an assertion failure if a precondition was violated; or
- Each function would be permissive about its preconditions, checking its inputs where necessary, but repairing erroneous inputs and proceeding as best it could.
So, he wrote two Unix shells: one in the strict style, and one in the permissive style.
The shell written in the strict style would crash, at first. Mike was fearless enough to use his work-in-progress as his login shell; crashing was incredibly inconvenient, as it would log him out of the machine completely. Nevertheless, he persisted; he found and fixed defects at a rapid rate, and soon enough the shell became a usable and useful tool.
The shell written in the permissive style also had defects. But he was never able to find and fix enough of them to make it usable. Eventually he gave up on this shell.
He concluded that it was better for most programs to be written in a strict and crashing style. Even when crashing was incredibly inconvenient, it made errors so much easier to diagnose and fix that you could build better software if you did it. Mike went on to become one of the eminent programmers of his generation, earning fame and fortune.
A Counter Example
I'm often amazed at how often I encounter forms on the web that don't trim extra space characters on inputs (especially common on mobile devices which tend to be quite liberal in appending a space at the end of an input).
Extra spacing often triggers errors when submitting forms, and the error messages are often too vague to pinpoint what the actual problem is with the form input. Not to mention that it can be tricky to spot extra spacing in inputs, especially for non-technical users.
On one end of the spectrum, the inflexible approach would be to reject inputs which contain extra spaces, but to also adequately signal to the user that such inputs are not valid. On the other end of the spectrum, a developer would simply trim the inputs before processing the data. In this case, the flexible approach seems more appropriate for a web form, whereas the inflexible approach would perhaps be more appropriate for a CLI-based application.
Waves Analogy
Let's discuss an analogy when it comes to the practical side of a flexible vs. inflexible approach.
Imagine driving a cigarette boat across a lake as fast and straight as possible. You will get to the other side very quickly, but you will create large waves. Now imagine doing the same in a kayak. As compared to the cigarette boat, it is much slower, more maneuverable, and creates virtually no Waves. The waves in this analogy refer to churn and second, third, and n-order effects as a result of pushing the code. In this case, unexpected bugs / issues discovered in QA or production, which force you to drop everything and debug / fix them.
"Outsourcing" Development
You can outsource a significant portion of your programming work to QAs and the general public. Do this by driving the cigarette boat. Code the happy path quickly, push, and move on. Let other people do the work of testing and finding issues for you (contending with the waves that you have created) over a period of time which elapses as you are working on other things.
If a critical issue is found, you'll find out about it. You might pay dearly for doing this, but sometimes it's worth it and even necessary. In a certain sense, all devs do this anyway, given that some issues only present themselves in production.
The Happy Medium
The happy medium is to write code which is tastefully defensive, and to ensure that you have robust observability tools in place to alert you of performance issues, bugs, etc. This allows you to spot issues in real time and to be prepared for when your clients / end users start running after you with pitchforks and torches.
Conclusion
Ultimately, this is a question of whether a programmer should err on the side of letting things fail vs. not. The experienced programmer is aware of this dynamic, takes easy wins in terms of defensive programming, and adjusts their programming style to fit the scenario.
Be water,
-MG