This article is part number 12 of the Readability series.

Conceptually, there are two kinds of conditional statements: ones that compute—or affect the computation of—a value and others that guard the execution of optional code (e.g. functionality enabled by a command-line flag). In this article we will focus on the former kind.

One way to think of those conditionals is as if they were to represent functions, just written inline. In other words: while it should be possible to move the conditional statement to a separate function, it has not been done because the algorithm is simple enough to not warrant it.

How can you translate this mentality into code?

The first guideline is to make every code path deal with the same input state. This sounds obvious because, upon entry to any branch, the program state is the same. However, the program state is a really broad concept, and therefore the data the conditional branches reference should be clearly delimited and well-defined.

The second guideline is to make every code path have similar “effects” on some particular output state. This can be setting a particular variable to a value, modifying a specific vector in different ways, etc.

In more practical terms, such a conditional inspects a set of input variables and uses them to compute an output value and/or to perform an action. This small set of input variables should be restricted and well-defined, and ideally consulted in a similar manner by all branches.

Let’s dive into an example:

color = BLUE
if item.color is not None:
    color = item.color

This little example violates the functional programming guideline of only assigning a value to a variable once. Leaving that aside, the point I want to make in this article is the following: if you were to move this to an auxiliary function so that the code looked like this:

color = get_color_with_default(item, BLUE)

Then, your function—especially if thought of in mathematical terms—would be something along these lines:

def get_color_with_default(item, default_color):
    if item.color is not None:
        return item.color
        return default_color

This implementation is clear: there are two code paths, the only state accessible are the two input parameters and the only thing this does is return a value. Therefore, we could rewrite our original code as:

color = None
if item.color is not None:
    color = item.color
    color = BLUE
assert color is not None

You will notice that this code has the exact same structure as the auxiliary function we defined, but the code is all placed inline.

In the general case, think about it this way: whenever you are writing a conditional statement, what would happen if you were to move the code into a separate function? What would be the inputs and the outputs of such function? If you have trouble defining these in a concise and generic manner, your conditional statement is not cohesive and is potentially hard to understand. Reorganize your code.

Go to posts index

Comments from the original Blogger-hosted post: