为了避免所有我可以在谷歌上搜索到的标准答案,我将提供一个你们都可以随意攻击的例子。

c#和Java(以及其他很多语言)有很多类型,有些“溢出”行为我一点也不喜欢(例如type。MaxValue +类型。SmallestValue ==类型。MinValue,例如int。MaxValue + 1 = int.MinValue)。

但是,鉴于我的邪恶本性,我将通过将此行为扩展为重写DateTime类型来对这种伤害进行侮辱。(我知道DateTime在. net中是密封的,但为了这个例子,我使用了一种与c#完全相似的伪语言,除了DateTime没有密封之外)。

被覆盖的Add方法:

/// <summary>
/// Increments this date with a timespan, but loops when
/// the maximum value for datetime is exceeded.
/// </summary>
/// <param name="ts">The timespan to (try to) add</param>
/// <returns>The Date, incremented with the given timespan. 
/// If DateTime.MaxValue is exceeded, the sum wil 'overflow' and 
/// continue from DateTime.MinValue. 
/// </returns>
public DateTime override Add(TimeSpan ts) 
{
    try
    {                
        return base.Add(ts);
    }
    catch (ArgumentOutOfRangeException nb)
    {
        // calculate how much the MaxValue is exceeded
        // regular program flow
        TimeSpan saldo = ts - (base.MaxValue - this);
        return DateTime.MinValue.Add(saldo)                         
    }
    catch(Exception anyOther) 
    {
        // 'real' exception handling.
    }
}

当然,如果可以很容易地解决这个问题,但事实仍然是,我不明白为什么不能使用异常(从逻辑上讲,我可以看到,当性能是一个问题时,在某些情况下应该避免异常)。

我认为在许多情况下,它们比if结构更清晰,并且不会破坏方法所做的任何契约。

恕我直言,“永远不要在常规程序流程中使用它们”的反应似乎并不是每个人都有,因为这种反应的力量可以证明。

还是我说错了?

我读过其他的帖子,处理各种特殊情况,但我的观点是,如果你们都是:

清晰的 尊重你的方法

拍我。


当前回答

在异常之前,C语言中有setjmp和longjmp可以用来完成类似的堆栈帧展开。

然后,同样的构造被赋予了一个名称:“Exception”。大多数答案都依赖于这个名字的含义来争论它的用法,声称例外是为了在特殊情况下使用。这从来不是最初longjmp的意图。只是在某些情况下,您需要在许多堆栈帧之间中断控制流。

异常稍微更通用一些,因为您也可以在同一个堆栈框架中使用它们。我认为这与goto的类比是错误的。Gotos是紧密耦合的一对(setjmp和longjmp也是如此)。异常遵循松散耦合的发布/订阅,这要干净得多!因此,在相同的堆栈框架中使用它们与使用gotos几乎不是一回事。

混淆的第三个来源与它们是被检查的异常还是未检查的异常有关。当然,在控制流和许多其他事情上使用未检查异常似乎特别糟糕。

然而,受控异常对于控制流来说是很好的,一旦你克服了所有维多利亚式的困扰,并生活了一点。

我最喜欢的用法是在一长段代码中使用throw new Success()序列,它不断尝试一件事,直到找到它要找的东西。每一件事——每一段逻辑——都可能有任意的嵌套,因此break也会像任何类型的条件测试一样出现。如果-否则模式是脆弱的。如果我编辑了一个else,或者以其他方式弄乱了语法,那么就有一个毛茸茸的bug。

使用throw new Success()线性化代码流。我使用本地定义的成功类(当然是勾选的),这样如果我忘记捕获它,代码将无法编译。我没有捕捉到另一种方法的成功。

有时我的代码会逐一检查,只有在一切正常的情况下才会成功。在这种情况下,我使用throw new Failure()进行了类似的线性化。

使用单独的函数会破坏划分的自然级别。所以返回解不是最优的。出于认知上的原因,我更喜欢在一个地方放一两页代码。我不相信超精细分割的代码。

除非有一个热点,否则jvm或编译器做什么对我来说不太重要。我不相信编译器有任何根本原因不检测本地抛出和捕获的异常,而只是在机器代码级别上将它们视为非常有效的goto。

至于在控制流的函数之间使用它们——即在常见情况下而不是特殊情况下——我不明白它们为什么会比多次中断、条件测试、返回来遍历三个堆栈帧而不是仅仅恢复堆栈指针的效率更低。

我个人并没有跨堆栈框架使用这种模式,我可以理解它需要复杂的设计才能做到这一点。但适当地使用它应该没问题。

最后,关于令人惊讶的新手程序员,这不是一个令人信服的理由。如果你温和地引导他们练习,他们就会爱上它。我记得c++曾经让C程序员感到惊讶和害怕。

其他回答

您是否曾经尝试调试一个在正常操作过程中每秒引发5个异常的程序?

我有。

该程序相当复杂(它是一个分布式计算服务器),在程序的一侧稍作修改,就可能很容易破坏完全不同的地方的某些东西。

我希望我可以启动程序,然后等待异常发生,但是在正常的操作过程中,在启动过程中大约有200个异常

我的观点是:如果你在正常情况下使用异常,你如何定位异常情况?

当然,还有其他强有力的理由不要过多使用异常,特别是在性能方面

我认为您可以使用异常进行流控制。然而,这种技术也有另一面。创建异常是一件代价高昂的事情,因为它们必须创建堆栈跟踪。因此,如果你想更频繁地使用异常,而不仅仅是发出异常情况的信号,你必须确保构建堆栈跟踪不会对你的性能产生负面影响。

减少创建异常成本的最佳方法是重写fillInStackTrace()方法,如下所示:

public Throwable fillInStackTrace() { return this; }

这样的异常不会填充堆栈跟踪。

除了上述原因之外,不使用异常进行流控制的另一个原因是它会使调试过程变得非常复杂。

例如,当我试图在VS中追踪一个错误时,我通常会打开“中断所有异常”。如果您正在使用异常进行流控制,那么我将定期在调试器中中断,并将不得不继续忽略这些非异常异常,直到我得到真正的问题。这很可能会让人发疯!!

我觉得你的例子没有错。相反,忽略被调用函数抛出的异常是错误的。

在JVM中,抛出异常的代价并不高,只需使用新的xyzException(…)创建异常,因为后者涉及堆栈遍历。因此,如果您预先创建了一些异常,您可以多次抛出它们而无需付出任何代价。当然,这种方式不能将数据与异常一起传递,但我认为无论如何这都是一件糟糕的事情。

我的经验法则是:

如果您可以做任何事情来从错误中恢复,捕获异常 如果这个错误是很常见的(例如。用户尝试使用错误的密码登录),使用returnvalues 如果您不能做任何事情来从错误中恢复,那么就让它不被捕获(或者在主捕获器中捕获它,以半优雅地关闭应用程序)

我认为异常的问题纯粹是从语法的角度来看的(我非常确定性能开销是最小的)。我不喜欢到处都是尝试块。

举个例子:

try
{
   DoSomeMethod();  //Can throw Exception1
   DoSomeOtherMethod();  //Can throw Exception1 and Exception2
}
catch(Exception1)
{
   //Okay something messed up, but is it SomeMethod or SomeOtherMethod?
}

. .另一个例子是,当你需要使用工厂将一些东西赋值给一个句柄时,该工厂可能会抛出异常:

Class1 myInstance;
try
{
   myInstance = Class1Factory.Build();
}
catch(SomeException)
{
   // Couldn't instantiate class, do something else..
}
myInstance.BestMethodEver();   // Will throw a compile-time error, saying that myInstance is uninitalized, which it potentially is.. :(

就我个人而言,我认为你应该为罕见的错误条件(内存不足等)保留异常,而使用returnvalues(值类,结构或枚举)来进行错误检查。

希望我正确理解了你的问题:)