Java:使用 RuntimeException 来逃避访问者
我非常想在 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 ) { }
}
这一切都很好,除了:
- 我可能忘记在某处捕获
ReturnException
并且编译器不会警告我。 - 我感觉很脏。
有一个更好的方法吗? 是否有我不知道的 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:
- 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.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(7)
我认为这是一种合理的方法,原因如下:
此外,还有那些认为未经检查的异常并不是那么糟糕的人 。 你的用法让我想起了 Eclipse 的 OperationCanceledException 用于退出长时间运行的后台任务。
它并不完美,但是,如果有详细记录,对我来说似乎没问题。
I think this is a reasonable approach for a few reasons:
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.
抛出运行时异常作为控制逻辑绝对是一个坏主意。 你感觉肮脏的原因是你绕过了类型系统,即你的方法的返回类型是一个谎言。
您有多种更加干净的选择。
1. Exceptions Functor 是
一个很好的技术,当你受限于你可能抛出的异常时,如果你不能抛出一个受检查的异常,就返回一个将抛出一个受检查的异常的对象。 例如,java.util.concurrent.Callable 就是该函子的一个实例。
请参阅此处了解此技术的详细说明。
例如,而不是这个:
这样做:
2。 不相交联合(又名“Either Bifunctor”)
此技术允许您从同一方法返回两种不同类型之一。 它有点像大多数人熟悉的 Tuple 技术,用于从方法返回多个值。 但是,这不是返回 A 类型和 B 类型的值,而是返回 A 类型或 B 类型的单个值。
例如,给定一个枚举 Fail,它可以枚举适用的错误代码,该示例将变为
...现在更加清晰,因为您不需要 try/catch:
Either 类并不是很难编写,但是 是一个功能齐全的实现由Functional Java 库提供。
3. Option Monad
有点像类型安全的 null,当您不想为某些输入返回值,但不需要异常或错误代码时,这是一个很好的技术。 通常,人们会返回所谓的“哨兵值”,但 Option 相当干净。
现在你已经......
这个调用很好而且干净:
事实上它是一个 monad让您可以链接调用而不处理特殊的 None 值:
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:
Do this:
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...
Making the call is now much cleaner because you don't need try/catch:
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...
The call is nice and clean:
And the fact that it's a monad lets you chain calls without handling the special None value:
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.
您不只是返回一个值有什么原因吗? 比如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.
我看到了以下选项:
RuntimeException
子类。 通过在最常见的dispatch
调用中捕获异常来检查是否存在严重问题,并在出现问题时报告该异常。I see the following options for you:
RuntimeException
subclass. Check for serious problems by catching your exception in the most general call todispatch
and reporting that one if it gets that far.为什么要从访问者那里返回值? 访问者的适当方法由正在访问的类调用。 完成的所有工作都封装在访问者类本身中,它应该不返回任何内容并处理它自己的错误。 调用类所需的唯一义务是调用适当的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
您必须使用 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.
我没有使用过你提到的 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, tellingaccept(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...), andyou 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...