“Any fool can write code that a computer can understand. Good programmers write code that humans can understand.”
Martin Fowler
Occum’s razor. Simplicity is best. Write legible code. Don’t repeat yourself.
These concepts have been drummed into CS majors and Software Engineers. They are simple and intuitive messages, but so many of us completely forget they exist most of the time.
Crazy long functions. Lazy variable names. Excessive refactoring. Comment overindulgence. We have all been there. We have all been witness to others committing such sins also. My GitHub repository is an elephant graveyard full of such code.
This is not only annoying but costly. Illegible, difficult to understand code mean that any improvements will require lots of time reading, deciphering, and tacking on further wacky code to bandage code that should have never been finalised in the first place. For personal projects this means that we never touch code again. For businesses this means long delays and frustrating developmental experiences when improving essential products.
There are of course many reasons why we can produce crappy illegible code. But from my limited experience I dare offer two among many reasons as worthy of closer inspection:
- 1. Lack of care
- 2. Obsession with perfection
Contradictory huh? Let me explain.
Lack of care
Many of us love to code. We love to hack and see things work. But many of us frankly could not give more when developing to think about the ramifications of a poor coding decision. Naming variables a, b, z, who cares right? It is only temporary, right? Tight coupling? I will refactor it all later. Sure.
When we develop, no matter how much refactoring is done, the initial decision as to the methodology to develop is an important one. Refactoring can help with code smells and the most obvious flaws, but it won’t make up for all, or even most deficiencies. Unless of course you really want to resolve most of the problem, which is let’s face it, incredibly dumb.
Also, sometimes crappy choices in coding design signal an ever deeper problem. We don’t know what we are coding exactly! (Shock, horror!). Of course we most likely know the end goal, but perhaps we have not really bothered to really break things down and defined the actual tangible code pieces required to get us there.
This does not mean that we should plan every minutiae of development like NASA would send a spaceship to the moon. But it does mean that certain basic and obvious development principles should be kept and followed during development.
Obsession with perfection
We as developers also love to be clever. We pick up on some repetitive problem, something that can be abstracted out and we jump on it like hyenas feasting on a straggling gazelle. We create endless classes, variables, methods, interfaces that we foresee that we will use in the next 48 months.
We like to think of ourselves as visionaries and that we understand the market better than those who have lived and breathed in the industry for decades. We, the chosen ones, the developers and engineers are the sole individuals endowed with magical powers and foresight to solve these crazy problems for mere mortals.
But this is silly. Even if we were visionaries, there is no way we can fully appreciate the exact permutations of how a software product will evolve.
We should be visionaries sure, and software allows industries to be changed or even broken. But we aren’t deities. We don’t know everything about the market, nor about all the needs of the users that will use the products that we are building. So why are we building end to end solutions that assume we do?
All the great engineer led companies that we look up to had evolutionary adaptability as part of their DNA. They did not create in one bang the final product. Their product and thus their code evolved with the learnings they gained from their users.
We as software developers should seek to create our code to accomodate this necessary flexibility. Not to over-prepare, but to program to what we need now, with the understanding that our code base will need to evolve in the future.
Solutions
There are no easy silver bullets, but there are prescribed methodologies which assist us with the problem.
Unit Tests
Of course you say, this is obvious. But if it is so obvious why is it that so much development goes on without proper use of Unit tests? Because it is too easy? Or is it because we are too lazy to do things properly.
We can avoid so many of our code smells by creating focused Unit Tests that guide the coding process. Processes such as Test Driven Development enable us to apply discipline by writing failing Tests first, then trying to pass these tests one by one and applying the Arrange-Act-Assert pattern (AAA).
The point is not that you are smarter than the tests. Of course you are. It is to instil discipline through process, not to give you a silver bullet.
Design Patterns
Some people hate them, some people love them too much. But whatever your emotions and opinions, it is undeniable that Computer programming as a field has benefited greatly from design patterns and general design principles. MVC. Dependency Injection. Adapter. The list goes on and on. The modern programming world will not be the same without them.
And design patterns can help us too. There are so many patterns that have a practical reason behind them and give us a light through the dark jungle.
Look, you don’t need to apply design patterns everywhere, nor apply them exactly as it is in academic books. In fact don’t apply them beyond the benefit that they offer, or else you are complicating your code for nothing.
But where there is an obvious pattern, or where you genuinely are hacking your way out of Mirkwood, come on it won’t hurt to just refer to the Gang of four. It is like when you want a bridge built, you wouldn’t expect the engineer to ignore proven bridge constructs to just ‘hack it out’ right? Likewise we should never forget that we are building the bridges and buildings of the world’s digital infrastructure.
Model the Domain
What we program are almost always sourced from equivalent counterparts in reality. Object Orientated Programming’s obvious appeal was that it could be used to better model this reality linked nature of programming.
But because programming is abstraction, sometimes we can be a bit too clever in abstracting away the reality to a higher level. Focusing back on the obvious things we are reflecting or catering to can help. This could be things as minor as the names of our variables, classes or methods. The User and domain could be used to design the code itself, such as principles such as Behaviour Driven Development.
Takeaways
Sometimes in our haste to be great programmers, we under-think or overcomplicate things. We need to strike a bit of a balance between these extremes.
I don’t profess to be an expert in this field, yet. But my limited exposure to my own crappy code makes me appreciate that I could benefit from trying to improve the process and approach of my programming to be more legible and scalable.
We are human after all, and humans are creatures of habit. We cannot solve our bad habits by wishing them away. That is why good patterns, design patterns and keeping in close proximity of the domain we are coding for can help focus us into less fallible coding machines.