Following recent discussion online, I’d like to make what I consider to be an important statement about code.
The purpose of code is to express the logic of the software both to the computer and to future developers.
There are various design conventions which, if followed, speak as loudly as the code, so we often find ourselves working with commonly known blocks of design to make things more predictable.
Jumbling everything up, or making it otherwise messy or cumbersome to navigate is also a bad idea. For example, putting the world in a single function over hundreds of lines (been there done that) is essentially putting a mountain in everyone’s way for future extension.
Making code impossible to debug is also a concern, though doing special things to open it up for debugging is probably worse.
For me there are a few principles that seem to make things easier:
- Avoid surprise
- Single Responsibility Principle – give each thing one job to do well, which kind of implies not repeating yourself
- Short methods – if it’s small, then you can pretty much see what it does in a heartbeat and it’s likely to be easy to give a meaningful name to
Taken to It Illogical Extremes
I read somewhere recently that if statements are evil and should be avoided. The premise of nested ifs being a poor alternative to other techniques like polymorphism (for choosing the right outcome based on a type of input) makes a lot of sense. I frequently use the Strategy Pattern in place of switch or if statements.
It’s a great thought experiment to ask how we could rewrite software without using certain statements, like
for. Indeed, I usually end up replacing lots of
if conditions around
null with the use of Java
Optional as I find it helps me express the thought process, rather than the moves to achieve it. E.g.
In the above pseudo code, we:
- require a non-null input
- check it has a particular state
- get its sub object
- require that thing is not empty
- navigate to its child object
- or, if any of the above doesn’t work, drop to a default
This obviates the need for if statements and explicit boundary checks, because it’s declaring the process, which implies those exact moves at runtime.
If I’m honest, the above can sometimes be a bit oblique to read and hard to debug, but only sometimes.
If Statements are Evil
They’re not. They’re pretty simple.
if statements, which should be the exception and even then should be avoided at nearly all costs, are pretty evil. Simple, easy to read
if statements are a good idea.
And this is where the problem of extremist thinking comes in. You can write software without
if. You can do something like this:
If you can honestly tell me that this is a better piece of code than:
then you’re in a dream world!
This is a trivial example, but there’s a big code smell in the centre of it, which is the use of a
map to act as an if statement, mapping
false to what are, essentially, the clauses of an if.
Maybe we could use a ternery instead, you might argue, and you’d be right, though that’s technically an
If You’re Going to Use Alternative Patterns Do It Well
Don’t get me wrong, there are some good arguments to be made for replacing convention structures with something else. Look at the cute extensibility of this FizzBuzz algorithm I wrote while rising to the challenge of doing it without
The above meets the criteria of explaining intent to other developers, and the code that used it to output Fizzes and Buzzes worked.
However, alternative techniques must be done elegantly and in the leanest way possible. If they’re just intellectual party tricks, then we may pay the price for them in terms of future maintenance costs, or runtime speed.
If you feel like your code has the effect of encrypting the behaviour of your application, then rewrite it.
No construct is evil, but all techniques can be used badly to create evil code smells.
Peer review can bring things back to an agreed sense of normal.