Is there a price to pay for using exceptions?
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:
- 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.
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.
4. Create an Employee where a fifth of them will throw an error and half of them would pass and surround it with a try/catch.
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
6. Create an Employee with name = “test”, but before I executed an isValid.
I executed these scenarios with total set in 100, 1000, 10000, 100000, 1000000. And here are the 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 me 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 part, 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. I 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.