DEV Community

loading...
Temporal

Write Errors That Don't Make Me Think

swyx
Infinite Builder 👷🏽‍♂️ I help people Learn in Public • Author, the Coding Career Handbook (https://www.learninpublic.org)
Originally published at docs.temporal.io Updated on ・4 min read

There is nothing more frustrating than a cryptic error message.

When something goes wrong, chances are you already knew it - all a cryptic error message does is confirm your suspicion and tell you that you're gonna need to read through a bunch of code to figure out your error.

As framework and library designers, we can and should do better for our users by carefully following a few design principles.

Why We Should Care

Who among us hasn't thrown up their hands at an unhelpful Error: NullPointerException or undefined is not a function? Of course, this is a straw man - unanticipated errors always leak internals. We should do our best to handle these errors as much as possible!

The frustrating part comes from errors that were anticipated but obviously not given any thought:

Example of unhelpful error message from Twitter screenshot

This is particularly aggravating because it compounds an already unhappy situation with an even worse debugging and bug reporting experience.

Instead, you could explain and suggest:

Alt Text

Why It's Important

Thoughtful error message design is one of the less glamorous, but most important, parts of Developer Exception Engineering. Most framework developers instinctively write error messages that make sense to them, but a few minutes of extra effort can save DAYS of debugging for everyone else (including maintainers!).

We must acknowledge two things:

  • there is immense information asymmetry between the framework developer and the framework user (developers know the codebase far better than users)
  • error messages are part of your developer experience too

You might be familiar with the old adage "If you fail to plan, you plan to fail." Here, we have a slight twist: If you fail to plan to fail, you are planning to fail your users when they need you most. Touting great developer experience with lousy error message design is like selling a beautiful car with seatbelts that don't work when there's a crash.

Error Message Style Guide

At Temporal, our current style guide reflects our engineering roots in Uber and Golang with some tactical recommendations:

  • Use errors.New for simple messages and fmt.Errorf for formatted messages. Make sure you're using the standard errors go package.
  • Error messages start with lowercase and no punctuation at the end: unable to open file instead of Unable to open file..
  • Error messages should be actionable, e.g. no such directory instead of loading service config.
  • Use unable to... instead of failed to..., can't..., or could not....
  • Messages should reflect the logical action, not merely a function name: load service config instead of LoadServiceConfig. This message should add actionable, meaningful context to the existing error message.

That said, as we evolve our developer experience philosophy, we are looking toward higher level design principles that help guide successful outcomes.

Error Message Design Principles

We are still in the early stages of pinning down our beliefs in what makes for great error messages, but here are some candidate principles running through my mind:

  • Errors should be written for USERS, not maintainers: Your error should make sense from a user point of view. For example, you should cite user-supplied configs and values familiar to users, not the internal variable and function names familiar to maintainers.
  • Errors should suggest WHY they happened, or even better - HOW to fix them: As a maintainer, you probably have a high amount of context as to the possible causes of an error - take the time to write down some hints for users. If there's a high probability you know what's wrong, the error message itself can suggest how to fix it. But even without that certainty, think about what the user is likely to need to log out to debug the error, and offer it in the error message itself. Where relevant, Errors should display what was expected vs what was received.
  • Errors should be searchable: Your users are most likely going to paste your errors into the search bars of your issue tracker, docs site, or Google. Consider the "SEO" of your errors. Sometimes offering a unique, human readable ID for each error can help narrow things down very quickly!
  • Errors should be logged: Errors that happen far more often than they ought to are a signal of a deeper issue. If a user code or configuration error happens too often, it isn't the user's fault - you have bad API design. If inside a terminal environment, you should offer some text formatting to make your errors visually distinct from normal log messages.
  • Last but not least, Errors should not be too verbose: You might be tempted to dump every piece of information under the sun, but this can be very annoying when scrolling through reams of errors. Most users don't read entire errors - make your words count, and link out to docs for fuller context. Errors are not docs.

Ultimately, the same high level principle applies to error messages as they do for anything involving human computer interaction: Don't Make Me Think!

Related

Discussion (27)

Collapse
dtinth profile image
Thai Pangsakulyanont • Edited

This is a really great post for referencing. Thanks for writing it!

For a really great example, I think the TypeScript compiler really gets it right, especially on the SEO:

I was pleasantly surprised when I saw that they even included this very specific error “error TS1185: Merge conflict marker encountered.”. Most other parsers gave a more generic message, such as “Unexpected token '<<'” (Chrome) or “Unexpected token” (Babel, although to its credit it also includes the relevant lines from the source code).

Collapse
swyx profile image
swyx Author

thanks Thai!

yeah the TS errors are next level... did you know they are even separated out for i18n? so they may have multiple error codes for the same English message just because it translates to different texts in other languages. i learned that from Orta's livestream.

Collapse
dtinth profile image
Thai Pangsakulyanont

😱 Whoa, that's really next level. Never knew that, thanks for mentioning it!

Collapse
_bkern profile image
Barry

I can't agree more. People fail to design errors and exceptions way too much. It is not as exciting as perhaps 'other' development but just as important. Another area I feel people don't spend enough time on is their domain modeling but that doesn't exactly fit this scenario. However, take this advice to heart and the more thought and design you put into these areas the better your code or app or library will be! If I could add one it would be to make errors unique. I hate when you encounter a library/codebase/app that returns the same 'error' for every scenario. Of course everything could have a valid reason (hopefully not that you just don't care about your error design) but the more you can guide the user or consumer the better.

Collapse
swyx profile image
swyx Author

thanks Barry! absolutely, that is a sign of lazy developers. of course, even better if the error has an error code. all in service of "Error SEO" :)