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

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

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

还是我说错了?

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

清晰的 尊重你的方法

拍我。


当前回答

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变得复杂了。

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

其他回答

很多答案的第一反应是:

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

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

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

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

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

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

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

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

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

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

好吧,实际上,如果你需要延续,而你的语言没有,你选择了错误的语言,你应该使用另一种语言。但有时你别无选择:客户端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.

一般来说,在低级别处理异常本身并没有什么问题。异常是一个有效的消息,它提供了一个操作为什么不能执行的很多细节。如果你能接受,你就应该接受。

一般来说,如果你知道有很高的失败概率,你可以检查…你应该去检查一下…即if(obj != null) obj.method()

在你的情况下,我对c#库不够熟悉,不知道date time是否有一种简单的方法来检查时间戳是否越界。如果是,只需调用If (.isvalid(ts)) 否则,您的代码基本上没问题。

所以,归根结底,这取决于哪种方式可以创建更干净的代码……如果防止预期异常的操作比处理异常更复杂;那么你就有我的权限来处理异常,而不是到处创建复杂的守卫。

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

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

一个美学原因是:

“尝试”总是伴随着“成功”,而“如果”并不一定伴随着“其他”。

if (PerformCheckSucceeded())
   DoSomething();

使用try/catch,它变得更加冗长。

try
{
   PerformCheckSucceeded();
   DoSomething();
}
catch
{
}

6行代码太多了。