搜集编码规范 尽可能搜集所有关于编写可维护代码的建议,例如 SquareBox 的建议 ,然后明目张胆地违反它们。 规避公司的编码规则 某些公司有严格的规定,不允许使用数字标识符,你必须使用预先命名的常量。要挫败这种规定背后的意图太容易了。比如,一位聪明的 C++ 程序员是这么写的: 1 2 3 | #define K_ONE 1
#define K_TWO 2
#define K_THOUSAND 999
|
编译器警告 一定要保留一些编译器警告。在 make 里使用 “-” 前缀强制执行,忽视任何编译器报告的错误。这样,即使维护代码的程序员不小心在你的源代码里造成了一个语法错误,make 工具还是会重新把整个包build 一遍,甚至可能会成功!而任何程序员要是手工编译你的代码,看到屏幕上冒出一堆其实无关紧要的警告,他们肯定会觉得是自己搞坏了代码。同样,他们一定会感谢你让他们有找错的机会。学有余力的同学可以做点手脚让编译器在打开编译错误诊断工具时就没法编译你的程序。当然了,编译器也许能做一些脚本边界检查,但是真正的程序员是不用这些特性的,所以你也不该用。既然你用自己的宝贵时间就能找到这些精巧的bug,何必还多此一举让编译器来检查错误呢? 把 bug 修复和升级混在一起 永远不要发布什么“bug 修复”版本。一定要把 bug 修复和数据库结构变更、复杂的用户界面修改,还有管理界面重写等混在一起。那样的话,升级就变成一件非常困难的事情,人们会慢慢习惯 bug 的存在并开始称他们为特性。那些真心希望改变这些”特性“的人们就会有动力升级到新版本。这样从长期来说可以节省你的维护工作量,并从你的客户那里获得更多收入。 在你的产品发布每个新版本的时候都改变文件结构 没错,你的客户会要求向上兼容,那就去做吧。不过一定要确保向下是不兼容的。这样可以阻止客户从新版本回退,再配合一套合理的 bug 修复规则(见上一条),就可以确保每次新版本发布后,客户都会留在新版本。学有余力的话,还可以想办法让旧版本压根无法识别新版本产生的文件。那样的话,老版本系统不但无法读取新文件,甚至会否认这些文件是自己的应用系统产生的!温馨提示:PC 上的 Word 文字处理软件就典型地精于此道。 抵消 Bug 不用费劲去代码里找 bug 的根源。只要在更高级的例程里加入一些抵销它的代码就行了。这是一种很棒的智力测验,类似于玩3D棋,而且能让将来的代码维护者忙乎很长时间都想不明白问题到底出在哪里:是产生数据的低层例程,还是莫名其妙改了一堆东西的高层代码。这一招对天生需要多回合执行的编译器也很好用。你可以在较早的回合完全避免修复问题,让较晚的回合变得更加复杂。如果运气好,你永远都不用和编译器前端打交道。学有余力的话,在后端做点手脚,一旦前端产生的是正确的数据,就让后端报错。 使用旋转锁 不要用真正的同步原语,多种多样的旋转锁更好 — 反复休眠然后测试一个(non-volatile的) 全局变量,直到它符合你的条件为止。相比系统对象,旋转锁使用简便,”通用“性强,”灵活“多变,实为居家旅行必备。 随意安插 sync 代码 把某些系统同步原语安插到一些用不着它们的地方。本人曾经在一段不可能会有第二个线程的代码中看到一个临界区(critical section)代码。本人当时就质问写这段代码的程序员,他居然理直气壮地说这么写是为了表明这段代码是很”关键“(单词也是critical)的! 优雅降级 如果你的系统包含了一套 NT 设备驱动,就让应用程序负责给驱动分配 I/O 缓冲区,然后在任何交易过程中对内存中的驱动加锁,并在交易完成后释放或解锁。这样一旦应用非正常终止,I/O缓存又没有被解锁,NT服务器就会当机。但是在客户现场不太可能会有人知道怎么弄好设备驱动,所以他们就没有选择(只能请你去免费旅游了)。 定制脚本语言 在你的 C/S 应用里嵌入一个在运行时按字节编译的脚本命令语言。 依赖于编译器的代码 如果你发现在你的编译器或解释器里有个bug,一定要确保这个bug的存在对于你的代码正常工作是至关重要的。毕竟你又不会使用其他的编译器,其他任何人也不允许! 一个货真价实的例子 下面是一位大师编写的真实例子。让我们来瞻仰一下他在这样短短几行 C 函数里展示的高超技巧。 1 2 3 4 5 6 7 8 9 10 11 | void * Realocate( void *buf, int os, int ns)
{
void *temp;
temp = malloc(os);
memcpy(( void *)temp, ( void *)buf, os);
free(buf);
buf = malloc(ns);
memset(buf, 0 , ns);
memcpy(( void *)buf, ( void *)temp, ns);
return buf;
}
|
- 重新发明了标准库里已有的简单函数。
- Realocate 这个单词拼写错误。所以说,永远不要低估创造性拼写的威力。
- 无缘无故地给输入缓冲区产生一个临时的副本。
- 无缘无故地造型。 memcpy() 里有 (void*),这样即使我们的指针已经是 (void*) 了也要再造型一次。另外,这样做可以传递任何东西作为参数,加10分。
- 永远不必费力去释放临时内存空间。这样会导致缓慢的内存泄露,一开始看不出来,要程序运行一段时间才行。
- 把用不着的东西也从缓冲区里拷贝出来,以防万一。这样只会在Unix上产生core dump,Windows 就不会。
- 很显然,os 和 ns 的含义分别是”old size” 和 “new size”。
- 给 buf 分配内存之后,memset 初始化它为 0。不要使用 calloc(),因为某些人会重写 ANSI 规范,这样将来保不齐 calloc() 往 buf 里填的就不是 0 了。(虽然我们复制过去的数据量和 buf 的大小是一样的,不需要初始化,不过这也无所谓啦)
如何修复 “unused variable” 错误 如果你的编译器冒出了 “unused local variable” 警告,不要去掉那个变量。相反,要找个聪明的办法把它用起来。我最喜欢的方法是: 大小很关键 差点忘了说了,函数是越大越好。跳转和 GOTO 语句越多越好。那样的话,想做任何修改都需要分析很多场景。这会让维护代码的程序员陷入千头万绪之中。如果函数真的体型庞大的话,对于维护代码的程序员就是哥斯拉怪兽了,它会在他搞清楚情况之前就残酷无情地将他踩翻在地。 一张图片顶1000句话,一个函数就是1000行 把每个方法体写的尽可能的长 — 最好是你写的任何一个方法或函数都不会少于1000行代码,而且里边是深度嵌套,这是必须的。 少个文件 一定要保证一个或多个关键文件无法找到。利用includes 里边再 includes 就能做到这一点。例如,在你的 main 模块里,你写上: Stdcode.h 是有的。但是在 stdcode.h 里,还有个引用: 然后,refcode.h 就没地方能找到了。 (【译者注】为啥找不到呢?仔细看看,现在还有人知道 a:\ 是什么吗?A盘!传说中的软盘…) 到处都写,无处会读 至少要把一个变量弄成这样:到处被设置,但是几乎没有哪里用到它。不幸的是,现代编译器通常会阻止你做相反的事:到处读,没处写。不过你在C 或 C++ 里还是可以这样做的。
【译注】:原文在后面还有一些内容,翻译时略有删减。删节的内容主要是: - 我看不懂的部分;
- 我觉得不怎么好笑的部分(其实很可能是因为没看懂所以找不到笑点);
- 不容易引起现代程序猿共鸣的老旧内容。
本人水平有限,时间匆忙,难免有误,请读者不吝指出。谢谢! |