3

I have some code in a Java 8 app that throws N different exception classes which come from a separate library. There's currently a separate handler for each exception class despite them sharing some common code. I'd like to refactor this to avoid:

  1. Repeating the list of exception classes that have some commonality (for example using a switch, instanceof or casting)
  2. Repeating calls to someCommonCode N times
class MyClass {
  public void errorHandler(FirstException e) {
    System.out.println("This error is not so bad");
  }

  public void errorHandler(SecondException e) {
    System.out.println("This error is worse");
  }
  public void someMethod() {
    try {
      riskItAll();
    } catch(FirstException | SecondException e) {
      someCommonCode();
      errorHandler(e);
      moreCommonCode();
    } catch(Exception e) {
      uncommonCode();
    }
  }
}

So far, I've been stuck trying to find documentation / examples for dealing with multiple catch blocks in this way as I haven't found the term used to describe the type of e inside such a block. There's no searchable terminology introduced on http://www.oracle.com/technetwork/articles/java/java7exceptions-486908.html

It could be a generic, but that would be surprising since you can't catch an instance of a type parameter.

The code snippet above does not build - the compiler error raised at errorHandler is

error: no suitable method found for errorHandler(RuntimeException)
method MyClass.errorHandler(FirstException) is not applicable (argument mismatch; RuntimeException cannot be converted to FirstException)
method MyClass.errorHandler(SecondException) is not applicable (argument mismatch; RuntimeException cannot be converted to SecondException)
NotJavanese
  • 4,072
  • 1
  • 15
  • 15
  • 1
    Maybe you can catch all exceptions (by catching `Exception`) and pass them to one handler that knows how to route them. That would require `instanceof`, but at least it'd only be in one place. – kichik Jul 18 '17 at 19:27
  • 1
    I think we may need some more info, such as the Exception classes, and the handlers. With the code you've given, the best way would be to just have `catch(FirstException e) { ... } catch (SecondException e) { ... } ...` – lucasvw Jul 18 '17 at 19:28
  • It's not clear to me what are you trying to do that you're not already doing. – Nir Alfasi Jul 18 '17 at 19:29
  • Good point alfasin, I've added to the question to explain that this code snippet does not compile. – NotJavanese Jul 18 '17 at 20:51

3 Answers3

2

Overloaded methods won't work. FirstException | SecondException e is a union type, which is a special case in Java that only exists for multi-catch exception variables. When you try to pass it to a method, the compiler treats it as the least upper bound of all the types in the union.

However, you can rethrow a union-type exception and catch its component types:

public void someMethod() {
    try {
        riskItAll();
    } catch (FirstException | SecondException e) {
        someCommonCode();
        try {
            throw e;
        } catch (FirstException e2) {
            System.out.println("This error is not so bad");
        } catch (SecondException e2) {
            System.out.println("This error is worse");
        }
        moreCommonCode();
    } catch (Exception e) {
        uncommonCode();
    }
}

But that's even uglier than instanceof:

public void someMethod() {
    try {
        riskItAll();
    } catch (FirstException | SecondException e) {
        someCommonCode();
        if (e instanceof FirstException) {
            System.out.println("This error is not so bad");
        } else {
            System.out.println("This error is worse");
        }
        moreCommonCode();
    } catch (Exception e) {
        uncommonCode();
    }
}

There isn't really any nicer way of doing it. Multi-catch blocks are designed for handling multiple exceptions the exact same way, not for simplifying partially duplicate code.

Sean Van Gorder
  • 3,393
  • 26
  • 26
  • 1
    Given your answer, this question could actually be construed as another way to ask https://stackoverflow.com/questions/8393004 and it is clear why the design of multi-catch does not allow it to solve this problem. This is a good direction to think about re-writing the code without duplication. – NotJavanese Jul 18 '17 at 22:25
1

We have an (somehow ugly) solution:

try {
 ... whatever
} catch (SomeBaseException e) {
  new Handler().handle(e);
}

where handle() simply does a cascade of if instanceof calls.

Unfortunately this turns pretty complicated quickly - so one important aspect there: whenever we touch handle() - we first go to the corresponding unit test and add a test case for the additional handling we have to add. This is a situation where TDD (test driven development) isn't "just helpful" - but mandatory.

GhostCat
  • 137,827
  • 25
  • 176
  • 248
  • `Handler` becomes a god class - make that a factory? `new ExceptionHandlerFactory(e).handle()`. Within the factory, have a static `Map` with `e.getClass()` as the key and the handler `Class` as the value to determine which handler to instantiate. This avoids instanceof and if/else. – Andrew S Jul 18 '17 at 19:42
  • 1
    Moving all the exception handling code to another method is just sweeping the complexity under the rug. Where's the implementation of `Handler.handle`? – Sean Van Gorder Jul 18 '17 at 21:32
0

It's not pretty, but this is a working implementation of multiple dispatch error handling that does not rely on the ability to patch the error classes themselves, or a specific base error class.

Neither exception names nor common code invocations were repeated, but this might be a poor dynamic language user's solution for any number of other reasons.

import java.lang.reflect.Method;
import java.util.HashMap;
import java.lang.reflect.InvocationTargetException;

class FirstException extends RuntimeException {

}

class SecondException extends Exception {

}

public class MyClass {
  public void errorHandler(FirstException e) {
    System.out.println("This error is not so bad");
  }

  public void errorHandler(SecondException e) {
    System.out.println("This error is worse");
  }

  public void someCommonCode() {
    System.out.println("Here's one of the usual errors:");
  }

  public void moreCommonCode() {
    System.out.println("That's it.\n");
  }

  public void uncommonCode() {
    System.out.println("Surprise!\n");
  }

  public static final HashMap<Class, Method> errorMap;

  static {
      HashMap<Class, Method> errMap = new HashMap<Class, Method>();
      for (Method method: MyClass.class.getMethods()) {
          String name = method.getName();
          Class[] parameters = method.getParameterTypes();
          if (parameters.length == 0 || !name.equals("errorHandler")) {
              continue;
          }
          Class parameter1 = parameters[0];
          if (method.getParameterCount() == 1 && Exception.class.isAssignableFrom(parameter1)) {
              errMap.put(parameter1, method);
          }
      }
      errorMap = errMap;
  }

  public void riskItAll(int risk) throws Exception {
      if (risk == 0) {
          throw new FirstException();
      } else if (risk == 1) {
          throw new SecondException();
      } else {
          throw new Exception();
      }
  }

  public void someMethod(int risk) {
    try {
      riskItAll(risk);
    } catch(Exception e) {
        Class errorClass = e.getClass();
        if (errorMap.containsKey(errorClass)) {
            someCommonCode();
            try {
                errorMap.get(errorClass).invoke(this, e);
            } catch (IllegalAccessException | InvocationTargetException reflectionError) {
                System.out.println("¯\\_(ツ)_/¯");
            }
            moreCommonCode();
        } else {
            uncommonCode();
        }
    }
  }

  public static void main(String[] args) {
      MyClass instance = new MyClass();
      instance.someMethod(0);
      instance.someMethod(1);
      instance.someMethod(2);
  }
}
NotJavanese
  • 4,072
  • 1
  • 15
  • 15