119

I'm currently doing a code review and one of the things I'm noticing are the number of exceptions where the exception message just seems to reiterate where the exception occurred. e.g.

throw new Exception("BulletListControl: CreateChildControls failed.");

All three items in this message I can work out from the rest of the exception. I know the class and method from the stack trace and I know it failed (because I've got an exception).

It got me thinking about what message I put in exception messages. First I create an exception class, if one does not already exist, for the general reason (e.g. PropertyNotFoundException - the why), and then when I throw it the message indicates what went wrong (e.g. "Unable to find property 'IDontExist' on Node 1234" - the what). The where is in the StackTrace. The when may end up in the log (if applicable). The how is for the developer to work out (and fix)

Do you have any other tips for throwing exceptions? Specifically with regard to the creating new types and the exception message.

3
  • 4
    Are these for log files or to present to the user? Commented Dec 23, 2010 at 12:14
  • 6
    For debugging only. They may end up in a log. They would not be presented to the user. I'm not a fan of presenting exception messages to the user. Commented Dec 23, 2010 at 13:12
  • 2
    One of the things I have found myself do repeatedly is to look up the full, absolute filename and add to a FileNotFoundException. Relative paths are hard to debug. Commented Sep 21, 2021 at 12:37

8 Answers 8

87

I'll direct my answer more to what comes after an exception: what's it good for and how should software behave, what should your users do with the exception? A great technique I came across early in my career was to always report problems and errors in 3 parts: context, problem & solution. Using this dicipline changes error handling enormously and makes the software vastly better for the operators to use.

Here's a few examples.

Context: Saving connection pooling configuration changes to disk.
Problem: Write permission denied on file '/xxx/yyy'.
Solution: Grant write permission to the file.

In this case, the operator knows exactly what to do and to which file must be affected. They also know that the connection pooling changes didn't take and should be repeated.

Context: Sending email to '[email protected]' regarding 'Blah'.
Problem: SMTP connection refused by server 'mail.xyz.com'.
Solution: Contact the mail server administrator to report a service problem.  The email will be sent later. You may want to tell '[email protected]' about this problem.

I write server side systems and my operators are generally tech savvy first line support. I would write the messages differently for desktop software that have a different audience but include the same information.

Several wonderful things happen if one uses this technique. The software developer is often best placed to know how to solve the problems in their own code so encoding solutions in this way as you write the code is of massive benefit to end users who are at a disadvantage finding solutions since they are often missing information about what exactly the software was doing. Anyone who has ever read an Oracle error message will know what I mean.

The second wonderful thing that comes to mind is when you find yourself trying to describe a solution in your exception and you're writing "Check X and if A then B else C". This is a very clear and obvious sign that your exception is being checked in the wrong place. You the programmer have the capacity to compare things in code so "if" statements should be run in code, why involve the user in something that can be automated? Chances are it's from deeper in the code and someone has done the lazy thing and thrown IOException from any number of methods and caught potential errors from all of them in a block of calling code that cannot adequately describe what went wrong, what the specific context is and how to fix it. This encourages you to write finer grain errors, catch and handle them in the right place in your code so that you can articulate properly the steps the operator should take.

At one company we had top notch operators who got to know the software really well and kept their own "run book" that augmented our error reporting and suggested solutions. To recognise this the software started including wiki links to the run book in exceptions so that a basic explanation was available as well as links to more advanced discussion and observations by the operators over time.

If you've had the dicipline to try this technique, it becomes much more obvious what you should name your exceptions in code when creating your own. NonRecoverableConfigurationReadFailedException becomes a bit of shorthand for what you're about to describe more fully to the operator. I like being verbose and I think that will be easier for the next developer who touches my code to interpret.

4
  • 1
    +1 This is a good system. Which is more important: Being sure the information gets across, or using short words?
    – Michael K
    Commented Dec 23, 2010 at 15:04
  • Context is unnecessary data. It's already present in stack trace. Having solution is preferable but not always possible/useful. Most problems is kind of gracefully stop application or ignore pending operations and back to application main execution loop hoping that next time you succeed... Name of exception class should be such that solution will become obvious, for FileNotFound or ConnectException you know what to do ))
    – gavenkoa
    Commented Feb 28, 2017 at 11:40
  • @gavenkoa Context is necessary; think of a user calling into a function into your library which executes in three steps internally; e.g. 1. initialization 2. execution and 3. cleanup. Context can now be used to indicate in which step the error occurred - the stack trace certainly wouldn't help, especially considering that the libraries internal functions are invisible to the user.
    – user277671
    Commented Jun 18, 2018 at 9:37
  • 2
    @ThomasFlinkow In your example traces will have init(), execute() and cleanup() in stack trace. With good naming schema in library and clean/understandable API you don't need of string explanations. And fail fast, don't carry broken state all over the system. Traces and logging records with unique ID can explain application flow/state.
    – gavenkoa
    Commented Jun 18, 2018 at 14:09
26

In this more recent question I made the point that exceptions should not contain a message at all. In my opinion, the fact that they do is a huge misconception. What I am proposing is that

The "message" of the exception is the (fully qualified) class name of the exception.

An exception should contain within its own member variables as many details as possible about precisely what happened; for example, an IndexOutOfRangeException should contain the index value that was found to be invalid, as well as the upper and lower values that were valid at the moment that the exception was thrown. This way, using reflection you can have a message automatically constructed which reads like this: IndexOutOfRangeException: index = -1; min=0; max=5 and this, together with the stack trace, should be all the objective information that you need in order to troubleshoot the problem. Formatting it into a pretty message like "index -1 was not between 0 and 5" does not add any value.

In your particular example, the NodePropertyNotFoundException class would contain the name of the property that was not found, and a reference to the node that did not contain the property. This is important: it should not contain the name of the node; it should contain a reference to the actual node. In your particular case this may not be necessary, but it is a matter of principle and a preferred way of thinking: the primary concern when constructing an exception is that it must be usable by code which might catch it. Usability by humans is an important, but only secondary concern.

This takes care of the very frustrating situation which you may have witnessed at some point in time in your career, where you may have caught an exception containing vital information about what happened within the message text, but not in within its member variables, so you had to do string parsing of the text in order to figure out what happened, hoping that the message text will stay the same in future versions of the underlying layer, and praying that the message text will not be in some foreign language when your program is run in other countries.

Of course, since the class name of the exception is the message of the exception, (and the member variables of the exception are the specific details,) this means that you need lots and lots of different exceptions to convey all the different messages, and that is fine.

Now, sometimes, as we write code, we come across an erroneous situation for which we just want to quickly code a throw statement and proceed writing our code instead of having to interrupt what we are doing to go create a new exception class so that we can throw it right there. For these cases, I have a GenericException class which does in fact accept a string message as a construction-time parameter, but the constructor of this exception class is adorned with a great big huge bright purple FIXME XXX TODO comment stating that every single instantiation of this class must be replaced with an instantiation of some more specialized exception class before the software system gets released, preferably before the code gets committed.

2
  • 11
    If you're in a language that doesn't have a GC, like C++, you should be very careful about putting references to arbitrary data into exceptions that you send up the stack. Chances are, whatever you're referencing has been destroyed by the time the exception is caught. Commented Apr 14, 2015 at 15:13
  • 5
    @SebastianRedl True. And the same could apply to C# and Java if the node object is guarded by a using-disposable (in C#) or try-with-resources (in Java) clause: the object stored with the exception would be disposed/closed, making it illegal to access it in order to get any useful information out of it at the place where the exception is handled. I suppose that in these cases some sort of summary of the object should be stored within the exception, instead of the object itself. I cannot think of a fool-proof way to generically handle this for all cases.
    – Mike Nakis
    Commented Apr 14, 2015 at 18:42
17

As a general rule, an exception should help developers pinpoint the cause by giving useful information (expected values, actual value, possible causes/solution, etc.).

New exception types should be created when none of the built-in types make sense. A specific type enables other developers to catch a specific exception and handle it. If the developer would know how to handle your exception but the type is Exception, he won't be able to handle it properly.

1
  • +1 - expected values vs actual values are extremely useful. In the example given in the question, you should not simply say that a method failed, but why it failed (basically, the exact command that failed, and the circumstances that caused the fail.) Commented Dec 23, 2010 at 13:45
4

In .NET don't ever throw new Exception("...") (like the author of the question has shown). The Exception is the root Exception type and is not supposed to ever be thrown directly. Instead throw one of the derived .NET exception types or create your own custom exception that derives from Exception (or another exception type).

Why not throw Exception? Because throwing Exception does nothing to describe your exception and forces your calling code to write code like catch(Exception ex) { ... } which is generally not a good thing! :-).

3

The things you want to be looking for to "add" to the exception are those elements of data that are not inherent in the exception or the stack trace. Whether those are part of the "message" or need to be attached when logged is an interesting question.

As you've already noted, the exception proably tells you what, the stacktrace probably tells you where but the "why" may be more involved (should be, one would hope) than just going to peer at a line or two and saying "doh! Of course". This is even more true when logging errors in production code - I've all too often been bitten by bad data thats found its way into a live system that doesn't exist in our test systems. Somethig as simple as knowing what the ID is of the record in the database that's causing (or contributing) to the error can save significant amounts of time.

So... Either listed or, for .NET, added to the logged exceptions data collection (c.f. @Plip!):

  • Parameters (this can get a bit interesting - you can't add to the data collection if it won't serialize and sometimes a single parameter can be surprisingly complex)
  • The additional data returned by ADO.NET or Linq to SQL or similar (this can also get a bit interesting!).
  • Whatever else might not be apparent.

Some things, of course, you won't know you need until you don't have them in your initial error report/log. Some things you don't realise you can get until you find you need them.

1

Best practices for exceptions from Microsoft:

Error messages target the developer who is handling the exception. The text of the Message property should completely describe the error and, when possible, should also explain how to correct the error. Top-level exception handlers may display the message to end-users, so you should ensure that it is grammatically correct and that each sentence of the message ends with a period. Do not use question marks or exclamation points. If your application uses localized exception messages, you should ensure that they are accurately translated.

https://learn.microsoft.com/en-us/dotnet/api/system.exception.message?view=net-8.0 https://learn.microsoft.com/en-us/dotnet/standard/exceptions/best-practices-for-exceptions

0

What are Exceptions for?

(1) Telling the user something went wrong?

This should be a last resort, because your code should intercede and show them something "nicer" than an Exception.

The "error" message should clearly and succinctly state what went wrong and what, if anything, the user can do to recover from the error condition.

e.g. "Please do not press this button again"

(2) Telling a Developer when went wrong?

This is the sort of thing that you log into a file for subsequent analysis.
The Stack Trace will tell the Developer where the code broke; the message should, again, indicate what went wrong.

(3) Telling an Exception handler (i.e. code) that something went wrong?

The Type of the Exception will decide which exception handler will get to look at it and the properties defined on the Exception object will allow the handler to deal with it.

The Exception's message is totally irrelevant.

-2

Don't create new types if you can help it. They can cause extra confusion, complexity and lead to more code to maintain. They may make your code had to extend. Designing an exception hierarchy requires a lot of though and testing. It's not an after thought. It's often best to use the built in language exception hierarchy.

The content of the exception message dependents on the receiver of the message - so you have to put yourself in that person's shoes.

A support engineer will need to be able to identify the source of the error as quickly as possible. Include a short descriptive string plus any data that can help troubleshoot the issue. Always include the stack trace - if you can - this will be the only true source of information.

Presenting errors to general users of your system depends on the type of the error: if the user can fix the problem, by providing different input for example, then a concise descriptive message is required. If the user can't fix the problem, then it's best to state an error has occurred and log/send an error to support (using guidelines above).

Also - don't throw up a big "HALT ERROR!" icon. It's an error - it's not the end of the world.

So in summary: think about the actors and use cases for your system. Place yourself in those user's shoes. Be helpful. Be nice. Think about this up front in your system design. From the perspective of your users - these exception cases and how the system handles them, are just as important as the normal cases in your system.

2
  • 10
    I disagree. There are many good reasons to implement your own exceptions when the language API doesn't cover your exact needs. One reason is that in a method where multiple things may fail, you can write different catch clauses for different types of exceptions, where you can react to the exact problem. Another is that you can separate multiple layers of exceptions representing different layers of abstraction, where the exact layer that an exception belongs to can be encoded in its type. Just using "Exception" or "IllegalStateException" and a message string doesn't help much there. Commented Dec 23, 2010 at 13:51
  • 1
    I also disagree. Exception types should make sense to the calling code that will consume it. For example if I'm calling a framework and this causes a FileDoesNotExistException internally then this might not make any sense to me as a caller of the framework. Instead it might be better to create a custom exception and pass in the thrown exception in as the inner exception.
    – bytedev
    Commented Oct 10, 2016 at 15:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.