[珠玑之椟]浅谈代码正确性:循环不变式、断言、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列元素排列成的矩形阵列.矩阵里的 ...
随机推荐
- #import 跟 #include、@class 之间的区别
#include 是 C/C++ 导入头文件的关键字 是完整的包含某个文件的内容(包括该文件中被导入的文件) #import 是 OC 导入头文件的关键字 #import 指令是 OC 针对 #in ...
- jquery插件文件上传
文件上传有很多jQuery插件,一般我最为常用的就是uploadify.js和ajaxfileupload.js,二者都是以file标签为依托,前者需要在页面初始化时就渲染插件,比较适合单纯的文件上传 ...
- 一个简单的Lua解释器
#include "stdafx.h" #include<stdarg.h> #include<stdlib.h> #include<stdio.h& ...
- c语言多线程队列读写
最近用c语言写了个简单的队列服务,记录一下,文件结构为 main.c queue.c queue.h,代码如下: 主函数 #define NUM_THREADS 200 #include <st ...
- Maven学习3-使用Maven构建项目
转自:http://www.cnblogs.com/xdp-gacl/p/4240930.html maven作为一个高度自动化构建工具,本身提供了构建项目的功能,下面就来体验一下使用maven构建项 ...
- 使用Script Component源处理不规则平面文件
微软 BI 系列随笔 - SSIS 2012 高级应用 - Script Component处理不规则平面文件 场景介绍 在使用SSIS从平面文件导入源数据时,最常遇到的是以下两种情况: 导入规则的平 ...
- zxing 一维码部分深入分析与实际应用,识别卡片数量,Android数卡器
打算修改zxing 源码应用到其它方面,所以最近花了点时间阅读其源码,无意中找到这篇博客,条码扫描二维码扫描——ZXing android 简化源码分析 对过程的分析还是可以参考的.原作者给出的一个基 ...
- NODEJS - express
1.express组织结构 app demo |---node_modules------用于安装本地模块. |---public------------用于存放用户可以下载到的文件,比 ...
- 手把手教你接口自动化测试 – SoapUI & Groovy
手把手教你接口自动化测试 – SoapUI & Groovy http://www.cnblogs.com/wade-xu/p/4236295.html 关键词:SoapUI接口测试,接口自动 ...
- HTML页面嵌入视频和JS控制切换视频的问题
文章摘自:http://www.cnblogs.com/jorton/archive/2012/03/19/vidio_in_site.html 首先,在页面中嵌入视频的HTML代码为: 1 < ...