DEV Community

loading...

Is there a price to pay for using exceptions?

Juani Galan 🇺🇾
Sr. Software Engineer @MercadoLibre working with React + Node JS, Redux, Java8. I love creating the best web experiences for everyone, from the developers that create the apps to the end users!
・5 min read

Have you ever wondered how exceptions start to shape and have an impact on your project? Are they useful? Do they affect performance in anyway?

During a recent class at my University there was a specific discussion related to exceptions and how they can take a toll on your project. Most of the conversation involved arguments about how exceptions work, when are they useful and how they affect your project, from several perspectives; code readability, coupling, performance and overall usefulness of exceptions. I wanted to explore this idea a little bit and see what implications exceptions have when you start using them.

In my experience, exception handling is a very vast and discussed topic, with several different opinions on it, which usually are formed due to each individual’s personal experience. I have seen programmers doing extensive and defensive code by doing a try/catch for every single operation or method execution, to surround everything in a big try/catch with one exception handling, to nothing at all.

Is exception handling useful?

Yes, it is very handy in scenarios were you are accessing specific resources that can fail in a critical way, for example, accessing a database, an API call, opening a file, etc. These situations will eventually fail, due to a timeout, a resource cannot be found, you did not comply with a specific rule in your database such as a foreign key or a required field, or an unexpected error. And exceptions allow you to recover from those scenarios and show something to the user, an Error Page, a Page not Found, a “Sorry, try again later”. Something meaningful that will let the user know that something went wrong, and not just have their window or browser closed or frozen.

Exceptions are very useful as well when you are developing libraries that are meant to be used by other teams or if you want to contribute to open source. Usually people don’t take the time to read how a specific library works, and they just use it. If there is something that they missed, throwing an exception would be a great way to educate them and control how they interact with your library.

But sometimes, could it be a little bit too much?

On the other hand, we have other scenarios were using an Exception would be a bit of an overkill, since you maybe could solve that scenario by other simple control structures.

Take the following example: You have a class called Employee, that has a constructor that accepts 1 parameter, a name. Imagine that name is required, and you cannot create an Employee without a name. So you decide to implement a custom exception, that when name == null, you throw this exception to anyone who wants to use it, which we are going to call EmployeeException. This would imply that:

  • Every part of your application that wants to create an employee, would need to surround the code in a try catch to catch that specific error
  • Every part of your application would have a dependency to both Employee and EmployeeException, which would increase coupling that could lead to maintenance problems in the future.
  • The try/catch would be a little bit confusing, since you would be throwing an EmployeeException. Was it because the name was empty? or did I have to set other variables as well? Or the name needs to have a specific format? Obviously you can change the name of the exception to something more understandable, but if you are working in a team or using an external library, and someone else coded that Exception, it could lead to confusion. And imagine if there are several errors that could throw that Exception? Would you create a custom exception for each of them? That could lead to increase more the coupling between your project and Employee.
  • It could have an impact on performance (I will explain this later on)

We could potentially resolve this by adding a static method to the Employee class that from a specific set of parameters, returns a boolean that shows if the Employee can be created or not and that would help with the points I stated above. This would be one of those alternatives to exception handling, validate inputs and create validation methods.

And how about the performance of my application?

For this discussion, I thought that I could take a more practical approach. So I created a simple Java Project that does follows the Employee scenario that I mentioned before. Basically we create an Employee with a constructor(String name) and we throw an exception if name is null. And we do the same but we add an isValid method to check before creating the Employee. I tested this using a for loop and running the constructor several times, because in order to see a difference in time execution, I need to create more than 1 Employee. So I tested the following scenarios:

1- Create an Employee with name = null and surround it with a try/catch.

Create an Employee with name = null and surround it with a try/catch.

2- Create an Employee where half of them will throw an error and half of them would pass and surround it with a try/catch.

second scenario

3- Create an Employee where half of them will throw an error and half of them would pass and surround it with a try/catch, and also on the catch, I execute ex.getStackTrace(), just to simulate exception handling.

third scenario

4- Create an Employee where a fifth of them will throw an error and surround it with a try/catch.

fourth scenario

5- Create an Employee with name = “test” and surround it with a try/catch, but since it is a valid name, no exceptions are thrown

fifth

6- Create an Employee with name = “test”, but before I executed an isValid

sixth

I executed these scenarios with total set in 100, 1000, 10000, 100000, 1000000. And here are the results (measured in milliseconds):

results

  • For i = 100, there is not much of a difference, executions take from 0 to 1 ms
  • For i = 1000, scenarios 1, 2, 3 take 3 times more than scenarios 4, 5, 6. This starts to give the idea that handling an exception takes more time. Furthermore from number 5, we can notice that adding a try/catch block does not slow down performance, if no exception is thrown.
  • For i = 10000, scenarios 1, 2, 3 take 16 to 23 times more than the other scenarios
  • For i = 100000, it sky rockets to 136 ms and 154 ms for scenarios 1 and 3, and 5 ms and 4 ms for scenarios 5 and 6.
  • For i = 1000000, scenarios where exceptions are thrown and managed can take up to 1 second.

To sum up!

Overall, we can say that exceptions are obviously one of the pillars for programming languages. They enable us to recover from several critical scenarios and allow us to convey that information to the user. Moreover, it allows us to track these errors on the catch closure, so we can learn from these errors and iterate our project to prevent them.

On the other hand, we need to take into consideration that exceptions are not only affecting the end user, but also the developer. Custom exceptions can lead to problems with code maintenance, readability and performance. IMHO would say that it is more of a case-by-case analysis, sometimes they can be quite helpful, but in other scenarios it could lead to some headaches! Just make sure you give a little bit of thought before introducing exceptions into the mix.

Discussion (1)

Collapse
nickhristov profile image
Nick Hristov

First of all the way you think of exceptions is somewhat off. You should think of exceptions as precisely what they are: an exception to the normal flow of a program and a useful way of rolling many layers of stack.

Their value becomes apparent the moment you try to use a language that does not have them. To illustrate: you have written a flow in that language that has no exceptions. You are writing modular code and have a decent set of small functions. As you write a function that's three calls deep into the main function, you realize that there is an error condition that has to terminate the flow. So your function now needs to return a status, and error string, and the original intended value. Of course, the calling function has to have the code to handle the error status and propagate it up. It itself needs to have an error status an error message and the actual result as a result tuple. On top of this, your innermost function gets called from multiple places, and when it errors out you have no idea where the call came from (you have no stack traces, just error messages). This quickly becomes a giant PITA, and you decide that exceptions can be pretty handy.

Second, EmployeeException should not exist. Use IllegalArgumentException instead, it's built for that. For nulls, throw NPEs.

Third, use checked exceptions only when you want to force the caller to handle the exception. In most cases the use of checked exceptions is unnecessary. Timeouts are a good example for checked exceptions. Interrupted threads as well.

Fourth, don't worry about the performance of exceptions. Use them only when you truly need them (not for flow control), most of the other code you write will be a much bigger performance issue rather than how fast is JVM native code.

Fifth, this is not how you do micro-benchmarks. If you want to test the performance of exceptions then test just for that. Your code also allocates memory and prints. While memory allocs are fast, under high throughput you can exhaust your allocation pools and then go kicks in. That will totally mess up your numbers. You are also not accounting for JIT and for CPU pipelining and branch prediction. In short, micro-benchmarking is a bit of a dark art and requires a lot of time and careful set up to get right.

Forem Open with the Forem app