预防性与反应性C#编程

时间:2008-12-30 18:50:18

标签: c# coding-style

除非我确定不会有任何错误,否则我总是会因为从不采取行动来防止异常情况。我学会了用C编程,这是真正做事的唯一方法。

使用C#我经常看到更多的被动编程 - 尝试做某事并处理异常。对我来说,这似乎是使用异常作为控制语句。我最初看到这几次,我认为这是不好的做法。但是在过去的几个月里,我已经看到了这一切,只是不得不怀疑 - 这是接受/有效还是只是流行病?

更新: 为了澄清一点,我看到的大多数异常处理都是像

这样的东西
try
{
    //open file
}
catch
{
    //message box for file not found
}

甚至更糟

try
{
    //open xml
    //modify xml (100+ lines of code)
}
catch
{
    //message for 'unspecified error'
}

我理解有时候异常处理非常好用(例如数据库连接),但我指的是使用异常来代替更“传统”的控制。我问这个是因为我觉得这种编程风格使用异常作为拐杖而不是恢复方法,并且想知道这是否是我在C#世界中必须学会期待的东西。

9 个答案:

答案 0 :(得分:7)

像往常一样,答案是“它取决于”,但我一般都赞同“快速失败”的理念。

我更喜欢使用try / finally(sans catch),除非我可以实际执行一些有用的操作来从特定代码块中的异常中恢复。抓住每一个可能的例外是不值得的。一般来说,快速失败比无声失败更可取。

另一方面,如果您知道如何从特定异常中恢复,那么请执行此操作。

假设您有一个文件传输库。如果传输因超时或网络故障而中断,它可能会抛出异常。那是合理的。如果图书馆无声地失败,你会感到恼火;检查返回代码更容易出错,并且不一定更具可读性。但也许您有一个业务规则,用于将一堆文件发送到服务器,您应该在放弃并要求用户干预之前至少尝试3次传输文件。在这种情况下,业务逻辑应该处理异常,尝试恢复,然后在自动解决方案失败时做任何应该做的事情(警告用户,安排稍后的尝试,或其他什么)。

如果您找到执行此操作的代码:

try
{
    // do something that could throw
    // ...
}
catch {} //swallow the exception

或:

catch { return null; }

可能打破了。当然,有时您调用的代码可能会抛出您真正不关心的异常。但我经常看到人们这样做只是为了让他们不必“处理”上游的异常;这种做法使事情变得更难调试。

有些人认为允许例外将责任链组合起来是不好的,因为你只是“希望”上游某人“奇迹般地”知道该怎么做。那些人错了。上游代码通常是 知道该怎么做的唯一地方。

偶尔,我会尝试/捕获并抛出一个不同的,更合适的异常。但是,如果可能的话,保护条款会更好。例如。 if (argument==null) throw new ArgumentNullException();比允许NullReferenceException传播调用堆栈更好,因为它更清楚出错了。

某些条件“永远不应该发生”或“我不知道可能发生的事情”应该被记录下来(例如,参见jboss日志记录),但是在它们关闭你的应用程序之前可以吞下它们,至少在某些条件下例。

ETA:可能会因特定异常而中断,然后显示一般的,模棱两可的错误消息。对于上面的第二个例子,这对我来说听起来很糟糕。对于你的第一个,“找不到文件”,这可能更合理(如果你真的抓住了那个特定的例外,而不仅仅是“一切”),除非你有更好的方法在其他地方处理这个条件。模态消息框对我来说通常是一个糟糕的“交互设计气味”,但这主要是在这一点上。

答案 1 :(得分:5)

在某些情况下,您被迫被动反应:检查和操作并不总是有效。

访问磁盘上的文件:磁盘是否正常工作?文件存在吗?我可以获得对该文件的读取权限吗?

访问数据库:服务器是否接受连接?我的证书好吗?数据库对象是否存在?名为/ typed的列是否适当?

所有这些事情都可能在检查和操作之间发生变化。

答案 2 :(得分:2)

你当然可以滥用例外是c#。在我看来,你永远不应该得到ArgumentNullException,因为你应该总是首先测试null。但是,在许多情况下,您无法检查出异常情况。任何与“外部世界”交互的东西(连接到Web服务器,数据库等)都可能引发异常。

尽可能地防止,但你仍然需要能够对其他一切作出反应。

答案 3 :(得分:2)

在我的C ++ COM时代,我学会了不要向后弯腰来处理“可能的”错误 - 也就是说,我不经常检查每个函数调用的返回值。那是因为我知道我无法成功处理未知条件:

  • 我不知道什么时候会失败,或者意味着什么。

  • 我无法实现,所以我无法测试我的错误处理代码。未经测试的代码是不起作用的代码。

  • 我不知道用户希望我做什么。

  • 例行错误处理可能会让程序继续运行,但不会以可靠,可预测的方式运行,用户将从中受益。

  • 快速失败&显着(而不是缓慢而巧妙地)不会使用户陷入虚假的安全感,并使程序员有更好的机会来诊断问题。

显然,我不是说“永远不会处理错误”,我说“只有在你能增加价值时才能处理错误”。

同样的原则适用于C#。我认为如果你可以把它包装在一个更相关的例外中,那么捕捉异常是一个好主意。扔掉那个。如果您确定用户将如何从处理异常中受益,那就去吧。但除此之外,请不要管它。

答案 4 :(得分:1)

我认为使用异常作为控制语句是不正确的。 .Net中的例外情况很慢。做自己的检查总是好得多,而不是使用异常来捕获“坏”值,例如Null,如Jon B mentioned

我在.Net 1.1应用程序中亲自发现,该应用程序创建了实验室数据的分隔文本文件。我使用异常来捕获具有无效数据的字段,但是一旦我用适当的检查代码替换了异常,应用程序就会以指数方式加速。

答案 5 :(得分:1)

线索在“例外”一词中。

如果参数为null是有效的业务条件,那么根据定义,null参数不是例外,并且应该在使用它之前进行测试。在这样的商业条件下使用例外来控制程序逻辑是邋coding的编码,并且应该用湿鳕鱼反复拍打犯罪者直到他悔改。

但是,除了性能之外,在这个例子中,总是盲目地测试null参数,并不总是在抛出异常时捕获异常。

如果参数不可能为null ,则不应该测试null - 除非在应用程序的最顶层,未处理的异常是被困,存储,通过电子邮件发送给支持团队,并向用户隐藏,并通过道歉方式向其显示合适的消息。

我曾经不得不调试一个应用程序,其中处理了每个可以想到的该死的异常,并且几乎不可能进行调试。它必须从生产中撤出,进入开发环境及其数据库,并且所有处理程序都被删除了。之后,这是微不足道的。

答案 6 :(得分:1)

在C ++中输入try-block是一项昂贵的操作(就CPU周期而言),因此您应该尽量减少使用它们。对于C#,进入try-block很便宜,所以我们可以用不同的方式使用它。

现在;在C#中捕获异常是昂贵的,仍然应该用于特殊情况,而不是用于一般程序逻辑,正如其他人已在此处所述......

答案 7 :(得分:0)

C / C ++可以同样具有反应性。你有多少次看到尝试过的东西,比如fopen后面会进行NULL检查?异常的优点是它们允许给出比NULL或False返回更多的信息。

大多数以支持它们的语言抛出异常的操作都无法事先“测试”以确保操作成功。

现在,运行时异常是不同的东西。这些通常是由无效数据或数据滥用引起的(除以零)。在我看来,这些异常绝不应该被视为处理它们的一种方式。

答案 8 :(得分:0)

如果不对异常策略进行全面讨论,很难讨论异常的最佳用法。例如,您的策略可以是以下任何一种:

  • 处理尽可能接近故障点的所有异常
  • 记录异常,然后重新抛出到调用者
  • 回滚到异常前状态,并尝试继续
  • 将异常翻译为相应的错误消息并显示给用户

异常处理通常很复杂,因为在同一个项目上工作的几个开发人员甚至可能没有相同的策略,甚至更糟糕的是,甚至不知道存在一个。因此,团队中的每个人都知道并理解战略是非常重要的。

有关异常处理和策略的良好起点,请参阅Ned Batchelder的博文Exceptions in the Rainforest