8.4 Exceptions
异常
用异常通知程序的其他部分,发生了不可忽略的错误
只在真正例外的情况下才抛出异常
不能用异常来推卸责任
避免在构造函数和析构函数中抛出异常,除非你在同一地方把它们捕获
在恰当的抽象层次抛出异常
在异常消息中加入关于导致异常发生的全部信息
避免使用空的catch语句
了解所用函数库可能抛出的异常
考虑创建一个集中的异常报告机制
把项目中对异常的使用标准化
- 对于像C++这类语言,其中允许抛出多种多样的对象、数据及指针的话,那么就应该为到底可以抛出哪些类的异常建立一个标准。为了和其他语言兼容,可以考虑只抛出Exception基类派生出的对象
- 考虑创建项目的特定异常类。用作项目中所有可能抛出的异常的基类。这样就能把记录日志、报告错误等操作集中起来并标准化。
- 规定在何种场合允许代码使用throw - catch语句在局部对错误进行处理。
- 规定在何种场合允许代码抛出不在局部进行处理的异常。
- 确定是否要使用集中的异常报告机制。
- 规定是否允许在构造函数和析构函数中使用异常。
考虑异常的替换方案
有些程序员用异常来处理错误,只是因为他所用的编程语言提供了这种特殊的错误处理机制。你心里应该自始至终考虑各种各样的错误处理机制:在局部处理错误、使用错误码来传递错误、在日志文件中记录调试错误信息、关闭系统或者其他的一些方式等。
最后请考虑你的程序是否真的需要处理异常,而不是直接释放所有已获得的资源并终止程序执行,而让用户去重新用正确的输入数据再次运行程序。
8.5 Barricade Your Program to Contain the Damage Caused by Errors
隔离程序,使之包容由错误造成的损害
隔栏(barricade)是一种容损策略(damage-containment strategy)。
在输入数据时将其转换为恰当的类型
Relationship Between Barricade And Assertions
隔栏与断言的关系
隔栏的使用使断言和错误处理有了清晰的区分。隔栏外部的程序应用错误处理技术,在那里对数据做的任何假定都是不安全的。而隔栏内部的程序里就应该使用断言技术,因为传进来的数据已在通过隔栏时被清理过了。如果隔栏内部的某个子程序检测到了错误的数据,那么这应该是程序里的错误而不是数据里的错误。
隔栏的使用还展示了“在架构层次上规定应该如何处理错误”的价值。规定隔栏内外的代码是一个架构层次上的决策。
8.6 Debugging Aids
辅助调试的代码
Don‘t Automatically Apply Production Constraints to the Development Version
不要自动地把产品版的限制强加于开放版之上
应该在开发期间牺牲一些速度和对资源的使用,来换取一些可以让开发更顺畅的内置工作。
Introduce Debugging Aids Early
尽早引入辅助调试的代码
Use Offensive Programming
采用进攻式编程
应该以这么一种方式来处理异常情况:在开发阶段让它显现出来,而在产品代码运行时让它能够自我恢复。这就是“进攻式编程”。
- 确保断言语句使程序终止运行。
- 完全填充分配到的所有内存,这样可以让你排查出内存分配错误。
- 确保每一个case语句中的default分支或else分支都能产生严重错误(比如让程序终止运行)。或者至少这些错误不会被忽略。
- 在删除一个对象之前把它填满垃圾数据。
- 让程序把它的错误日志文件用电子邮件发送给你,这样你就能了解到在已发布的软件中还发生了哪些错误——如果这对你所开发的软件适用的话。
有时候,最好的防守正是大胆进攻。在开发时惨痛地失败,能让你发布产品后不会败得太惨。
Plan to Remove Debugging Aids
计划移除调试辅助代码
使用类似ant和make这样的版本控制工具盒make工具 版本控制工具可以从同一套源代码编译出不同版本的程序。在开发模式下,你可以让make工具把所有的调试代码都包含进来一起编译。而在产品模式下,又可以让make工具把那些你不希望包含在商用版中的调试代码排除在外。
使用内置的预处理
编写你自己的预处理器
使用调试存根
8.7 Determing How Much Defensive Programming to Leave in Production Code
确定在产品代码中该保留多少防御式代码
保留哪些检查重要错误的代码
去掉检查细微错误的代码
去掉可以导致程序硬性崩溃的代码
保留可以让程序稳妥地崩溃的代码
为你的技术支持人员记录错误信息
确认留在代码中的错误消息是友好的
8.8 Being Defensive About Defensive Programming
对防御式编程采取防御的姿态
过度的防御式编程也会引起问题。要考虑好什么地方你需要尽心防御,然后因地制宜地调整你进行防御式编程的优先级。
CHECKLIST:Defensive Programming
核对表:防御式编程
一般事宜
子程序是否保护自己免遭有害输入数据的破坏?
你用断言来说明程序假定吗?其中包括了前条件和后条件吗?
断言是否只是用来说明从不应该发生的情况?
你是否在架构或高层设计中规定了一组特定的错误处理技术?
你是否在架构或高层设计中规定了是让错误处理更倾向于健壮性还是正确性?
你是否建立了隔栏来遏制错误可能造成的破坏?是否减少了其他需要关注错误处理的代码的数量?
代码中用到辅助调式的代码了吗?
如果需要启用或禁用添加的辅助助手的话,是否无需大动干戈?
在防御式编程时引入的代码量是否适宜——既不过多,也不过少?
你在开发阶段是否采用了进攻式编程来使错误难以被忽略?
异常
你在项目中定义了一套标准化的异常处理方案吗?
你是否考虑过异常之外的其他替代方案?
如果可能的话,是否在局部处理了错误而不是把它当成一个异常抛到外部?
代码中是否避免了在构造函数和析构函数中抛出异常?
所有的异常是否都与抛出它们的子程序处于同一抽象层次上?
每个异常是否都包含了关于异常发生的所有背景信息?
代码中是否没有使用空的catch语句?(或者如果使用空的catch语句确实很合适,那么明确说明了吗?)
安全事宜
检查有害输入数据的代码是否也检查了故障的缓冲区溢出、SQL注入、HTML注入、整数溢出以及其他恶意输入数据?
是否检查了所有的错误返回码?
是否捕获了所有的异常?
出错消息中是否避免出现有助于攻击者攻入系统所需的信息。
Key Points
要点
- 最终产品代码中对错误的处理方式要比“垃圾进、垃圾出”复杂得多。
- 防御式编程技术可以让错误更容易发现、更容易修改,并减少错误对产品代码的破坏。
- 断言可以帮助人尽早发现错误,尤其是在大型系统和高可靠性的系统中,以及快速变化的代码中。
- 关于如何处理错误输入的决策是一项关键的错误处理决策,也是一项关键的高层设计决策。
- 异常提供了一种与代码正常流程角度不同的错误处理手段。如果留心使用异常,它可以成为程序员们知识工具箱中的一项有益补充,同时也应该在异常和其他错误处理手段之间进行权衡和比较。
- 针对产品代码的限制并不适用于开发中的软件。你可以利用这一优势在开发中添加有助于更快地排查错误的代码。
第8章防范式编程下(代码大全4)