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 'abc@xyz.com' 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 'abc@xyz.com' 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.
Exceptions do not contain useful details because the concept of exceptions has not matured yet enough within the software engineering discipline, so many programmers do not understand them fully, and therefore they do not treat them properly.
Yes, IndexOutOfRangeException
should contain the precise index that was out of range, as well as the range that was valid at the time that it was thrown, and it is contemptible on behalf of the creators of the .NET runtime that it doesn't. Yes, Oracle's table or view not found
exception should contain the name of the table or view that was not found, and again, the fact that it does not is contemptible on behalf of whoever is responsible for this.
To a large part, the confusion stems from the misguided original idea that exceptions should contain human-readable messages, which in turn stems from a lack of understanding of what exceptions are all about, so it is a vicious cycle.
Since people think that the exception should contain a human-readable message, they believe that whatever information is carried by the exception should also be formatted into the human-readable message, and then they are either bored to write all the human-readable message-building code, or they are afraid that doing so might be divulging an inadvisable amount of information to whatever prying eyes might see the message. (The security issues mentioned by other answers.)
But the truth of the matter is that they should not be worrying about that because the exception should not contain a human-readable message. Exceptions are things that only programmers should ever see and/or deal with. If there is ever a need to present failure information to a user, that has to be done at a very high level, in a sophisticated manner, and in the user's language, which, statistically speaking, is unlikely to be English.
So, for us programmers, the "message" of the exception is the class name of the exception, and whatever other information is pertinent to the exception should be copied into (final/readonly) member variables of the exception object. Preferably, every single conceivable little bit of it. This way, no message needs to (or should) be generated, and therefore no prying eyes can see it.
To address the concern expressed by Thomas Owens in a comment below:
Yes, of course, at some level, you will create a log message regarding the exception. But you already see the problem with what you are saying: on one hand, an exception log message without a stack trace is useless, but on the other hand, you don't want to let the user see the entire exception stack trace. Again, our problem here is that our perspective is skewed by traditional practices. Log files have traditionally been in plain text, which may have been fine while our discipline was in its infancy, but perhaps not any more: if there is a security concern, then the log file must be binary and/or encrypted.
Whether binary or plain text, the log file should be thought of as a stream into which the application serializes debug information. Such a stream would be for the programmers' eyes only, and the task of generating debugging information for an exception should be as simple as serializing the exception into the debug log stream. This way, by looking at the log you get to see the exception class name, (which, as I have already stated, is for all practical purposes "the message",) each of the exception member variables which describe everything which is pertinent-and-practical-to-include-in-a-log, and the entire stack trace. Note how the formatting of a human-readable exception message is conspicuously missing from this process.
P.S.
A few more of my thoughts on this subject can be found in this answer: How to write a good exception message
P.P.S.
It appears that a lot of people were being ticked off by my suggestion about binary log files, so I amended the answer once again to make it even more clear that what I am suggesting here is not that the log file should be binary, but that the log file may be binary, if need be.
Best Answer
Those messages are for other developers
Those messages are expected to be read by developers to help them debug the application. This can take two forms:
Active debugging. You're actually running a debugger while writing code and trying to figure out what happens. In this context, a helpful exception will guide you by making it easy to understand what's going wrong, or eventually suggesting a workaround (although this is optional).
Passive debugging. The code runs in production and fails. The exception is logged, but you only get the message and the stack trace. In this context, a helpful exception message will help you quickly localize the bug.
Since those messages are often logged, it also means that you shouldn't include sensitive information there (such as private keys or passwords, even if it could be useful to debug the app).
For instance,
IOSecurityException
type of an exception thrown when writing a file is not very explicit about the problem: is it because we don't have permissions to access a file? Or maybe we can read it, but not write? Or maybe the file doesn't exist and we don't have permissions to create files there? Or maybe it's locked (hopefully, the type of the exception will be different in this case, but in practice, types can sometimes be cryptic). Or maybe Code Access Security prevents us from doing any I/O operation?Instead:
is much more explicit. Here, we immediately know that permissions on the directory are set correctly (otherwise, we won't be able to know that the file exists), but permissions at file level are problematic.
This also means that if you cannot provide any additional information which is not already in the type of the exception, you can keep the message empty.
DivisionByZeroException
is a good example where the message is redundant. On the other hand, the fact that most languages let you throw an exception without specifying its message is done for a different reason: either because the default message is already available, or because it will be generated later if needed (and the generation of this message is enclosed within the exception type, which makes perfect sense, "OOPly" speaking).Note that for technical (often performance) reasons, some messages end up being much more cryptic than they should be. .NET's
NullReferenceException
:is an excellent example of a message which is not helpful. A helpful message would be:
Those messages are not for end users!
End users are not expected to see exception messages. Never. Although some developers end up showing those messages to the users, this leads to poor user experience and frustration. Messages such as:
mean absolutely nothing to an end user, and should be avoided at all costs.
The worst case scenario is to have a global try/catch which throws the exception to the user and exits the app. An application which cares about its users:
Handles exceptions in the first place. Most ones can be handled without disturbing the user. Network is down? Why not wait for a few seconds and try again?
Prevents a user from leading the application to an exceptional case. If you ask the user to enter two numbers and divide the first one by the second one, why would you let the user to enter zero in the second case in order to blame him a few seconds later? What about highlighting the text box in red (with a helpful tool tip telling that the number should be different than zero) and disabling the validation button until the field remains red?
Invites a user to perform an action in a form which is not an error. There are no enough permissions to access a file? Why not ask the user to grant administrative permissions, or pick a different file?
If nothing else works, shows a helpful, friendly error which is specifically written to reduce user's frustration, help the user to understand what went wrong and eventually solve the problem, and also help him prevent the error in the future (when applicable).
In your question, you suggested to have two messages in an exception: a technical one for developers, and the one for end users. While this is a valid suggestion in some minor cases, most exceptions are produced at a level where it is impossible to produce a meaningful message for the users. Take
DivisionByZeroException
and imagine that we couldn't prevent the exception from happening and can't handle it ourselves. When the division occurs, does the framework (since it's the framework, and not business code, which throws the exception) know what will be a helpful message for a user? Absolutely not:Instead, one can let it throw the exception, and then catch it at a higher level where we knew the business context and could act accordingly in order to actually help the end user, thus showing something like:
or maybe:
Those messages are not for parsing
Exception messages are not expected to be parsed or used programmatically either. If you think that additional information can be needed by the caller, include it in the exception side by side with the message. This is important, because the message is subject to change without notice. Type is a part of the interface, but the message is not: never rely on it for exception handling.
Imagine the exception message:
You want to extract the “500”, “6”, “4” and “377” values. Think a bit about the approach you will use to do the parsing, and only then continue reading.
You have the idea? Great.
Now, the original developer discovered a typo:
should be:
Moreover, the developer considers that month/week/one hour are not particularly relevant, so he also does an additional change:
What happens with your parsing?
Instead of parsing, you could be using exception properties (which can contain whatever one wants, as soon as the data can be serialized):
How easy is to use this data now?
Sometimes (such as within .NET), the message can even be translated into user's language (IMHO, translating those messages is absolutely wrong, since any developer is expected to be able to read in English). Parsing such messages is close to impossible.