All*_*ang 20 c# defensive-programming
我和我的同事就这段代码进行了辩论:
var y = null;
if (x.parent != null)
y = x.parent.somefield;
Run Code Online (Sandbox Code Playgroud)
我的观点是,在代码所在的位置,x.parent不应该是null.当它为空时,我们有一个严重的问题,我想知道它!因此,不应该存在空检查,并且发生下游异常.
我的同事说这是防御性编程.并且null检查确保代码不会破坏应用程序.
我的问题是,这是防御性编程吗?还是一个不好的做法?
注意:重点不在于谁.我试图从这个例子中学习.
Ale*_*dro 15
有趣的问题.从我的角度来看,是否包括检查是数据验证的好坏,它来自何处以及检查失败时会发生什么.
"x.parent不应该是空的"是一个严肃的假设.你需要非常肯定它才能安全起来,当然有经典的"它永远不会发生".......直到它发生,这就是为什么我认为审查可能性很有趣.
我看到两件不同的事情要考虑.
数据来自哪里?
如果它来自同一类中的另一个方法,或来自某个相关类,因为你对它有或多或少的完全控制,放松你的防御是合乎逻辑的,因为你可以合理地假设它不太可能有不好的数据开始使用,或者如果它发生,在调试/测试期间尽早捕获错误并且甚至为它进行一些单元测试是相当容易的.
相反的情况是,如果用户输入的数据,或从文件或URL读取的数据,通常是外部的任何内容.由于您无法控制程序正在处理的内容:无论如何,在以任何方式使用它之前,请尽可能彻底地验证它,因为您会遇到可能导致问题的伪造/丢失/不完整/不正确/恶意信息路径.
中间情况可以是输入来自同一系统内的另一层/层.决定是否进行完全验证或将其视为理所当然更加困难,因为它是另一个内部软件,但可能会在以后单独替换或修改.我倾向于在越过边界时再次验证.
如何处理验证?
if在某些情况下,使用(如您的样本中)简单地跳过某些任务或使用可能没问题.例如,如果用户输入了一些数据,而这只是显示工具提示或其他次要信息,则跳过可能是安全的.但是如果这段代码做了一些重要的事情,那反过来填充了一些强制条件或者执行了其他一些过程,那么这不是正确的方法,因为它会导致下一次代码运行出现问题.问题是,当你跳过一些代码时,它必须是安全的,没有任何副作用或不必要的后果,否则你会隐藏一些错误,而且在以后的开发阶段很难调试.
优先中止当前流程是早期验证的理想选择,当完全预期失败并且您确切知道如何响应它时.一个示例可能是缺少必填字段,进程中断并向用户显示消息,询问缺少的信息.简单地忽略错误并不严重,但也不足以引发破坏正常程序流的异常.当然,您仍然可以使用异常,具体取决于您的体系结构,但无论如何都要捕获它并优雅地恢复.
当"不可能"真的发生时,抛出异常总是一种选择.在这种情况下,您无法为继续进行某些变更或仅取消当前流程提供合理的响应,这可能是由于某个地方的错误或错误输入,但重要的是您想要了解并拥有关于它的所有细节,所以最好的方法是让它尽可能大声爆炸,以便异常起泡并到达一个全局处理程序,中断所有内容,保存到日志文件/ DB /无论如何,发送一个崩溃向您报告并找到恢复执行的方法,如果这是可行或安全的.至少如果您的应用程序崩溃,请以最优雅的方式执行此操作,并留下痕迹以供进一步分析.
一如既往,这取决于具体情况.但是使用if来避免编写异常处理程序肯定是一种不好的做法.必须始终存在,然后一些代码可能依赖它 - 无论是否合适 - 如果失败并不重要.
看起来你的同事误解了"防御性编程"和/或异常.
防御性编程是关于防止某些类型的错误.
在这种情况下x.parent == null是一个错误,因为您的方法需要使用x.parent.SomeField.如果parent为null,则值SomeField显然无效.使用无效值执行任务或执行任务将产生错误且不可预测的结果.
所以你需要防止这种可能性.保护的一个非常好的方法是,NullPointerException如果你发现了,就扔掉一个x.parent == null.该例外将阻止您从中获取无效值SomeField.它将阻止您进行任何计算或使用无效值执行任何任务.它将中止所有当前的工作,直到错误得到适当的解决.
注意异常不是错误; 其中的无效值
parent是实际错误.例外是一种保护机制.例外是一种防御性编程技术,它们不是必须避免的.
由于C#已经抛出一个异常,你实际上不需要做任何事情.事实上,事实证明,你的同事"以防御性编程的名义"的努力实际上正在取消该语言提供的内置防御性编程.
我注意到许多程序员对异常情况过于偏执.异常本身不是错误,它只是报告错误.
你的同事说:"空检查确保代码不会破坏应用程序".这表明他认为例外会破坏申请.它们通常不会"破坏"整个应用程序.
如果不良异常处理使应用程序处于不一致状态,则异常可能会破坏应用程序.(但如果错误被隐藏,则更有可能.)如果异常"逃脱"某个线程,它们也可以破坏应用程序.(转义主线程显然意味着你的程序已经非常不合适地终止.但是即使转义子线程也足够糟糕,操作系统的最佳选择是GPF应用程序.)
但是,异常会中断(中止)当前操作.这是他们必须做的事情.因为如果你编写一个调用DoSomething哪个方法的调用DoStep1; 一个错误的DoStep1手段是DoSomething不能完成其工作正常.继续打电话是没有意义的DoStep2.
但是,如果在某些时候您可以完全解决特定错误,那么请务必:执行此操作.但请注意强调"完全解决"; 这并不意味着只是隐藏错误.此外,只记录错误通常不足以解决它.这意味着要达到以下目的:如果另一个方法调用您的方法并正确使用它,则"已解决的错误"不会对调用者正确执行其工作的能力产生负面影响.(无论打电话者是谁.)
也许完全解决错误的最好例子是在应用程序的主处理循环中.它的工作是:等待队列中的消息,从队列中提取下一条消息并调用适当的代码来处理消息.如果在返回主消息循环之前引发了异常并且未解决,则需要解决该异常.否则异常将转义主线程,应用程序将被终止.
许多语言在其标准框架中提供了一个默认的异常处理程序(程序员可以覆盖/拦截它的机制).默认处理程序通常只向用户显示错误消息,然后吞下异常.
为什么?因为如果您没有实现不良的异常处理,您的程序将处于一致状态.当前消息已中止,可以处理下一条消息,就好像没有任何错误一样.您当然可以将此处理程序覆盖为:
Abort可能意味着您甚至不需要告诉用户,可能是因为您之前显示过一条消息.)如果您可以在不首先引发异常的情况下解决错误,那么这样做会更清晰.但是,有时错误无法在首次出现时解决,或者无法提前检测到.在这些情况下,应引发/抛出异常以报告错误,并通过实现异常处理程序(C#中的catch块)来解决它.
注:在异常处理程序服务器两种不同的用途:首先,他们提供您进行清理(或地方回滚代码),特别是因为那里是一个错误/异常.其次,它们提供了一个解决错误并吞下异常的地方.注意:在前一种情况下,重新引发/抛出异常是非常重要的,因为它尚未得到解决.
在关于抛出异常并处理它的评论中,你说:" 我想这样做,但我被告知它在任何地方都会创建异常处理代码."
这是另一种误解.根据前面的附注,您只需要处理异常,其中:
担忧可能是由于有缺陷的因果分析造成的.您不需要回滚代码只是因为您抛出了异常.还有许多其他原因可以抛出异常.回滚代码是必需的,因为如果发生错误,该方法需要执行清理.换句话说,在任何情况下都需要异常处理代码.这表明防止过度异常处理的最佳方法是以这样的方式进行设计,以减少对错误进行清理的需要.
所以不要" 不抛出异常 "以避免过多的异常处理.我同意过多的异常处理是不好的(参见上面的设计考虑).但是当你应该不回滚时更糟糕,因为你甚至不知道有错误.
| 归档时间: |
|
| 查看次数: |
6485 次 |
| 最近记录: |