
How to use exceptions correctly
- Transfer
Using exceptions to control the flow of a program is a long - standing topic . I would like to summarize this topic and give examples of the correct and incorrect use of exceptions.
In most cases, we read the code more often than we write. Most programming practices are aimed at simplifying the understanding of the code: the simpler the code, the less bugs it contains and the easier it is to support.
Using exceptions to control the progress of a program masks the programmer’s intentions, so this is considered bad practice.
The ProcessItem method complicates the understanding of the code, because it is impossible to say what are the possible results of its implementation simply by looking at its signature. Such a code violates the principle of least surprise , because throws an exception even in the case of a positive outcome.
In this particular case, the solution is obvious - you need to replace throwing an exception with a return of a Boolean value. Let's look at more complex examples.
Perhaps the most common practice in the context of exceptions is to use them if you receive invalid input.
Obviously, this approach has some advantages: it allows us to quickly "return" from any method directly to the catch block of the CreateEmployee method.
Now let's look at the following example:
What do these two samples have in common? Both of them allow you to interrupt the current thread of execution and quickly jump to a specific point in the code. The only problem with this code is that it significantly degrades readability. Both approaches make it difficult to understand the code, which is why the use of exceptions to control the flow of program execution is often equalized using goto.
When using exceptions, it is difficult to understand exactly where they are caught. You can wrap the code that throws the exception in a try / catch block in the same method, or you can put the try / catch block several levels up the stack. You can never know for sure whether this is done intentionally or not:
The only way to find out is to analyze the entire stack. The exceptions used for validation make the code much less readable because they do not clearly show the intentions of the developer. It is impossible to just look at such a code and say what might go wrong and how to react to it .
Is there a better way? Of course:
Specifying all checks explicitly makes your intentions much more clear. This version of the method is simple and obvious.
So when to use exceptions? The main goal of exceptions is surprise! - indicate an exceptional situation in the application. An exceptional situation is a situation in which you do not know what to do and the best way out for you is to stop the current operation (possibly with preliminary logging of the error details).
Examples of exceptional situations may include problems connecting to the database, lack of necessary configuration files, etc. Validation errors are not an exception , as a method that checks incoming data, by definition, expects them to be incorrect.
Another example of the correct use of exceptions is code contract validation. You, as the author of a class, expect customers of this class to abide by its contracts. The situation in which the method contract is not respected is exceptional and deserves to throw an exception.
Whether the situation is exceptional depends on the context. The developer of a third-party library may not know how to deal with problems connecting to the database, because he does not know in what context his library will be used.
In case of a similar problem, the library developer is not able to do anything with it, so throwing an exception would be a suitable solution. You can take Entity Framework or NHibernate as an example: they expect that the database is always available and if it is not, throw an exception.
On the other hand, a developer using the library can expectthat the database goes offline from time to time and develop your application with this in mind. In the event of a database failure, the client application may try to repeat the same operation or display a message to the user with a proposal to repeat the operation later.
Thus, the situation can be exceptional from the point of view of the underlying code and expected from the point of view of the client code. How in this case to work with the exceptions thrown by such a library?
Such exceptions should be caught as close as possible to the code that throws them . If this is not the case, your code will have the same flaws as the sample code with goto: it will not be possible to understand where this exception is handled without analyzing the entire call stack.
As you can see in the example above, the SaveCustomer method expects database problems and intentionally catches all errors related to this. It returns a Boolean flag, which is then processed by code upstream.
The SaveCustomer method has an understandable signature telling us that there may be problems during the client saving process, that these problems are expected and that you should check the return value to make sure everything is in order.
It is worth noting the widely known practice that is applicable in this case: you do not need to wrap such code in a generic handler. The Generic handler claims that any exceptions are expected, which is essentially not true.
If you really expect any exceptions, you do it for a very limited number of them, for which you know for sure that you can handle them. Putting a generic handler causes you to swallow exceptions you don't expect, putting the application in an inconsistent state.
The only situation in which generic handlers are applicable is placing them at the highest level on the application stack to catch all exceptions not caught by the code below, in order to secure them. You should not try to handle such exceptions; all you can do is close the application (in the case of a stateful application) or terminate the current operation (in the case of a stateless application).
How often do you come across code like this?
This is an example of incorrect use of a generic exception handler. The code above implies that all exceptions coming from the body of the method are a sign of an error in the process of creating a custom. What is the problem with this code?
In addition to the similarity with the “goto” semantics discussed above, the problem is that the exception coming to the catch block may not be the exception we know. The exception can be either the ArgumentException that we expect, or ContractViolationException. In the latter case, we hide the bug, pretending that we know how to handle such an exception.
Subsequently, a similar approach is taken by developers who want to protect their application from unexpected crashes. In fact, such an approach only masks problems, making the process of catching them more complicated.
The best way to deal with unexpected exceptions is to stop the current operation completely and prevent the spread of an inconsistent state across the application.
Link to the original article: Exceptions for flow control in C #
Exceptions instead of ifs: why not?
In most cases, we read the code more often than we write. Most programming practices are aimed at simplifying the understanding of the code: the simpler the code, the less bugs it contains and the easier it is to support.
Using exceptions to control the progress of a program masks the programmer’s intentions, so this is considered bad practice.
public void ProcessItem(Item item)
{
if (_knownItems.Contains(item))
{
// Do something
throw new SuccessException();
}
else
{
throw new FailureException();
}
}
The ProcessItem method complicates the understanding of the code, because it is impossible to say what are the possible results of its implementation simply by looking at its signature. Such a code violates the principle of least surprise , because throws an exception even in the case of a positive outcome.
In this particular case, the solution is obvious - you need to replace throwing an exception with a return of a Boolean value. Let's look at more complex examples.
Exceptions for validating incoming data
Perhaps the most common practice in the context of exceptions is to use them if you receive invalid input.
public class EmployeeController : Controller
{
[HttpPost]
public ActionResult CreateEmployee(string name, int departmentId)
{
try
{
ValidateName(name);
Department department = GetDepartment(departmentId);
// Rest of the method
}
catch (ValidationException ex)
{
// Return view with error
}
}
private void ValidateName(string name)
{
if (string.IsNullOrWhiteSpace(name))
throw new ValidationException(“Name cannot be empty”);
if (name.Length > 100)
throw new ValidationException(“Name length cannot exceed 100 characters”);
}
private Department GetDepartment(int departmentId)
{
using (EmployeeContext context = new EmployeeContext())
{
Department department = context.Departments
.SingleOrDefault(x => x.Id == departmentId);
if (department == null)
throw new ValidationException(“Department with such Id does not exist”);
return department;
}
}
}
Obviously, this approach has some advantages: it allows us to quickly "return" from any method directly to the catch block of the CreateEmployee method.
Now let's look at the following example:
public static Employee FindAndProcessEmployee(IList employees, string taskName)
{
Employee found = null;
foreach (Employee employee in employees)
{
foreach (Task task in employee.Tasks)
{
if (task.Name == taskName)
{
found = employee;
goto M1;
}
}
}
// Some code
M1:
found.IsProcessed = true;
return found;
}
What do these two samples have in common? Both of them allow you to interrupt the current thread of execution and quickly jump to a specific point in the code. The only problem with this code is that it significantly degrades readability. Both approaches make it difficult to understand the code, which is why the use of exceptions to control the flow of program execution is often equalized using goto.
When using exceptions, it is difficult to understand exactly where they are caught. You can wrap the code that throws the exception in a try / catch block in the same method, or you can put the try / catch block several levels up the stack. You can never know for sure whether this is done intentionally or not:
public Employee CreateEmployee(string name, int departmentId)
{
// Это баг или метод специально был помещен сюда без try/catch блока?
ValidateName(name);
// Rest of the method
}
The only way to find out is to analyze the entire stack. The exceptions used for validation make the code much less readable because they do not clearly show the intentions of the developer. It is impossible to just look at such a code and say what might go wrong and how to react to it .
Is there a better way? Of course:
[HttpPost]
public ActionResult CreateEmployee(string name, int departmentId)
{
if (!IsNameValid(name))
{
// Return view with error
}
if (!IsDepartmentValid(departmentId))
{
// Return view with another error
}
Employee employee = new Employee(name, departmentId);
// Rest of the method
}
Specifying all checks explicitly makes your intentions much more clear. This version of the method is simple and obvious.
Exceptions for Exceptions
So when to use exceptions? The main goal of exceptions is surprise! - indicate an exceptional situation in the application. An exceptional situation is a situation in which you do not know what to do and the best way out for you is to stop the current operation (possibly with preliminary logging of the error details).
Examples of exceptional situations may include problems connecting to the database, lack of necessary configuration files, etc. Validation errors are not an exception , as a method that checks incoming data, by definition, expects them to be incorrect.
Another example of the correct use of exceptions is code contract validation. You, as the author of a class, expect customers of this class to abide by its contracts. The situation in which the method contract is not respected is exceptional and deserves to throw an exception.
How to deal with exceptions thrown by other libraries?
Whether the situation is exceptional depends on the context. The developer of a third-party library may not know how to deal with problems connecting to the database, because he does not know in what context his library will be used.
In case of a similar problem, the library developer is not able to do anything with it, so throwing an exception would be a suitable solution. You can take Entity Framework or NHibernate as an example: they expect that the database is always available and if it is not, throw an exception.
On the other hand, a developer using the library can expectthat the database goes offline from time to time and develop your application with this in mind. In the event of a database failure, the client application may try to repeat the same operation or display a message to the user with a proposal to repeat the operation later.
Thus, the situation can be exceptional from the point of view of the underlying code and expected from the point of view of the client code. How in this case to work with the exceptions thrown by such a library?
Such exceptions should be caught as close as possible to the code that throws them . If this is not the case, your code will have the same flaws as the sample code with goto: it will not be possible to understand where this exception is handled without analyzing the entire call stack.
public void CreateCustomer(string name)
{
Customer customer = new Customer(name);
bool result = SaveCustomer(customer);
if (!result)
{
MessageBox.Show(“Error connecting to the database. Please try again later.”);
}
}
private bool SaveCustomer(Customer customer)
{
try
{
using (MyContext context = new MyContext())
{
context.Customers.Add(customer);
context.SaveChanges();
}
return true;
}
catch (DbUpdateException ex)
{
return false;
}
}
As you can see in the example above, the SaveCustomer method expects database problems and intentionally catches all errors related to this. It returns a Boolean flag, which is then processed by code upstream.
The SaveCustomer method has an understandable signature telling us that there may be problems during the client saving process, that these problems are expected and that you should check the return value to make sure everything is in order.
It is worth noting the widely known practice that is applicable in this case: you do not need to wrap such code in a generic handler. The Generic handler claims that any exceptions are expected, which is essentially not true.
If you really expect any exceptions, you do it for a very limited number of them, for which you know for sure that you can handle them. Putting a generic handler causes you to swallow exceptions you don't expect, putting the application in an inconsistent state.
The only situation in which generic handlers are applicable is placing them at the highest level on the application stack to catch all exceptions not caught by the code below, in order to secure them. You should not try to handle such exceptions; all you can do is close the application (in the case of a stateful application) or terminate the current operation (in the case of a stateless application).
Exceptions and fail-fast principle
How often do you come across code like this?
public bool CreateCustomer(int managerId, string addressString, string departmentName)
{
try
{
Manager manager = GetManager(managerId);
Address address = CreateAddress(addressString);
Department department = GetDepartment(departmentName);
CreateCustomerCore(manager, address, department);
return true;
}
catch (Exception ex)
{
_logger.Log(ex);
return false;
}
}
This is an example of incorrect use of a generic exception handler. The code above implies that all exceptions coming from the body of the method are a sign of an error in the process of creating a custom. What is the problem with this code?
In addition to the similarity with the “goto” semantics discussed above, the problem is that the exception coming to the catch block may not be the exception we know. The exception can be either the ArgumentException that we expect, or ContractViolationException. In the latter case, we hide the bug, pretending that we know how to handle such an exception.
Subsequently, a similar approach is taken by developers who want to protect their application from unexpected crashes. In fact, such an approach only masks problems, making the process of catching them more complicated.
The best way to deal with unexpected exceptions is to stop the current operation completely and prevent the spread of an inconsistent state across the application.
Conclusion
- Throw an exception if and only if you need to declare an exception in the code.
- Use return values when validating incoming data.
- If you know how to handle exceptions thrown by the library, do it as close as possible to the code that throws them.
- If you are dealing with an unexpected exception, stop the current operation completely. Don't pretend to know how to deal with such exceptions.
Link to the original article: Exceptions for flow control in C #