Click here to Skip to main content
Click here to Skip to main content
Go to top

The art of creating exceptions

, 6 Dec 2010
Rate this:
Please Sign up or sign in to vote.
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:

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

do write:

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:

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:

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.

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:

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:

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:

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:

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

Share and Enjoy:PrintDiggStumbleUpondel.icio.usFacebookYahoo! BuzzTwitterGoogle BookmarksDZoneLinkedInSlashdotTechnorati

License

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

Share

About the Author

jgauffin
Founder Gauffin Interactive AB
Sweden Sweden
Founder of OneTrueError, a .NET service which captures, analyzes and provide possible solutions for exceptions.
 
blog | twitter
Follow on   Twitter   LinkedIn

Comments and Discussions

 
GeneralThanks for the article PinmemberPlamen Velikov3-Aug-11 2:38 
GeneralCopyPasteException Pinmembersjelen15-Dec-10 0:20 
GeneralRe: CopyPasteException Pinmemberjgauffin15-Dec-10 2:08 
GeneralNice article PinmemberLokanta_brahmachari13-Dec-10 4:14 
GeneralFileNotFoundException PinmemberRichard Deeming10-Dec-10 8:42 
GeneralRe: FileNotFoundException Pinmemberjgauffin13-Dec-10 9:21 
GeneralMy vote of 4 Pinmembervdasus8-Dec-10 4:39 
GeneralMy vote of 5 Pinmemberdpflovely7-Dec-10 22:11 
GeneralAbout those lazy SOBs who throw FileNoFoundException spelling exceptions PinmemberclintonG6-Dec-10 6:12 
GeneralRe: About those lazy SOBs who throw FileNoFoundException spelling exceptions Pinmemberrustylee20056-Dec-10 6:18 
GeneralRe: About those lazy SOBs who throw FileNoFoundException spelling exceptions Pinmemberjgauffin6-Dec-10 9:24 
GeneralSome tid bits... PinmemberAndrew Rissing6-Dec-10 4:19 
GeneralRe: Some tid bits... Pinmemberjgauffin6-Dec-10 8:17 
GeneralRe: Some tid bits... PinmemberAndrew Rissing6-Dec-10 13:58 
Generalnice PinmemberRozis3-Dec-10 13:19 
GeneralRe: nice Pinmemberjgauffin6-Dec-10 9:29 

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

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

| Advertise | Privacy | Mobile
Web02 | 2.8.140916.1 | Last Updated 6 Dec 2010
Article Copyright 2010 by jgauffin
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid