A function which is responsible for writing data to a file might not know that it is part of a “save” function (as opposed to a “set preferences”, “export” or “store in cache” function, or any other operation that involves writing files.
)Thus, errors need to propagate upwards from the lowest layers of the software stack to the highest, and as they do so, the error object needs to be transformed: low-level errors get translated into high-level errors that the user can understand, often by adding additional context information.
This works because each layer of the software stack that the error rises through understands the context of the current operation, even if the layers below it do not.
The end of this process is a message displayed to the user.
For a desktop or web application, this will often be in the form of an error dialog or alert.
InternationalizationNote that at some point in this process, we’ll need to account for the fact that the user might not be a native speaker of English (or whatever language the program author uses).
So the error message will have to be translated into whatever language corresponds to the user’s current locale preference.
Again, the low-level subsystems might not have any knowledge of the user’s locale.
For example, it’s common in single-page web applications that the user’s language choice is a browser preference setting, unknown to the server.
In such cases, the language translation can only happen in the client.
A common mistake that I often see is server programmers generating error messages that are English strings, expecting those strings to be displayed to the user verbatim.
In my own work, we have a strict rule: no readable text comes from the server API unless that text was input by a user (because presumably the user is able to write in a language that they, or their chosen collaborators, can understand).
This does not mean that those error codes will never be read by humans however; because you see in addition to the end user, there is a second audience for errors: developers.
When debugging an error condition, it’s much easier for a developer to know the meaning of “duplicate-username” than “Error 1459”.
Thus, even though your error codes are not English sentences, it’s best if they still can be comprehensible to a human reader.
ComprehensivenessA critical problem with error handling is that it’s not possible to effectively handle all of the possible errors for an operation if you don’t know what all the possible errors are.
Modern programming languages and frameworks make it extremely easy for programmers to invent new types of errors at a moment’s notice.
For example, in Python, all you have to do is derive a new subclass of “Exception”.
But what most programming languages and API specifications don’t provide is a way to automatically discover the list of all possible errors that could be produced by a given operation.
(In fact Java tried hard to make this happen with their “checked errors” concept, but in practice it was cumbersome enough that many programmers ended up working around it.
)Worse, many popular frameworks are designed such that low-level errors are propagated upwards without any translation or modification, despite the fact that the different layers generate errors that have different and incompatible formats.
For example, one web server system I worked with would produce errors with completely different structure, depending on whether the error occurred at the database layer, the application layer, the data validation layer, or the request routing layer.
That is, each of those four layers had a completely different idea of what an error looked like, and those errors would be transmitted directly to the client.
This made the client-side error handling code extraordinarily complex.
Solving this requires a great deal of diligence on the part of the developers responsible for generating the errors.
Basically you have to document every possible error that can be generated by each individual operation.
This is a lot easier if you plan up front how errors are going to be handled, establish some basic standards early on, and then stick to those standards.
RecoveryAs mentioned, the final stage in error handling is for the user to take action in response to an error.
Since most users aren’t technical experts, it is important to get the messaging right so that they can understand what happened, and more importantly, what they should do next.
It can be helpful if an error message includes suggestions for possible remedies.
If a file cannot be saved because the disk is full, you can suggest that the user free up some space before trying again.
However, you have to be careful not to give the user bad advice.
The remedy that you suggest may not work; whatever condition caused the error might only be a symptom of a deeper cause that you know nothing about.
You also don’t want to overwhelm the user with a lot of technical detail.
In some cases, an the user may need to seek additional human assistance.
This introduces yet a third audience for errors: customer support.
End users will often include error details in their request for help; this may even be in the form of a screen shot.
Ideally, there will be sufficient clues in the error message for the customer support agent to be able to deduce what has gone wrong, although those clues can be subtle so as not to distract the end user.
For example, an error message may phrased in a way that is distinctive from other, similar errors.
ConclusionError messages are an important part of application design, and are part of a lifecycle process which results in the user being able to recover from the error.
How errors are handled and presented can have a large impact on whether this process will be successful.
See AlsoThe Power of Negative ThinkingEngineering Insightsmedium.
comPTSD: Post-Traumatic Software DesignEngineering Insightsmedium.
comEngineering InsightsLife lessons for the aspiring software engineermedium.
comTalin’s Index of EssaysDiverse topics including computer games, politics, philosophy and science fiction.