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

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结构更清晰,并且不会破坏方法所做的任何契约。

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

还是我说错了?

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

清晰的 尊重你的方法

拍我。


当前回答

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

我有。

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

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

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

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

其他回答

您可以使用锤子的爪来拧螺丝,就像您可以为控制流使用异常一样。这并不意味着这是该特性的预期用途。if语句表示条件,其预期用途是控制流。

如果您以一种意想不到的方式使用特性,同时又选择不使用为此目的而设计的特性,那么将会有相关的成本。在这种情况下,清晰度和性能没有真正的附加价值。与广泛接受的if语句相比,使用异常能给您带来什么?

换句话说:你能做并不意味着你应该做。

性能如何?在负载测试一个。net web应用程序时,我们在每个web服务器上模拟了100个用户,直到我们修复了一个常见的异常,这个数字增加到500个用户。

异常基本上是非本地的goto语句,具有后者的所有结果。在流控制中使用异常违反了最小惊奇原则,使程序难以阅读(请记住,程序首先是为程序员编写的)。

而且,这不是编译器供应商所期望的。他们希望很少抛出异常,并且他们通常让抛出代码非常低效。抛出异常是。net中最昂贵的操作之一。

然而,一些语言(尤其是Python)使用异常作为流控制结构。例如,如果没有其他项,迭代器将引发StopIteration异常。即使是标准的语言结构(比如for)也依赖于此。

我不认为使用异常来进行流控制有什么错。异常有点类似于延续,在静态类型语言中,异常比延续更强大,所以,如果你需要延续,但你的语言没有它们,你可以使用异常来实现它们。

好吧,实际上,如果你需要延续,而你的语言没有,你选择了错误的语言,你应该使用另一种语言。但有时你别无选择:客户端web编程就是最好的例子——没有办法绕过JavaScript。

An example: Microsoft Volta is a project to allow writing web applications in straight-forward .NET, and let the framework take care of figuring out which bits need to run where. One consequence of this is that Volta needs to be able to compile CIL to JavaScript, so that you can run code on the client. However, there is a problem: .NET has multithreading, JavaScript doesn't. So, Volta implements continuations in JavaScript using JavaScript Exceptions, then implements .NET Threads using those continuations. That way, Volta applications that use threads can be compiled to run in an unmodified browser – no Silverlight needed.

在异常之前,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程序员感到惊讶和害怕。