Java:使用 RuntimeException 来逃避访问者

发布于 2024-07-10 13:34:08 字数 1682 浏览 8 评论 0原文

我非常想在 Java 程序中使用未经检查的异常作为短路控制流构造。 我希望这里有人能建议我更好、更干净的方法来处理这个问题。

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

Visitor 超类(来自 XTC 库)定义了

Object dispatch(Node n)

哪些回调通过dispatch形式的反射方法

Object visitNodeSubtype(Node n)

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

private static class ReturnException extends RuntimeException {
}

现在,return语句的访问者方法看起来像

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 ) { }
}

这一切都很好,除了:

  1. 我可能忘记在某处捕获 ReturnException 并且编译器不会警告我。
  2. 我感觉很脏。

有一个更好的方法吗? 是否有我不知道的 Java 模式来实现这种非本地控制流?

[更新] 这个特定的示例有些无效:Visitor 超类捕获并包装异常(甚至是 RuntimeException),因此抛出异常并没有真正的帮助。 我已经实现了从 visitReturnStatement 返回 enum 类型的建议。 幸运的是,这只需要在少数地方进行检查(例如,visitCompoundStatement),因此实际上比抛出异常要少一些麻烦。

总的来说,我认为这仍然是一个有效的问题。 不过,如果您没有依赖第三方库,那么整个问题可以通过合理的设计来避免。

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:

  1. I may forget to catch a ReturnException somewhere and the compiler won't warn me.
  2. 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 RuntimeExceptions), 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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

墨落画卷 2024-07-17 13:34:08

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

  • 您正在使用第 3 方,并且无法添加已检查的异常
  • 在大量访问者中到处检查返回值,而仅在少数情况下才需要这样做,这是不必要的负担

此外,还有那些认为未经检查的异常并不是那么糟糕的人 。 你的用法让我想起了 Eclipse 的 OperationCanceledException 用于退出长时间运行的后台任务。

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

I think this is a reasonable approach for a few reasons:

  • You are using a 3rd party and are unable to add the checked exception
  • Checking return values everywhere in a large set of visitors when it's only necessary in a few is an unnecessary burden

Also, there are those that have argued that unchecked exceptions aren't all that bad. Your usage reminds me of Eclipse's OperationCanceledException which is used to blow out of long-running background tasks.

It's not perfect, but, if well documented, it seems ok to me.

左岸枫 2024-07-17 13:34:08

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

您有多种更加干净的选择。

1. Exceptions Functor 是

一个很好的技术,当你受限于你可能抛出的异常时,如果你不能抛出一个受检查的异常,就返回一个将抛出一个受检查的异常的对象。 例如,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。 不相交联合(又名“Either Bifunctor”)

此技术允许您从同一方法返回两种不同类型之一。 它有点像大多数人熟悉的 Tuple 技术,用于从方法返回多个值。 但是,这不是返回 A 类型和 B 类型的值,而是返回 A 类型或 B 类型的单个值。

例如,给定一个枚举 Fail,它可以枚举适用的错误代码,该示例将变为

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. Option Monad

有点像类型安全的 null,当您不想为某些输入返回值,但不需要异常或错误代码时,这是一个很好的技术。 通常,人们会返回所谓的“哨兵值”,但 Option 相当干净。

现在你已经......

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 更容易编写,但同样,一个全功能的实现由Functional Java 库提供

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

Throwing a runtime exception as control logic is definitely a bad idea. The reason you feel dirty is that you're bypassing the type system, i.e. the return type of your methods is a lie.

You have several options that are considerably more clean.

1. The Exceptions Functor

A good technique to use, when you're restricted in the exceptions you may throw, if you can't throw a checked exception, return an object that will throw a checked exception. java.util.concurrent.Callable is an instance of this functor, for example.

See here for a detailed explanation of this technique.

For example, instead of this:

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

Do this:

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. Disjoint Union (a.k.a. The Either Bifunctor)

This technique lets you return one of two different types from the same method. It's a little bit like the Tuple<A, B> technique that most people are familiar with for returning more than one value from a method. However, instead of returning values of both types A and B, this involves returning a single value of either type A or B.

For example, given an enumeration Fail, which could enumerate applicable error codes, the example becomes...

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);
}

Making the call is now much cleaner because you don't need 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
}

The Either class is not very difficult to write, but a full-featured implementation is provided by the Functional Java library.

3. The Option Monad

A little bit like a type-safe null, this is a good technique to use when you do not want to return a value for some inputs, but you don't need exceptions or error codes. Commonly, people will return what's called a "sentinel value", but Option is considerably cleaner.

You now have...

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

The call is nice and clean:

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

And the fact that it's a monad lets you chain calls without handling the special None value:

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

The Option class is even easier to write than Either, but again, a full-featured implementation is provided by the Functional Java library.

See here for a detailed discussion of Option and Either.

夏至、离别 2024-07-17 13:34:08

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

Is there a reason you aren't just returning a value? Such as NULL, if you really want to return nothing? That would be a lot simpler, and wouldn't risk throwing an unchecked runtime exception.

尾戒 2024-07-17 13:34:08

我看到了以下选项:

  1. 继续定义 RuntimeException 子类。 通过在最常见的 dispatch 调用中捕获异常来检查是否存在严重问题,并在出现问题时报告该异常。
  2. 如果节点处理代码认为搜索应该突然结束,则让其返回一个特殊对象。 这仍然迫使您检查返回值而不是捕获异常,但您可能更喜欢这种方式的代码外观。
  3. 如果树遍历要被某些外部因素停止,请在子线程中完成所有操作,并在该对象中设置同步字段,以便告诉线程提前停止。

I see the following options for you:

  1. Go ahead and define that RuntimeException subclass. Check for serious problems by catching your exception in the most general call to dispatch and reporting that one if it gets that far.
  2. Have the node processing code return a special object if it thinks searching should end abruptly. This still forces you to check return values instead of catching exceptions, but you might like the look of the code better that way.
  3. If the tree walk is to be stopped by some external factor, do it all inside a subthread, and set a synchronized field in that object in order to tell the thread to stop prematurely.
腹黑女流氓 2024-07-17 13:34:08

为什么要从访问者那里返回值? 访问者的适当方法由正在访问的类调用。 完成的所有工作都封装在访问者类本身中,它应该不返回任何内容并处理它自己的错误。 调用类所需的唯一义务是调用适当的visitXXX方法,仅此而已。 (这假设您在示例中使用重载方法,而不是为每种类型覆盖相同的访问()方法)。

访问者不应更改所访问的类,或者必须了解其用途,除非它允许访问发生。 返回值或引发异常将违反此规定。

访客模式

Why are you returning a value from your visitor? The appropriate method of the visitor is called by classes that are being visited. All work done is encapsulated within the visitor class itself, it should return nothing and handle it's own errors. The only obligation required of the calling class is to call the appropriate visitXXX method, nothing more. (This assumes you are using overloaded methods as in your example as opposed to overriding the same visit() method for each type).

The visited class should not be changed by the visitor or have to have any knowledge of what it does, other than it allows the visit to happen. Returning a value or throwing an exception would violate this.

Visitor Pattern

小嗲 2024-07-17 13:34:08

您必须使用 XTC 的访客吗? 这是一个非常简单的接口,您可以实现自己的接口,它可以抛出 checked ReturnException,您不会忘记在需要的地方捕获它。

Do you have to use Visitor from XTC? It's a pretty trivial interface, and you could implement your own which can throw checked ReturnException, which you would not forget to catch where needed.

十年九夏 2024-07-17 13:34:08

我没有使用过你提到的 XTC 库。 它如何提供访问者模式的补充部分 - 节点上的 accept(visitor) 方法? 即使这是一个基于反射的调度程序,仍然必须有一些东西可以处理语法树上的递归?

如果这个结构迭代代码很容易访问,并且您还没有使用 visitXxx(node) 方法的返回值,那么您是否可以利用一个简单的枚举返回值,甚至一个布尔标志,告诉accept(visitor) 不递归到子节点?

如果:

  • accept(visitor) 未由节点显式实现(存在某些字段或访问器反射,或者节点只是为某些标准控制流逻辑实现子获取接口,或出于任何其他原因...),并且

  • 您不想弄乱库的结构迭代部分,或者它不可用,或者不值得努力...

那么作为最后的手段,我想例外可能是您的仍然使用普通 XTC 库的唯一选择。

不过,这是一个有趣的问题,我可以理解为什么基于异常的控制流让你感觉很肮脏......

I've not used the XTC library you mention. How does it supply the complementary part of the visitor pattern - the accept(visitor) method on nodes? Even if this is a reflection based dispatcher, there must still be something that handles recursion down the syntax tree?

If this structural iteration code is readily accessible, and you're not already using the return value from your visitXxx(node) methods, could you exploit a simple enumerated return value, or even a boolean flag, telling accept(visitor) not to recurse into child nodes?

If:

  • accept(visitor) isn't explicitly implemented by nodes (there's some field or accessor reflection going on, or nodes just implement a child-getting interface for some standard control-flow logic, or for any other reason...), and

  • you don't want to mess with the structural iterating part of the library, or it's not available, or it's not worth the effort...

then as a last resort I guess that exceptions might be your only option whilst still using the vanilla XTC library.

An interesting problem though, and I can understand why exception-based control flow makes you feel dirty...

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文