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

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

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

还是我说错了?

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

清晰的 尊重你的方法

拍我。


当前回答

很多答案的第一反应是:

你是在为程序员和最小惊讶原则写东西

当然!但如果只是一直不是更清楚。

这并不令人惊讶,例如:divide (1/x) catch (divisionByZero)对我(Conrad和其他人)来说比任何if都更清楚。事实上,这种编程是不被期待的,纯粹是传统的,实际上,仍然是相关的。也许在我的例子中,if会更清楚。

但是在这个问题上,DivisionByZero和FileNotFound比if更清楚。

当然,如果它的性能较差,需要无数的每秒时间,你当然应该避免它,但我仍然没有读到任何好的理由来避免整体设计。

就最小惊讶原则而言:这里存在循环推理的危险:假设整个社区使用了糟糕的设计,那么这种设计将成为预期!因此,这个原则不能是一个圣杯,应该仔细考虑。

对于正常情况的例外,你如何定位异常情况?

在许多反应中,像这样的东西闪耀着光芒。抓住他们,不是吗?你的方法应该是清晰的,有良好的文档记录,并遵守它的契约。我必须承认,我不明白这个问题。

对所有异常进行调试:都是一样的,只是有时会这样做,因为不使用异常的设计很常见。我的问题是:为什么这么普遍?

其他回答

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

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

让我们假设您有一个做一些计算的方法。它必须验证许多输入参数,然后返回一个大于0的数字。

使用返回值来表示验证错误,这很简单:如果方法返回的数字小于0,则发生错误。如何判断哪个参数无效?

我记得在我学习C语言的时候,很多函数返回的错误代码是这样的:

-1 - x lesser then MinX
-2 - x greater then MaxX
-3 - y lesser then MinY

etc.

它真的比抛出和捕获异常的可读性更差吗?

我真的不明白你是如何在你引用的代码中控制程序流的。除了ArgumentOutOfRange异常,您将永远不会看到其他异常。(所以你的第二个catch条款永远不会被击中)。您所做的只是使用代价极高的抛出来模拟if语句。

此外,您也没有执行更危险的操作,即仅仅抛出一个异常,以便在其他地方捕获它以执行流控制。你实际上是在处理一个特例。

你可能会对Common Lisp的条件系统感兴趣,它是对异常的一种概括。因为你可以以一种可控的方式展开堆栈,也可以得到“重启”,这非常方便。

这与其他语言的最佳实践没有太大关系,但它向您展示了在您正在考虑的方向上,使用一些设计思想可以完成什么。

当然,如果您像溜溜球一样在堆栈上上下跳动,仍然存在性能考虑,但这比大多数捕获/抛出异常系统所体现的“哦,糟糕,让我们离开”这种方法要普遍得多。

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

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

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