The following is a interesting take on software complexity and how we design systems. The author proposes the STUPID principles, which stands for Simples, Testable, Ubiquitous, Proper, Incremental and Decoupled.
https://bdicroce.medium.com/smart-programmers-write-stupid-code-397765a14b14
Smart programmers write STUPID code because they understand that accidental complexity in software can open the door to failure in a project.
The Pain
At the time of this writing, my watch informs me that it’s 9:30PM.
I woke up earlier this morning in a good, optimistic mood about my day, but now I’m tired.
I’m not physically tired per se, but more like bummed out that despite all these great technologies at our disposal to create the greatest of software, we, as individuals who write code professionally, tend to value complexity over simplicity for various reasons not worth enumerating.
To be more specific, we tend to constantly fall in the trap of accidental complexity when it comes to writing or maintaining code. I’ve seen this since the first day I started earning a living in the industry, and I made it a central piece of a presentation that I gave on the subject of technical debt a little while ago (Purging the Technical Debt by Using Static Code Analysis Tools on YouTube).
One author that had a profound effect in instilling in me a passion for software development is Fred Brooks who wrote a collection of essays under the title The Mythical Man-Month.
In that book, Brooks sheds a light on those two types of complexities which is very nicely verbalized by Adrian Colyer on his blog The Morning Paper in post titled “No Silver Bullet — essence and accident in software engineering”:
Brooks considers the difficulties encountered in building software and divides them into two kinds: essential difficulties, those inherent in the nature of software; and accidental difficulties, where we make things harder for ourselves than we need to, but that are not inherent to the process. Accidental difficulties we can chip away at, but essential difficulties will always remain.
The Assumption
Well, I said earlier that I wasn’t going to enumerate the reasons why we tend to diverge our code into the realm of accidental complexity, but allow me just a few seconds to elaborate a bit on this.
Humans are not robots. We’re emotional beings. We have ups, and we have downs. We don’t fully understand everything that we tackle, and sometimes we elevate our assumptions high enough that we consider them to be truthful. We’re also lazy and unpredictable. All these should be enough to either understand or project why writing code tends to take the easy route that leads to Accident Complexity City.
But we’re also passionate beings.
Passionate about making time to understanding things. To learn. To satisfy our curiosity. To try. To fail. To do. To improve. To collaborate. To be a better version of ourselves today than we were tomorrow, and to plan ahead to be a better version of ourselves tomorrow, than we are today.
And we’re smart.
We’re very smart.
But also lazy, unpredictable, emotional, etc. 😉
The Proposition
Smart people get things done by making greater use of their imagination than their intelligence. When faced with a problem, they’ll allow their creativity and imagination to take the lead on how to solve it in way that would be in the best interest of their time, money and other resources that they deem important to them. The last bits of the solution might be solved with the use of their intelligence which is mostly a composite of experience, wisdom, trials, successes, failures and knowledge.
We tend to find shortcuts that will help us to get from point A to point B in the least amount of time, with the least amount of pain, and some kind of justified pride that the shortcut paid off dividends.
For example, if you want to improve the quality of your code, you can opt to adhere to the SOLID principles of programming. I agree with the principles and ideas of SOLID. Personally, the basis of it can be resumed in doing object-oriented programming correctly with the mentality of doing the right thing right. However, during my days of consulting, I met many people shouting and praising the principles of SOLID without paying attention to its values, which many times over led them to write fragile code (for the reasons already enumerated above). When I asked them to formulate in their own words what they truly understand about SOLID, I tend to get incomplete, unstructured, unsure answers. Perhaps because most of its principles may be a bit too abstract.
Smart people tend to have the ability to explain complex or abstract things to people of all ages. By vulgarizing and using metaphors, they’re apt to carefully explain a complex thing to a child. And we all know that if you can explain something to a kid, and the kid nods back in affirmation, chances are that you’re in good control of whatever subject you just shared with that child.
So how about this. What if we use our “smartness” to simply write STUPID code. Code that is so STUPID, that if the compiler could generate an emotion, you would see a tear drop from the top right side of your monitor, and your speaker would play the sound of a standing ovation.
What is this thing that I’m proposing? This STUPID thing?
Well, it’s quite simple. And the first letter stands for simple. Pretty clever, eh?
The S stands for SIMPLE
A while ago, I wrote an article titled “Slow Down. Finish Faster.” In it, I described the importance of slowing down in order to better understand what you’re doing. If you slow down just enough, you will reach your objective not only in a faster way (because you’ll avoid rollbacks or unnecessary change after the fact), but in a simpler way.
A simpler way that will allow you to adjust your code based on the ever changing requirements that the future holds for your system. That to me is the real definition of agility.
And you’ll see how my STUPID method promotes agility in an intuitive manner.
In essence, keep your code simple by avoiding the “necessity” of injecting accidental complexity and increasing its technical debt for no good reason.
Remember that popular acronym, KISS? Keep it simple, STUPID. 😉
The T stands for TESTABLE
You may prove that something works today, but you might not be able to prove that it still works tomorrow when a side effect is introduced in your codebase. As such, make tests to be the primary citizens of your codebase.
Whether they’re unit tests or integration tests, treat them with the same respect you give to your seatbelts and airbags when you’re driving in a dangerous highway with unpredictable drivers all around you. Because that’s exactly what your code will go through at runtime. Stop assuming that something works. Take the time, instead, to prove that it works. That way, you can continue evolving your codebase knowing that whatever assumptions you made in the past and continue to make today are actually true.
You want to be agile? Write tests. Without tests, you might get the feeling that you’re going fast, but that’s a fallacy. Either you’ll go fast, but in reverse. Or you’ll go fast, and hit something hard that will cause you more pain than a simple rollback.
Write code that is SIMPLE and TESTABLE. Already, that’s a good direction for adhering to agile programming.
The U stands for Ubiquitous
The idea was fostered by Eric Evans in his seminal book Domain-Driven Design: Tackling Complexity in the Heart of Software. And it’s one of my favorite aspects to keep in mind when programming.
By using the model-based language pervasively and not being satisfied until it flows, we approach a model that is complete and comprehensible, made up of simple elements that combine to express complex ideas.
…
Domain experts should object to terms or structures that are awkward or inadequate to convey domain understanding; developers should watch for ambiguity or inconsistency that will trip up design.
Whatever object that you create in your codebase, make sure to either minimize, or better, completely eliminate any nuance and ambiguity that it may generate to the reader of the code. Remember that you’re not writing code to please a compiler. A compiler will not call you in the middle of the night to tell you how amazing your code is. A co-worker, however, might call you in the middle of the night during his on-call night shift to ask you why you introduced an object X that has the same meaning as object Y in the codebase because a bug related to Y now needs to be fixed, but without understanding the side effects of X.
That’s accidental complexity. And it sucks. More than a vacuum.
Make an effort to keep one language in your codebase. If you can opt for the language of the domain, even better. When you talk about X with a client, make sure that it’s X that is represented in the code. Be exact and be intentional in the naming of your objects (classes, methods, properties, types, modules, etc.)
If you don’t know how to name something, chances are that you don’t understand what you’re trying to accomplish. There’s a missing puzzle in the game. That’s fine. Take the time to look for that missing piece by talking to your domain experts or people with the required knowledge of the domain. A puzzle can only be completed when all its pieces are accounted for in the first place.
The P stands for Proper
We take showers to avoid diseases or skin issues from taking form in our bodies, and to smell good. Well, that’s why I shower anyways. We brush our teeth to avoid giving too much of our money to the dentist once every nine months or so. Right? Well, maybe, but more importantly it’s to avoid the accidental complexity of cavities and gingivitis forming in our gums.
Alright, we get the picture. It’s nice to be clean, Brian. Well, it’s more than that. It’s important to be clean. And that same level of urgency applies to your codebase too. We clean the things that we value, therefore we should tidy up our codebase because we’re generating a lot of value in it, and in return it provides a lot more value back.
That value should be a net positive. Always. All the time.
By keeping your code proper, or clean, you invite others to respect and value it, but more importantly you can also change it quicker. You see again how this STUPID thing enables agility in your codebase? You want to refactor some pieces of code into patterns for whatever good reason? No problem, my friend. A clean, proper code invites such a thing with arms wide open.
Recently at work, I have had enough of the same comments given by the same developer during a pull request: “The code is not well formatted. There should be two spaces after the curly brace.”
F.M.L.
There’s two spaces missing after the curly brace, but the last time you wrote a unit test, Iron Man snapped his fingers in Thanos’ face and people in the theatres were going crazy.
Therefore, I decided to automate the formatting of the entire codebase by integrating a linting phase to our codebase. Case closed. Now that developer actually has something more constructive to share in the pull requests. But he was right. It’s important to care about the esthetic details of your codebase.
If one apple a day keeps the doctor away, a nice and proper codebase keeps useless PR comments away.
And more important than that, it allows the reader to make sense of your code. Like I said previously, you don’t write code for the compiler. You write it for the reader. The poor soul that will have to maintain or change your codebase when you’re in vacation or somewhere else in life. Or maybe it’s for your future self. Write your code in such a way that your future self will be grateful to your past self who made the decision to tidy up his or her code at that time.
The I stands for Incremental
I once heard a phrase from Kent Beck that is now imprinted in my mind:
Make it work. Make it right. Make it fast.
Recently, I gave myself permission to add another item on that list.
Make it better.
Either at every iteration or at any moment you can, after you’ve optimized the heaven out of your code, allow yourself to think of a way to make it better. Maybe better testable? Or perhaps better portable to other platforms? Whatever fits your world to make it better, make it a responsibility to make it just that. Better.
We don’t write software from the get-go. Just like building a puzzle takes time and is built incrementally, so is a software. The increment can be due to a change of requirement and not necessarily because of a new feature or a bug fix.
If you maintain to keep your code Simple, Testable, Ubiquitous and Portable, you can easily build it incrementally. Brick by brick. Floor by floor. As you get deeper knowledge of the domain, as you appropriate yourself better with the technologies, you get to solidify your codebase with more value.
Keep that in mind, wherever you are in your career as a developer, to build your system incrementally. Take a look again at the quote above from Kent Beck. It speaks of an incremental approach of building software. You don’t have to do everything in the first day. Rome wasn’t build in one day, neither will your system be, or should be.
Don’t rush.
Go slow, finish faster.
The D stands for Decoupled
You cannot easily test a system that is highly coupled to external services like a database, a third-party system, or God forbid, the Internet.
You cannot rapidly develop using your local development machine either when your code is coupled to those kinds of hard dependencies. If you don’t believe me, ask my Russian colleague what he thinks about debugging our system at work that’s coupled to a SAP service in Wisconsin. Trust me, it’s not fun for him.
Decouple the dependencies to your system. One approach that I have adopted in the past years is that of the Entity-Control-Boundary architectural pattern that is based on the Hexagonal Architecture proposition.
I love it.
I love it so much, that I have developed Cloudgenda using that approach. I’m a solo developer on that SaaS-based project, and I strongly believe that the reason why I can quickly and continuously add value to the system for my customers is because none of its dependencies are directly coupled to external services. In fact, every dependency is exposed and handled through a Port and Adapter object, and realized with dependency injection by configuration. That makes my entire codebase super easy to test and to refactor at will. And pleasant to work on as well.
This one goes hand-in-hand with the D of SOLID which stands for Dependency Inversion Principle. But it can also be applied to how you decouple your database tables when denormalizing them. Or when you’re making good usage of the object-orientated paradigm by choosing composition over inheritance in some instances. We can call that one “logical decoupling”, but the benefits are just the same as “physical decoupling”.
The Final Word
TLDR; STUPID: Simple. Testable. Ubiquitous. Proper. Incremental. Decoupled.
My watch now tells me that it’s 11:30PM and the final word is that I’m tired enough to head to bed now.
But just before doing that (and brushing my teeth!), I just wanted to reiterate that software development shouldn’t be hard. Actually, it’s not hard. It’s a fun and pleasant endeavor. But it does tend to get hard because of the “accidental” decisions that bring forth all this mess that we refer to “accidental complexity”.
It’s what slows you down when you’re trying to go fast. It’s what’s pulling you down while you’re trying to go up. It’s what sometimes makes you look at the Jobs section of LinkedIn because you’re about to give up working on the codebase at your place of work. Maybe give yourself a chance…or even better, give STUPID a chance.
Maybe once day there’ll be conference talks about this STUPID method. And if that ever should happen, I’ll probably be wearing this t-shirt with pride because I know it works.
In all seriousness, when you write code keep in mind that you’re writing it for your future self and for others. Invest the time now to make your code a healthy living thing in a digital world where it can grow to be a valuable contributor to our world and our lives.
You’re smart. And smart programmers write STUPID code because it’s a smart way to write code intelligently.
It’s midnight now.
In the words of Jim Carrey in The Truman Show,
Good morning, and in case I don’t see ya, good afternoon, good evening, and good night!