Introduction:
Exceptions are a main and crucial part of the software development industry. There is a saying in the industry -
Bugs will be there, there is nothing you can do to stop it but not able to answer where, how, and why the bug happens, is a problem.
In this article, we are going to uncover some of the basics of exceptions and how to handle them.
- What are Exceptions?
- Types of it.
- How to use it.
- Good practice.
- Bubbling up and some common mistakes.
- Real-life ideas.
What is an Exception?**
If a normal program execution flow is disrupted by an unintended event resulting in a different outcome is called an exception.
Let me give an example to make things more clear.
Suppose we have written a program that supposes to add two numbers and give us a result back. That's pretty much simple and straightforward. If this program executes as we want to then it's fine, but if it fails to return a valid result then we can assume some unwanted thing happened and because of this program failed to return a valid result, causing an unintended outcome.
Types of Exceptions:
In C#, There is one base class called Exception. It can be divided into two subparts.
- System Exception.
- Application Exception.
System Exceptions:
System exception is the base class for all built-in exception classes in .NET. There are many types of it. Common exceptions are -
- IndexOutOfRange
- NullReference
- InvalidOperation
- Argument
- ArgumentNull
- ArgumentOutOfRange
- AccessViolation
Application Exception:
It was recommended before, to derived if anyone wants to make their own custom exceptions based on a business rules violation. But now it was deprecated. Microsoft recommends using the Exception class for any custom stuff.
How to Use it?
To have a better understanding we use to try, catch/finally syntax to handle the exceptions.
The syntax looks like this
The idea is we want to write the suspected code into the try block and if the error happens we want to handle it (catch block) or give specific instruction after the error (finally block) has happened.
Good practice:
The good practice is to explicitly mention what type of exception you are receiving or throwing.
Let's take a look at some examples:
internal bool IsEmailValid(string email)
{
if (string.IsNullOrEmpty(email) throw new NullReferenceException("Field is empty");
// continue with the logic
}
public bool CreateAccount(string email) {
try {
if (IsEmailValid(email))
Console.WriteLine("Yahhooo"); return true;
}
Catch(NullReferenceException ex) {
Console.WriteLine($"{ex.Message}");
// handle
// log the error.
}
return false;
}
But if you are not sure, it's okay to use the Base class directly.
public bool CreateAccount(string email) {
try {
if (IsEmailValid(email))
Console.WriteLine("Yahhooo"); return true;
}
Catch(Exception ex) {
Console.WriteLine($"{ex.Message}");
// handle
// log the error.
}
return false;
}
The common misconceptions:
In general, I have seen a lot of developers misunderstand or misguided themselves about throw and throw ex.
If you understand this, it's great but if you have some kind of confusion about why and what's the difference between these two then let's clear things up together.
as Exception is a class and when we want to catch an Exception in the catch block, generally we want to give the exception type and a variable name to catch that type of exception. For example
try {
}
catch(NullReferenceException ex) {
throw ex;
}
Here ex is the name of a variable type NullReferenceException class (I have given this variable name ex, you can define whatever you feel right). So if we want to print it or log somewhere else it's easy to print the variable and get what happened.
try {
}
catch(NullReferenceException ex) {
Console.WriteLine(ex);
throw ex;
}
The problem lies in the last line, throw ex part. What throw does is pass out the exception into the previous layer (from where this specific method calls) as it is supposed to do. This is a valid code when any of the following conditions is true:
- If you are debugging.
- If you are in the first layer / very first starting point of your code like from your API code or your main program file.
- If you want to print or log this error into a logger file or in the cloud.
Problem? 😋
You should not leave throw ex into your library code or into your dependent layer. Why?
Because it will change your stack call information. (Call stack: Call stack information is the event where the exception's calling information is stored. Like where the first error happens)
Solution?
It's pretty simple, If you want to bubble up your exception without changing any information (retaining all of the correct information) you will use just throw not throw ex;
try {
}
catch(NullReferenceException) {
// like this
throw;
}
what it will do, is just pass all of the stack information to its called method.
When to use this?
- If you don't want to change any call stack info.
- If you are suspecting some part of your code is in the dependent layer.
Let's see some real code examples to verify this, shall we?
I have written two very simple methods, one is containing throw and another containing throw ex
Throw Ex
Throw
If you look into both of these images you will have the see the differences between these two ways. (I have uploaded this into GitHub if you want to take a look at it)
The Production life scenario:
If you are working for a company or working on a large project, it's very difficult to maintain lots of try-catch in the codebase. It doesn't make any sense to log everywhere. We are programmers why do we want to write more duplicate code just to log? We will write it once and modular generic that can be used everywhere. The concept is to make a global exception handler. In .net ecosystem, there are some specific ways to build the global exception handlers regardless if you are using .net framework or .net core, It's very easy now-a-days.