Exception handling the right way – 7 simple steps

Some days ago, I had an interesting discussion regarding error and exception handling with my coworker.

He was surprised, how many exceptions my code actually throws.

However, we had some excellent points, where exceptions are not always the best choice.

1. Exceptions show only one error

No matter what you do, or how you do, the user must be informed as good as possible about the current issue. The user can only solve the issue, if he exactly knows what is going wrong.

If the user made several mistakes, he needs to receive some kind of summary. There is nothing more annoying as receiving one error message, fixing it and receiving another one. Did the fix cause the issue or were there several issues?

Unfortunately, this cannot be solved with exception. Actually, it can be, but it shouldn’t.

You need to compose all validation issues in one single object and return it back to the user.

class IssueValidator
{
	public bool IsValid(Issue issue)
	{
		var errors = Validate(issue);
		
		return errors.Count == 0;
	}
	
	public List<string> Validate(Issue issue)
	{
		var errors = new List<string>();
		
		var issueName = issue.GetName();
		var description = issue.GetDescription();
		
		if (!string.IsNullOrWhiteSpace(issueName))
		{
			errors.Add("The name of the issue cannot be empty. Please define a name first.");
			
		}
		if (!string.IsNullOrWhiteSpace(issueName))
		{
			errors.Add("Please write a short description first.");
		}
		
		return errors;
	}
	
}

If your data class is more complex, feel free to combine multiple validators by creating sub validator instances and merge the output.

In most cases, you don’t need any kind of complex return types. A simple string does the job very well.

2. Do not validate and throw

What is the responsibility of an validator?

Correct: to validate an any kind object.

Which results do you expect?

Exactly: Whenever the object is valid or not (bool) and (if the data class is more complex) all the issues.

If the object is invalid, and the validator could determine this, why the heck there is a need to throw an exception?

Yes, the object is actually invalid, but there is no need to throw an exception within the validator. He did his job. Throw only, if the validator cannot (for any reason) perform his job.

As you can see in the example above, my validator contain two methods. One to check whenever the object is valid or not and the second to get all validation issues. And this is actually what you need.

3. Do validate or throw instead

To validate whenever something is valid or now, you have exactly two possibilities:

  1. Check if an operation can be performed.
  2. Check if an operation could be performed.

The difference is, when to validate.

Let me argue:

An operation (or system) must have only one responsibility (single responsibility principle). This operation must be performed perfectly. If this is not possible – for any reason – it must throw an Exception. Do not return magic values or even null. Nope, throw an exception.

To prevent this, we can try to predict if this operation can be performed. Do not call this operation/method if this is not the case.

Let me make a simple example:

double Divide(double divident, double divisor)
{
	if (divisor == 0)
		throw new DivideByZeroException();
	
	return divident / divisor;
}

If we need to implement the Divide method, we know that this operation cannot be performed if we try to divide by zero. Therefore, we need to throw an exception here!

Anyway, we can predict this and do something else:

var divident = 100;
var divisor = 0;
if (divisor > 0) 
{
	var number = Divide(divident, divisor);
}

A bad solution for the above (divide) method would be:

double Divide(double divident, double divisor)
{
	if (divisor == 0)
		return 0;
	
	return divident / divisor;
}

Why is this not good? Because some valid combinations (0 / n) could also return this result. For this reason, this method is not reliable.

3. How to throw exceptions

Every language contains any kind of native Exceptions. C# for example, contain something like ArgumentException, NullReferenceException, FileNotFoundException, etc.pp. Java contain e.g. NullPointerException, ArrayIndexOutOfBoundsException or something like that. PHP has only Exception and JavaScript does not have any, but you can throw every object.

Actually, there is no need to know all these exceptions. Just forget them. They do not exist for your framework 🙂

Always create a new class and inherit from the base class exception.

In C# or other languages with multiple constructor support, it could be similar to that:

public class CalculatorException : Exception
{
    public CalculatorException()
    {
    }

    public CalculatorException(string message) : base(message)
    {
    }

    public CalculatorException(string message, Exception inner) : base(message, inner)
    {
    }
}

Pro-tip: if you throw an exception, always write it inside your inline documentation. Write down which exception can be thrown, under which condition as well as the error message.

We will need this information in the next step.

4. How to catch exceptions

Catching exceptions sounds straightforward. Actually, it doesn’t.

By implementing a method, which can throw an exception (you can determine this by reading the inline documentation) you must always ask you:

“Can this exception be handled somehow?” It is totally valid to catch the exception and throw another one. In this case, you need to make sure, the user will get some additional benefit. Either by describing the root cause or adding some more parameters.

Whenever you catch and wrap it into another one, do not wrap an Exception of the same type into another one. In this example, it is obvious, but you can catch some CalculatorException and put it into another CalculatorException.

try
{
	Divide(5, 0);
}
catch(DivideByZeroException ex) when (ex.GetType() != typeof(CalculatorException))
{
	throw new CalculatorException("operation could not be calculated", ex);
}

Whenever you mask an exception, always put the exception as an additional parameter to the new exception. If you don’t do this, it will be the same effect as exception swallowing.

5. Never swallow exceptions

Swallowing exceptions is something like: “Hey here is an issue, but I don’t tell you.”

This approach can be expensive.

Let me tell you a little story.

It was a medium small company, let call it “FooSoft”. FooSoft had a fantastic product with a fabulous sales team. This sales team sold this product to many customers.

But some customers wanted FooSoft to extend the product based on their needs. In theory, this wouldn’t be an issue.

It comes to the day, when FooSoft had more bug issues than improvements within the backlog. So, the lead developer decided to surround every critical statement with a try-catch.

Everyone was happy. The software was as stable as hell.

Unfortunately, the bugs didn’t disappear. They remain. This product falsified existing data.

FooSoft doesn’t exist anymore because the product was designed for lawyers.

Ouch!

6. Never catch exception

Catching the generic exception is always a bad idea.

try {
    Divide(5, 0);
} catch(Exception ex) {
    // do something
}

It is something like: “Hey, I can handle every issue in the world. Even a nuclear war!”.

This approach can also lead to a different expectation.

Consider the following C# Code:

try 
{
	var config = File.ReadAllText("C:\\config.ini");
	// parse config file and create a config object within 7431 LoC
} 
catch(Exception ex) 
{
	Console.WriteLine("config not found!");
}

We have several issues here. This code would only run correct, if the file actually does not exist. But what if:

  • The user does not have permission to read the file?
  • The config file itself is invalid?
  • The developer is an idiot and implement a bug within the parsing algorithm?

Yeah, the user will receive the message, that the config file is not present, what is absolutely untrue. To be honest, this is behavior would be very misleading.

7. Never use exceptions for your control flow

Remember the 4th point, where I’ve mentioned to handle exceptions as good as possible?

It is a very thin line between using exceptions for control flow and use it only for any kind of error handling.

An exception is something, what is – as the name indicates – some unexpected behavior.

var x = int.Parse("one");
Console.WriteLine(x);

This code would throw a FormatException in C#.

While this one, would print ‘0’ on the screen:

int.TryParse("one", out var x);
Console.WriteLine(x);

But both methods are perfectly valid and should be used wisely.

For example, if you have any fallback value and expect some invalid values, use TryParse. If you rely on the result (e.g. parsing a salary list) use the Parse()-method. But in this case, the user must be notified, that the salary list is invalid and all operations must be rolled back.

Conclusion

Exception handling is very easy in theory, but very complex in production. From my point of view, it is easy because we (developer) do not consider edge cases often enough.

We need to divide two numbers, so we do it. But we often ignore the possible division by zero.

Another situation is, what we want to read a config file and expect, that this file is existing. But what if this is not the case? Or the user does not have enough permissions to read it?

There are many examples out there which needs to consider.

So, every time when the operation could not be performed, just throw a specific exception and handle it later on. But handle it only, if you can really do it. Do not try to resolve issues, when you cannot do this. Just follow the SHISHO-principle (shit-in-shit-out).

Leave a Reply

Your email address will not be published. Required fields are marked *