Introduction
The concepts of cyclomatic complexity and cognitive complexity in computer science allow us to make decisions about the code we write so that it can be less complicated and easier to understand. This is why multiple if statements, and even worse, nested if statements are frowned upon. They make our code harder to understand and more rigid.
In 1972 Edsger W. Dijkstra wrote a paper for the ACM Turing Lecture called “The Humble programmer”. One of the subjects in this paper, is the need of a competent programmer to know the limited size of his own skull, so that he will avoid among other things, clever tricks like the plague.
Both of the above can be considered that are included in the principle known as the KISS principle. Although I have met a couple programmers that believe that multiple if statements and/or nested if statements are actually a good coding practice, because they can make your code easier to change by copy/pasting whatever you need to the relevant if statements, most programmers would agree that a code made like this would be hard to scale, hard to understand and more prone to bugs.
When we write code, we don’t actually write a program. The program is the executable that the compiler will generate. What we do, is that we write in a language that is made for humans, and this language will be used by the compiler to create the program. That means, that our first priority should be that the code we write, will be easy to understand not only by us, but by other programmers as well, or even by our future selves. Our codebase is like a recipe that has two audiences. One audience is the compiler and the other audience is the people who read our code. The higher the level of the language we use, the higher the priority should be, on making our code easier for our human audience to understand.
But this post, is not about multiple if statements, it is about something I call implied if statements. I have never seen it written anywhere explicitly, even though I have seen it mentioned as part of other rules or principles, most notably the rule that each variable represent only one thing. For this reason, I decided to write about it here, because I have seen it in code enough times to make me have my head scratching about what is really happening in a program.
What Are The Implied If Statements
Our code gives us the chance, to write down everything we have in our head, so that we will need less space in our head to remember things, and more space to think about the puzzle we are trying to solve. For this reason, not only data, but also logic should be written explicitly in our code.
Sometimes though, a logical if statement can exist only in the head of the programmer that wrote the code. Before I get to a real-world example, let’s see an example of what I mean. Let’s suppose we have a method in a class called Book
that returns the number of pages for that book:
public int GetNumberOfPages(){...}
When we see a method like that, we expect that this method will return the number of pages. But this may not be the case. After calling that method, we find out that the result is -1.
What does this -1 mean?
That the book doesn’t exist? Probably not, because the book object would be null.
That the front page doesn’t exist? The front page is part of the pages of a book, so probably that is not it.
Maybe the Book
class has a Pages
object that hasn’t been initialized?
Whatever the case, we cannot know, unless we read the documentation, if it exists, or dive into the Book
class code and try to understand what does this -1 mean. This is an example of an implied if statement: If the number of pages is -1 then ....
This statement exists only in the head of the programmer who wrote the code. Everyone else, has to dive into the codebase to understand that implied if statement, and even worse, from that moment on, whenever he reads code relevant to the Book
class, has to keep some space in his head occupied. In that space, the logical expression If the number of pages is -1 then ....
, always exists, and that means there is less space in our head to deal with the problems at hand.
This example is something I came up, before I get to a real world example. No matter how big of an imagination someone has, I think reality always can beat any imaginary example.
A Real World Example
Sometime ago, I was asked to look at some code. I won’t write the exact code, because I don’t want to point any fingers, but in that code there was a statement that looked something like this:
Damage = FireDamage - FireDamage * FireResistancePercentage
If someone reads that statement, he will probably think that it calculates the amount of damage: The amount of Damage
will be equal to the FireDamage
taken, reduced by the amount of FireResistancePercentage
this entity has.
In reality though, this statement had multiple implied if statements. After looking at the codebase, I found out that the Damage
value can be negative. This is an implied if statement, that exists only in the head of the programmer that wrote that code:
If the Damage
value is greater than zero then the Damage
variable represents damage, but if the value of Damage
is less than zero, then the Damage
variable represents healing.
I found out, that this implied if statement had been created because of another implied if statement that existed in the codebase:
If the FireResistancePercentage
value is between 0 and 1 then the FireResistancePercentage
variable represent fire resistance, but if the FireResistancePercentage
is greater than 1, then the FireResistancePercentage
variable represents fire affinity that its value is equal to the FireResistancePercentage
value minus 1.
How can someone know this, just by looking at the original statement? Those are implied if statements that there is no way for someone who didn’t write the code to know, unless he dives into the codebase.
Even the person who wrote that code, makes a disservice to himself. From that moment on, those implied if statements have to be in his head. Whenever he has to do something that is even remotely related to the Damage
variable, those implied ifs, will occupy valuable space in his head, space that would be much more valuable to be used in writing new code, not remembering old code.
This doesn’t stop there though. Someone reading that one line statement, has to wonder: If the Damage
variable can be negative, what about the FireDamage
variable? Can this be negative too? And what about the FireResistancePercentage
variable, can this be negative?
Whoever reads that statement, has to go and check inside the codebase about the other variables too. If they can hold negative values, what exactly those values represent?
If the FireDamage
variable has a negative value, does that represent healing from fire?
If the FireResistancePercentage
value is negative does that represent fire weakness?
What about the combination of those values? What if the FireDamage
is negative and the FireResistancePercentage
is greater than one, what happens then? Does this mean that a creature taking healing from fire that also has fire affinity then in reality takes damage from fire?
What if the FireDamage
is negative and the FireResistancePercentage
is negative too, what happens then? Does a creature taking healing from fire that also has a fire weakness takes more healing from fire?
The moment someone understands that the Damage
variable represents also healing, when it has a negative value, then he has to go check a big piece of the codebase, to understand if the other variables also represent two things. Even if he understands those representations, all those implied if statements have to be in his head all the time.
The solution to all the above, is actually easy: A variable called Damage
should represent damage, a variable called Healing
should represent healing and so on. This would mean that the statement:
Damage = FireDamage - FireDamage * FireResistancePercentage
actually does what it says, and its meaning can be understood just by reading it, and not having to dig inside the codebase.
Simple is Different from Convenient
The whole reasoning for the above statement was that it is simpler. Instead of having to write a lot of code, for every other possible situation like healing, fire affinity or fire weakness, everything was in one statement.
I have to disagree. Having code that exists only inside someone’s head, is not less code. It is the same amount of code, but not written down. The code that it is not written, still exists inside the coder’s head and every time he has to write code about damage, either new code or fix a bug, then he has to remember all the above.
Anyone looking at the code, has to understand what is happening by reading pieces of code all over the codebase, so that the implied code can eventually be in his head too, and even the maintainer of that codebase will have nightmares trying to find a bug or implement a new functionality. What if the requirements changed and suddenly there was a spell that increases or decreases the fire resistance? What would happen if there was a level where fire healing was not possible, or even a combination of those two?
Trying to write new functionality, while having space in your head occupied by all those implied if statements, makes a lot harder to write code and solve new problems. Instead of using our full mental capacity to create new code, we reserve some to remember code that is not written down, but implied, in some other part of our codebase.
The moment we include an implied if statement in our code, this will create a cascading effect of implied if statements. Like in the Damage
example above, an implied if statement will create the need to include more implied if statements, for every variable that exists in any operation with the variable that ‘carries’ that implied if.
A programmer should be careful to document any logic that exists in his head, in his codebase. Sometimes this doesn’t happen because this implied logic seems natural. When we are concentrated in a specific problem, some things we consider them naturally true, even if they are implied, because we are in a state of mind that is all about the specific problem we are dealing with.
Other times it is plain old laziness. An excuse we give to ourselves, because we are too lazy to write the code we have in our head to our codebase, so we say to ourselves: ‘I will remember that, it is implied’. Whatever the reason, our codebase should contain all our logic about how we solve a specific problem written down, not some of it.
Conclusion
The reason that we have high level languages, is that we can write down our logic in a way that is easier for humans to understand. Fewer lines of code, does not make our code simpler, especially if there is code that only exists in our head and is never written down. Our code is the documentation for our future selves and the other programmers who will take a look at it. Inside the code, all our logic should be documented. Our code is not the program. The program will be created by the compiler from our code.
Keeping things simple, is often an excuse for laziness. The reasoning that by remembering things so that they don’t need to be written down, we write less code and that makes it simpler, is flawed. The logic of how our program works has to be simple. There is no difference in simplicity if all that logic is written down or some of it is written in code and some of it is implied and should exist in the head of the programmer who wrote the code and in the head of every programmer who ever reads it. In fact, implied logic makes our code more complicated, not simpler because of the fewer lines of code.
Less is not always more, if by less, we mean less code written and more code implied.
I hope you enjoyed this post. Thank you for reading, and as always, if you have any questions or comments you can use the comments section, or contact me directly via the contact form or by email. Also if you don’t want to miss any of the new blog posts, you can always subscribe to my newsletter or the RSS feed.