Can Your Error Handling do that?

Warnings, Multiple Errors, Partial Success, OH MY!

Posted by Jake Corn on May 9, 2020

Things Happen

In programming we would like our programs to be as "correct" as possible. Meaning that an addition function is only ever called with things that can be added, or that an API gets inputs that it can actually complete its work.

Reality Comes knocking.

Truth be told, a lot of times you don't know how your piece of functionality will be called. We seldom make "Closed Systems" these days.

So how can we handle it?

Robust Software

Correctness vs Robustness is a concept I first heard of from Code Complete by Steve McConnell.

He uses an example where a screen when scrolled to the bottom has a bit of jitter due to self-righting. An alternative is that the program crashes due to the screen not being the right size for the software. Maybe an error message is shown too.

As contrived as this example might seem, it compares to plenty of natural scenarios.

Could the Program continue its work...

... without the screen being the right size?

Yes.

Should it?

Yes. (It depends)

In many scenarios we find that we can handle non-ideal conditions and self-right. Moving on in this manner allows the user to complete as much of there work as possible.

Did an error occur? Maybe, probably not... (it depends on if there is a hard requirement that the user of the software use the exact right size monitor).

What about when a program can not continue its work?

The ideal scenario is when our code/software can be unconditional but conditions do occur where a user is doing something that our programs can not handle.

-- or frequently, another developer is calling our code with inputs that we can't make work.

This is when clear error messaging needs to occur.

Errors

Good errors let the user know as much about the flaw as possible without leaking too many internal details. (It's similar to hiding information behind an interface.)

Messages and Reporting

A good error message is something like

  POST /person failed. Person first_name is a required string. received: nil

An example of a bad error message might be.

  Fail

Both let the user know that work could not be completed.

The first lets the user know that they did something wrong. The second only that something went wrong.

A lot of good error messaging is a balance of sending good actionable information without leaking the details of your functionality.

An example of leaking too much detail would be letting an end user see a stack trace, or a "could not cast object to integer" type of message.

The user updates their post body and resends it again...

  POST /person failed. Person last_name is a required string. received: nil

then they update again...

  POST /person failed. Person age is a required integer. received: nil

And they're starting to wish they'd used someone else's software.

Send HELP!

Send them a reference of how this endpoint is supposed to work, send them an example, SEND THEM HELP!

A much better error message would be something like.

  POST /person failed. Person last_name is a required string. received: nil

  Example:
  POST /person
  {
    "first_name": "Billy",
    "last_name": "Bob",
    "age": 45,
    "email": "billy_bob@email.com"
  }

That a lot better! what about a link? (Cheaper for network packets, but forces user to look at something else.)

  POST /person failed. Person last_name is a required string. received: nil

  Link: https://api.app.com/documentation/person

That would be okay too.

If we already know the failure conditions, why only send back one error at a time?

Reporting on a set of errors with the input is also an acceptable strategy.

Imagine if instead of having to hit our api multiple times to find their error, we could tell them all the errors?

  POST /person failed.

  Reasons:
    - Malformed Input(s):
      - first_name is required string. received nil
      - last_name can not be shorter than 2 characters. received "a"
      - age must be a number. received "Middle"

  Example:
  POST /person
  {
    "first_name": "Billy",
    "last_name": "Bob",
    "age": 45,
    "email": "billy_bob@email.com"
  }

This allows the user to make their required changes without having to call and recall.

Warnings?

What about potential issues that don't necessarily prohibit work from occuring? That is exactly when a warning might be useful.

Imagine your api is called with EXTRA information. Is your user expecting that extra information to be created along with the core information?

We don't know.

Giving them heads up helps them understand things a little better.

  POST /person Success!.

  Warnings:
    - Extra Input - (Unsaved):
      - Invalid Field: "ssn".
      - Invalid Field: "address"

  Example:
  POST /person
  {
    "first_name": "Billy",
    "last_name": "Bob",
    "age": 45,
    "email": "billy_bob@email.com"
  }

Otherwise you might be receiving calls/emails about "Our data is not saving correctly!"

Warning Capture may also be a method to start dogfooding additional use-cases for the api. If a lot of people are trying to call it with an "address" field and we save off those warnings, we can know that "address" is a highly desirable field for the person endpoint.

Partial Success

Can your user succeed partially? If so, can we help them complete the rest of their work?

I don't have as much to say here, other than not to confuse partial success with success. If it is truly the same, then it is not partial.

-- jake