I am being powerfully tempted to use an unchecked exception as a short-circuit control-flow construct in a Java program. I hope somebody here can advise me on a better, cleaner way to handle this problem.
The idea is that I want to cut short the recursive exploration of sub-trees by a visitor without having to check a "stop" flag in every method call. Specifically, I'm building a control-flow graph using a visitor over the abstract syntax tree. A return
statement in the AST should stop exploration of the sub-tree and send the visitor back to the nearest enclosing if/then or loop block.
The Visitor
superclass (from the XTC library) defines
Object dispatch(Node n)
which calls back via reflection methods of the form
Object visitNodeSubtype(Node n)
dispatch
is not declared to throw any exceptions, so I declared a private class that extends RuntimeException
private static class ReturnException extends RuntimeException {
}
Now, the visitor method for a return statement looks like
Object visitReturnStatement(Node n) {
// handle return value assignment...
// add flow edge to exit node...
throw new ReturnException();
}
and every compound statement needs to handle the ReturnException
Object visitIfElseStatement(Node n) {
Node test = n.getChild(0);
Node ifPart = n.getChild(1);
Node elsePart = n.getChild(2);
// add flow edges to if/else...
try{ dispatch(ifPart); } catch( ReturnException e ) { }
try{ dispatch(elsePart); } catch( ReturnException e ) { }
}
This all works fine, except:
- I may forget to catch a
ReturnException
somewhere and the compiler won't warn me. - I feel dirty.
Is there a better way to do this? Is there a Java pattern I am unaware of to implement this kind of non-local flow-of-control?
[UPDATE] This specific example turns out to be somewhat invalid: the Visitor
superclass catches and wraps exceptions (even RuntimeException
s), so the exception throwing doesn't really help. I've implemented the suggestion to return an enum
type from visitReturnStatement
. Luckily, this only needs to be checked in a small number of places (e.g., visitCompoundStatement
), so it's actually a bit less hassle than throwing exceptions.
In general, I think this is still a valid question. Though perhaps, if you are not tied to a third-party library, the entire problem can be avoided with sensible design.