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;
}
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));
}
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)
{
using (var connection = CreateConnection())
{
var cmd = connection.CreateCommand();
try
{
action(cmd);
cmd.ExecuteNoQuery();
}
catch (DbException err)
{
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:
- Create exception classes containing as much context information as possible
- Always create a constructor that takes an inner exception
- Throw exceptions to help prevent them in the future
- Try to include as much context information as possible