Control flow for invalid actions – Conditionals, try/catch and Booleans

An important part of clean code is handling control flow properly. In this article, we’ll examine control flow for actions that might fail. This means actions that might be invalid, or might even crash.

We’ll look at:

  • using conditionals vs using try / catch
  • options for handling control flow in your code

We won’t be talking about errors. Errors are covered in the error handling series. For information on what’s considered an error, please see Terminology – Errors vs non-errors.)

Conditionals vs try / catch

A popular debate in programming is "look before you leap" (LBYL) vs "easier to ask for forgiveness rather than permission" (EAFP).

The debate applies to operations that may fail.

It doesn’t apply to:

  • errors. You’ll have to use error handling for those.
  • operations that don’t fail (don’t throw exceptions and don’t return error values). You’ll have to use conditionals for those.
  • error values. These can’t be used in all of the cases where exceptions can. For example, you can’t use error values to recover from the code null.doSomething().

So, with operations that may fail, should you use conditionals, or try / catch?

Both are valid options.

For example, you could have code like this, using conditionals:

const user = {
  name: 'Bob',
  age: null,
};

function handleUserEvent() {
  if (user.age === null) {
    // notify user that they haven't submitted their age, so their age can't be doubled
  } else {
    user.age *= 2;
  }
}

This style is referred to as "look before you leap" (LBYL).

Or, you could have code like this, using exceptions:

const user = {
  name: 'Bob',
  age: null,
};

function handleUserEvent() {
  try {
    user.age *= 2;
  } catch (error) {
    // notify user that they haven't submitted their age, so their age can't be doubled
  }
}

This style is sometimes referred to as "easier to ask for forgiveness rather than permission" (EAFP).

When choosing which one to use, you should consider the convention in your programming language. Alternatively, you can make your own decision based on the pros and cons of each.

The convention in your programming language

Different programming languages have different conventions.

"Look before you leap" (LBYL) is the convention used in languages such as C# and Java. For example, the .NET best practices for exceptions recommend using conditions instead of exceptions if possible.

"Easier to ask for forgiveness rather than permission" (EAFP) is the convention used in languages such as Python. For example, the Python glossary mentions that EAFP is more common in Python.

Generally, it’s good to follow the convention in your programming language.

Pros and cons of each

You can also make your own decision on which to use. Here are some (very quick) pros and cons of either methodology.

Advantages for EAFP

When using try / catch, the "happy path" (normal code execution) is at the top of the function / method. The error handling is at the bottom. Some people consider this better for readability.

Another point (which depends on your programming language) is that try / catch can be slightly better for performance. That’s because code in a try block executes immediately. However, code in an if block has to execute the condition first. If, the majority of the time, the code doesn’t throw an exception, then try / catch can result in better performance. (Having said that, I don’t actually have a source with a thorough performance comparison.)

Advantages for LBYL

One advantage with LBYL is that exceptions maintain their semantic meaning. Exceptions traditionally mean "exceptional". This means something that shouldn’t happen under normal program execution. This means errors. It doesn’t mean normal conditions.

Another advantage for LBYL is that exceptions (mostly) signify errors. This makes it easier to distinguish between errors and normal conditions in your code.

Another advantage is that the flow of control of the program is more explicit. Exceptions are like GOTOs. They are jumps in the code. They can make the execution flow of your code harder to track and harder to debug. In comparison, conditional statements have explicit control flow.

Finally, using exceptions may require more care. If you’re not careful, you may catch exceptions that you didn’t intend to. This can result in silent bugs. For example:

function foo() {
  try {
    // foo normally returns InvalidArgumentsError to signify that the action can't be completed
    // but it can also return an Error due to a bug
    foo('hello', 'goodbye');
  } catch (error) { // catches all errors, bug is swallowed silently and not handled appropriately
  }
}

Other notes

One argument for EAFP is that it prevents certain race conditions. For example, if you’re using conditionals, you might first check to see if a resource is available for use. Then, you might attempt to use it. However, between the time that you checked and the time that you tried to use the resource, something else may have started using it.

Here’s a code example:

if (someResource.IsAvailable()) {
  // too late, by the time the code below is called
  // something else is already using the resource
  someResource.Use();
}

Instead, the code should be like this:

try {
  someResource.Use();
} catch (Exception ex) {
  // handle exception
}

Personally, I don’t see this as a valid argument for preferring EAFP. In this case, you’re forced to use try / catch (or error values). You don’t have a choice. But, that doesn’t mean that you must use try / catch everywhere.

A similar argument can be made for conditionals. Sometimes, you’ll have two different code paths. Neither code path throws an exception. In this case, you must use conditionals. But, just because of this case, it doesn’t mean that you must always always use them.

My personal preference

Here is my personal preference. You’re welcome to have the opposite one.

Personally, I prefer LBYL.

That’s because I care about the advantages that LBYL provides. Namely:

  • error handling is used for errors. This makes it easier to distinguish between errors and normal conditions.
  • conditionals have a more explicit flow of control. This makes it easier to debug and track the flow of your program.
  • you won’t accidentally catch exceptions you didn’t intend to catch

I also don’t care about the advantages that EAFP provides:

  • the performance benefits are insignificant. They shouldn’t drive the decision. (Premature optimisation and all that.)
  • stylistically, I prefer error handling at the top and normal execution code after

Options for handling control flow in your code

Conditionals vs try / catch aside, you have a few options for handling control flow in your code. These are:

  • tell, don’t ask
  • look before you leap
  • use try / catch
  • return a Boolean

Here is each option in more detail:

Tell, don’t ask

"Tell, don’t ask" is a guideline that helps you write better code. It helps you structure code so that it’s easy to work with. As a side benefit, it also reduces the number of conditionals you need.

Normally, if you don’t use "tell, don’t ask", some code will be like this:

  1. obtain an object from somewhere
  2. ask the object for some data
  3. use a condition to see what you can do with that data
  4. do something with that data or with the object

Here’s a code example:

function calculateArea(shape) { // obtain shape object
  const type = shape.type; // ask object for some data
  if (type === 'circle') { // have a condition to see what you can do
    return Math.PI * shape.radius ** 2; // do something
  } else if (type === 'rectangle') {
    return shape.length * shape.width;
  }
}

class Circle {
  constructor(radius) {
    this.radius = radius;
    this.type = 'circle';
  }
}

class Rectangle {
  constructor(length, width) {
    this.length = length;
    this.width = width;
    this.type = 'rectangle';
  }
}

Instead, with "tell, don’t ask", the code is like this:

  1. get an object
  2. do something with it

To achieve this, you use polymorphism. You put the logic inside the object. Then, you tell the object what to do. You don’t have to ask it what it’s capable of doing first.

Here’s a code example:

function calculateArea(shape) {
  // no need to query object, just tell it to do something
  return shape.calculateArea();
}

class Circle {
  constructor(radius) {
    this.radius = radius;
  }
  // logic to calculate area is inside the object
  calculateArea() {
    return Math.PI * this.radius ** 2;
  }
}

class Square {
  constructor(length, width) {
    this.length = length;
    this.width = width;
  }
  // logic to calculate area is inside the object
  calculateArea() {
    return this.length * this.width;
  }
}

In the code above, the classes Circle and Square both have a method for calculating the area. Then, in the standalone function, you just call the method. You don’t need conditionals to check what type the shape is.

This is an application of the principle of separation of concerns. Separation of concerns talks about organising your code into sensible units that are easy to work with. For more information about it, please see Clean code and programming principles – The ultimate beginner’s guide.

However, "tell, don’t ask" isn’t always possible. You can’t just keep piling logic inside objects. Sometimes, you’ll need to use different control flow mechanisms.

For example, you might have two different systems that work together. In this case, you can’t necessarily move logic from one system to the other. You’ll have to use something like if / else instead.

Here’s a code example:

function handleNewTowerRequest() {
  if (moneySystem.balance < 500) {
    // tell player they don't have enough money
  } else {
    moneySystem.reduce(500);
    defenceSystem.buildDefenceTower();
  }
}

In the code above, you can’t necessarily put the line moneySystem.reduce(500) inside the defenceSystem code. You may want to keep the two systems separate. In this case, you’ll use if / else and let some other code handle the interaction between the two systems.

Look before you leap

"Look before you leap" refers to checking if something is valid before attempting it.

If you prefer conditionals over error handling, then this is a very good option.

The format is something like:

  1. if (some condition to check if something is possible)
  2. then: do something
  3. otherwise: do something else

Some example code is:

function handleNewTowerRequest() {
  if (moneySystem.balance < 500) {
    // tell player they don't have enough money
  } else {
    moneySystem.reduce(500);
    defenceSystem.buildDefenceTower();
  }
}

Here’s another example:

function handleUserFormSubmission(userData) {
  if (userData.name === '') {
    // tell user that the "name" is required
  } else if (userData.email === '') {
    // tell user that the "email" is required
  } else {
    registerUser(userData);
  }
}

.NET recommends this option.

Use try / catch

Another option is to attempt to do something, but throw and handle an error if it doesn’t work. If you prefer error handling over conditionals, then this is the alternative to "look before you leap".

For example:

const user = {
  name: 'Bob',
  age: null,
};

function handleUserEvent() {
  try {
    user.age *= 2;
  } catch (error) {
    // notify user that they haven't submitted their age, so their age can't be doubled
  }
}

This option is the convention in Python.

Return a Boolean

The last option is to attempt to do something without checking. Then, return a Boolean value that signifies whether it succeeded or not.

For example:

function buildTower() {
  if (!player.balance < 500) {
    return false;
  } else {
    player.balance -= 500;
    // do some stuff to build the tower
    return true;
  }
}

function main() {
  const wasSuccessful = buildTower();
  if (!wasSuccessful) {
    // notify the user that they don't have enough money
  } else {
    // continue normal execution
  }
}

This option is… acceptable… However, it has multiple disadvantages. It:

  • breaks the command-query separation principle
  • doesn’t work as well if the function needs to return a normal value. (The code would be messier to handle this case.)
  • tends to result in worse code structure than using "look before you leap". Namely, lower-level code now needs to do the check. This means that it needs access to more stuff and potentially separate systems. This may be bad for the structure of the code.

For more information on the "worse code" point, please see other posts on clean code. For example how to write clean code units and clean code and programming principles – The ultimate beginner’s guide.

Recommendations

My personal recommendations, in order of priority, are to:

  1. consider whether the code structure can be improved to remove the need for conditions. For example, use "tell, don’t ask".
  2. pick either "look before you leap" or "easier to ask for forgiveness rather than permission"
  3. return a Boolean

Final notes

So that’s it for this article. I hope that you found it useful.

As always, if any points were missed, or if you disagree with anything, or have any comments or feedback then please leave a comment below.

For the next steps, I recommend looking at the other articles in the error handling series.

Alright, thanks and see you next time.

Credits

Image credits:

  • Train tracks – Photo by Nubia Navarro (nubikini) from Pexels
  • Legos – Photo by Daniel Cheung on Unsplash
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments