谈谈assert

来源:互联网 发布:ios icon制作软件 编辑:程序博客网 时间:2024/06/05 02:32

摘要

  • 如果所有人都不犯错误,bug free,我们不需要assert。
  • assert不是处理可预期错误的方法。
  • 如果违背了assert的条件,别死撑了,早死早投胎。

正文

要谈assert,先要讲两句defensive programming。首先明确一点,DP是一种软件开发方法。是一种实践,不是一种理论。

大家知道,我是学软件理论的。从一个理论学者看来,程序(program)是一个推理系统(reasoning system),其中的包含的性质(properties)随时间的展开而不断更新。这么说是不是不明觉厉呢?且听我慢慢道来。

比方说我们程序里有这么一句

int x = 10;

当程序执行了这一句,从DP的角度看发生了这么几件事情:

  1. 程序有了一个新的name,x;
  2. 程序有了一个新的property:x==10。

当我们看到一个循环:

for (int i = 0; i != n; ++i) {}

我们知道当循环退出的时候,i==n。这个例子我们从一行程序的性质,提取出了一段程序的性质。这种性质称作后置条件(post-condition)。后置条件是一个代码段结束时必须满足的性质。


但是我们知道,这个循环要想退出是有条件的:n必须非负。也就是说,除非我们意图一个死循环。这段程序还需要一个前置条件(pre-condition)。前置条件是代码正确执行的前提。

除了这两种性质,我们其实还知道:如果循环体没有改变i的值,i是递增的。这个性质是这个循环的不变性(invariant)。

将这个思想运用到比程序块更大的范围,我们不难想到一个函数、一个方法、一个对象、一个程序都有其各自的前置条件、后置条件、不变性。最近闹得沸沸扬扬的流血事件,不就是忘记检查一个前置条件嘛。

DP的第一条观点:三性质是一段代码、一个函数、一个方法、一个对象在整个程序执行期里不变的性质。套用马哲黑话,这是全局的、本质的性质,是十个指头中的九个指头。

DP的第二条观点:程序是人写的,人总会犯错。

基于这两条基本观点,DP提出这么几条实践:

  • 假设其他人总会犯错。模块(先不管什么叫模块)显式写下自己的前置条件,并在模块入口处检查之。无论调用者是否有保证。
  • 假设自己总会犯错。模块执行期内需要反复检查自己的不变性。
  • 不给别人添堵。模块结束时检查自己的后置条件。

从软件工程的角度看,DP是更完备的模块化:模块的实现只依赖自己的前置条件,不依赖调用处的上下文。


终于轮到assert出场了。

对于不变性和后置条件,如果自己不写出bug,是不需要的。自己可能bug free吗?不可能。所以我们要assert。如果出bug怎么办?不要试图掩盖bug,这样只会引发一连串新的bug。留下一些调试信息,尽快退出才是对付bug的人间正道。确实,程序退出会影响可用性,但相比写坏数据,我宁可牺牲可用性,没商量。这里唯一需要考虑的是,要不要core dump。我们内存太多了,core dump一次可能花费几分钟甚至更久——真是幸福的烦恼呢。

前置条件则需要多费些口水了。处理前置条件C++传统上有三种方法:返回码、抛异常、assert。说一下我的标准:返回码、抛异常是模块预期的行为的一部分,不是处理前置条件的手段;只有assert是处理前置条件的手段;前置条件也不是处理可预期错误的方法。前置条件同不变性和后置条件一样,一旦违背就退出吧,没有什么可以挽回的了。

这是说,假设你在写一个除法例程。如果你认为除数为零是一个合法的输入,那么用返回码或者抛异常(更进一步,还应该用一个ut覆盖这个行为)。如果你认为除数不可以为零,需要调用者保证这一点,那么这是一个前置条件,应当用assert。

很清楚,是不是?理想总是美丽,现实往往骨感。事有盘根错节,人有懈怠之心,我也多有犯错的时候。

问题之一:什么是模块。除法例程是不是模块?整个linux内核?这里面没有科学。
问题之二:assert有代价。assert有运行时的代价,更有转变思维的代价,故而码农并不总愿意写assert,愿意写也不见得总能写对、写好。这里面全是经验。

好在DP不是新提出的开发方法,我们还有一些经验法则可以借鉴。
1. assert是不应该发生的情况,至少非常非常不可能发生:rpc传错参数非常可能,命令行参数非常可能填错,磁盘、网络非常容易出错;int64_t加加减减溢出在大多数用途下很不可能,取系统时间非常不可能取不到。
2. 代价不大的性质,尽可能检查,线上也要检查。
3. 反之,代价大的性质,只在debug版本检查吧。违背了就core dump。

4. 性质求准不求多。比如对于排序例程,后置条件可以写出千千万万,我只要两条:1. 输出的元素和输入的元素是一样的;2. 递增。


0 0
原创粉丝点击