Hacker News new | ask | show | jobs
by ravenstine 835 days ago
I don't mean to throw shade on the whole book, but I don't think the section on errors takes things in the right direction.

A distinction should be made between errors and exceptions. In JavaScript and many languages, we conflate the two and use exception handling as logic flow control. In my experience, this can end up being a headache and encourage unnecessarily weird structuring of code.

Look at this example from the page on errors:

---

function getAccount() {

  let accountName = prompt("Enter an account name");

  if (!Object.hasOwn(accounts, accountName)) {

    throw new Error(`No such account: ${accountName}`);

  }

  return accountName;
}

---

The possibility that a user will enter a account name that doesn't exist is not an exception, but we are treating it like one in this case. In order to handle this exception when getAccount is called, we have to wrap it or some higher level scope in a try-block and then regex-match the error message if we want to handle it differently from other errors.

You might be saying "it's just an example", but there's plenty of production code in the wild that is written this way. Maybe this could be improved by subclassing Error, but now you're having to manage a bunch of clutter and using object-oriented features as a way to reliably determine what kind of exception you're dealing with.

I find this pattern to be preferable:

---

const ACCOUNT_NOT_FOUND_ERROR_CODE = 1;

function getAccount() {

  let accountName = prompt("Enter an account name");

  if (!Object.hasOwn(accounts, accountName)) {

    return {

      accountName: null,

      error: {

        code: ACCOUNT_NOT_FOUND_ERROR_CODE,

        message: `No such account: ${accountName}`,

      }

    };

  }

  return { accountName, error: null };
}

---

Then we can call the function like this:

---

const { accountName, error } = getAccount();

if (error) {

  if (error.code === ACCOUNT_NOT_FOUND_ERROR_CODE) {

    errorModalService.show(error.message);

  }
} else {

  // do something with the account name
}

---

No doubt, you may still want to catch exceptions at a higher level scope, but at the nice thing here is that exceptions (almost) always represent actual unexpected conditions that aren't being handled properly while return values with error codes represent expected conditions and can be handled like any other logic in your code. It also reduces any ambiguity of how an error should be handled but without subclassing. An error can even contain more information than just a message if you want it to.

Also, if you really want to ignore an error for some reason, then you can just pretend that the error doesn't exist. No need to use a try-catch where the catch-block is a no-op.

I wish we'd encourage this sort of pattern, but maybe that's one of many pipe dreams of mine.

4 comments

In other languages like Java this can work because of checked exceptions (can work, I know there are a lot of mediocre devs who don't know what to do with checked exceptions but that's another issue). But in JS this is a terrible way to deal with any problem that can be handled.
I'd agree, but incidentally I've also been extracting string labels into some higher level, such as a constant or message creator function that looks up the message by it's code in some dictionary. It helps with readability, particularly in modern view libraries, to just have all your labels in one place and not have to scan the JSX or HTML for the string literals, and likewise all modifications will produce more concise diffs, testing could be easier if you're doing string matching, localization is more manageable with fewer scattered external dependencies on translation hooks or w/e
1. Exceptions can have codes, too, you know, and often do. 2. In the example, the exception is meant to bubble up all the way to the consumer. Different consumers can then handle as they see fit, even just displaying the exception message to the user.
Yes, exceptions can have codes. As far as I have experienced, their error instances don't do anything better than a plain object, and a non-exception error object can more clearly take a wide variety of shapes. It also limits potential confusion between errors or exceptions raised by your code and that of some dependency.

It is true that exceptions "bubble", and plenty of developers take advantage of that behavior. In my opinion, it is not helpful for errors that are expected to happen as part of normal application behavior. Bubbling of errors creates a sense of logical indirection. Errors as return values communicate that likely nothing " wrong" actually happened. Good communication through code reduces the amount of time developers spend using the wrong assumptions when debugging.

There is also no reason why errors in a return value can't be returned by the caller. When you learn to love objects/hashes as return values, you can get the same benefits of bubbling but with a more clear path to follow when getting a picture of where a value is coming from and why.

In the case of actual exceptions, like accessing a property on undefined, an error being thrown is appropriate because, like you say, it can be bubbled up. The nice thing about reserving exceptions for this sort of thing is you might get away with a single try-catch at the highest scope and have a single straight forward error modal for when an unexpected problem occurs. Then you can otherwise implement errors in your code without the possibility that they will escape through bubbling and trigger behavior that is not immediately obvious when following the path of execution.

This is not to say that the tradition of using errors and try-catch for normal application control flow is inherently bad. Its just another tool. I do subjectively believe they are counter productive when used that way, and encourage others to try something like my approach. I think we would benefit by making it a standard practice.

like Result in Rust
Haven't even looked at Rust code until just now, but yes, that looks like a more formalized version of this idea. Interesting!