ExceptionExtensibility

From APIDesign

Jump to: navigation, search

It has been mentioned in the page about exceptions that changing a code to throw a new exception is not compatible change. However this is not fully true, because Java exceptions are regular classes, and classes support inheritance, one can define new subtypes of existing exceptions and yet keep the code written by your API users compatible. Imagine there is a method in version one throwing ordinary I/O exception:

public static int compute(int x, int y) throws IOException {
  if (y - x == 1) throw new IOException("For some reason I cannot deal with this!");
  return x + y;
}

Now people can use this method to do many things, complex ones or trivial like to sum two numbers:

int result;
try {
  result = compute(1, 2);
} catch (IOException ex) {
  Logger.getLogger("mylogger").log(Level.WARNING, "Problem!", ex);
  result = -1;
}

This is a valid use of the compute API method. As authors of the library, we value our users and want to support this API use in future. However we also want to please more and more users. If some other ones require us to provide better information about the values of x and y, instead of just throwing the IOException, can we help them? Can we change the contract and yet pretend we have not changed anything? Yes, this is possible, just imagine version two of the library:

public static int compute(int x, int y) throws IOException {
  if (y - x == 1) throw new StrangeXYException(x, y);
  return x + y;
}
 
public final class StrangeXYException extends IOException {
  int x, y;
  StrangeXYException(int x, int y) {
    super("For some reason I cannot deal with this!");
    this.x = x;
    this.y = y;
  }
 
  public int getX() { return x; }
  public int getY() { return y; }
}

The previously written client code remains valid. A subclass of IOException is thrown, it is matched by the catch (IOException ex) block and everything continues to work as it used to. Yet, if one is interested in more detailed information about the failure, one can catch the version two newly defined exception:

int result;
try {
  result = compute(1, 2);
} catch (StrangeXYException ex) {
  // compute ourselves meaningful result
  result = ex.getX() + ex.getY();
} catch (IOException ex) {
  Logger.getLogger("mylogger").log(Level.WARNING, "Problem!", ex);
  result = -1;
}

The object oriented nature of try/catch statements makes evolution perfectly possible. With every new release one can define new specialized exception as subclass of some already existing one. The previously working code can remain unaffected, the new client code can extract the additional information from the new exception class exposed in the API. This is much more pleasant evolution than for example the one available with switch/case where inheritance is not taken into account at all.

Personal tools
buy