[英]Exceptions vs return codes : do we lose something (while gaining something else)?
我的问题很模糊 :o) - 但这里有一个例子:
当我编写 C 代码时,我能够在出现故障时记录计数器的值:
<...>
for ( int i = 0 ; i < n ; i++ )
if ( SUCCESS != myCall())
Log( "Failure, i = %d", i );
<...>
现在,使用异常,我得到这个:
try
{
<...>
for ( int i = 0 ; i < n ; i++ )
myCall();
<...>
}
catch ( Exception exception )
{
Log( "Failure ! Maybe in myCall() ? Don't know. i's value ? No clue." );
}
当然,可以在 try/catch 语句之外声明“i”(这就是我正在做的)。 但我不喜欢它 - 我喜欢在使用它们的地方声明变量,而不是之前。
但也许我在这里错过了什么。 你有什么优雅的解决方案吗?
预先感谢 ! 西尔万。
添加:myCall() 是一个晦涩的 API 调用 - 我不知道它会抛出什么。 另外,我当然可以在每次调用周围添加一个 Try/Catch 块,但是我最好使用返回代码? 然后我会在重要的代码行周围添加很多噪音吗?
怎么样:
for(int i = 0; i < n; i++)
{
try
{
myCall();
}
catch(Exception e)
{
Log(String.Format("Problem with {0}", i));
}
}
我认为你错了,这并不像许多其他人那样令人惊讶。
异常不能用于程序流。 再读一遍,这很重要。
例外是“哇,这不应该发生”的错误,您希望在运行时永远不会看到这些错误。 显然,您会在第一个用户使用它的那一刻看到它们,这就是为什么您必须考虑它们可能发生的情况,但您仍然不应该尝试将代码放入捕获、处理和继续,就好像什么也没发生一样。
对于这样的错误,您需要错误代码。 如果您将异常用作“超级错误代码”,那么您最终会编写您提到的代码 - 将每个方法调用包装在 try/catch 块中! 你还不如返回一个枚举,而不是,它的速度快了很多,并显著更易于阅读比垃圾一切错误返回码7行代码,而不是1(其也更可能是正确的代码太-看到erikkallen的答复)
现在,在现实世界中,方法通常会在您不希望它们抛出的异常(例如 EndOfFile)中抛出异常,在这种情况下,您必须使用“try/catch wrapper”反模式,但是如果你要设计你的方法,不要在日常错误处理中使用异常——只在特殊情况下使用它们。 (是的,我知道很难让这种设计正确,但很多设计工作也是如此)
我不喜欢“现在,有例外……”的表达。
例外是您在编程中使用它的一种工具- 如果您认为它是最好的选择,请使用它,否则,请不要使用。
我遵循个人规则,即在内部代码中不抛出任何可以避免抛出的异常。 对于公开可用的 DLL 的 API,前提条件检查应保持启用状态,如果失败则触发异常,是的; 但对于内部逻辑,我很少(如果有的话)在我的设计中包含例外。 相反,当我使用某个函数来记录它会在发生某些糟糕情况时抛出的情况时,我倾向于立即捕获异常 - 毕竟这是一个预期的异常。
如果您认为您的非特殊选择更好 - 坚持下去!
是的。 2件事。
将 try-catch 块放在有意义的地方。 如果您对 myCall 的异常(以及 i 的值)感兴趣,请使用
for ( int i = 0 ; i < n ; i++ )
try { myCall(); } catch ( Exception exception ) {
Log( "Failure, i = %d", i );
}
针对不同的错误抛出不同类的对象。 如果您对财务处理中发生的逻辑错误感兴趣,请抛出财务::逻辑错误,而不是 std::exception("error msg") 或其他内容。 这样你就可以捕捉到你需要的东西。
从我的角度来看,这里有两件事。
期望异常本身包含关于 i 的值的信息,或者不太具体地关于它被评估的上下文和出了什么问题,这并不令人发指。 举个简单的例子,我永远不会直接抛出InvalidArgumentException
; 相反,我会确保我将准确的描述传递给构造函数,例如
public void doStuff(int arg) {
if (arg < 0) {
throw new InvalidArgumentException("Index must be greater than or equal to zero, but was " + arg);
}
...
这可能不会明确记录 i 的值,但在大多数情况下,您将能够了解导致错误的输入的问题。 这也是支持异常链的论据——如果你在每个概念级别捕获、包装和重新抛出异常,那么每个包装都可以添加自己的相关变量,这些变量太高而无法被基本的低级错误看到或理解.
或者,如果你的myCall
函数真的太抽象而无法知道发生了什么,那么我发现在进行调用之前以更高的详细级别记录日志效果很好,例如
try
{
<...>
for ( int i = 0 ; i < n ; i++ )
DebugLog("Trying myCall with i = " + i);
myCall();
<...>
}
catch ( Exception exception )
{
Log( "Failure ! Maybe in myCall() ? Don't know. i's value ? No clue." );
}
这样,如果出现问题,您可以检查您的高详细调试日志,并在引发异常的调用之前找到i
的状态。
考虑一下 Raymond Chen 的意见以及 Microsoft 的 x64 思维。
((raymond chen 例外)) 作为一个谷歌查询足以让你找到他的经典文章“更干净、更优雅和错误——仅仅因为你看不到错误路径并不意味着它不存在.” 和澄清“更干净,更优雅,更难识别”。
(( x64 异常模型 )) 将您带到 MSDN 文章“开始编程 64 位 Windows 系统需要知道的一切”,其中包含引用“基于表的异常处理的缺点(相对于基于 x86 堆栈的模型)是从代码地址查找函数表条目比仅仅遍历一个链表花费更多的时间。好处是函数没有在每次函数执行时设置一个 try 数据块的开销。”
总结一下这句话,在 x64 中,设置一个从未使用过的“捕获”是免费的或几乎免费的,但实际上抛出 - 捕获异常比在 x86 中慢。
如果您抛出的对象可以保存上下文信息,可以告诉您有关错误性质的一些信息,那么它会更优雅。
从 istream 派生一个可抛出的对象,您可以使用 >> 将信息流式传输到其中。 教对象如何显示自己<<。
当您检测到错误情况时,在以下级别或 N 个级别以下。 用好的上下文信息填充你的对象,然后扔掉它。 当您捕获对象时,告诉它在日志文件和/或屏幕和/或您想要的任何位置显示其上下文信息。
当然,可以在 try/catch 语句之外声明“i”(这就是我正在做的)。
嗯……如果你真的需要知道i
的值,那么这似乎是一个记录工具——结构化异常处理可能不是最好的方法。 如果您想有条件地处理异常(即仅在调试时),请将try
块放入循环中。 由于这可能会影响性能(取决于您的环境),因此只能在调试模式下执行此操作。
第一件事,第一。 如果您正在捕获 Exception,那您就错了。 您应该捕获您期望的特定异常。
但除此之外,如果您的异常是由您自己的代码抛出的,您可以使用智能异常来包含您需要了解的有关该异常的所有数据。
例如,ArrayIndexOutOfBoundsException 将包含寻址的索引。
您可以通过两种方式获得更具体的信息。 首先,不要捕捉异常,捕捉特定的异常。 其次,在需要确定哪个函数调用抛出异常的函数中使用多个 try/catch 语句。
在我看来,在这种情况下不应该使用异常,但如果你真的需要它们,我会采用以下方式:
您可以将“i”作为参数传递给 myCall(); 函数,如果发生任何错误,将抛出一些特殊异常。 像:
public class SomeException : Exception
{
public int Iteration;
public SomeException(int iteration) { Iteration = iteration; }
}
循环块:
try
{
for(int i = 0; i < n; ++i)
{
myCall(i);
}
}catch(SomeException se)
{
Log("Something terrible happened during the %ith iteration.", se.Iteration);
}
最后是 myCall() 函数:
void myCall(int iteration)
{
// do something very useful here
// failed.
throw new SomeException(iteration);
}
嗯! 你可以这样做:
try
{
<...>
for ( int i = 0 ; i < n ; i++ )
try {
myCall();
} catch(Exception e) {
println("bloody hell! " + i);
}
<...>
}
我认为 Exceptions 比 Java 展示给你的更酷。 真正有趣的是让调试器在每个未处理的异常上出现,然后在失败时查看堆栈,这样您就可以检查 i 而无需更改一行代码。 也就是说,我相信,例外应该是什么。
许多异常处理工具(Delphi 的MadExcept就是其中之一)允许您在捕获异常时检索整个堆栈跟踪。 所以,你会确切地知道它被扔到哪里了。
获取“i”的常用技术是捕获异常,并在重新抛出异常之前向其添加额外数据(istream 技术)。 这很少是必要的,但如果你坚持......
您可以从RuntimeException派生并创建自己的mycall()抛出的异常。 然后你可以通过try / catch捕获它。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.