一、善于防守——健壮代码的防御性编程技巧
1、防御性编程。提供一些以外的输入,测试代码是否会崩溃(正确的代码是绝不会崩溃的)。
2、凡是可能出错的事,准会出错。不要做任何设想,没有记录下来的设想会不断地造成缺陷,特别是随着代码的增长。
3、编码的目标是清晰,而不是简洁,复杂的结构或不常用的语言技巧会扼杀代码的可维护性。
4、将所有的变量保持在尽可能小的范围内。不到万不得已,不要声明全局变量。如果变量可以声明为函数内的局部变量,就不要在文件范围上声明。如果变量可以声明为循环体内的局部变量,就不要在函数范围上声明。
5、编译器的警告可以捕捉到许多愚蠢的编码错误,在任何情况下都启用他们,确保你的代码可以安安静静的完成编译。
6、审慎的处理内存。对于在执行期间所获取的任何资源,必须彻底释放。例如,A引用B,B又引用A,除此之外没有对A和B的引用,这会导致对象永远不会被清除,这是一个难以发现的内存泄露形式。
7、在声明位置初始化所有变量。C/C++中如果你意外的使用了一个没有初始化的变量,那么你的程序在每次运行的时候都将得到不同的结果,这取决于当时内存中的垃圾信息是什么。
8、审慎的进行强制转换。例如,你试着将一个64位的整数转换为较小的8位数据类型,那么其他的56位会怎么样呢?你的执行环境可能会突然抛出异常,或者悄悄地使你数据的完整性降级。
9、给程序添加约束。①检查所有的数组访问是否都在边界内;②在废弃指针之前断言指针是非零的;③确保函数的参数有效;④在函数的结果返回之前对其进行充分的检查;⑤在操作对象之前证明它的状态是一致的。
二、精心布局——源代码的版面和样式
1、代码风格的各个方面(如函数和变量的命名习惯),以及与其紧密相关的程序结构(例如,不要使用goto,或只编写但入口单出口的函数)等其他编码问题,决定了你编写程序的风格。
2、了解你的源代码的真正读者:其他的程序员。编写代码时要为他们的利益着想,代码越清晰越好,也更便于自己修复漏洞。
3、选择一种好的编码风格,并坚持使用它。如果你的团队已经有了一个编码标准,那么就使用这个标准,不要使用你自己的风格。但是,不要盲目地放弃你的风格,要了解这样做的代价和收益是否划算。如果你的公司还没有一种内部风格,推动这项工作并制定一个吧...
4、圣战:对它说不。不要卷入其中,远离是非之地。圣战是精力的浪费,作为一名职业程序员,你应该从这些无谓的争论中挣脱出来。会引起争论的地方有很多,如编辑器、编译器、方法论、最好的语言,等等。这些争论已经持续了很多年,他们还将持续下去,不会有人赢得这场争论,没有人能够给出正确答案,因为正确答案根本不存在。这种争论不过是某个人想把自己的偏好(虽然堂而皇之)强加给别人的一个机会。
三、名正言顺——为有意义的事物起有意义的名称
1、学习如何清晰地命名事物——一个对象的名称应该清晰地描述这个对象。在命名上,将重点放在清晰而非简洁上。不过,有一种情况适合使用较短的(甚至是一个字母的)变量名,即循环计数器。
2、①命名变量。对于一个名词,例如,GUI应用程序中的变量名可以是ok_button和main_window,甚至是那些不能与现实世界的对象相对应的变量,也可以被赋予名词形式的名称,如elapsed_time或exchange_true。如果不是名词,变量通常会是一个名词化的动词,例如count。数字变量的名称描述了对其值的解释,如widget_length。逻辑变量的名称通常是一个条件语句形式的名称,可以很自然的通过名称来判断其值是true还是false。
②命名函数。有意义的函数名称应该避免使用be、do和perform等词语。例如,函数apples()是做什么的呢?它是返回苹果的个数,还是将其他东西变成苹果,还是生产苹果?我们应该隐藏函数内部的具体实现过程,始终从使用者的角度来为函数命名(这也就是函数的作用——概况和抽象)。例如,一个函数计算苹果的数量,命名应该是countApples()。
③命名类型。类可以描述一些具有状态的数据对象,在这种情况下,其名称将可能是一个名词。他可能是实现某种虚毁掉接口的函数对象(仿函数)或类,在这里,其名称很可能是一个动词,也许还包含了一种公认的设计模式的名称。接口类倾向于根据接口的功能来命名,类似Printable和Serializable的名称很常见。例如,DataObject是一个差劲的名称:这个类也许包含着数据,并且显然要用于创建对象——这并不需要重复声明。在名称中避免使用多余的词,尤其是在类型名中避免使用以下词语:calss、data、object和type。
④命名名字空间。给命名空间和包赋予反应其内容的逻辑关系的名称。如果内容是库的接口的一部分,将其作为库的名称。如果内容是某个大型系统的一个部分,则选择一个描述这个部分的名称:UI、filesystem或controls都是很好的名称。不要选择重复按时命名对象是一个集合的名称——controls_group就是一个不好的名称。
⑤命名宏。C/C++中的宏总是大写的以使它们突出,并且是审慎命名的以避免冲突。不要为其它任何对象使用这种全大写的名称,永远不要。使用唯一的文件名或项目名前缀将是很有用的,宏名称PROJECTFOO_MY_MACRO要比MY_MOCRO更安全。
⑥命名文件。为窗口定义界面的C/C++文件命名时应该是widget.h而不是widget_interface.h或其他类似的变体。在命名文件是,有许多细微但很重要的问题:注意大写,一些文件系统不区分大小写,但是当移植到大小写很重要的平台时会出现错误;由于某种原因,如果你的文件系统认为foo.h和Foo.h是两个不同的文件名,那就不要使用它;尽量确保你创建的所有文件都具有不同的名称,即使这些文件都分布在不同的目录中,安排你的文件以使你可以在文件中包含library_one/version.h和library_two/version.h等信息,从而不造成困惑。
四、不言自明——编写“自文档化”代码的技巧
1、不要编写需要外部文档支持的代码,这样的代码是脆弱的,要确保你的代码本身读起来就很清晰。
2、尽可能使用现有的语言功能来描述约束或行为。例如:如果你要定义一个永远都不改变的值,那么就强制它定义为一个常量类型(C语言中使用const);如果一个变量不应当包含负值,那么就使用一种无符号类型(如果你的语言提供了这种类型);使用枚举来描述一组相关的值;选择适当的类型,在C/C++语言中,将值得大小放入size_t变量,将指针的运算结果放入ptrdiff_t变量。
3、命名常量。诸如"if ( counter == 76 )"这样的代码会让人困惑不解,它隐藏了含义,如果像下面这样const size_t bananas_per_cake = 76 if( count == bananas_per_cake ) { //做香草蛋糕 },就会清晰很多,而且如果你在代码中需要多次用的常量76,只需要修改一处即可。
五、随篇注释——如何编写代码注释
1、编写得好的代码实际上并不需要注释,因为每行代码都可以自我解释。像f()和g()这样的函数名会大声叫嚷,要求注释来说明他们,但是像someGoodExample()这样的函数名根本就不需要注释。
2、好的注释解释为什么(代码的目的),而不是怎么样(代码的过程)。
3、遵守黄金法则:一个事实——一个源头,不要在注释中重复代码。例如:++i; //increment i
4、当你发现自己在编写密密麻麻的注释来解释你的代码时,赶快停下来。先记住你在做些什么,然后考虑是否可以更改代码或算法,以使其变得更清晰一些。
5、不要把需要剔除的代码包含在注释中,这会让人感到迷惑。使用C语言的“#ifdef().. . #endif”或某种等价的功能,这些功能块相互嵌套,并具有更清晰的含义(特别是在你后来忘记回来整理这些代码时显得重要)。
6、注释还可以用作代码中内嵌的标志,你可以看到“//XXX”、“//FIXME”或“//TODO”零散的分布在尚未完成的文件中,最好是让程序抛出异常(或debug()),这样就不容易忘记完成那段缺少的代码。
7、注释是会过时的,要相信代码而怀疑注释,当修改代码时要维护代码周围的所有注释。
六、人非圣贤——处理不可避免的情况——代码中的错误情形
1、错误可能而且必将发生。几乎任何操作都会带来意想不到的结果,这种结果和有缺陷的程序中的bug不同,因为你预先就知道错误会发生。例如,你想打开的数据库文件可能已经被删除,磁盘空间随时都有可能用完,使你无法再存储任何内容,或者你要访问的Web服务器目前不可用。
2、错误从何而来。错误可以归类为三种:①用户错误,用户可能提供了错误的输入或试图进行荒谬的操作,一个好的程序会指出错误所在,并帮助用户来改正它,例如提示用户输入什么内容;②程序员错误,用户按下的按钮都正确代码还是崩溃了,这是程序员的代码缺陷,可以利用返回值来检查可能出现的错误;③意外情况,用户按下的按钮都正确,程序员也没有犯错误,但是由于命运之神的捉弄让我们遇到某个无法避开的事情,也许是网络连接失败了,也行是打印机墨水用光了,或者是没有剩余的硬盘空间。我们可以在检测到错误时弹出一个消息框来通知用户,或者在中间层代码检测到错误时向客户端代码自动发送信号。
3、错误报告机制。可以遇到问题就立即中止程序,这比在代码中到处处理错误要容易,但并不是一种好的工程解决方案。可利用的机制有:①返回值(bool返回值,更高级的方法是列举所有可能的退出状态并返回一个相应的原因代码一个值代表成功其余之分别代表不同的异常终止情况)、②错误状态变量(不推荐使用。设置一个共享的全局错误变量在调用函数之后查看这个变量)、③异常捕获、④信号。错误报告机制使用取决于体系结构,Java和C#倾向于使用异常,C++可以选择放弃异常以便于移植到不支持异常的平台或与较早C语言的对接。
4、处理错误。日志,在程序日志中详细记录你所遇到的所有错误,努力收集相关信息;报告,用户体验记录;恢复,如果遇到错误不知怎么解决时将错误上传寻求帮助;忽略,这是错误的方式;传播,当下级函数调用失败时进行清理并将错误报告向上传播。
5、你的代码不可避免的会遇到一些必须由用户来清理的错误,如果你要提示用户来解决问题,首先应该有一些总体思路:①用户并不像程序员那样思考,所以以他们所期望的方式来呈现信息;②确保你的消息不显得高深莫测;③不要呈现毫无意义的错误代码,例如:没有用户知道当面对“Error code 707E”的时候该怎么办;④将后果严重的错误与仅仅是警告区分开;⑤只在用户完全理解每种选择的后果时再提出问题(即使像“是否继续?”这样简单的问题)。
6、要考虑的问题:①是否恰当的进行了清理,可靠地代码即使在错误发生的情况下也不会泄露资源;②不要在错误报告中向外界泄露不恰当的信息;③正确的使用异常,遵循语言规则,防止滥用;④如果在追踪一个程序的正常执行过程中不应该发生的错误,考虑使用断言;⑤发现和修正一个错误的时间越早,造成的麻烦就越少;⑥人们忽略你的错误几乎是不可能的,只要有一点点机会,就有人恶意的使用你的代码,也可能是意外输入。
7、要小心的错误:①检查所有的函数参数;②检查执行过程中关键点处的不变条件是否都满足;③在使用任何来自外部的值之前都要检查其有效性;④检查所有系统调用和其他下级函数调用的返回状态。