防御性编程概念

防御性编程概念




1 什么是防御性编程

防御性编程(Defensive programming)是防御式设计的一种具体体现,它是为了保证,对程序的不可预见的使用,不会造成程序功能上的损坏。它可以被看作是为了减少或消除墨菲定律效力的想法。防御式编程主要由于可能被滥用,恶作剧或无意地造成灾难性影响的程序上。

这是一种谨慎而细致的编程技术,由于软件开发过程中的不可预测性,任何人都不能保证自己的代码不会存在任何问题,甚至由于主观因素,我们很难想象程序员可能出现的问题,程序员无法遇见程序可能出现的错误,自然也无法预先判断,这就导致了在出现问题后,我们往往会感到吃惊,不知道为何会这样,然后慢慢调试,却不知道自己的调试可能引入新的错误,我们把这些软件错误称为bug。

Bug的英文意思是臭虫,这个词可是颇有历史,“Bug”的创始人格蕾丝·赫柏(Grace Murray Hopper),是一位为美国海军工作的电脑专家,也是最早将人类语言融入到电脑程序的人之一。而代表电脑程序出错的“bug” 这名字,正是由赫柏所取的。1947年9月9日,赫柏对Harvard Mark II设置好17000个继电器进行编程后,技术人员正在进行整机运行时,它突然停止了工作。于是他们爬上去找原因,发现这台巨大的计算机内部一组继电器的触点之间有一只飞蛾,这显然是由于飞蛾受光和热的吸引,飞到了触点上,然后被高电压击死。所以在报告中,赫柏用胶条贴上飞蛾,并把“bug”来表示“一个在电脑程序里的错误”,“Bug”这个说法一直沿用到今天。

在不使用防御性编程技术的项目中,我们常常可能按如下方式开发程序:

对于使用防御性编程技术的项目中,开发软件的流程往往变成这样:

当然,防御性编程并不能排除所有的程序错误。但是问题所带来的麻烦将会减少,并易于修改。防御性程序员只是抓住飘落的雪花,而不是被埋葬在错误的雪崩中。防御性编程是一种防卫方式,而不是一种补救形式。我们可以将其与在错误发生之后再来改正错误的调试比较一下。调试就是如何来找到补救的办法。

2 被误读的防御性编程

对防御性编程而言,常常存在以下误读1

  • 检查错误

如果代码中存在明显的可能出错的情况,任何情况下你都应该给予判断和检查,因此这不是防御性编程。

  • 测试

测试是为了让程序中更多的bug被发现,任何程序的开发都需要进行适当的测试,因此测试不能算防御性编程。

  • 调试

防御性编程是在发现所存在的错误之前,通过适当的“陷阱”代码进行检测,防止错误的发生,调试是在发现错误之后进行的,因此不能算防御性编程。

3 防御性编程技术的观点

防御性编程技术和思想往往成为辩论的焦点,我们来看看辩论的正方观点和反方观点1

3.1 反方观点

  • 应用防御性编程技术,会消耗额外的资源和性能。
  • 防御性编程技术降低了整体代码的效率;任何代码的执行都需要消耗CPU性能和时间。对于少量代码的项目,或许不严重,但如果是大型项目,消耗的资源就大了
  • 每种防御性设计,都需要编制额外的代码,既然我们平时做的已经够多了,为什么要多此一举编写防御性程序代码呢?貌似没有必要

3.2 正方观点

  • 使用防御性编程技术,可以防止无法预知的因素导致的程序错误,使得程序员可以节省大量的调试时间来关注其他有意义的问题,还记得墨菲定律么?只要可能存在的问题,那就一定存在问题
  • 损失额外的效率来保证程序的正常运行,远比保证程序运行效率,却随时有可能崩溃要有意义
  • 我们可以在项目的一些发布版本中,通过条件编译或相似技术,来移除或者优化防御性程序代码,来补偿程序中因防御性代码可能导致的性能问题
  • 防御性编程避免了大量的安全问题,这在现代软件开发中是一个重大的问题。避免这些问题可以带来很多好处。

4 防御性编程的基本原则

  1. 采用良好的程序设计风格,提高可读性和可维护性
  2. 永远不要相信所有人,没有人可以保证自己的代码和思想一定不会出问题
  3. 不要像赶集一样写代码,把流程先考虑清楚,在落笔去写
  4. 编程的目标是编写思路清晰的程序,而不是编写足够简洁的代码
  5. 代码是谁写的,优先情况下应让代码的原编写者参与维护,不要随便让不清楚项目情况的人参与维护
  6. 尽可能检查所有可能的返回值,捕获可能抛出的异常
  7. 使用安全的数据结构、函数和程序代码,如多线程环境下,使用线程安全代码甚至可重入代码
  8. 所有变量在使用之前需初始化
  9. 永远不要进行必要限度之外的类型转换
  10. 只在需要使用变量时定义变量

5 防御性编程技术的基本方法

5.1 断言检测技术

  很多编程语言都有自己的断言检测方法,如C/C++、java等,就拿C/C++为例子,断言函数的定义如下:

#ifdef NDEBUG

    #define assert(expression) ((void)0)

#else

    _ACRTIMP void __cdecl _wassert(
        _In_z_ wchar_t const* _Message,
        _In_z_ wchar_t const* _File,
        _In_   unsigned       _Line
        );

    #define assert(expression) (void)(                                                       \
            (!!(expression)) ||                                                              \
            (_wassert(_CRT_WIDE(#expression), _CRT_WIDE(__FILE__), (unsigned)(__LINE__)), 0) \
        )

#endif

5.2 异常处理机制

通常,OOP(既面向对象编程语言)会提供一套异常处理机制,既try-throw-catch结构,如C/C++的以下结构:

try {
o//你的程序代码,如果发生异常,使用throw
} catch(exception &ex) {
o//异常处理代码
}

如果要抛出异常,必须使用throw,对于硬件异常,无法使用这种方式进行诊断。

对于硬件异常,比如非法指针,可以使用操作系统的异常处理机制,如windows的SEH/VEH,linux的signal机制进行检测,以下引用MSDN有关SEH的相关介绍2

  An exception is an event that occurs during the execution of a program, and requires the execution of code outside the normal flow of control. There are two kinds of exceptions: hardware exceptions and software exceptions. Hardware exceptions are initiated by the CPU. They can result from the execution of certain instruction sequences, such as division by zero or an attempt to access an invalid memory address. Software exceptions are initiated explicitly by applications or the operating system. For example, the system can detect when an invalid parameter value is specified.
  Structured exception handling is a mechanism for handling both hardware and software exceptions. Therefore, your code will handle hardware and software exceptions identically. Structured exception handling enables you to have complete control over the handling of exceptions, provides support for debuggers, and is usable across all programming languages and machines. Vectored exception handling is an extension to structured exception handling.
  The system also supports termination handling, which enables you to ensure that whenever a guarded body of code is executed, a specified block of termination code is also executed. The termination code is executed regardless of how the flow of control leaves the guarded body. For example, a termination handler can guarantee that clean-up tasks are performed even if an exception or some other error occurs while the guarded body of code is being executed.

SEH既结构化异常处理,是windows为程序员们提供的检测硬件异常的机制,我们可以使用以下形式:

__try {
//正常程序执行的代码
} __except(filter) {
//异常处理代码
}

filter表达式返回一个处理值,代表异常的处理方案,异常处理方式有3种:
1. EXCEPTION_EXECUTE_HANDLER: 该异常被处理。从异常处下一条指令继续执行。
2. EXCEPTION_CONTINUE_SEARCH:不能处理该异常,让别人处理它吧。
3. EXCEPTION_CONTINUE_EXECUTION:该异常被忽略。从异常处继续执行。

可以使用顶级异常处理函数(TopLevelEH),该函数会在异常发生后,如果异常没有被正常处理的话,最终会交给该处理函数,可以使用如下函数设置该处理程序:

LPTOP_LEVEL_EXCEPTION_FILTER WINAPI SetUnhandledExceptionFilter(
  _In_ LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter
);

linux/unix下使用信号机制进行处理,具体的本文不细说。

打赏

发表评论

电子邮件地址不会被公开。 必填项已用*标注

扫码二维码快速访问本页

防御性编程概念 – 起航天空