In the most basic form, a conditional has two branches and the conditional expression inspects a single variable on entry. For example:
if balance >= 0: color = BLACK else: color = RED
In this little code snippet, the condition
balance >= 0 only depends on one variable, and the two branches of the conditional cover all possible values of the input variable. I.e. the first branch covers all cases where the balance is positive or zero, and the second branch covers all cases where the balance is negative. The balance variable cannot have a value not covered by these cases. Easy.
Now let’s consider trickier conditionals: those that have compound expressions mixing multiple variables. Take a look at this:
if checking_balance < 0 and savings_balance < 0: color = RED else: color = BLACK
This simple conditional statement appears to have two code paths based on the way the code is written. However, there are actually four different possibilities:
checking_balance < 0 and savings_balance < 0
checking_balance >= 0 and savings_balance < 0
checking_balance < 0 and savings_balance >= 0
checking_balance >= 0 and savings_balance >= 0
In the previous code snippet, only one of these conditions was explicitly stated and the handling for the other three was folded into the else clause. This is OK in the sense that the program may be doing exactly what we want it to do. The reader of the code, on the other hand, lacks information: did the programmer consider the four cases? Did he really intend to bundle them together, or instead something was missed?
My guideline to make the above “obvious” is to unfold the conditional to explicitly describe all possible cases. This forces me to have to think about these cases, and if nothing special has to be done, to document the fact:
if checking_balance < 0 and savings_balance < 0: color = RED elif checking_balance >= 0 and savings_balance < 0: color = BLACK elif checking_balance < 0 and savings_balance >= 0: color = BLACK else: assert checking_balance >= 0 and savings_balance >= 0 color = BLACK
This idiom does not affect the semantics of your code and a smart compiler should be able to optimize the various cases out. However, this style shows to the reader that you have thought about all the corner cases, and it expresses what the intent is in these cases. Furthermore, when you are later writing tests, you can more easily determine what the corner cases of your code are by simple visual inspection of all code paths.
Another important thing to note in the code above is the addition of an assertion to the
else clause. Because the expression is compound, and because there are more than two code paths, it is not immediately obvious what case is being handled in the last branch. By adding an assertion that indicates what the condition is, this becomes obvious. We’ll cover more on this topic on the next post.
Lastly, be aware that the guideline proposed in this post does not necessarily make sense in all cases. For example, conditionals that handle a special behavior controlled by a boolean flag only have one branch, and explicitly writing an alternate branch with a simple
pass statement would be pointless.
I suspect that mastering this idiom and determining when it is good to use it or not is trickier than the other suggestions I’ve been presenting. The only way to get good at these things is by lots of practice and, especially, by reading tons of other people’s code to realize the ways in code can be confusing!