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

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

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

还是我说错了?

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

清晰的 尊重你的方法

拍我。


当前回答

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

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

public Throwable fillInStackTrace() { return this; }

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

其他回答

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

Josh Bloch在《Effective Java》中广泛地讨论了这个主题。他的建议很有启发性,也适用于。net(细节除外)。

特别地,例外应用于特殊情况。原因主要与可用性有关。对于一个给定的方法,要最大限度地使用,它的输入和输出条件应该受到最大限度的约束。

例如,第二种方法比第一种方法更容易使用:

/**
 * Adds two positive numbers.
 *
 * @param addend1 greater than zero
 * @param addend2 greater than zero
 * @throws AdditionException if addend1 or addend2 is less than or equal to zero
 */
int addPositiveNumbers(int addend1, int addend2) throws AdditionException{
  if( addend1 <= 0 ){
     throw new AdditionException("addend1 is <= 0");
  }
  else if( addend2 <= 0 ){
     throw new AdditionException("addend2 is <= 0");
  }
  return addend1 + addend2;
}

/**
 * Adds two positive numbers.
 *
 * @param addend1 greater than zero
 * @param addend2 greater than zero
 */
public int addPositiveNumbers(int addend1, int addend2) {
  if( addend1 <= 0 ){
     throw new IllegalArgumentException("addend1 is <= 0");
  }
  else if( addend2 <= 0 ){
     throw new IllegalArgumentException("addend2 is <= 0");
  }
  return addend1 + addend2;
}

无论哪种情况,都需要检查以确保调用者正确地使用了您的API。但在第二种情况下,您需要它(隐式地)。如果用户没有读取javadoc,软异常仍然会被抛出,但是:

你不需要记录它。 你不需要测试它(取决于你的攻击性有多强 单元测试策略是)。 您不需要调用者处理三个用例。

最基本的一点是,异常不应该用作返回代码,很大程度上是因为你不仅让你的API变得复杂了,还让调用者的API变得复杂了。

当然,做正确的事情是有代价的。代价是每个人都需要理解他们需要阅读和遵循文档。希望是这样。

标准的回答是,异常是不正常的,应该在特殊情况下使用。

其中一个对我来说很重要的原因是,当我在维护或调试的软件中读取try-catch控制结构时,我试图找出为什么最初的编码器使用异常处理而不是if-else结构。我希望能找到一个好的答案。

请记住,您不仅为计算机编写代码,还为其他编码员编写代码。有一个与异常处理程序相关联的语义,您不能仅仅因为机器不介意就将其丢弃。

很多答案的第一反应是:

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

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

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

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

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

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

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

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

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

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

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

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