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 about a general kind of error, (say, an
SQLException which basically means 'something went wrong with the RDBMS') and you were capable of handling certain cases, (say, a
Connection Refused situation can be handled by waiting and retrying,) and you could see that in the message text of this
SQLException among other useless garbage said "Connection Refused", so you knew that you could handle it, but this information was not encoded in the exception object in any other way, so you had to actually perform string parsing on the exception message text, and you prayed that the text would not change in the next version of the RDBMS, or be "connexion refusée" in some user's environment.
(Obviously, the exception should have been a
ConnectionRefusedSQLException, and it could have spared us from the useless supposedly-human-readable message text.)
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.