Acerca de mi

Soy Javier Ferrer González.

Actualmente trabajo como desarrollador web backend en Uvinum, estoy finalizando el Grado en Ingeniería Informática en la FIB-UPC y doy clases en el Máster de Programación Web de Alto Rendimiento de LaSalle BCN.

En este blog encontrarás posts sobre internet, programación y reflexiones variadas.

Si quieres saber más acerca de mi, aquí tienes mi Currículum y el formulario de contacto.
Acerca de mi

Why throwing exceptions is better than returning error codes

In this post I would like to make a step by step guide specifying the pros and cons of every possible solution in order to illustrate why is better to throw an Exception than to return an error code. Also, we’ll see how SOLID principles can be applied in this kind of scenarios in order to improve our Software Design and avoid code smells like the switch statement.

Return error code vs throw Exception

1. Base scenario – Error codes & switch statement

Here we have an example of a possible login validation service:

And here we have a probable use of this method

Problems:

  • Code semantics. It’s quite difficult to understand what is happening there without reading the entire code. In order to know what does it mean to have a “-1” as a result of executing the checkLogin method, I have to checkout how I deal with it (the “Invalid credentials” message logged in the errorLogger), or I have to take a look at the checkLogin method implementation.
  • What about changing an error code value? I would have to review all the usages of the checkLogin method in order to change the received values!

2. Replace Magic Number with Symbolic Constant

Here you have another approach solving the 2 main problems described before:

And, as in the previous step, here you can see one possible use of this method:

Improvements over the previous step:

  • We’ve applied the Replace Magic Number with Symbolic Constant refactor, which as we said before, improves the semantics of our code so that now we’ve a more readable code.
  • If we want to edit one error code value, we only have to edit the value of the constant, so it’s more tolerant to changes.

Problems:

  • The most important one arises if we want to make an Extract Method refactor inside the checkLogin method: We’ll have to carry the error code from the checkLogin method until the switch-case statement which captures and deals with the error. Making all the intermediate methods conscience about this kind of behavior and having to return it. We probably need more flexibility in order to delegate in outer layers of our application the logic to deal with this exceptional situation.

3. Replace Error Code with Exception

Taking into account the problem described before, the only thing which can help in this case is to apply the Replace Error Code with Exception refactor, but for the sake of the learning process, we’ll apply it in a weird way in order to see the benefits between the different intermediate approaches. We could end up with something like the following:

Here you have how we would deal with the exception:

Improvements over the previous step:

  • With this approach, we’re solving the problem of having the error code across our entire application: from the error trigger point, to the error catching one. We can simply don’t take into account this exception in one point of our application, making the outer points having to deal with it.

Problems:

Pokémon Exception Handling (gotta catch'em all)

Pokémon Exception Handling (gotta catch’em all)

  • We’re introducing another kind of mistake here: we’re catching all kind of RuntimeException that occurs below the execution flow of the checkLogin method. The problem is clear: What if the checkLogin method makes some DB queries in order to validate something and the DB server is down?
    In this case, we’ll trow a RuntimeException below the execution flow of the checkLogin method and we’ll catch it at this point of the application, and as you can guess, we’re not the ones who has the responsibility to deal with this other kind of exceptions.
    This kind of scenarios already has a name: Pokemon Exception Handling (Pokemon – gotta catch ‘em all).
  • Additionally, here we’re duplicating the error string. Something we could solve extracting it to a constant as we’ve done with the magic number, but since it is a specific situation in this point of the example, I prefer to focus on the previous point problem instead of distracting you with additional unnecessary code modifications.

3.1. Propagate other kinds of exceptions

We could make a workaround in the exception handling like this one:

Improvements over the previous step:

  • We don’t have to deal with exceptions with error codes different than the login related ones. We’re propagating these other kind of exceptions to the outer layers of our application.

Problems:

  • What about collisions in terms of error code numbers? If another kind of error has the same error code than the login related ones, we’ll catch it and deal with it as if it were a login related error.
  • Semantics. We’re letting go the opportunity to reflect in code our use case semantics. We’re using a generic RuntimeException instead of taking profit of this opportunity and making an specific kind of Exception for our specific case.

4. Create specific RuntimeException child classes

So, finally, we could end up with one specific exception for each exceptional situation, something like:

Using this approach, we could deal with the exceptions as you can see here:

Improvements over the previous step:

  • We don’t have possible collisions in terms of exception error codes, because we’re “using the exception class as its identifier”.
  • Also, as a collateral effect, our code has more semantics.

Problems:

  • Even in this case,  we’re not dealing with the root of the problem: We’re violating the Open/Closed Principle from the SOLID principles. We still have to modify “the hidden switch-case” that we’ve represented as different catch clauses. This kind of switch clauses are considered a Code Smell (even if they aren’t a pure “switch” clause such as the serie of catch sentences or a serie of elseif). The explanation is simple:
    • Now, we’ll have to add another catch clause if we’ve to add another business rule that specify to raise a login error (banned users for instance).
    • And if we’ve any other call to the checkLogin method in our application, we’ll also have to edit those other try-catchs.
  • Furthermore, we’re repeating code. In our specific case, we’ve to deal with the two errors in the same way. So we could abstract this kind of behavior.

5. Create an intermediate abstract Exception class

So finally, we can abstract this case thanks to an abstract exception class for the login use case exceptions, having the following class structure in terms of exceptions:

Invalid login exception classes hierarchy

Invalid login exception classes hierarchy

We’ll continue using them as we’ve done in the previous step:

But in order to be able to capture both of them in the same catch, we’ll capture the abstract one and make use of the getCode and getMessage Exception class methods:

Improvements over the previous step:

  • This change could be done thanks to the Replace Type Code with Subclasses and Replace Conditional with Polymorphism refactorings, and with this approach, if we implement another kind of error in the login process (banned user for instance), we’ll only have to add another exception class without having to modify the point of dealing with this new error, because it’s agnostic of the specific type of error. This makes our code even more tolerant to changes and totally OCP compliant. Here you have the example of adding this new case:

The new exception class would have to extend from the previously described InvalidLoginException abstract class in order to be able to catch it:

Invalid login exception classes hierarchy v2

Invalid login exception classes hierarchy v2

We can throw it as the previous ones:

And as you can imagine, there’re no modifications to the try-catch block because of the previous exposed reason (extending from the InvalidLoginException class):

Problems:

  • Now, we’ve too many logic inside the checkLogin method. It’s easy to suppose that it would have too many lines of code, making it more difficult to read.
  • We’ll not be able to reutilize some of the validations.

5.1. Encapsulating the login validation

At this point, we can suppose that the business logic implemented in the checkLogin function is too long, so we can make use of the Extract Method refactoring in order to leave a more readable and reusable code:

The outer logic will remain as:

Conclusions

Once we’ve arrived to this point, we could have a sensation of “good enough” with our code, but taking a closer look at the final checkLogin method, we can see some kind of generic behavior there. I mean, we could make a 6th iteration with our solution in order to implement an observer design patter compositing the userValidator service with all the validators. But it could leave our class structure a little bit over-engineered and it’s something out of this post scope 🙂

By the way, our design has a very little flaw: What if someone extends the InvalidLoginException exception not defining a specific code and message?
We could ensure that the login error code and message are present applying the template method design pattern with an abstract query method defined in the InvalidLoginException class. But for this specific case, I don’t find it necessary despite being a robuster solution. As you can imagine, it depends on your case and context.

Here you have the complete source code of each iteration. I plan to publish all the blog posts source code in this GitHub repository :).

I hope you’ve learned why is it better to use exceptions and how to apply some refactorings in order to avoid some code smells. If you have another proposal don’t hesitate and leave a comment below!

Share this Story

Posts relacionados

6 Comments

  1. julienrf

    3 febrero, 2015 at 0:08

    Hi, please have a look at [Try](http://scala-lang.org/api/current/#scala.util.Try) (similar to returning errors as values but without the hassle of carrying them everywhere).

    Responder

    • Javier Ferrer González

      8 febrero, 2015 at 20:15

      Hi Julien, thanks for your comment, this “Try” class it’s quite interesting!

      By the way, if I’ve understood it properly, it only allows you to specify 2 possible cases (Success or Failure) so, at the end, it’s true that you will not have to carry on with the errors, but you’ll incur in the problems described on the step 3 (catching all kind of failures below that execution point without providing semantics).

      Nevertheless, it could be useful for some scenarios 🙂

      Responder

  2. Gert

    8 febrero, 2015 at 19:38

    • Javier Ferrer González

      8 febrero, 2015 at 20:04

      Hi Gert, thanks for your comment. I really appreciate it and I’m sorry for the grammar mistake.

      I’ve fixed it 🙂

      Responder

  3. Kevin C

    10 marzo, 2015 at 1:34

    Thanks for a fantastic article. Love the iterative approach.

    Responder

Deja un comentario

Subscríbete por email

Recibe las noticias que se publiquen en tu buzón:

Google+

Facebook