Staircase Code

· 381 words · 2 minute read

Code style is a topic that has ignited many arguments and while it is not an unimportant topic there is arguably a far more important topic that often gets overlooked: code structure.

One “error” that I’ve seen a lot of developers make concerns how they structure their conditions.

In some cases a condition has two equally valid alternatives:

if user_is_logged_in:
  render_account_page()
else:
  render_login_page()

However in other cases, one of the branches is the happy path, while the other is the non-happy or error path:

if some_operation():
  # happy path
else
  # error path

In the simple case as shown above, this form of the condition might be fine. However once the code becomes more complicated, you get what I like to call “staircase code”:

if operation1():
  if operation2():
    if operation3():
      if operation4():
        if operation5():
          # handle success
        else
          # handle error
      else
        # handle error
  else
    # handle error
else
  # handle error

So what’s wrong with staircase code? Well, it makes it difficult for the person reading the code to determine which error handling statements are associated with a particular condition statement.

In some cases the reader might not even notice that error handling code is missing entirely, which could lead to a bug. Did you notice that the above example is missing some error handling?

So what is the alternative? Let’s look at the base case again:

if some_operation():
  # happy path
else
  # error path

If we invert the condition, we get:

if !some_operation():
  # error path
else
  # happy path

Which is logically equivalent to:

if !some_operation():
  # error path
  return

# happy path

If we apply this principle to the staircase code from above, we get:

if !operation1():
  # handle error
  return

if !operation2():
  # handle error
  return

if !operation3():
  # this is the error path that is missing in the above example
  return

if !operation4():
  # handle error
  return

if !operation5():
  # handle error
  return

# handle success

One of the reasons I like golang so much, is that handling errors like this is idiomatic:

value, err := Operation()
if err != nil {
  return err
}

// do something with 'value'

Remember, if your code is used by enough people, 1-in-a-million events will occur on a regular basis.

Check for errors and handle them!