Clean Code: Beautiful Error Handling

Error handling is a trouble thing in writing robust codes. Everyone understands it is critical, especially for the library codes, but few could handle errors beautifully. In this post, I will discuss how we can write clean error handling codes.

Principle 1: Functions Without Side Effects

As we have discussed in Clean Functions, one function should do only a single job, which also helps remove its potential side effects.

Side effects are lies. Your function promises to do one thing, but it also does other hidden things.

Robert C. Martin, Clean Code, p44.

Functions with side effects are everywhere. For example, one may write a method with an initialization call. However, it is dangerous to initialize an object for every function call since it may remove previously-stored data. The only reasonable situation to add an initialization call is at the beginning of a test function because usually, tests are independent.

I support that improper error handling can also introduce side effects. For example, some functions may clean the data before throwing an error, while others not. From the programmers’ perspective, they can hardly differentiate them and will always forget to include an external error handler function for the second case.

To remove side effects from error handling, we have the following solution.

Principle 2: Prefer Exceptions to Returning Error Codes

Compared to using some nested IF-ELSE to immediately check the error, I believe a TRY-CATCH is a better solution for error handling. To write clean codes, you should have your main codes locate closely. However, if you write IF-ELSE to check the error immediately and handle them there, you compensate with your code locality.

Consider the following example:

if (deletePage(page) == E_OK) {
  if (registry.deleteReference(page.name) == E_OK) {
    if (configKeys.deleteKey(page.name.makeKey() == E_OK) {
      logger.log("page deleted");
    } else {
      logger.log("configKey not deleted");
    }
  } else {
    logger.log("deleteReference from registry failed");
  }
}  else {
  logger.log("delete failed");
  return E_ERROR;
}
try {
  deletePage(page);
  registry.deleteReference(page.name);
  configKeys.deleteKey(page.name.makeKey());
}
catch (Exception e) {
  logger.log(e.getMessage());
}

The second one is definitely more readable and comprehensive because its main codes are located together.

Different languages may have different supports for error handling. In some languages like Go, it is common to return an error for all critical functions. Its reason is to ensure check for every function call like C programs. Meanwhile, it also provides Defer, Panic, and Recover support where you can adopt TRY-CATCH styles. I will have another new post talking about error handling practice in Go, and you can also refer to the official guide: Error handling and Go.


Read more about the Clean Code series:

To buy the book:

3 thoughts on “Clean Code: Beautiful Error Handling

Leave a Reply

Your email address will not be published.