If you’ve ever faced such temptation to wrap inner exceptions within some neatly-defined exception type, you’d better off fight the devil. Let exceptions be themselves and freely fly high the call stack. 💸

To elaborate on the issue, consider the hypothetical DataProcessor and the general purpose exception type DataProcessingFailure below:

class DataProcessingFailure extends Error {
  constructor(public innerError: Error) {
    super();
  }
}

class DataProcessor {
  process() {
    try {
      // Some processing
    } catch (err) {
      throw new DataProcessingFailure(err)
    }
  }
} 

Here, the process() method only raises a DataProcessingFailure exception, if any. Whatever exception occurs (e.g., DivisionByZero) the caller of process() just catches a DataProcessingFailure instance. So, DataProcessingFailure is a wrapper exception.

The reason behind advising against wrapper exceptions is the change of behavior that you exert on the downstream system seen by higher level controllers. For instance, you could mask a well-defined exception that itself is an intricate part of the domain model. Another example is when your wrapper could mask network related exceptions, most of which resolve just by retrying, albeit if some caller at higher level of call stack could catch them as they are.

Putting it all together, you’d logically prefer minimizing footprints/side-effects of various components/implementations on each other, so that you could avoid unwanted couplings/special-treatments.


About Regular Encounters
I’ve decided to record my daily encounters with professional issues on a somewhat regular basis. Not all of them are equally important/unique/intricate, but are indeed practical, real, and of course, textually minimal.