Exception handling is often a scary thought, usually left till it is too late and normally not done well enough, yet it is not too difficult to do well. Below are some thoughts to keep in mind next time you design, code and implement an application.
Some time ago, I read a very interesting article on exception oriented programming in MSDN, but I cannot find it again. I will try to document what I can remember from it, and what I have learned from it. If this article goes well, I am going to try to follow it up with more detail, and some samples of how it has worked for me.
I am planning on making this article 1 of 2 or 3, depending on responses and feedback.
I'm not the greatest .NET developer, and this article was not written with any specific technology in mind. Exception handling, like I describe below, is a concept far more than it is a catch block. Irrespective of what technology you develop in, if you need to do exception handling, this article might help.
The difference between delivering an Ok solution, and a great solution could lie in correct Exception Handling, and workable Exception Management.
What are Exceptions
The word exception means “An action/event that is not part of ordinary operations or standards”, and the same applies in programming.
Exceptions are only used when the “normal” flow of code is not followed, for example if data is read from a database and a value that is not compatible with the expected value is returned (this would typically be an application exception).
Often one exception could spawn one or more other exceptions with relevant data, for example if creating a Customer transaction throws an exception, a Business Exception can also be thrown to show that the Customer balance could have a problem.
Throwing Exceptions are not used for passing values between methods or procedures (return values for example), primitive types or classes must be used for this.
Exceptions are used in a wide variety of ways, we will focus on the following:
- Application Exceptions
- Security Exceptions
- Permissions Exceptions
- Business Exceptions
When to Throw an Exception
Exceptions can be thrown throughout your code to abort processing and return control to the first calling method with exception handling.
There is a significant difference between throwing an exception and returning (aborting) from your method, for example:
Method2 returns a value, control will flow to
Method2, if however
Method2 throws an exception,
Method1) will never be executed and the call stack will be unwound to the first exception handler or the highest level possible, possibly crashing the executing thread/application.
So throw an exception if you want to abort all the following statements, not when your method has completed and you want to exit. Keep in mind that later your method can be called from 3rd party methods, throwing an unnecessary exception could cause problems in calling functionality.
In summary: throw an exception if “normal” execution may not continue, return a value (or
null) when your method has completed processing and needs to return control back to the calling method.
When to Catch an Exception
The rule of thumb is to do as little as possible exception handling (it was designed by developers after all), but keep in mind that there is a fine line between simplicity and lacking.
The rule of thumb should be extended to say “Only catch an exception if you want to either:
- log it and re-throw it, or
- recover from it”
This can however become quite confusing and developer discretion needs to be applied.
If you are in a large method that calls many sub-methods, it could be useful to wrap each sub-method call in an exception handler, but remember to only do it if you want to log it or recover from it.
It is quite useful sometimes to handle an exception in your method, log it with additional data, and re-throw it.
The 2 most common places for handling exceptions are:
- in the user interface and
- in your service layer
Both of these need to be stable and highly available so handling exceptions here are very important.
It is important to have comprehensive exception handling where it is necessary (not lacking).
In a service layer, you want to ensure that every method has sufficient exception handling to ensure that the service stays available. Ensure that no matter what the calling method does, the service will stay available.
In a user interface you want to ensure that no matter what he user does, the application will stay stable, typically every control event handler needs to be handled as the actions of the user could cause an exception to be thrown.
If in doubt, handle exceptions in a logical way, but if you are writing large exception handling code blocks, you are probably not doing it correctly.
Assumptions must be made throughout the application code. Typically the “main success scenario” is where we assume everything is in order to complete a process, for example the required Customer and Items all exist to be able to complete a Sales Order. When a User enters an invalid Customer ID, we have an exception to the rule/assumption that must be handled.
Another example is where we assume the data type returned by a stored procedure is the same during production as it was during development. We do not test the data type of every value returned as that would add significant overheads, and if the data types do not match we definitely have an exception to the normal execution since something changed to be incorrect.
Assumptions are often “Branched” to cater for real world scenarios, so instead of letting an exception be thrown because a
Customer does not exist for an
Order, we first test if the
Customer exists, and respond accordingly.
Choosing whether to branch the assumption or to handle the exception often depends on the situation and architecture of the application or service.
Branching a process for every possible problem will cause you to write more branching code than process code, so make sure to only branch where necessary.
Logging of Exceptions
Logging of exceptions is only slightly less important than catching the exceptions. If exceptions are not logged, it will be very hard to debug complex exceptions, and monitoring of the health of applications becomes virtually impossible.
In services or background processing, logging of exceptions are just as important as catching them. In a user interface it can be acceptable to only display the exception, entered values that fail data type validation is a good example of this.
It is important to store exception information in a centrally reportable data store, like a database, for example, it is however much more important to get the exception logged.
In production environments, it can happen that security configuration for data store access becomes invalid which could cause the logging of the exception in the centrally reportable data store impossible, in which case it is important to have a second data store (preferably of different and less complex technology) available to log the exception detail. Most operating systems provide this natively on the local machine, and the native logs can also be monitored.
When logging an exception, it is important to log as much information as possible. Debugging capabilities are greatly increased when sufficient information is logged with an exception. It is often useful to log business data with the exception, but care should be taken that sensitive data is not logged where it is publicly available (like user names & passwords, or sensitive HR data). It is crucial to provide a unique identifier for an exception log for future reference. A number of reference formats are commonly used for identifiers, often a Globally Unique Identifier (GUID) is used but it can be intimidating to a user and hard to quote as it is a 36 character string, a unique sequential number is often more practical.
It is often helpful to have more than one exception log and to split the log entries according to type. In high volume environments, detail can be lost in the volume and debugging can be hampered. It is recommended to split Business Exceptions from Application Exceptions for example as you would typically have different people monitoring the logs.
In some cases, it is required to have the ability for a support person to update the exception log with an explanation or resolution description. One should never have a high volume of exception logs on a production environment, and managing exceptions should be a high priority in any environment.
Application Exceptions (also considered to be technology exceptions) are exceptions that pertain to the application execution and technology used. The most common application exception is probably “Object reference not found”, typically this happens when the assumption that an object instance exists is not true. Another common application exception is when a change is made in a data store for example and data types no longer match, we will get a run time exception and the application can no longer continue executing.
Application exceptions are also the base of all exception types like Business Exceptions or Security Exceptions; if inadequate exception handling is performed on a super type Exception the Application Exception handling should catch those exceptions as well.
It is important to always assume that application exceptions can occur, and appropriate handling of the exceptions must be put in place.
Because Application exceptions are usually technical in nature it is important to not show all the detail to a user. Users will unlikely understand detail displayed and will build up fear/resistance to the application. Applications exceptions should be logged and a unique reference to the log be given to the user for future reference when communicating to a support person. Support calls could also be automatically logged with the reference number.
The following detail should be a minimum when logging Application exceptions:
- Application Name, or a good reference to the service or back ground processor where the exception originated. As much version information of the application/process available
- The method name in which the exception occurred, and as much detail of the call stack as available
- The computer executing the process at the time of the exception
- An exact date and time the exception occurred. This should not be the time the log entry was created, but when the exception occurred, if there is a significant difference
- A unique reference to the log entry
- If available the User Name executing the process
- Any appropriate data pertaining to the method call, if it is not sensitive data. Method parameter values can greatly assist in debugging of problems.
Security Exceptions are often implemented in the same fashion as Permission Exceptions, depending on the environment or application architecture.
The main difference between Security Exceptions and Permission Exceptions is the nature of the actions performed by the user. When trying to access a restricted resource like a network folder, a Security Exception would typically occur. When trying to execute a process that is not allowed, a Permission Exception could be appropriate.
It is important to communicate the Security Exception to the user performing the action, giving a clear description of the restricted resource, and if possible a description of how to obtain appropriate Security privileges.
When logging a Security Exception, it is important to store the following detail:
- The Unique Identifier of the User performing the action
- A clear description of the attempted Action and the Restricted Resource
- An exact date and time the exception occurred
- A unique reference to the log entry
- The action taken by the User, if more than one option action can be performed. If for example, the user applied access to the restricted resource, this should be logged with the Exception Log.
Permission Exceptions are usually related to “soft” resources such as Business Processes, an application function or a report for example, rather than a network resource or the like.
When a User’s permissions are inadequate to access a certain resource, it is important to communicate to the user the lack of permissions and what the minimum requirements are. If available, also describe how the user could apply for elevated permissions.
Logging Permission Exceptions are very similar to logging Security Exceptions as described above. In high volume environments however it could be helpful to log Security Exceptions and Permission Exceptions in separate logs; else a clear indication of the log type could be useful for debugging or environment management.
A Business Exception occurs when a User attempts to execute a process with parameters outside of the defined Business Rules, or when a defined Business Process could not be completed successfully from end to end, leaving the process in an undesirable state.
Most common examples of Business Exceptions are for example when a user tries to create an
Order for a
Customer that is not allowed
Orders, or for example a process requires a number of documents be created and one of the documents fails leaving the process in an unbalanced state.
When displaying the exception to the user, it is important to describe clearly to the full Business Rule that failed, or which part of a complete process failed, and how to respond to the Exception. If alternatives are available, these must be clearly described to the user, or where the user can find these alternatives. In the case where a process is left in an undesirable state due to an exception, corrective actions should be described as far as possible.
When logging Business Exceptions, it is crucial to store every detail of the exception. Exceptions should be stored in a separate log to other types of exceptions as you would typically have Business Analysts or “Super Users” monitor and respond to Business Exceptions.
In cases where a process can be continued (also called resumable business exceptions) after intervention, all details of the data of the process must be logged to enable resuming. A good example of this is Invoicing a Customer with inadequate credit rating, as soon as the Customer’s credit rating is increased the invoice can be executed. It would be helpful if the user does not have to recapture the entire Invoice.
In the case where a process is left in an undesirable state, it is important to log as much information as would be required to correct the state of the process. An example of this is if a Credit Document could not be created for a Customer, leaving the Customer Balance incorrect, the Customer ID and the Amount should be stored allowing a system administrator to manually correct the situation, or a Customer Account manager to communicate with the Customer.
Along with the above mentioned data, the following should also be logged with a Business Exception:
- The User Name of the person executing the process, if available
- The Name of the Business Rule that failed evaluation, or a description of the process and the part of the process that failed
- If available, the corrective measures recommended to the user
- If available, the options taken by the user in response to notification of the exception
- A unique reference to the exception log
- An exact date and time the exception occurred
Implementing Exception Handling
Exception handling is often implemented after development is almost completed, and due to consumed timelines, only a bare minimum (lacking) exception handling can be implemented.
Exception handling should be part of an application design, and part of the approval and sign off process with a customer.
Well constructed Exception handling and management is mostly re-usable and an initial investment will prove valuable in future implementations.
A number of toolsets and technologies are available to assist in exception handling but what is important is that Exception handling must be seen as important to application functionality and quality.
Ensuring that your exceptions are well handled will increase your Customer/User's experience and confidence, it will improve the ease of ownership of your application, increase the stability and availability and make maintenance easier to name just a few, all of that for not much effort at all.
Points of Interest
In a follow-up article, I will show how exception handling can be made easy with self written classes and methods based on the above descriptions.
- 19th January, 2009: Initial post