Explicit exceptions - a solution to fragile code dealing with exceptions

I like this proposal, it looks very interesting in order write clean code, when your code is relying extensively on exceptions and error handling.

However I don't understand why this would be the caller that would declare which exceptions the function would be expected to throw. Maybe there is something I'm missing, but for me it would be more logical to declare which exceptions are expected to be thrown inside the function definition. I don't see why it would be expected to catch a NotFound exception in some places, while in other places that would be considered as an error.

A syntax similar to Java could be used for that:

function getUser (id) throws 'NotFound', 'ServiceUnavailable' {
  if (!user.has(id)) {
    throw new Exception('NotFound', 'User was not found');
  }

  if (!serviceAvailable()) {
    throw new Exception('ServiceUnavailable');
  }

  return users.get(id);
}

Like this, if a caller function is receiving an error that isn't 'NotFound' or 'ServiceUnavailable', it would know that, by the function definition, this was not expected, and this will be escalated to a fatal error like you proposed.

function getUserOrDefault (id, defaultValue = null) {
  try {
    // No use to tell which exceptions we expect each time we are calling the function
    // as it is already declared in the function definition
    return getUser(id);
  }
  catch (ex) {
    // Usual exception handling
    if (ex instanceof Exception && ex.code === 'NotFound') {
      return defaultValue;
    }
    if (ex instanceof Exception && ex.code === 'ServiceUnavailable') {
      return defaultValue;
    }

    // Escalate the exception if not expected
    throw ex;
  }
}

The use of a try-catch around a function call, that has declared its expected exceptions in its definition, could even be enforced by using a linter, in order to ensure that every expected exception is always handled.

Also, I think that this syntax (or even your syntax proposal) should support using the exception class function (if one exists), instead of using the exception code string:

// Create Exceptions
const NotFoundException = new Exception('NotFound', 'User was not found');
const ServiceUnavailableException = new Exception('ServiceUnavailable');

const users = new Map()

function getUser (id) throws NotFoundException, ServiceUnavailableException {
  if (!user.has(id)) throw NotFoundException;
  if (!serviceAvailable()) throw ServiceunavailableException;

  return users.get(id);
}


function getUserOrDefault (id, defaultValue = null) {
  try {
    return getUser(id);
  }
  catch (ex) {
    if (ex instanceof NotFoundException) {
      return defaultValue;
    }
    if (ex instanceof ServiceUnavailableException) {
      return defaultValue;
    }

    throw ex;
  }
}

We could even go further:

We could imagine a simpler catch handling, by allowing to specify the class function (or code string) next to the catch keyword, and also allowing multiple catch blocks after a try one.

function getUserOrDefault (id, defaultValue = null) {
  try {
    return getUser(id);
  } catch NotFoundException (ex) {
    return defaultValue;
  } catch ServiceUnavailableException (ex) {
    return defaultValue;
  }
}

This would allow to get rid of the nested if conditions which are quite verbose, and also get rid of the default exception escalation in order to handle unexpected exceptions.

I find that it would end up looking way more readable, and also more explicit about expected exceptions.

Here would be an even simpler version combining the two catch, as they handle both exceptions the same way:

function getUserOrDefault (id, defaultValue = null) {
  try {
    return getUser(id);
  } catch NotFoundException, ServiceUnavailableException {
    return defaultValue;
  }
}

Here, the symmetry between the function proptotype

throw NotFoundException, ServiceUnavailableException

and the function call catch:

catch NotFoundException, ServiceUnavailableException

would be quite elegant, and explicit about the fact that every expected exceptions are indeed handled.

I know this last part goes a bit beyond your proposal, but I came up with this while I was thinking about it. I think this could be a thing to try to implement at the same time, as we are try to see how we could improve errors and exceptions handling in general.

2 Likes