田纳西.威廉斯(Tennessee Williams):
我们彼此猜忌,实属情非得已。这是我们防止背叛的唯一武器。
一、编写优秀代码:
《程序设计原理》中M.A.Jackson写道:
软件工程师的智慧,就在于他们是否开始意识到:使程序能用和使程序正确,这两者之间有什么样的差别。
- 可用的代码:提供常规输入集,代码给出常规输出;一旦有意外输入,代码崩溃。
- 正确的代码:对于所有输入集,有正确的输出;
- 优秀的代码:一定是正确的代码;逻辑容易理解;代码自然;容易维护。
产品级的代码,面对不常见的输入时不会崩溃,也不会出现错误的结果;同时,满足其他要求,包括『可重入』、『线程安全』、时间约束等。
二、一种防止代码漏洞百出的手段——防御性编程
墨菲定律(Murphy's Law):
凡是可能出错的事,准会出错。
防御性编程通过预见到(至少预先推测到)问题所在,断定代码中每个阶段可能出现的问题,并做出相应的防范措施,来防止这类意外产生。
三、防御性编程技巧
使用好的编码风格和合理的设计
命名合理;审慎地使用括号;
编码前,设计好接口避免闪电式编程
每敲一个字,都想清楚你输入的是什么
进入下一个环节之前,完成上一个代码段的所有任务不要相信任何人
任何人,包括你自己,都可能把缺陷引入你的程序逻辑中
用怀疑的眼光审视所有的输入和所有的输出,直到确保正确为止。
在程序各处添加安全检查保持代码简单
将复杂的代数运算拆分为一些列单独的语句,使逻辑清晰。不要让任何人做他们不该做的修补工作
面向对象语言中,将属性设置为private,并提供public函数操作它们;
如果变量可以声明为函数内局部变量,不要在文件范围内声明;
如果变量可以声明为循环体内局部变量,不要在函数范围内声明。编译时开启所有警告开关
编译器的警告能捕捉到许多愚蠢的编码错误;使用静态分析工具
编译器只能对代码进行有限的静态分析;
静态分析工具:C语言的lint;.NET的FxCop使用安全的数据结构
最常见的安全隐患为缓冲溢出,缓冲溢出是由于不正确地使用固定大小的数据结构而造成的;
避免的方法:
使用更安全的不允许破坏程序的数据结构——使用类似C++的string类
对不安全的数据结构使用更安全的操作检查所有的返回值
大多数难以察觉的错误都是因为程序员没有检查返回值而出现的重视所有稀有资源,审慎地管理它们的获取和释放
显式地终止那些不再使用或不会被自动清除的对象的引用
不要循环引用(A引用B,B引用A)在声明时对变量初始化
尽可能推迟变量的声明
使变量声明的位置和使用它的位置尽量接近,从而防止干扰其他代码
不要在多个地方重用同一个临时变量使用标准化语言工具,写标准化语言
使用好的诊断信息日志工具
审慎地使用强制转换
数据的强制转换会影响代码的可移植性细则
提供默认行为:如同switch语句都带default一样,写一个不带else的if语句应当深思;
遵从语言习惯:
检查数值上下限:防止数值型变量上溢和下溢;确保每次运算可靠稳定(被除量不能为0)。
正确的设置常量:尽可能把可以设置为常量的都设置为常量。约束
前置条件:输入一段代码前必须为真的条件,一般对参数作限定;
后置条件:编写一段代码后必须为真的条件,一般对结果作判断;
不变条件:每当程序执行到达特定点(循环中、方法调用等)时为真的条件,防止逻辑错误;
断言:任何关于程序在给定位置状态的陈述;约束的内容
检查所有的数组访问是否都在边界内
在废弃指针之前断言指针是非零的
确保函数参数有效
在函数结果返回之前对其进行充分检查移除约束
通常在程序构建的开发和调试阶段,才需要约束检验;确保程序逻辑正确后,理论上可以移除。
C/C++标准库提供了公共机制——断言;指定为NDEBUG编译,可移除断言
Java通过JVM启动或禁用断言机制
.NET在框架Debug中提供断言机制
内容相关:
可重入代码:允许被多个进程同时访问和使用的一段代码,而且无论哪个进程调用它,所得到的结果都是一样。为了防止某一个进程的修改而导致不同进程的结果不同,可重入代码中一般采用局部变量不使用全局变量或静态变量。
线程安全:如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,表示你的代码是线程安全的。
静态分析:程序运行前,执行代码检查