繁体   English   中英

预防性与反应性C#编程

[英]Preventive vs Reactive C# programming

除非我确定不会有任何错误,否则我总是会因为从不采取行动来防止异常情况。 我学会了用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#世界中必须学会期待的东西。

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

我更喜欢使用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:可能会破坏特定的异常,然后显示一般的,模糊的错误消息。 对于上面的第二个例子,这对我来说听起来很糟糕。 对于你的第一个,“找不到文件”,这可能更合理(如果你真的抓住了那个特定的例外,而不仅仅是“一切”),除非你有更好的方法在其他地方处理这个条件。 模态消息框对我来说通常是一个糟糕的“交互设计气味”,但这大部分都不是重点。

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

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

访问数据库:服务器是否接受连接? 我的证书好吗? 数据库对象是否存在? 名称/ type的列是否正确?

所有这些都可以在检查和操作之间改变。

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

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

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

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

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

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

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

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

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

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

我相信你是正确的,使用异常作为控制语句是一种不好的做法。 .Net中的例外情况很慢。 如Jon B 所述 ,做自己的检查总是好得多,而不是使用异常来捕获“坏”值,例如Nulls。

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

线索在“例外”一词中。

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

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

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

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

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

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

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

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

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

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

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

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

有关异常处理和策略的良好起点,请参阅Ned Batchelder的博客文章“雨林中的异常”

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM