Author: Ben Bucksch (Please check with the author before modifying)
No access to View
Code that encounters errors must not call the error prompt directly. Rather, it will raise an exception, and the caller will determine the appropriate way to show the error, considering the UI context where the function was invoked.
This allows not only a separation between model and view. It also allows to adapt the error UI to the concrete situation.
Where and how to show errors
As a general rule:
- If the error happens as part of an action in a dialog, show the error inline in the dialog
- If the error happens as a direct result of a user action, e.g. button click, show an error popup, because the user will wait for the result and will want to know that the action he just initiated did not work.
- If the error happened during a function that was triggered automatically without a direct user action at that moment, for example a periodical mail check, we will use other non-intrusive means to signal the error.
- For example, errors during mail account login:
- If we set up a new mail account and test the connection, we will want to show errors inline within the dialog. We will also want to go back to the previous step and let the user fix the mail account parameters. Ideally, we’ll even want to know which part failed, so that we can highlight the right fields.
- If the account has been set up and was working before, and now the user clicks "Get messages" and that fails, we might want to show an error popup dialog.
- If poll the account for new mails, and suddenly it fails, and it’s one of 5 accounts that failed, we might decide that it’s better to mark the account red, and not push the error in the user’s face.
- This example shows that the very same logic functions - account login and mail check – might require very different error UIs, depending on why it was called. An architecture that bases on exceptions and avoids direct error UI calls allows that.
Some errors can be handled programmatically. In the account setup dialog, we’ll want to know that the server hostname could not be found, so that we can mark the server name field red and ask the user to fix that one. To allow that, exceptions must be computer-readable.
This will be achieved with a combination of exception classes (e.g. ServerError or IMAPAuthenticationError) and error codes.
Error message construction
Error messages must still be detailed.
- If contacting a certain server failed, it is not helpful to see only "Login failed". The user will have no means to fix the error.
- Rather, we need to also see why it failed, for example: "Login failed. Login mechanism CRAM-MD5 is configured, but not supported by the server".
- The above message is also missing the server name, so that the user can know which account failed.
- Thus, a better error message is: "Login failed for account Yahoo Fred. Login mechanism CRAM-MD5 is configured, but not supported by the server imap.mail.yahoo.com".
Error messages must contain
- a generic, high-level description of the problem ("Login failed")
- a detailed description of the problem ("CRAM-MD5 is configured, but not supported by the server"), which at the same time gives a hint at how to fix it and
- specifics about where the error happened, in this case the account name and server name, but it could just as well be a file name.
This kind of information is best gathered at the place where the error happens, not at the UI level. Because a) only that place has all the information and b) given that there are typically many callers, this avoids code duplication.
Thus, as a general rule, the place that encounters the error and creates the exception will also create the translated error message, and set the "message" field of the exception to a text that can be directly shown to an end user and understood by both normal users and experts, and which is helpful to fix the problem.
This allows callers to be completely ignorant of any errors that might happen, and just choose a nice way to display the error message. If the caller needs to specifically handle a certain error, he can still do so with the error codes. He can still use generic handling for all other errors.