Which is better/more efficient: check for bad values or catch Exceptions in Java
在Java中更有效:检查坏值以防止异常或让异常发生并捕获它们吗?
下面是两个示例代码块来说明这一区别:
1 2 3 4 5 6 7 8 9 10 11 12 | void doSomething(type value1) { ResultType result = genericError; if (value1 == badvalue || value1 == badvalue2 || ...) { result = specificError; } else { DoSomeActionThatFailsIfValue1IsBad(value1); // ... result = success; } callback(result); } |
对战
1 2 3 4 5 6 7 8 9 10 11 12 | void doSomething(type value1) { ResultType result = genericError; try { DoSomeActionThatFailsIfValue1IsBad(value1); // ... result = success; } catch (ExceptionType e) { result = specificError; } finally { callback(result); } } |
一方面,你总是在做比较。另一方面,我真的不知道系统内部如何生成异常、抛出异常,然后触发catch子句。它听起来效率较低,但是如果它在非错误情况下不增加开销,那么平均来说效率更高。这是什么?它是否添加了类似的检查?在为异常处理而添加的隐式代码中是否存在这种检查,即使附加了显式检查层?也许它总是取决于异常的类型?我不考虑什么?
我们还假设所有"坏值"都是已知的——这是一个显而易见的问题。如果您不知道所有的坏值——或者列表太长而且不规则——那么无论如何,异常处理可能是唯一的方法。
那么,每种方法的优缺点是什么?为什么?
要考虑的附带问题:
- 如果大多数情况下值为"坏"(会引发异常),您的答案会如何更改?
- 这在多大程度上取决于正在使用的虚拟机的具体情况?
- 如果对language-x提出同样的问题,答案会不同吗?(更普遍的情况是,它询问是否可以假定检查值总是比依赖异常处理更有效,因为它增加了当前编译器/解释器的开销。)
- (新)抛出异常的动作很慢。输入一个try块是否有开销,即使没有引发异常?
相似之处如下:
- 这类似于此答案中的代码示例,但声明它们仅在概念上相似,而不是编译后的实际情况。
- 前提类似于这个问题,但在我的例子中,任务的请求者(例如"某物")不是方法的调用者(例如"dosomething")(因此没有返回)。
这个很相似,但我没有找到我问题的答案。
和其他太多的问题相似,除了:
我不是在问理论上的最佳实践。我在问更多关于运行时性能和效率的问题(这意味着,对于特定情况,会有非意见的答案),尤其是在资源有限的平台上。例如,如果唯一错误的值只是一个空对象,那么检查它或只是尝试使用它并捕获异常会更好/更有效吗?
"如果大多数情况下值为"bad"(将引发异常),您的答案将如何更改?"我想这就是钥匙。与比较相比,异常是昂贵的,因此您确实希望在异常情况下使用异常。
同样,您关于这个答案可能如何变化的问题取决于语言/环境的联系:在不同的环境中,异常的开销是不同的。例如,当第一次抛出异常时,.NET 1.1和2.0的速度非常慢。
纯粹从效率的角度出发,结合您的代码示例,我认为这取决于您期望看到坏值的频率。如果不好的值不太常见,则比较更快,因为异常代价很高。但是,如果坏值非常罕见,则使用异常可能更快。
不过,归根结底,如果您在寻找性能,请分析您的代码。这段代码甚至可能不是一个问题。如果是的话,那么尝试两种方法,看看哪个更快。同样,这取决于你期望看到坏值的频率。
我几乎找不到有关抛出异常的成本的最新信息。很明显,一定有一些,您正在创建一个对象,并且可能正在获取堆栈跟踪信息。
在具体的示例中,您将讨论:
1 2 3 4 5 6 7 | if (value1 == badvalue || value1 == badvalue2 || ...) { result = specificError; } else { DoSomeActionThatFailsIfValue1IsBad(value1); // ... result = success; } |
我这里的问题是,如果(可能不完全)复制调用方中的逻辑,而该逻辑应该由您所调用的方法所拥有,那么您就处于危险之中。
因此我不会进行这些检查。您的代码没有执行实验,它"知道"应该发送的数据,我想?因此,抛出异常的可能性应该很低。因此,保持简单,让被叫方进行检查。
嗯,例外情况更昂贵,是的,但对我来说,它是关于如何权衡效率成本和糟糕设计。除非您的用例需要它,否则始终坚持最佳设计。
真正的问题是,你什么时候抛出一个异常?在特殊情况下。
如果您的参数不在您要查找的范围内,我建议您返回错误代码或布尔值。
例如,一个方法,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | public int IsAuthenticated(String username, String password) { if(!Validated(username,password) { // just an error // log it return -2; } // contacting the Database here if cannot connect to db { // woww this is HUUGE throw new DBException('cannot connect'); // or something like that } // validate against db here if validated, return 0; // etc etc } |
那是我的2美分
在我看来,如果只是为了拥有一个安全运行的系统,那么您应该在任何可能引发异常的地方使用try/catch块。如果首先检查可能的数据错误,则可以更好地控制错误响应。所以我建议两者都做。
像这样的问题就像问,
"用所有抽象函数编写接口或基类是否更有效?"
哪一种效率更高重要?其中只有一个是解决特定情况的正确方法
为了安全起见,假设异常是昂贵的。它们通常是,如果不是,至少会促使您明智地使用异常。(进入一个try块通常是非常便宜的,因为实现者尽其所能做到这一点,即使以使异常变得更昂贵为代价。毕竟,如果异常被正确使用,代码进入try块的频率将比它抛出的频率高很多倍。)
更重要的是,例外是一个样式问题。异常情况的例外使代码更简单,因为错误检查代码更少,所以实际的功能更清晰、更紧凑。
然而,如果在更正常的情况下抛出异常,那么读者必须记住无形的控制流,这与Intercal的
我的建议适用于我所知道的每种语言和环境:
别担心这里的效率。除了效率之外,还有很强的理由可以证明以有效的方式使用异常。
自由使用试块。
在特殊情况下使用例外。如果可能出现异常,则测试异常并以另一种方式进行处理。
通常,人们会假设try-catch更昂贵,因为它在代码中看起来更重,但这完全取决于jit。我的猜测是,如果没有一个真实的案例和一些性能度量,就无法判断。比较可能会更昂贵,尤其是当您有许多值时,例如,或者因为在许多情况下
至于应该选择哪一个(如"代码样式"),我的答案是:确保用户在失败时收到有用的错误消息。其他的都是品味问题,我不能给你规定。
我个人的观点是,异常表明某些东西被破坏了——这很可能是一个使用非法参数或除数为零或找不到文件等调用的API。这意味着可以通过检查值来抛出异常。
对于你的代码的读者——也是我个人的观点——如果你能确定它不是被各种奇怪的抛出(如果被用作程序流的一部分,它本质上是伪装的goto),那么遵循这个流程就容易多了。你根本就没什么可想的了。
我认为这是件好事。"聪明的"代码很难让人信服。
另一方面,JVM变得更聪明了,为提高效率而编码通常是没有回报的。
最理想的情况是,我想你会发现这可能是一次洗漱。他们都会表现得很好,我认为抛出异常永远不会成为你的瓶颈。您可能更关注Java的设计(以及其他Java程序员会期待什么),这是抛出的异常。Java是非常围绕抛出/捕获异常而设计的,您可以打赌设计者会尽可能高效地实现该过程。
我认为这主要是一种哲学和语言文化之类的东西。在Java中,一般公认的做法是方法签名是方法和调用它的代码之间的一种契约。因此,如果收到不正确的值,通常会抛出未检查的异常,并让它在更高的级别上处理:
1 2 3 4 5 6 7 8 9 | public void setAge(int age) { if(age < 0) { throw new IllegalArgumentException("Array can't be negative"); } this.age = age; } |
在这种情况下,调用者破坏了他们的契约结束,所以您向他们吐出了他们的输入,并有一个异常。"throws"条款是在你因某种原因不能履行合同时使用的。
1 2 3 4 5 6 7 8 9 10 11 | public void readFile(String filename) throws IOException { File myfile = new File(filename); FileInputStream fis = new FileInputStream(myfile); //do stuff fis.read(); //do more stuff } |
在这种情况下,作为方法编写者,您已经破坏了合同的结尾,因为用户给了您有效的输入,但是由于IOException,您无法完成他们的请求。
希望能让你走上正轨。祝你好运!
注意,如果您的代码不抛出异常,那么它并不总是意味着输入在界限内。依赖于标准JAVA(API+JVM)抛出异常,例如EDCOX1、1或EDCOX1×2是验证输入的一种非常不健康的方式。垃圾进入有时会产生垃圾,但没有例外。
是的,例外是相当昂贵的。它们不应该在正常的处理流程中被抛出。