KEEP K.I.S.S.

tk's blog

对于断言 ASSERT 的理解

昨天看了《代码大全》的“防御式编程”章节,解惑了长期以来自己对于断言的理解。

书中给了使用断言的指导意见,如下

  1. 用错误处理代码来处理预期会发生的状况,用断言来处理绝不应该发生的状况。
  2. 避免把需要执行的代码放到断言中
  3. 用断言来注解并验证前条件和后条件
  4. 对于高健壮性的代码,应该先使用断言再处理错误
其中有一段话,很清晰地说出了断言的典型使用情况:
对来源于内部系统的可靠的数据使用断言,而不要对外部不可靠的数据使用断言,对于外部不可靠数据,应该使用错误处理代码。断言可以看成可执行的注释。
系统外部的数据(用户输入,文件,网络读取等等)都是不可信的,需要严格检查(通常是错误处理)才能放行到系统内部,这相当于一个守卫。而对于系统内部的交互(比如子程序调用),如果每次也都去处理输入的数据,也就相当于系统没有可信的边界了,会让代码变的臃肿复杂;而事实上,在系统内部,传递给子程序预期的恰当数据应该是调用者的责任,系统内的调用者应该确保传递给子程序的数据是恰当可以正常工作的。这样一来,就隔离了不可靠的外部环境和可靠的系统内部环境,降低复杂度。
 
但是在开发阶段,代码极可能包含缺陷,也许是处理外部数据的程序考虑的不够周全,也许是调用系统内部子程序的代码存在错误,造成子程序调用失败。这个时候,断言就可以发挥作用,用来确诊到底是那部分出现了问题而导致子程序调用失败。在清理了所有缺陷之后,内外有别的信用体系就建立起来。等到发行版时候,这些断言就应该没有存在必要。
 
附一个C++可以用的自定义断言
#ifndef _ASSERT_EX_H
#define _ASSERT_EX_H

#include <stdlib.h>
#include <stdio.h>

#ifdef _DEBUG
#define _ASSERT(exp, message) { \
if (!(exp)) {		\
        fprintf(stdout, "Assertion failed: \"%s\"\nMessage: \"%s\"\nline: %d\nfile: \"%s\"\n", \
                #exp, message, __LINE__, __FILE__); \
	exit(EXIT_FAILURE);						\
	 }     \
}
#else
#define _ASSERT(exp, message)
#endif

#endif //_ASSERT_EX_H