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

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

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

还是我说错了?

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

清晰的 尊重你的方法

拍我。


当前回答

有一些通用的机制,语言可以允许一个方法退出而不返回值,并unwind到下一个“catch”块:

Have the method examine the stack frame to determine the call site, and use the metadata for the call site to find either information about a try block within the calling method, or the location where the calling method stored the address of its caller; in the latter situation, examine metadata for the caller's caller to determine in the same fashion as the immediate caller, repeating until one finds a try block or the stack is empty. This approach adds very little overhead to the no-exception case (it does preclude some optimizations) but is expensive when an exception occurs. Have the method return a "hidden" flag which distinguishes a normal return from an exception, and have the caller check that flag and branch to an "exception" routine if it's set. This routine adds 1-2 instructions to the no-exception case, but relatively little overhead when an exception occurs. Have the caller place exception-handling information or code at a fixed address relative to the stacked return address. For example, with the ARM, instead of using the instruction "BL subroutine", one could use the sequence: adr lr,next_instr b subroutine b handle_exception next_instr:

要正常退出,子例程只需执行bx lr或pop {pc};在异常退出的情况下,子例程将在执行返回之前从LR中减去4,或者使用sub LR,#4,pc(取决于ARM的变化,执行模式等)。如果调用者没有被设计为适应它,这种方法将会非常严重地故障。

A language or framework which uses checked exceptions might benefit from having those handled with a mechanism like #2 or #3 above, while unchecked exceptions are handled using #1. Although the implementation of checked exceptions in Java is rather nuisancesome, they would not be a bad concept if there were a means by which a call site could say, essentially, "This method is declared as throwing XX, but I don't expect it ever to do so; if it does, rethrow as an "unchecked" exception. In a framework where checked exceptions were handled in such fashion, they could be an effective means of flow control for things like parsing methods which in some contexts may have a high likelihood of failure, but where failure should return fundamentally different information than success. I'm unaware of any frameworks that use such a pattern, however. Instead, the more common pattern is to use the first approach above (minimal cost for the no-exception case, but high cost when exceptions are thrown) for all exceptions.

其他回答

但是你并不总是知道在你调用的方法中发生了什么。您将无法确切地知道异常抛出的位置。不需要更详细地检查异常对象....

我的经验法则是:

如果您可以做任何事情来从错误中恢复,捕获异常 如果这个错误是很常见的(例如。用户尝试使用错误的密码登录),使用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(值类,结构或枚举)来进行错误检查。

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

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

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

如果您对控制流使用异常处理程序,那么您就太笼统和懒惰了。正如其他人提到的,如果在处理程序中处理处理,就会发生一些事情,但究竟发生了什么?本质上,如果您将异常用于控制流,则您是在为else语句使用异常。

如果您不知道可能会发生什么状态,那么您可以使用异常处理程序处理意外状态,例如当您必须使用第三方库时,或者当您必须捕获UI中的所有内容以显示良好的错误消息并记录异常时。

但是,如果您知道哪里可能出错,而不使用if语句或其他检查方法,那么您就是懒惰。允许异常处理程序成为您知道可能发生的事情的所有人是懒惰的,它稍后会回来困扰您,因为您将试图根据可能错误的假设来修复异常处理程序中的情况。

如果您在异常处理程序中放入逻辑以确定究竟发生了什么,那么如果您没有将该逻辑放入try块中,那么您将非常愚蠢。

异常处理程序是最后的手段,当您无法阻止某些事情出错时,或者事情超出了您的控制能力。比如,服务器宕机并超时,而您无法阻止抛出异常。

最后,提前完成所有检查,显示您知道或预期会发生什么,并使其显式化。代码的意图应该清楚。你更愿意读什么?