Some information about exceptions in software systems
Throughout my software development exposures, defining and handling exceptions has always constituted a fair amount of my programming considerations. This post collates information about exceptions that I had came across.
The need for handling errors in your codes
Handling errors in your codes is intuitive - you are writing codes that people will use and people make mistakes. A good read of 100 things every designer needs to know about people by Susan Weinschenk will give you great insights on that.
Preventing people from making mistakes
Perhaps it may be better to prevent people from making mistakes while using a program feature, but there are times when it is beyond our control or technically challenging. A batch processing job that receives work input from the HTML file upload dialog box can be given a file type that is not expected. Even if we implement restriction on the file type, there is still no way to stop mischievous users from uploading files with invalid data format.
Unavoidable mistakes
The people part is one part of the story; components that you use in your code may fail too. Insufficient storage, invalid permissions, server does not like your request and what not. For example, making a WSDL call via C# codes generated from wsdl.exe may result in
- Server code encountering errors in processing the wsdl call.
- Server process could not find the code to handle the wsdl call.
- Server refusing the client request.
- Client cannot connect to the server at the network layer.
- Client does not have network connection.
- And many unexpected ones like Microsoft fly-by compiler attempts to create a temporary file but the operating system doesn't have a temporary folder.
What are exceptions?
Exceptions are programming constructs that encapsulate error information. In an object oriented environment, they are usually defined with a generic exception construct and thrown around when unwanted conditions occurs.
Suppose that we want to define an exception that is thrown when our program cannot initialise itself due to missing prerequisites. One way to do so in C# is as follows:
public class MissingPrerequisitesException : ApplicationException { // Declare an enum that provides a set of // reasons for callers public enum ExceptionReason { MissingComponents, MissingConfigurationFile, MissingConfigurations, }; // Take note of the reason private ExceptionReason _reason; public ExceptionReason Reason { get { return this._reason; } private set { this._reason = value; } } public MissingPrerequisitesException( ExceptionReason reason, string message) : base(message) { this.Reason = reason; } public MissingPrerequisitesException( ExceptionReason reason, string message, Exception inner) : base(message, inner) { this.Reason = reason; } } // end public class MissingPrerequisitesException
And when our program detects a missing configuration file, it can throw an instance of the exception class with the throw
keyword:
public void LoadConfigurations() { if (!File.Exists("config-file.txt")) { throw new MissingPrerequisitesException( MissingPrerequisitesException.ExceptionReason.MissingConfigurations, "Configuration file does not exists!"); } // end if // Read configurations from the file // ... } // end public void LoadConfigurations()
Why exceptions?
Exceptions contain rich information about an error that happened
Implicitly, exceptions contain information for developers to know how and what error had happened.
For instance, the caller of LoadConfigurations
can enclose the call in a try-catch block and print the exception stack trace:
private void Init() { try { LoadConfigurations(); // Configure other components // ... } catch (MissingPrerequisitesException mpe) { // The error that occurred is due to // missing prerequisites Console.WriteLine("Missing prerequisites!"); // Print the message Console.WriteLine(mpe.); // Print the stack trace Console.WriteLine(mpe.StackTrace); } } // end private void init()
And when the config-file.txt file cannot be found by the program, the program will output something like this:
Missing prerequisites! Configuration file does not exists! at Program.LoadConfigurations() in d:\poc\exceptions\Program.cs:line 47 at Program.Init() in d:\poc\exceptions\Program.cs:line 27
The 3rd line is the stack trace. Reading from the bottom up (from the 4th line), a developer can know that there is a Program.cs file and the error happens in the following sequence:
-
The
Init
method is called - In the Init() method, the
LoadConfigurations
method is called.
And because the LoadConfigurations
method is at the top of the stack trace, it is the method that reports a missing configuration file.
Those are standard items that our MissingPrequisitesException class inherits from its parent class. We can also introduce more information within our custom defined exceptions to aid the exception handling code in reacting to the exception. For instance, with the Reason
property that was introduced in the MissingPrequisitesException class, the exception handling code can react to different cases of missing prerequisites with a switch
statement:
switch(mpe.Reason) { case MissingPrerequisitesException.ExceptionReason.MissingComponents: Console.WriteLine("Missing components, please check your installation."); break; case MissingPrerequisitesException.ExceptionReason.MissingConfigurationFile: Console.WriteLine("Missing configuration file, make sure that config-file.txt is in the same directory as the running program."); break; case MissingPrerequisitesException.ExceptionReason.MissingConfigurations: Console.WriteLine("Missing configurations, make sure that all needed configurations are supplied via config-file.txt."); break; } // end switch(mpe.Reason)