这个主题和代码的实际写作有关,而且内容和用法相互交织,以下只是对于其内容的一个划分。《编程珠玑》上只用了两个章节20页左右的篇幅介绍,如果希望能获得更多的实例和技巧,我比较推崇《程序设计实践》 (Practise of Programming)、《编程精粹:编写高质量C语言代码》(Writing Solid Code)这两本书,只要有一般的C语言基础就能读懂,而且读起来比较快,读完后能提高不少coding的实践水平。

    目录


循环不变式(invariant)

  循环不变式主要用来帮助理解算法的正确性,具体来看,比较针对于循环迭代。形式上很类似与数学归纳法,它是一个需要保证正确断言。对于循环不变式,必须证明它的三个性质:

初始化:它在循环的第一轮迭代开始之前,应该是正确的。

保持:如果在循环的某一次迭代开始之前它是正确的,那么,在下一次迭代开始之前,它也应该保持正确。

终止:循环能够终止,并且可以得到期望的结果。

具体的使用实例可以参考我的旧作一篇:如何写出正确的二分查找?——利用循环不变式理解二分查找及其变体的正确性以及构造方式


debug之脚手架

  听起来挺玄乎,其实所谓的脚手架,就是在debug版本里加入的为了验证程序是否正确的额外代码,比如一条为了确定循环中临时变量是否按期望变化的printf语句,这比复杂的调试器更快。我相信很多人在写代码时都这样做过,看到这里,我们并不需要为自己过去“简陋”的调试方式而不安,而是继续合理地使用它。

  脚手架能完成更多的工作,不仅限于变量追踪。利用断言,可以建立脚手架进行程序的自动测试而不是人为的追踪,也可以利用clock()建立脚手架,把待测的代码放在两个clock()中间测试运行时间(相比之下,我更喜欢用gettimeofday())。以上三种脚手架的用法是《编程珠玑》提到的例子,关于这种代码可以做更多的引申,以下内容结合了Practise of Programming和Writing Solid Code的相关内容。

1.#ifdef DEBUG

  很多人都使用过下面这样的代码:

#ifdef DEBUG
...
#endif

  这种想法是同时维护调试和非调试(即交付)两个版本,在调试版本中自动地查错;最后提交交付版本。当然,这种方法的关键是保证调试代码不在最终产品中出现。只是这种代码在原来的代码里看起来十分突兀(排版不好看,而且可能看上去喧宾夺主),使用断言assert()是一个代替方案之一,它只在定义了DEBUG时才有效,在我的Ubuntu的assert.h里是如果定义了NDEBUG就无效。关于断言会在后面详写,这里点到为止。

2.外壳函数/包裹函数

  做法是把待测试或者可能发生错误而需要检测的函数用一段代码加一个外壳,或者说是包裹起来,在其中增加出错检查和处理代码,并提供合理的返回值。这两种名称前者出自《程序设计实践》,后者见于UNP,举一个UNP上的简单的例子:

int Socket(int family, int type, int protocol)
{
int n;
if( (n = socket(family,type,protocol)) < )
err_sys("socket error");
return n;
}

3.用不同的算法验证正确性

  如果编写了一个较快的算法又担心其不正确,想要检查其正确性怎么办?在脚手架中构造一个包括能提供同样功能并且正确但速度较慢的算法(往往是旧版本中所留下的),比较两者的执行结果。

4.提高代码覆盖度

  在if判断中,总有一部分代码未必执行。如果想要测试不常执行的代码的正确性,可以用脚手架强制执行这部分代码。

5.消除随机性,使错误重现

  利用脚手架对分配的内存块用garbage值0xA3而不是0填充,这样当发现指针指向0xA3A3或内容是连续的0xA3A3时,显然是个未定义的值,需要排错。具体的取值和系统有关,0xA3是早期macintosh的建议用的garbage值。

6.不要担心脚手架带来的性能损失

  正如Writing Solid Code所言,不要把对交付版本的约束应用到相应的调试版本上,要用大小和速度来换取错误检查能力。脚手架只是为了查错,在交付版本中它们是不存在的。


断言(assert)

  正如在脚手架中提到的,断言可以对程序正确性的测试。除此以外,在一段小型的代码demo中,编写断言远比精心编制一套完整的出错处理机制或者繁复的#ifdef DEBUG要简单的多。

  为了帮助理解这个宏,Writing Solid Code探讨了assert宏的一种实现机制:

#ifdef DEBUG
void _Assert(char*, unsigend);
#define ASSERT(f) \
if(f) \
NULL; \
else
_Assert(__FILE__,__LINE__)
#else
#define ASSERT(f) NULL
#endif

  而真正的处理函数是

void _Assert(char* strFile, unsigned uLine)
{
fflush(stdout);
fprintf(stderr,"\nAssertion failed:%s, line %u\n",strFile,uLine);
fflush(stderr);
abort();
}

  为什么要用宏定义+函数实现,并将宏中的__LINE__传递给后面实现的函数而不是仅仅靠宏本身实现?因为__LINE__用其所在的行号替换内容,使用函数则只会变成该函数内部的行号,而使用宏则只是把__LINE__放到对应需要检查的位置。更具体的说明可以参考以下链接:

http://stackoverflow.com/questions/11214260/behavior-of-line-in-inline-functions

http://stackoverflow.com/questions/7929291/get-code-line-with-line

  另外,这个断言宏可以作为《编程珠玑(续)》习题3.5的完善解答。

“珠玑之椟”系列简介与索引

往期回顾:

位向量/位图的定义和应用

估算的应用与Little定律

随机数函数取样与概率

[珠玑之椟]浅谈代码正确性:循环不变式、断言、debug的更多相关文章

  1. Java基础之浅谈异常与了解断言

    一.产生错误原因 用户输入错误 设备错误 物理限制 代码错误 二.解决错误---异常 在Java中异常对象都是派生于Throwable类的一个实例. 我们一般将异常分为两种:①Error和②Excep ...

  2. iOS 核心动画 Core Animation浅谈

    代码地址如下:http://www.demodashi.com/demo/11603.html 前记 关于实现一个iOS动画,如果简单的,我们可以直接调用UIView的代码块来实现,虽然使用UIVie ...

  3. iOS 自定义转场动画浅谈

    代码地址如下:http://www.demodashi.com/demo/11612.html 路漫漫其修远兮,吾将上下而求索 前记 想研究自定义转场动画很久了,时间就像海绵,挤一挤还是有的,花了差不 ...

  4. 浅谈PHP代码设计结构

    浅谈PHP代码设计结构 您的评价:       还行  收藏该经验       coding多年,各种代码日夜相伴,如何跟代码友好的相处,不光成为职业生涯的一种回应,也是编写者功力的直接显露. 如何看 ...

  5. 浅谈Kotlin(二):基本类型、基本语法、代码风格

    浅谈Kotlin(一):简介及Android Studio中配置 浅谈Kotlin(二):基本类型.基本语法.代码风格 浅谈Kotlin(三):类 浅谈Kotlin(四):控制流 通过上面的文章,在A ...

  6. 浅谈android代码保护技术_ 加固

    浅谈android代码保护技术_加固 导语 我们知道Android中的反编译工作越来越让人操作熟练,我们辛苦的开发出一个apk,结果被人反编译了,那心情真心不舒服.虽然我们混淆,做到native层,但 ...

  7. 浅谈Android保护技术__代码混淆

    浅谈Android保护技术__代码混淆   代码混淆 代码混淆(Obfuscated code)亦称花指令,是将计算机程序的代码,转换成一种功能上等价,但是难于阅读和理解的形式的行为.将代码中的各种元 ...

  8. 浅谈struts2之chain

    转自:http://blog.csdn.net/randomnet/article/details/8656759 前一段时间,有关chain的机制着实困绕了许久.尽管网上有许多关于chain的解说, ...

  9. [技术]浅谈OI中矩阵快速幂的用法

    前言 矩阵是高等代数学中的常见工具,也常见于统计分析等应用数学学科中,矩阵的运算是数值分析领域的重要问题. 基本介绍 (该部分为入门向,非入门选手可以跳过) 由 m行n列元素排列成的矩形阵列.矩阵里的 ...

随机推荐

  1. 由于xrdp、gnome和unity之间的兼容性问题,在

    由于xrdp.gnome和unity之间的兼容性问题,在Ubuntu 14.04版本中仍然无法使用xrdp登陆gnome或unity的远程桌面,现象是登录后只有黑白点为背景,无图标也无法操作.与13. ...

  2. 关于kindeditor中点击图片后,滚动条往上顶的bug

    比如现在我插入两张图片, 无论我点击哪张图片,里边的滚动条都会往上顶. 本来以为往上会有解决方法,一查结果没有:然后想着去官网查查,然而什么都没有,想到官网提交这个bug,结果没地方提交. 怎么解决, ...

  3. hiho一下120周 后缀数组一·重复旋律

    后缀数组一·重复旋律 时间限制:5000ms 单点时限:1000ms 内存限制:256MB 描述 小Hi平时的一大兴趣爱好就是演奏钢琴.我们知道一个音乐旋律被表示为长度为 N 的数构成的数列. 小Hi ...

  4. 【转载】基于ANSYS APDL的有裂纹平板问题的断裂力学仿真(PLANE183)

    原文地址:http://blog.sina.com.cn/s/blog_9e19c10b0102vnw7.html 对于一般的强度问题,我们总是用应力来度量其强度的.但是对于有裂纹的,高强度的构件,使 ...

  5. Chrome渲染Transition时页面闪动Bug

    前段时间,有同事和会员反馈使用Chrome访问淘宝首页会出现画面闪动的现象,但是我在Mac和Win下面的Chrome都无法重现这个问题,后来重装了一遍Win7下的Chrome Beta版本,终于重现了 ...

  6. Python chr() ord() unichr()

    chr()函数用一个范围在range(256)内的(就是0-255)整数作参数,返回一个对应的字符. unichr()跟它一样,只不过返回的是Unicode字符,这个从Python 2.0才加入的un ...

  7. Jetty与Tomcat的区别 转

    Jetty与Tomcat的区别 由于没有研究过Tomcat,所以区别不好说,这里暂时就网上的一些言论和自己所了解到的一些总结下(摘自于许令波). Jetty 的架构从前面的分析可知,它的所有组件都是基 ...

  8. MyEclipse Spring 学习总结三 SpringMVC

    MyEclipse Spring 学习总结三 SpringMVC 一.SpringMVC原理 1.Springmvc 框架介绍 1)Spring 框架停工了构建Web应用程序的全功能MVC模块.Spr ...

  9. Django缓存使用方法

    Django缓存分为Session和Cookie:Session为放在服务器端的缓存:Cookie为放在客户端(浏览器)的缓存. Session一般用来保存登录会话:Cookie一般用来保存一些个性化 ...

  10. HBase体系结构(转)

    HBase的服务器体系结构遵循简单的主从服务器架构,它由HRegion服务器(HRegion Server)群和HBase Master服务器(HBase Master Server)构成.HBase ...