I have spent a lot of time considering this issue. I believe that the MOST IMPORTANT issue in writing error messages is to communicate accurately to the user. The purpose of an error message to help the user to eliminate the problem that existed. Such a problem may be a misunderstanding between the user and the program (i.e. the user thought that a file name could have a slash in the name, but that simply is not possible) or it may be an environmental problem (i.e. the disk ran out of space).
In my experience, most software has lousy error reporting. Either cryptic numbers, or overused generic error messages with no specifics. You need to do whatever it takes communicate as accurately and completely as possible. Anything that stands in the way of this will reduce the quality of your error messages. The question to ask yourself is: how will I successfully make all error messages as good as possible.
I have found, then, that putting the complete error message directly in the code is the best for making sure that the message is clear, accurate, and complete. I work on some large codes based (> 1 million lines of code) and I have NOT had a problem finding those error messages by searching, because I make the error message as complete as possible.
If I have the same error being reported many times, I make a method/subroutine, and put the test and exception throw in there, instead of copying the same code in many places.
I don't have anything against defining a set of constants, and then using them to look up the error message, but
- I have found that most programmers will make much poorer messages in this case.
- The extra overhead of allocating a new constant, and putting that text into another resource file, and then arranging a call for it, is enough overhead that programmers will often simply reuse an existing message even when it is not a perfect fit.
- Programmers think they will come back later and provide a better one, but they never do.
- When you are removed from the context of the code that is testing for the problem, it is hard to write an accurate error message.
- Spreading this information across two or three different files makes the code more complicated and difficult to maintain.
- Once you define a constant it is difficult to remove even if you remove the original code
- If the condition changes, it is hard to know whether you can just change the message, or whether a new message should be created.
- If your goal is to clearly, accurately, and completely communicate to the user, then such use of constants and look-up files gets in the way of that communications, with no real compensating benefit.
In actuality, I separate errors into two categories: errors that can be due to user input, and errors that are due to programmers. The ones for users need to be localizable, and those need to reference into resource files. For these, I use constants that are NOT numerical, but instead are fair approximations of the error message. Then the resource bundle uses that to look up a localized version of the message. However, for the programmer errors, I embed them directly in the code, and you only get English. I find that most (maybe 60% to 75%) are programmer oriented errors.