Java:使用RuntimeException从访客转义

我正强烈地试图在Java程序中使用未经检查的异常作为短路控制流构造。 我希望这里的某个人能够以更好,更清洁的方式向我提出建议,以解决这个问题。

这个想法是,我想减少访问者对子树的递归探索,而不必在每个方法调用中检查“停止”标志。 具体来说,我使用抽象语法树上的访问者构建控制流图。 AST中的return语句应停止对子树的探索,并将访问者发送回最近的包含if / then或loop块。

Visitor超类(来自XTC库)定义

Object dispatch(Node n)

它通过表单的反射方法回调

Object visitNodeSubtype(Node n)

dispatch没有声明抛出任何异常,所以我声明了一个扩展了RuntimeException的私有类

private static class ReturnException extends RuntimeException {
}

现在,返回语句的visitor方法看起来像

Object visitReturnStatement(Node n) {
    // handle return value assignment...
    // add flow edge to exit node...
    throw new ReturnException();
}

并且每个复合语句都需要处理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 ) { }
}

这一切工作正常,除了:

  • 我可能会忘记在某处发现ReturnException ,编译器不会警告我。
  • 我觉得很肮脏。
  • 有一个更好的方法吗? 有没有Java模式我不知道实现这种非本地流量控制?

    [UPDATE]这个具体的例子证明是有些无效的: Visitor超类捕获并包装异常(即使是RuntimeException ),所以抛出异常并不会真正起作用。 我已经实现了建议,从visitReturnStatement返回一个enum类型。 幸运的是,这只需要在少数地方进行检查(例如visitCompoundStatement ),所以它实际上比抛出异常少一点麻烦。

    总的来说,我认为这仍然是一个有效的问题。 虽然也许,如果你不与第三方库绑在一起,整个问题都可以通过合理的设计来避免。


    我认为这是一个合理的方法,原因如下:

  • 您正在使用第三方并且无法添加检查的异常
  • 如果仅在少数访问者中需要检查大量访问者中的返回值,则是不必要的负担
  • 而且,有些人认为未经检查的例外情况并不是那么糟糕。 您的用法让我想起了Eclipse的OperationCanceledException,它用于清除长时间运行的后台任务。

    这并不完美,但如果有详细记录,对我来说似乎就没有问题。


    抛出运行时异常作为控制逻辑肯定是一个坏主意。 你感到肮脏​​的原因是你绕过了类型系统,即你的方法的返回类型是谎言。

    你有几个更清洁的选项。

    1.例外函子

    一个很好的技巧,当你限制你可能抛出的异常时,如果你不能抛出一个检查的异常,则返回一个会抛出一个检查异常的对象。 例如,java.util.concurrent.Callable是此仿函数的一个实例。

    在这里看到这个技术的详细解释。

    例如,而不是这个:

    public Something visit(Node n) {
      if (n.someting())
         return new Something();
      else
         throw new Error("Remember to catch me!");
    }
    

    做这个:

    public Callable<Something> visit(final Node n) {
      return new Callable<Something>() {
        public Something call() throws Exception {
          if (n.something())
             return new Something();
          else
             throw new Exception("Unforgettable!");
        }
      };
    }
    

    2.不相交的联盟(又名双联组合)

    这种技术可以让你从同一种方法返回两种不同类型之一。 这有点像Tuple<A, B>技术,大多数人都熟悉从方法中返回多个值。 但是,不是返回A和B类型的值,而是返回A或B类型的单个值。

    例如,给定枚举失败,可能枚举适用的错误代码,该示例变为...

    public Either<Fail, Something> visit(final Node n) {
      if (n.something())
        return Either.<Fail, Something>right(new Something());
      else
        return Either.<Fail, Something>left(Fail.DONE);
    }
    

    打电话现在更清洁,因为你不需要try / catch:

    Either<Fail, Something> x = node.dispatch(visitor);
    for (Something s : x.rightProjection()) {
      // Do something with Something
    }
    for (Fail f : x.leftProjection()) {
      // Handle failure
    }
    

    Either类不是很难编写,但Functional Java库提供了一个全功能的实现。

    3.选项Monad

    有点像类型安全的null,当你不想为某些输入返回值时,这是一种很好的技巧,但不需要异常或错误代码。 通常情况下,人们会返回所谓的“定点值”,但选项相当干净。

    你现在有...

    public Option<Something> visit(final Node n) {
      if (n.something())
        return Option.some(new Something());
      else
        return Option.<Something>none();
    }    
    

    这个电话很好,很干净:

    Option<Something> s = node.dispatch(visitor));
    if (s.isSome()) {
      Something x = s.some();
      // Do something with x.
    }
    else {
      // Handle None.
    }
    

    事实上,它是一个monad,可以让你连接呼叫而不处理特殊的None值:

    public Option<Something> visit(final Node n) {
      return dispatch(getIfPart(n).orElse(dispatch(getElsePart(n)));
    }    
    

    Option类比Either更容易编写,但功能性Java库提供了全功能的实现。

    请参阅此处以了解Option和Either的详细讨论。


    是否有一个原因,你不只是返回一个值? 比如NULL,如果你真的想要什么都不返回? 这将会简单得多,并且不会冒着抛出未经检查的运行时异常的风险。

    链接地址: http://www.djcxy.com/p/47687.html

    上一篇: Java: using a RuntimeException to escape from a Visitor

    下一篇: Monad interface in C++