为什么不"尝试"各地的方法?

时间:2015-12-23 16:18:44

标签: c# .net exception exception-handling api-design

我想知道,为什么Microsoft开发人员不向我们提供"尝试"各地不可靠的方法版本?

如果我使用数据库连接或smtp客户端,我总是必须考虑例外:

try
{
    smtpClient.Send(message);
}
catch (SmtpException)
{
    // test with real smtp server and analyze an exception and it's contents here
}

但我想要的是用户:

if(!smtpClient.TrySend(message, out reason))
{
    // analyze reason here
}

对我来说最大的问题是异常性能开销。我无法承担在我的服务的每个客户电话上抛出异常。如果我需要检查几十个连接/提供者,这可能会导致每个客户端请求有几十个异常。

我的问题是:.NET中这种API设计决策的基本原理是什么?我不认为这是一个错误,因为它无处不在,而不是单一的技术。

更新 我必须再给你一个.NET异常的例子来停止谈论网络延迟。例如,如果停止Windows服务,System.Diagnostics.EventLog.WriteEntry方法可以抛出System.ComponentModel.Win32Exception

6 个答案:

答案 0 :(得分:6)

  

我想知道,为什么Microsoft开发人员不向我们提供"尝试"各地不可靠的方法版本?

由于我不是微软的员工,我只能猜测。我的猜测是有两个原因:

  • 在设计.NET类库的第一个版本时,他们没有想到它 - 请注意,TryParse方法已在更高版本中添加。

  • 性能差异不够重要(见下文),以保证实施和维护新Try...方法的成本。 .NET Framework还需要更紧急的其他内容。

  

对我来说最大的问题是异常性能开销。

严重怀疑。 "性能开销"一个SMTP超时(=几秒)占据"performance overhead" of throwing an exception(不到一毫秒)超过1000倍。

答案 1 :(得分:4)

考虑到您正在调用基于网络的服务,抛出异常本身的成本而不是会导致性能下降。找出何时抛出异常是罪魁祸首:系统需要尝试网络调用,等待超时或返回失败,然后才抛出异常。

实际投掷的成本很低,因此提供Try方法无助于提高性能。但是,它可以帮助您提高可读性,这不是一件小事。为此,您可以编写自己的Try包装器/扩展方法,并隐藏其中的异常捕获:

public static bool TrySend(this SmtpClient client, object message, out Error reason) {
    try {
        client.Send(message);
        reason = null;
        return true;
    } catch (SmtpException ex) {
        reason = ex.Reason;
        return false;
    }
}

答案 2 :(得分:4)

好的,你还没有看到异常点。你所建议的(一个尝试...版本的所有东西)基本上完全击败了例外。异常之处在于您不需要检查每个方法调用的结果。你只是假设一切都会成功(常规案例),如果一次调用失败,它将被捕获"并在您的调用堆栈中的预定位置处理。您的代码将更具可读性,因为它只有重要的步骤,而不是管道。尝试......对于更有可能/更常见的情景来说是很好的#34;失败"并不是真正的错误或问题,而只是正常流量控制逻辑的一部分。

异常是OO方式,返回错误代码是旧学校。考虑一个深层嵌套的调用层次结构,并负责处理所有级别的错误代码并适当地处理它们。将其与使用例外进行比较。你看到了光吗? : - 。)

答案 3 :(得分:1)

与您正在执行的发送/数据库操作相比,异常性能开销可以忽略不计。 因此,有些情况(如这些)支持TryParse模式有点不需要。

如果您处于导致“数十个异常”的“数十个连接/提供者”的情况下,可能值得检查它发生的原因,那么您的smtp服务器可能是一个全局问题。

P.S。披露我是微软员工,但即使我在工作中说苹果或谷歌,我也会回答你同样的问题。

答案 4 :(得分:1)

您不经常看到Try方法的主要原因是异常会传达某些信息。一个特殊的"条件发生,某事必须处理它。您刚刚执行的操作非常重要,失败会导致整个过程关闭。抛出异常的方法无法处理这种情况,抛出异常会强制某些代码块来处理这个问题。

网络调用对于应用程序的成功运行至关重要,大多数情况下,异常是恰当的。此外,异常包含消息和堆栈跟踪,这对于调试故障至关重要。您提出的解决方案会删除关键调试信这并不总是至关重要的,就像解析int一样,特别是当它通常很容易从错误中恢复时。

网络呼叫失败或写入光盘是一个完全不同的野兽。

答案 5 :(得分:1)

这个问题当然是合理的。以Eric Lippert的blog entry on exceptions为基础,并假设你采取了必要的预防措施,以确保发送失败,因为某些网络条件(而不是因为你在某处传递了无效的参数),我会将SmtpException归类为外生的 - 就像文件一样打开,你可能无法阻止它。因此,我不会将SmtpException归类为烦恼,这不是糟糕设计选择的结果(如int.Parse)。

确保SmtpException确实表示异常,异常将是一个合适的工具。与其他人所说的那样,开销可以忽略不计。