英文:
Java: How to propagate a "method exit" when tucking-in exceptions?
问题
* 通过“方法退出” - 我的意思是方法中的操作,比如```return```或```throw new...```,编译器认为这是方法的结束 - 如果您可以告诉我“方法退出”的正确表达方式,我会编辑问题
我的问题如下:
- 我经常使用```throw new RuntimeException(...```。
- 因此,我决定将它“隐藏起来”,如下:
```public static void quickRaise(String msg) { throw new RuntimeException(msg); }``` <BR>
然后我可以重复使用它。<BR>
(将来这将有助于我改进围绕引发运行时异常的过程,甚至可以切换到自定义异常类,而无需在代码中搜索异常抛出的位置)
- 然而,在以前我可以这样编写:
public MyType doSomething() {
try {
//...
return new MyType(parameter);
} catch (Exception e) {
throw new RuntimeException("msg")
}
}
编译器会正确理解“这个方法通过return或throw退出”,因此没有逻辑“死路”。
当我将```throw new RuntimeException("msg")```更改为```quickRaise("msg")```时,编译器不再认为我的方法“完成”。它抱怨缺少return语句,即使```quickRaise```在语义上等同于```throw```(或者至少是我尝试这么做!)
让我试着通过一个可复现的示例来重申这个问题(这将无法编译,这就是问题所在):
public static void main(String[] args) {
System.out.println(doSomething());
}
public static String doSomething() {
try {
//... 这里进行一些有趣的操作
return "Something";
} catch (Exception e) {
quickRaise("无法找到握手");
//throw new RuntimeException("如果取消对此行的注释,它将会编译!");
}
}
public static void quickRaise(String msg) {
throw new RuntimeException(msg);
}
英文:
- By "method exit" - I mean the actions in a method such as
return
orthrow new...
that the compiler considers the end of a method - if you could please tell me the accepted word for "method exit", I will edit the question
My problem is the following:
-
I do a lot of
throw new RuntimeException(...
-
So, I decided to "tuck it in" as:
public static void quickRaise (String msg) { throw new RuntimeException(msg); }
<BR>And then I can reuse it.<BR>
(This will help me in the future to enhance the procedure around raising Runtime Exceptions and even
switch to a custom Exception class, without fishing in the code for exception throws) -
However, where before I could write:
public MyType doSomething() { try { //... return new MyType (parameter); } catch (Exception e) { throw new RuntimeException("msg") } }
And the compiler would correctly understand that "this method either exits by return or by throw" and therefore there are no logical "dead ends"
When I changed throw new RuntimeException("msg")
to quickRaise("msg")
, the compiler no longer considers my method "complete". It complains about a missing return statement, even though quickRaise
is semantically equivalent to throw
(or at least this is what I am trying to do!)
Let me try to reiterate the problem by a reproductive example (this will not compile, which is the problem):
public static void main(String[] args) {
System.out.println(doSomething());
}
public static String doSomething () {
try {
//... Some fun stuff going on here
return "Something";
} catch (Exception e) {
quickRaise("Could not find handshakes");
//throw new RuntimeException("If you uncomment this line, it will compile!");
}
}
public static void quickRaise (String msg) {
throw new RuntimeException(msg);
}
答案1
得分: 2
你的想法是极不明智的。
例如,这只是糟糕的代码风格:
try {
someIO();
} catch (IOException e) {
throw new RuntimeException("Problem with IO");
}
糟糕的地方在于你现在已经丧失了有关问题的实际信息。这些信息被锁定在异常的五个单独部分中:它的类型(例如,FileNotFoundException
),它的消息(例如,“目录/foo/bar不存在”),它的堆栈跟踪,它的因果链,以及作为可抛出对象,该特定类型异常的任何额外细节(例如,某些 SQLException 的 DB 引擎特定错误编码)。
丢弃这些信息是愚蠢的。
要修复这个问题,你只需要添加原因:
} catch (IOException e) {
throw new RuntimeException("IO problem", e);
}
现在,IOException 被标记为你要抛出的异常的 原因,这意味着在错误日志中,你将会看到它 + 消息 + 它的堆栈跟踪 + 任何它可能有的原因的堆栈跟踪。
要让编译器意识到方法在这里结束,你只需要将它抛出:
public static RuntimeException quickRaise(String msg) {
throw new RuntimeException(msg);
return null; // 无所谓,我们永远不会执行到这里
}
// 使用方式:
throw quickRaise(msg);
但是,正如我之前解释过的,这是一个非常糟糕的想法。
其次,拥有“我只想抛出一个异常,也许以后我想要替换我抛出的异常类型”的想法也并不现实:你需要为情况选择一个适当的异常,因此你无法一开始就编写一个适用于所有情况的抛出方法。
那么,我该怎么做?
首先,学会接受 throws
子句。如果你的方法从根本上执行 I/O 操作(例如,它的 javadoc 和/或名称明显表明,例如 saveGame(Path p)
或 scanUserHome
),那么它应该声明为 throws IOException
。
如果你的方法是一个入口点(也就是说,它是你的代码开始运行的第一个点),那么你的方法应该声明为 throws Exception
。例如,你的 public static void main()
方法应该 throws Exception
。有时入口点不是 main 方法,而是其他什么东西(例如 web 处理程序路由钩子),有时候反向的愚蠢框架阻止你这样做,但通常会有包装功能(例如 } catch (Exception e) { throw new ServletException(e); }
)。
对于既不是方法目的的一部分,更多是实现细节的异常,又非常不太可能出错,而且除了硬崩溃之外没有太多其他选择的情况,是的,可以重新包装为 RuntimeException。对于所有这样的异常来说,很少有在全局范围内进行更改的意义。最多,你可能会事后意识到失败的可能性比你最初想象的要大一些,然后为其创建一个适当的异常并记录此行为。但这又是基于每个方法的基础,而不是一种可以套用的通用方法。
英文:
Your idea is highly inadvisable.
For example, this is just bad codestyle:
try {
someIO();
} catch (IOException e) {
throw new RuntimeException("Problem with IO");
}
The reason it's bad is that you have now obliterated the actual information about the problem. That information is locked into 5 separate parts of that exception you just caught: Its type (for example, FileNotFoundException
, its message (e.g. "Directory /foo/bar does not exist"), its stack trace, its causal chain, and as throwables are objects, any particular extra detail for that particular kind of exception (such as the DB-engine-specific error coding for some SQLException).
Throwing this info away is silly.
All you'd need to do to fix this, is to add the cause:
} catch (IOException e) {
throw new RuntimeException("IO problem", e);
}
Now the IOException is marked as the cause of the exception you are throwing, which means in error logs you'll see it + the message + the stack trace of it + the stack trace of any causes it had as well.
All you need to do to make the compiler realize that the method ends here, is to throw it:
public static RuntimeException quickRaise(String msg) {
throw new RuntimeException(msg);
return null; // doesn't matter, we never get here
}
// to use:
throw quickRaise(msg);
But, as I explained before, this is a very bad idea.
Secondarily, having the idea of 'I just want to throw an exception and maybe later I want to replace the kind of exception I throw' also doesn't really work out: You need to pick a proper exception for the situation, therefore you cannot write a one-size-fits-all throw method in the first place.
Okay, so what do I do?
Primarily, learn to embrace throws
clauses. If your method fundamentally does I/O (for example, the javadoc of it and/or the name makes that obvious, it is for example saveGame(Path p)
, or scanUserHome
), then it should be declared to throws IOException
.
If your method is an entrypoint (as in, it is the first point where your own code begins running), then your method should be declared to throws Exception
. For example, your public static void main()
method should throws Exception
. Sometimes an entrypoint isn't main but something else (a webhandler routing hook for example), and sometimes backwards silly franeworks prevent you from doing that, but there tends to be a wrap functionality (such as } catch (Exception e) { throw new ServletException(e); }
).
For exceptions which are both [A] fundamentally not part of the method's purpose, but more part of an implementation detail and [B] is very unlikely to go wrong and there's not much you can do other than hard crash if it would, then, yeah, rewrap as RuntimeException. There isn't a lot of point in ever changing this 'globally' for all such exceptions. At best you belatedly realize that failure is a bit more likely than you originally thought and either create a proper exception for it and document this behaviour. But that's, again, on a per-method basis, not something you can apply in blanket fashion.
答案2
得分: 1
你的方法与编译器需要看到流程在抛出语句处终止的需求根本不符。
我建议创建一个实用方法,仅构造一个异常,然后从原始点抛出它。
要么这样做,要么在每次调用quickRaise()之后添加虚拟返回。
英文:
Your approach is fundamentally at odds with the need for the compiler to see that the flow terminates at the throw statement.
I'd suggest having a utility method that just constructs an exception, which you then throw from the original point.
It's either than or put dummy returns after each call to quickRaise().
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论