Click here to Skip to main content
15,881,027 members
Articles / Programming Languages / C#

The Art of Creating Exceptions

Rate me:
Please Sign up or sign in to vote.
4.50/5 (16 votes)
6 Dec 2010LGPL33 min read 31.1K   27   16
This will be my final piece on exception handling. It's been fun, but the show must go on, right? Got any suggestion on what I should rant about in this blog? Leave a comment. Continue reading →

Please take a moment and think about WHY you throw exceptions.

You done? Have you come up with a reason? You are probably thinking: “That b!@tch is a total moron. I throw exceptions to know that something exceptional has happened!”. If that is the case, please stop reading here. (The first part of the thought is totally fine by me, I can be a total moron.)

.
.
.
.
.
.

I SAID STOP READING!!!!

.
.
.
.
.
.
.

Really!

.
.
.
.
.
.
.
.
.

No? Well. Ok then. Want to be an exception master? Got some ninja skills? Have you bought a jump suit? I have actually created a diploma (yes, it’s true) that I will email to anyone who leaves a comment that says: “I throw exceptions to be able to try to prevent them in the future”. Because that’s what you should really try to do. Sure, some exceptions that are thrown CAN’T be prevented. But a lot of them can.

Let’s take FileNotFoundException an example. It can in MOST cases be prevented, if you are not a lazy SOB, by checking if the file exists before trying to open it. The exception can STILL be thrown if the file is deleted between the check and you opening it. Life is harsh, isn’t it.

The problem is that if you do not provide any contextual information, you will have a hard time trying to prevent it in the future.

Instead of just writing:

C#
throw new FileNotFoundException("Ohhh, the file was missing! *buhu*");

do write:

C#
throw new FileNotFoundException(string.Format("Failed to find '{0}'.", amazingFileName));

Even better would have been if you could add some more context and add the filename as a parameter:

C#
throw new FileNotFoundException(string.Format
  ("User photo for user #{0} was not found", userId)) { Filename = amazingFilename };

That gives a lot more information to work with, don’t you think? When designing exceptions, you should ask yourself “What kind of context information can I provide in this exception to make it easier to prevent it?”. Regarding a file, it’s quite obvious. Hence I would design the exception like this:

C#
public class FileNotFoundException : IOException
{
      public FileNotFoundException(string message, string fileName)
	     : base(message)
	{
		Filename = fileName;
	}

        // *ALWAYS* include a constructor which takes an inner exception.
	//
	// I WILL HUNT YOU DOWN AND GIVE YOU SOME SPANKING IF YOU DON'T!
       public FileNotFoundException(string message, string fileName, Exception inner)
		: base(message, inner)
	{
	}

      public string Filename { get; private set; }
}

Let’s look at another example.

I’ve talked about a DataSourceException previous posts. It could be used to report problems in the data layer (regardless of the type of data source). The first thing that comes into my mind is that we have a target and some parameters. When using a web service, the target is the URI and the parameters are anything posted. Likewise, for SQL, the target is a SQL query and the parameters are the parameters used in the SQL query. Doing it like this creates a potential security risk. Do NOT expose exception details for the end users, never ever.

C#
public class DataSourceException : Exception
{
	public DataSourceException
	(string message, string target, Dictionary<string, object> parameters)
	     : base(message)
	{
			Filename = fileName;
	}

         public FileNotFoundException(string message, Exception inner, 
		string target, Dictionary<string, object> parameters)
		: base(message, inner)
	{
	}

	public string Target { get; private set; }
	public Dictionary<string, object> Parameters { get; private set; }
}

How it can be used for a webservice request:

C#
try
{
	var user = myWebSvc.GetUser(userId);
	if (user == null)
	  throw new InvalidOperationException
	(string.Format("Failed to find user #{0}", userId)); //kilroy was here
}
catch (WebException err)
{
    throw new DataSourceException(string.Format
	("Failed to download user.", userId), myWebSvc.Uri, userId);
}

Or for a database:

C#
string errMsg = "Failed to update user";
    ExecuteCommand(cmd => {
	cmd.CommandText = "UPDATE user SET name=@name WHERE id=@id";
	cmd.AddParameter("name", name);
	cmd.AddParameter("id", userId);
}, errMsg);

Where did all code go? It’s a small method looking like this:

C#
public void ExecuteCommand(Action<IDbCommand> action, string errMsg)
{
	// CreateConnection() uses DbProviderFactory and
	// throws DataSourceException if connection fails
	using (var connection = CreateConnection())
	{
		var cmd = connection.CreateCommand();
		try
		{
			action(cmd);
			cmd.ExecuteNoQuery();
		}
		catch (DbException err) //notice that I only handle DbException?
		{
			// WTF is this? Check next code snippet.
			// note that I *return* an exception from 
                       	// the extension method (instead of throwing it)
			throw cmd.ToException(errMsg, err);
		}
		finally
		{
			cmd.Dispose();
		}
	}
}

I used a small extension method:

C#
public static class DataExtensions
{
	public static Exception ToException(this IDbCommand command, 
			string errMsg, Exception inner)
	{
		var parameters = new Dictionary<string, object>();
		foreach (var p in command.Parameters)
			parameters.Add(p.ParameterName, p.Value);
		throw new DataSourceException(errMsg, inner, 
			command.CommandText, parameters);
	}
}

Summary

You should be fine if you stop to think “I throw exceptions to inform that something exceptional happens” and instead start thinking “I throw an exception to help try to prevent the exceptional cases from happening in the future”. Having that mindset helps you create much more detailed exception classes (and hopefully also provide that information).

Each time you are about to throw an exception, ask yourself: What information can I provide in this method to make it easy to find out why the exception was thrown? It might take a couple of minutes longer, but how long does it take to debug your application if you do NOT get that information? Usually a lot longer.

Action points for you:

  1. Create exception classes containing as much context information as possible
  2. Always create a constructor that takes an inner exception
  3. Throw exceptions to help prevent them in the future
  4. Try to include as much context information as possible

License

This article, along with any associated source code and files, is licensed under The GNU Lesser General Public License (LGPLv3)


Written By
Founder 1TCompany AB
Sweden Sweden

Comments and Discussions

 
GeneralThanks for the article Pin
Plamen Velikov3-Aug-11 2:38
Plamen Velikov3-Aug-11 2:38 
GeneralCopyPasteException Pin
sjelen15-Dec-10 0:20
professionalsjelen15-Dec-10 0:20 
GeneralRe: CopyPasteException Pin
jgauffin15-Dec-10 2:08
jgauffin15-Dec-10 2:08 
GeneralNice article Pin
Lokanta_b13-Dec-10 4:14
Lokanta_b13-Dec-10 4:14 
GeneralFileNotFoundException Pin
Richard Deeming10-Dec-10 8:42
mveRichard Deeming10-Dec-10 8:42 
GeneralRe: FileNotFoundException Pin
jgauffin13-Dec-10 9:21
jgauffin13-Dec-10 9:21 
GeneralMy vote of 4 Pin
vdasus8-Dec-10 4:39
vdasus8-Dec-10 4:39 
GeneralMy vote of 5 Pin
dpflovely7-Dec-10 22:11
dpflovely7-Dec-10 22:11 
GeneralAbout those lazy SOBs who throw FileNoFoundException spelling exceptions Pin
Clinton Gallagher6-Dec-10 6:12
professionalClinton Gallagher6-Dec-10 6:12 
GeneralRe: About those lazy SOBs who throw FileNoFoundException spelling exceptions Pin
rustylee20056-Dec-10 6:18
rustylee20056-Dec-10 6:18 
GeneralRe: About those lazy SOBs who throw FileNoFoundException spelling exceptions PinPopular
jgauffin6-Dec-10 9:24
jgauffin6-Dec-10 9:24 
GeneralSome tid bits... Pin
Andrew Rissing6-Dec-10 4:19
Andrew Rissing6-Dec-10 4:19 
GeneralRe: Some tid bits... Pin
jgauffin6-Dec-10 8:17
jgauffin6-Dec-10 8:17 
GeneralRe: Some tid bits... Pin
Andrew Rissing6-Dec-10 13:58
Andrew Rissing6-Dec-10 13:58 
Generalnice Pin
Rozis3-Dec-10 13:19
Rozis3-Dec-10 13:19 
GeneralRe: nice Pin
jgauffin6-Dec-10 9:29
jgauffin6-Dec-10 9:29 
We are talking about exceptions that haven't been handled, right?

First of all: catch them as close to the console as possible (in a console app) or in your service class (if a windows service).

Then (any or all of the alternatives):

a) Write them to the event log if you are monitoring it
b) Email them to yourself
c) Write them to a logfile dedicated for errors.

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.