Useless Error Messages

Good practice demands that we show the user an error message when exceptions occur so they know what went wrong. I want to make the case that good design makes exceptions the exception — something so wrong that the user can not do anything about it. In most cases, the best approach will be to show the user a single, generic message when exceptions occur.

An Unexplainable Exception

As always, the context is a modern mainstream web application. So we will start out of the blocks with a simple registration form. We require a user’s email and a password that is exactly 4 characters long. We can assume that the form has working validation on both the client side and the server side.

To our surprise, we receive an email from a customer. He complains that the registration isn’t working and attaches the following screenshot:

Obviously, the user’s password is exactly 4 characters long. Just as obviously, the form validation agreed with that and set the border colour to green. For some reason, however, the backend reached another opinion and displayed the error message.

Our user does not know what he did wrong. Even worse, he does not know how to proceed with the registration.

How did it come that far? How could we have avoided that? Before we go into detail, let’s clarify some basic things.

Exception === Uncontrolled State

We should not mistreat exceptions as alternatives to if conditions.

For example:

public boolean validateRegistration(String email, String password) {
  /* some validation code */
  try {
    this.checkPassword(password)
  } catch (PwLengthException ple) {
    return false;
  }

  return true;
}

public void checkPassword(String password) throws PwLengthException {
  if (password.size != 4) {
    throw new PwLengthException(“The password requires a lenght of 4”);
  }
}

If the password is invalid, then the function `checkPassword` will throw an exception. The exception is used as a conditional expression in the caller.

However, we should consider each exception as an unexpected, and therefore uncontrolled state, of our application.

Rocket science takes that to the maximum. If an exception bubbles up to the main routine, then it usually triggers the system’s self-destruction commands. The developers understood the true nature of exceptions: the system is beyond their control. That state requires extreme measures to prevent even more catastrophic events.

Ariane 5 triggering self-destroy after an unhandled buffer overflow exception in 1996. Source: ESA

We are web developers, not rocket scientists. Exceptions in our systems will never require similarly drastic measures like destroying the user’s laptop because the password complexity is wrong :).

Like rocket scientists, however, we design our systems in a way that exceptions never occur. If they do, then we are in unknown territory.

The example can be corrected to:

public boolean validateRegistration(String email, String password) {
  /* some validation code */
  if (!this.isValidPassword(password)) {
    return false;
  }

  return true;
}

public boolean isValidPassword(String password) {
  return password.size == 4;
}

UI as Gatekeeper against Invalid Data

How can we achieve an application design that makes exception throws theoretically impossible?

It is the task of the user interface to keep the user on the right track. Each possible action should be part of a defined process. There should be no way for the user to step out of that designed path.

Invalid input from the user is part of this process. Since it is a planned state of our application, there is no reason to throw an exception. It only becomes a problem, when that invalid input passes unfiltered to our backend. Therefore, the UI needs to handle it via validation.

So our frontend acts as the gatekeeper. It makes sure that only valid data finds its way into our system. That implies that our system, theoretically, cannot enter an uncontrolled state.

Our password input example is just one type of input that requires frontend validation. A user’s access may require a security clearance. Or a user’s country may define regional restrictions. Even more commonly, a user must manually enter a valid URL.

UI as Gatekeeper

Of course, this does not free us from proper validations in our backend. One cannot blame the frontend, for example, if users are bypassing it on purpose. They could change URL parameters. Or they may be sniffing endpoints and sending manipulated data.

In these cases, we are in an uncontrolled situation. Throwing an exception is justified. But please catch it with a standard error screen.

From Theory to Practice

Let’s recap. We’ve made sure that exceptions are only thrown when the application enters an uncontrolled state. We’ve made sure that only valid data can enter our system. Therefore, no frontend-lead action can fail the system, right? Wrong!

Let’s come back to our initial example. It turns out that our server-side validation code somehow used an array of bytes instead of a simple String...

public void finishRegistration(String email, String password) {
  /* some validation code */
  if (!this.isValidPassword(password.getBytes())) {
  /**
   * only for safety reasons. Can’t happen due
   * to proper client validation
   */
   throw new PwLengthException(
       “The password requires a length of 4”);
  }
  return true;
}

public boolean isValidPassword(byte[] password) {
  return password.length == 4;
}
In UTF-8 an 'ü' takes two bytes. Source: imgflip.com

You think this doesn’t happen in practice? Well, my friend … these things happen ... all the time.

Unknown Root Cause

As we have seen, the user input itself was not the root cause. It is our application which has a bug.

Worse, we display an error message to our users that is incorrect. That simply confuses them. After all, they did everything right.

If we are honest our message should read something like “Dear user, your password seems to be fine. For some as-yet unexplainable reason, it hasn’t been accepted by the backend. We’ve messed up. Sorry.”

How to fix this bug?

It should be clear by now that, with sound validation mechanisms in place, exceptions still occur. But we can be quite sure that, if an exception is thrown, it is not the fault of the user.

Therefore, it does not make sense to put our efforts into a module capable of showing different error messages, each one internationalised and supporting substitution of contextual information like “Password requires a length of 4 instead of %s”.

Just apologize. Then make sure we log the incident along with all contextual information we can gather. Let’s use a sophisticated log collector and optimise our internal processes to fix these errors as quickly as possible. That’s all we can do.

Summary

An Exception is no alternative to an if condition.

The user interface should make sure that an exception from the user’s side can’t occur.

Don’t confuse your users by showing them misleading information.

Leave a Reply