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.