[珠玑之椟]浅谈代码正确性:循环不变式、断言、debug
这个主题和代码的实际写作有关,而且内容和用法相互交织,以下只是对于其内容的一个划分。《编程珠玑》上只用了两个章节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的完善解答。
“珠玑之椟”系列简介与索引
往期回顾:
[珠玑之椟]浅谈代码正确性:循环不变式、断言、debug的更多相关文章
- Java基础之浅谈异常与了解断言
一.产生错误原因 用户输入错误 设备错误 物理限制 代码错误 二.解决错误---异常 在Java中异常对象都是派生于Throwable类的一个实例. 我们一般将异常分为两种:①Error和②Excep ...
- iOS 核心动画 Core Animation浅谈
代码地址如下:http://www.demodashi.com/demo/11603.html 前记 关于实现一个iOS动画,如果简单的,我们可以直接调用UIView的代码块来实现,虽然使用UIVie ...
- iOS 自定义转场动画浅谈
代码地址如下:http://www.demodashi.com/demo/11612.html 路漫漫其修远兮,吾将上下而求索 前记 想研究自定义转场动画很久了,时间就像海绵,挤一挤还是有的,花了差不 ...
- 浅谈PHP代码设计结构
浅谈PHP代码设计结构 您的评价: 还行 收藏该经验 coding多年,各种代码日夜相伴,如何跟代码友好的相处,不光成为职业生涯的一种回应,也是编写者功力的直接显露. 如何看 ...
- 浅谈Kotlin(二):基本类型、基本语法、代码风格
浅谈Kotlin(一):简介及Android Studio中配置 浅谈Kotlin(二):基本类型.基本语法.代码风格 浅谈Kotlin(三):类 浅谈Kotlin(四):控制流 通过上面的文章,在A ...
- 浅谈android代码保护技术_ 加固
浅谈android代码保护技术_加固 导语 我们知道Android中的反编译工作越来越让人操作熟练,我们辛苦的开发出一个apk,结果被人反编译了,那心情真心不舒服.虽然我们混淆,做到native层,但 ...
- 浅谈Android保护技术__代码混淆
浅谈Android保护技术__代码混淆 代码混淆 代码混淆(Obfuscated code)亦称花指令,是将计算机程序的代码,转换成一种功能上等价,但是难于阅读和理解的形式的行为.将代码中的各种元 ...
- 浅谈struts2之chain
转自:http://blog.csdn.net/randomnet/article/details/8656759 前一段时间,有关chain的机制着实困绕了许久.尽管网上有许多关于chain的解说, ...
- [技术]浅谈OI中矩阵快速幂的用法
前言 矩阵是高等代数学中的常见工具,也常见于统计分析等应用数学学科中,矩阵的运算是数值分析领域的重要问题. 基本介绍 (该部分为入门向,非入门选手可以跳过) 由 m行n列元素排列成的矩形阵列.矩阵里的 ...
随机推荐
- python ftplib.FTP 获取当前路径下所有目录
FTP 模块里有一个dir函数,可以打印出当前路径下所有文件,但是这个函数没有返回值,只是打印出来. 还有一个nlst函数,可以返回一个文件名的列表,但是只有文件名,没有详细信息,无法判断是否是目录. ...
- [原创]cocos2d-x研习录-第二阶 概念类之节点类(CCNode)
节点类CCNode在基本概念中并不存在,它是为了建立基本概念之间的关联关系而抽象出来的中间辅助类.这个类在Cocos2D-x中极为重要,它为概念类之间搭建了一座宏伟的桥梁.它的继承关系图如下: ...
- connect VisualVM to Tomcat
https://blogs.oracle.com/jmxetc/ http://stackoverflow.com/questions/1051817/unable-to-connect-to-tom ...
- 各种Linux发行版本优缺点对比[转]
转自:http://www.zzbeidaqingniao.com/linux/20100127/1495.html linux最早由Linus Benedict Torvalds在1991年开始编写 ...
- Oracle表空间不足
Oracle临时表空间不足 -- 用户的缺省表空间.临时表空间 select t.username, t.default_tablespace, t.temporary_tablespace from ...
- Spring中的ApplicationContext事件机制
ApplicationContext的事件机制是观察者设计模式的实现,通过ApplicationEvent类和ApplicationListerner接口来实现. 1. 创建EmailEvent pu ...
- LINQ查询操作符之Select、Where、OrderBy、OrderByDescending、GroupBy、Join、GroupJoin及其对应的查询语法
介绍 ·Select - Select选择:延迟 ·Where - Where查询:延迟 ·OrderBy - 按指定表达式对集合正序排序:延迟 ·OrderByDescend ...
- HTML5能取代IOS原生应用吗
介绍 移动应用程序(App)和HTML5都是目前最火的技术,二者之间也有不少重叠之处.在移动设备浏览器里运行的html5的web页面,也可以重新打包成不同平台上运行的app.目前很多浏览器都有很好的跨 ...
- Correlation Filter in Visual Tracking系列二:Fast Visual Tracking via Dense Spatio-Temporal Context Learning 论文笔记
原文再续,书接一上回.话说上一次我们讲到了Correlation Filter类 tracker的老祖宗MOSSE,那么接下来就让我们看看如何对其进一步地优化改良.这次要谈的论文是我们国内Zhang ...
- Spark ML 文本的分类
最近一直在研究Spark的分类算法,因为我们是做日志文本分类,在官网和各大网站一直没找到相应的Demo,经过1个多月的研究,终于有点成效. val sparkConf = new SparkConf( ...