来自
http://www.briup.com/bbs/post/vi ... amp;tpg=1&age=0
为了在Java编程领域创造更多的就业机会, 在此我将一些从专家那里学到的一些关于如何编写难以维护代码的方法传授给大家. 用了这些这些方法后, 你的后继者要花上几年的时间才能做一个甚至是最简单的修改. 另外, 如果你严格地遵守这些规则, 你也就保证了你自己终生不会被解雇, 因为除了你, 没有其他人能够维护这些代码. 再一次说明, 你如果严格遵守了所有这些规则, 甚至连你自己也不能维护这些代码了!
看了热血沸腾啊,恨不得立刻就弄一个范例代码出来。
介绍
Never ascribe to malice, that which can be explained by incompetence.
拿破仑
这篇文章就象冰糖一样, 用糖来催化纤维, 在糖水中浸泡, 很快就失去了控制.
为了在Java编程领域创造更多的就业机会, 在此我将一些从专家那里学到的一些关于如何编写难以维护代码的方法传授给大家. 用了这些这些方法后, 你的后继者要花上几年的时间才能做一个甚至是最简单的修改. 另外, 如果你严格地遵守这些规则, 你也就保证了你自己终生不会被解雇, 因为除了你, 没有其他人能够维护这些代码. 再一次说明, 你如果严格遵守了所有这些规则, 甚至连你自己也不能维护这些代码了!
你当然不想这么过分. 你的代码不应该看起来完全不可维护, 只是有一些. 不然就有重写或者重构的危险.
这个长篇的文章分成一下几个部分, 以便能更快地读入.
一般原则
命名
伪装
文档
程序设计
代码混淆
测试
语言选择
与其他人打交道
完全靠自己
非主流语言中的把戏
各种各样的技巧
哲学
鞋匠没有鞋子
贡献者
操纵白蚁:I:I
一般原则
Quidquid latine dictum sit, altum viditur.(拉丁文)
无论什么东西, 如果用拉丁文来说, 看起来总是很深奥的.
为了挫败那些维护程序员, 你必须懂得他是如何思考. 他拥有你那冗长的程序, 他没有时间去全部读一遍, 更不用说去弄懂它了. 他只是想快速找到要改的地方, 进行修改, 然后离开, 并且这些修改不能产生副作用.
他对你的程序只有一管之见, 每次只能看见你的程序的很小的一部分. 你要保证他不能单凭这种方式便能弄懂你的程序. 你要让他尽可能艰难地找到他要找的地方. 但更重要的是, 如果他有把握忽略一些事情, 要让他尽可能地感到难以应付.
程序员往往会安于约定俗成. 每次只要你巧妙地违背这些惯例, 你就能让他不得不用一个放大镜仔细阅读你的每一行代码.
你可能知道, 每一个语言特性都能让代码不可维护 -- 当然是在适当滥用的情况下.
命名
"e;When I use a word," Humpty Dumpty said, in a rather scornful tone, "it means just what I choose it to mean - neither more nor less."e;
Lewis Carroll -- Through the Looking Glass, Chapter 6
许多编写不可维护代码的技巧都来自于给变量和方法命名的艺术. 这些名字对于编译器来说无关紧要. 这就给了你很大的范围使用它们, 把那些后继的维护程序员搞得迷迷糊糊.
1.
给小孩取名的新用途
:
book_cover 20,001 Names For Baby
0-380-78047-X
Carol McD. Wallace
amazon.com Barnes and Noble
amazon.ca chapters
amazon.co.uk amazon.de
买一本给小孩取名的书, 那么你就永远不会缺变量名了. Fred 是一个非常好的名字, 也很容易输入. 如果你在寻找容易输入的变量名, 就试一试 asdf, 如果你使用的是 DSK 键盘, 就是 aoeu.
2.
单字母变量名
: 如果你把你的变量叫做 a, b, c, 那么使用一个简单的编辑器来寻找变量的实例几乎是不可能的. 而且, 没有人能猜出它们代表什么. 如果有人想打破自从 FØRTRAN 时代就使用 i, j, k 作为索引变量的传统, 就分别使用 ii, jj, kk 来代替, 并且提出警告, 考虑一下西班牙审理会对付异教徒所做的.
3.
创造性地拼写错误
: 如果你必须使用描述性的变量和函数名, 就把它们拼错. 在一些地方拼错函数和变量名, 在其他地方又拼写正确(就象 SetPintleOpening SetPintalClosing), 就能有效地使 grep 或者 IDE 搜索技术无效. 这种方法效果出奇地好. 还可以增加国际化口味, 比如在不同的剧院(threaters / threatres) 拼写 tory 或者 tori.
4.
保持抽象
: 在命名函数和变量时, 大量使用象 it, everything, data, handle, stuff, do, routine, perform 以及数字之类的抽象词, 例如 routineX48, PerformDataFunction, DoIt, HandleStuff 以及 do_args_method.
5.
使.用.缩.写.
: 用缩写来保证代码简洁. 真正的程序员从来不定义缩写;只是本能地看懂它.
6.
同义词代用
: 以防单调, 可以使用同义词典来为一个词查找尽可能多的意思相同的替代词语, 比如 display, show, present. 这样你含糊地表明了存在一些不同, 但实际上并不存在. 但是, 如果有两个相似的函数有很大的不同, 那么总使用相同的词来描述两个函数(比如, print 意味着"写入文件", 或者"打印到纸上", 或者"显示在屏幕上"). 在任何情况下, 如果有人要求你写一份词汇表来准确地描述你在项目中所用到的术语, 千万不要屈从. 因为这样做就违背了我们这个信息隐藏技术的结构化设计原则, 显得外行.
7.
使用其他语言的复数形式
: 一个 VMS 脚本保存了从各种"Vaxen"返回的"statii". Esperanto, Klingon 和 Hobbitese 都有资格作为起这个作用的语言. 你可以给伪世界语的复数形式(pluraloj)添加 oj, 你正在为世界和平而尽力呢.
8.
字母大写(CapiTaliSaTion)
: 随机地把一个单词中间的某个音节的第一个字母大写. 例如: ComputeRasterHistoGram().
9.
重用名字
: 在语言允许的任何地方, 让类, 构造函数, 方法, 成员变量, 参数和局部变量都是同一个名字. 另外, 在 {} 块内, 重用局部变量的名字. 目的就是强迫维护程序员仔细检查每个变量实例的作用范围. 特别在 Java 中, 用一般的方法冒充构造函数.
10.
加重音的字母 (Åccented Letters)
: 在变量名中使用加重音的字母. 例如:
typedef struct { int i; } ínt; 其中第二个 ínt 的 í 实际上是一个尖重音的 i. 一个简单的编辑器几乎不可能分辨出重音符号的斜体部分.
11.
利用编译器的名字长度限制
: 假如编译器只能识别出一个名字的前, 比方说 8 个字符, 然后可以是任意结尾, 例如一种情况下是 var_unit_update(), 另一种情况下是 var_unit_setup(), 编译器就会把两者都识别为 var_unit.
12.
下划线, 一个真正的朋友
: 使用 _ 和 __ 作为标识符.
13.
语言混和
: 随机地混合使用两种语言(自然语言或者计算机语言). 如果你的老板坚持让你使用他的语言, 告诉他如果用你自己的语言, 你能更好的组织你的想法. 如果这个没用的话, 那么就以语言歧视来控告你的雇主, 获得高额赔偿.
14.
扩展 ASCII
: 扩展的 ASCII 字符作为变量名也是完全有效的, 包括 ß, Ð, 和ñ 等字符. 但是在一个简单编辑器中, 不用拷贝/粘贴的方法几乎不可能输入它们.
15.
来自其他语言的名字
: 使用外语字典作为变量名字的来源, 例如, 使用德语 punkt 代替 point. 那些没有象你那样很好地掌握德语的维护程序员们, 一定会很满意猜测单词意义的多文化体验过程.
16.
来自数学的名字
: 选用那些以数学运算作掩饰的名字作为变量名. 例如:
openParen = (slash + asterix) / equals;
17.
眼花缭乱的名字
: 给变量取一些与程序无关的但是具有意义的名字. 例如:
marypoppins = (superman + starship) / god; 这让读者们混淆, 因为他们很难把这些具有意义的词语和他们当前正在考虑的逻辑联系起来.
18.
改名重用
: 这个技巧在 Ada 里面很有用, 很多标准的迷惑技术对于 Ada 都不能使用. 你现在使用的包和对象的命名者都是笨蛋. 与其说服他们修改, 不如你使用新名字和子类型来重新把它们命名为你自己的一套. 保证留下了一些老名字, 让人一不注意就掉进陷阱.
19.
什么时候用 i
: 不要使用 i 作为最里层的循环变量. 使用其他的来代替. 把 i 随便用作其他目的, 尤其是非 int 的变量. 类似的使用 n 作为循环变量.
20.
习惯方案
: 不要理会 Sun 编码惯例 (Sun Coding Conventions), 毕竟, Sun 也是这么做的. 幸运的是, 当你违反的时候, 编译器并不会泄漏秘密. 目标就是使用那些只在大小写等小地方上不同的名字. 如果你被迫使用大写惯例, 你仍然可以在选择不明确的时候捣乱. 例如, 同时使用 inputFilename 和 inputfileName. 发明你自己的非常复杂的命名规范, 然后指责其他人不遵循它.
21.
小写的 l 看起来非常象数字 1
: 使用小写 l 表示 long 常量. 例如, 10l 看起来更象错误的 101, 而不是 10L. 摒弃任何可以明确区别以下字母的字体: uvw wW gq9 2z 5s il17|!j oO08 `'" ;: ,. m nn rn {[()]} 保持创新.
22.
把全局名字重新用作为私有名字
: 在模块 A 中定义一个全局数组, 并且在模块 B 的头文件中定义一个具有相同名字的私有数组. 这样看起来你在模块 B 中使用的是全局的数组, 而实际上不是. 在注释中不要涉及到这样的重复.
23.
循环再用
: 通过以矛盾的方法循环重用变量来尽可能地混淆作用范围. 例如, 假设你有全局变量 A 和 B, 以及函数 foo 和 bar. 如果你知道 A 经常传递给 foo, 而 B 经常传递给 bar, 那么保证你把 foo 定义成 foo(B), bar 定义成 bar(A), 这样在函数中 A 经常是 B 的引用, 反过来也一样. 有了更多的函数和全局变量, 你就能创造出非常混乱的一个网络, 充满了相同名字的相关但又矛盾的用法.
24.
再循环使用你的变量
: 在作用范围允许的规则下, 重用已有的没有关联的变量. 类似的, 把同一个临时变量用于两个不相关的目标(减少栈的空间). 一种更"恶毒"的变体, 搞乱你的变量. 比如, 在一个非常长的方法的开头给一个变量赋值, 在中间再赋值一次, 巧妙地改变变量的意思, 象把从 0 开始的下标改为从 1 开始的下标. 保证不要对这个改变给出解释.
25.
Cd wrttn wtht vwls s mch trsr
: 在变量或者方法名中使用缩写时, 为了不至于厌烦可以使用同一个单词的几个变体. 甚至不时地写出普通写法. 这就有效地打击了那些懒惰的程序员, 他们只使用简单的搜索工具就想看懂你程序的一部分. 把变化看作是取乐花招的变换, 例如, 把美国拼写 color 和国际拼法 colour 以及 dude-speak kulerz 混合起来. 如果你把它们完全拼出来, 那么每个名字只有一种拼写, 对于维护者来说太容易记住了. 因为一个词的缩写可以有很多种, 所以使用缩写, 你可以使用各种不同的变量达到同一个显而易见的目的. 额外的好处就是, 维护者不会注意到它们是不同的变量.
26.
误导的名字
: 保证一个方法比它的名字所表明的事要多做或者少做一些. 一个简单的例子, 一个叫做 isValid(x) 的方法有其他的副作用, 把 x 转化为二进制并存入数据库.
27.
m_
: 来自 C++ 世界的一个命名惯例是在类成员之前加 "m_". 这本来是用来帮助你把它们和方法区分开的, 只要你忘了方法的开头字母也是 "m".
28.
o_apple obj_apple
: 在每个类实例前面用 "o" 或 "obj" 的前缀表明你正在考虑一个很大的, 多态的结构.
29.
匈牙利命名法
: 匈牙利命名法是代码混淆技术中的核武器, 一定要用它; 由于被这个命名法所污染的代码量, 没有其他方法能比精心设计的匈牙利命名法攻击更快地杀死一个维护工程师. 下面的一些技巧能帮助你破坏匈牙利命名法原来的意图:
* 坚持使用在 C++ 或其他语言中代表常数的 "c" 能直接增强变量的常数性.
* 找出并使用与当前语言不同的另外一种语言中和匈牙利命名法不同的前缀. 例如, 在编写 C++ 代码时对每一种控制类型都坚持使用 PowerBuilder 中的 "l_" 和 "a_" {local 和 argument} 的作用范围前缀, 并总是使用带有烂匈牙利命名法的 VB 式风格. 假装不知道那些明显可见的 MFC 代码中, 控制类型并没有使用匈牙利命名法的事实.
* 经常违背匈牙利命名法的这个原则: 让常用的变量携带最少的额外信息. 通过以上提出的技巧以及坚持在每个类类型前加上自定义的前缀就可以达到这个目的. 绝不要让人提醒你没有前缀就表示它是一个类. 这个方法的重要性也不能太过夸张: 如果你不能坚持遵循原则, 源代码可能会充斥了许多短小的变量名并有一个较高的元音/辅音比. 在最坏的情况下, 这能导致混淆策略的失败并再现了一种自然的英语记法!
* 绝对要破坏的匈牙利式记法的一个原则: 函数参数和常见符号必须用一个有意义的名字, 而匈牙利记法前缀本身就是极好的临时变量名.
* 坚持在你的匈牙利前缀前加上许多无用的信息. 考虑一下这个真实的例子: "a_crszkvc30LastNameCol", 这个词耗费了一个维护工程师小组大约 3 天的时间才得出这个词的含义, 原来这个长变量表示的是一个常数, 引用和函数参数, 保存了数据库中一个叫做 "LastName", 类型为 Varchar[30] 的字段, 并且这个字段还是这个表的主键. 当适当地和"所有的变量都应该是公有的"原则结合起来时, 这个技巧就能够使数千行的代码立即作废!
* 利用 - 人的大脑只能同时处理 7 项信息 - 这个原则的优势. 例如, 用以上标准写的代码有以下特性:
o 一个赋值语句就带有 14 条类型和名字信息.
o 一个带有三个参数, 返回结果中带有 29 项类型和名字信息的函数.
o 寻找一种方法来增强这种极好但又极其简洁的标准. 向管理部门和同事施加影响, 建议使用一种能表示星期几的前缀来隔离代码, 如 Monam和 FriPM.
o 用一个中等复杂的嵌套结构就能打败人的短期记忆, 尤其是当维护程序员不能在屏幕上同时看见每个块的开始和结束的时候.
30.
再看匈牙利命名法
: 匈牙利命名法下一个技巧就是"改变一个变量的类型, 但是不改变它的名字". 这在从 Win16 :- WndProc(HWND hW, WORD wMsg, WORD wParam, LONG lParam) 到 Win32 WndProc(HWND hW, UINT wMsg, WPARAM wParam, LPARAM lParam) 的移植中常常会用到, 其中 w 表示它们是字 (word), 而实际上它们常常表示的是长整 (long). 当移植到 Win64 的时候这个方法的真正价值才体现出来, 那时候参数都是 64 位长, 但是老的 "w" 和 "l" 前缀却永远保留下来.
31.
分解, 重用, 再循环
: 如果你需要定义一个结构来保存回调数据的话, 总把它叫做 PRIVDATA. 每个模块都可以定义自己的 PRIVDATA. 在 VC++ 中, 能具有混淆调试器的优势, 这样当你有一个 PRIVDATA 结构, 在 watch 窗口中想要展开时, 它不知道你需要的是哪个 PRIVDATA, 所以它就随便选择一个.
32.
模糊的电影参考
: 利用常数变量例如 LancelotsFavouriteColour 来代替 blue, 并赋值为十六进制 $0204FB. 这个颜色在屏幕上看起来是纯蓝的, 一个维护程序员必须算算或者使用图形工具来看看这个颜色看起来到底是什么样的. 只有非常熟悉 Monty Python 和电影 the Holy Grail, 才知道 Lancelot 最喜欢的颜色是蓝色. 如果一个维护程序员不能想到所有 Monty Python 的电影, 他就没有资格作为一个程序员.
文档
Any fool can tell the truth, but it requires a man of some sense to know how to lie well.
任何傻瓜都能讲真话, 但一个人需要一些能力才知道如何更好地说谎.
Samuel Butler (1835 - 1902)
Incorrect documentation is often worse than no documentation.
不正确的文档常常比没有文档更糟糕.
Bertrand Meyer
既然计算机忽略注释和文档, 那么你就可以肆无忌惮地撒谎, 做一切在你权力范围能做的事来迷惑可怜的维护程序员.
1.
在注释中撒谎
: 你不必要刻意地去撒谎, 只是没有让注释随着代码而更新.
2.
给明显的事实写文档
: 在代码中到处加上诸如 /* add 1 to i */ 等的注释, 但是永远不要写下象包或者方法的整体目的这样有用的文档.
3.
只写出怎么做的文档, 而不是为什么.
: 只写出一个程序到底在做什么的细节, 而不是它要完成什么. 这样, 如果有 zbug 的话, 修改者就不知道代码到底应该做什么.
4.
避免给"明显事实"写文档
: 例如, 如果你正在写一个航班预订系统, 要保证如果你要加一条航班的话, 至少有 25 个地方需要修改, 但绝对不要写出哪里需要修改. 你之后的程序员只有在完全理解你程序中的每一行之后, 才敢修改你的代码.
5.
正确使用文档模版
: 考虑一下使用能自动产生代码文档的函数文档原型. 这些原型能够从一个函数(或者方法, 或者类)拷贝到另外一个, 但是永远不要填写. 如果由于某些原因你必须填写, 那么一定要保证这些函数所有参数的命名都是一样的, 所有的警告也都是一样的. 当然这些都与当前的函数完全无关.
6.
正确使用设计文档
: 当实现一个非常复杂的算法的时候, 在编码之前使用传统的软件工程原则做一个合理的设计. 写一个极其详细的设计文档, 描述了这个复杂算法中每一步的细节. 文档写得越详细就越好.
事实上, 设计文档应该把算法分成结构化步骤的层次, 然后在文档中以自动编号的单独的段来描述. 标题至少要 5 层. 当你完成的时候, 要确定你把整个结构分解得非常彻底, 有超过 500 个这样自动编号的段落. 例如, 其中一段可能是: (这是一个真实的例子)
1.2.4.6.3.13 - 显示出所有活动的冲击, 能够对其应用所选择的缓冲. (省略一些伪码)
然后, (这就是害群之马) 当你编写代码的时候, 给每一个这样的段落写一个相对应的全局函数, 叫做:
Act1_2_4_6_3_13() 不要给这些函数写文档, 要知道, 这是设计文档要做的事!
既然设计文档是自动编号的, 要使它跟上代码的更新是极端困难的. (因为这些函数的名字当然是静态的, 而不是自动编号的). 这对你来说不是个问题, 因为你不需要保持文档一直是最新的. 实际上, 要做你能做的一切来销毁文档的痕迹.
你之后的那些人只能找出一个或两个矛盾的地方, 但那些早期设计文档的草稿早就扔在那些破烂 286 机器后面房间里的灰尘中了.
7.
度量单位
: 永远不要给出任何变量, 输入, 输出, 或者参数的度量单位, 例如英尺, 公尺, 盒等. 这虽然在计算的时候没什么用, 但在工程上却非常重要. 可以推论出, 永远不要给那些为转换而使用的常量的度量单位作注释, 或者是它们的值是怎么来的. 虽然这只是小小地欺骗了一下, 但对于混淆注释中错误的度量单位却非常有效. 如果你想要更恶毒的话, 可以创造你自己的度量单位, 以自己或者某个不著名的人来命名, 但却不详细说明它. 如果有人来质问你, 你就说这样就能使用整数而不是浮点数来进行算术运算了.
8.
陷阱
: 不要给代码中的陷阱写出文档. 如果你觉得一个类的代码可能有 bug, 自己知道就可以了. 如果你已经对如何重新组织或者重写代码有了想法, 为了天堂(for heaven'ssake), 不要把它们写下来. 记住电影 Bambi 中 Thumper 的话"如果你不能把事情说好, 那就干脆不要说". 如果写代码的人看到你的注释会怎么样? 公司的老板看到它们会怎么样? 客户呢? 你可能会让自己被炒鱿鱼. 一个匿名的注释, 写着"这里需要修改", 能够让奇迹出现, 特别是当不知道注释指什么的时候. 保持含糊, 没有人会感到自己受了批评.
9.
给变量写文档
: 绝不要给变量声明写注释. 变量如何使用, 它的范围, 它的合法值, 它隐含或者显示出来的小数位数, 它的度量单位, 它的显示格式, 它的数据输入规则(全部填写, 必须输入等) 什么时候它的值是可以信任的, 所有这些事实都应该在程序的代码中得到. 如果你的老板强迫你写注释, 那么就在方法体中间夹杂一些, 但不要在声明的地方, 甚至是一个临时变量也不要.
10.
在注释中进行贬损
: 要阻碍任何使用外部维护的包, 可以在你的代码中夹杂一些诋毁处于领导地位的软件公司的句子, 特别是有可能签约做这项工作的公司. 例如:
/* 经过优化的内部循环.
这些代码比那些 Software Services Inc. 公司的笨蛋们聪明多了, 它们使用 <math.h>
中的烂函数, 可能要使用 50 倍的内存 & 时间.
*/
class clever_SSInc
{
.. .
} 如果可能, 把这些诋毁的东西放在代码中在语法上比较重要的地方, 以及注释中, 这样在进行管理的时候, 如果他们要在把它外包出去进行维护之前消除这些, 就可能会破坏代码.
11.
象在打孔卡片上的 CØBØL 一样作注释
: 始终要拒绝接受开发环境的进步, 尤其是 SCID. 不要相信谣言: 所有的函数和变量声明都绝不会超过一次点击. 始终要假设在 Visual Studio 6.0 中开发的代码可能由某个使用 edlin 或者 vi 的人来维护. 坚持苛刻的注释规则, 要把代码完全淹没.
12.
Monty Python 般的注释
: 给一个叫做 makeSnafucated 的方法加上 Java 文档 /* make snafucated */. 决不在任何地方定义 snafucated 的意思. 只有傻瓜才不知道自己已经完全知道了 snafucsated 的意思是什么. 要看这个技巧的经典例子, 请参阅 Sun AWT Java Doc.
程序设计
The cardinal rule of writing unmaintainable code is to specify each fact in as many places as possible and in as many ways as possible.
编写不可维护代码的主要原则就是: 在尽可能多的地方, 以尽可能多的方式来指明每一个事实.
Roedy
1. 编写可维护代码的关键就是只在一个地方说明应用程序的每个事实. 要改变想法, 只需要在一个地方修改, 就能保证整个应用程序还能工作. 因此, 编写不可维护代码的关键就是: 在尽可能多的地方, 以尽可能多的方式一遍又一遍地指明同一个事实. 令人高兴的是, 象 Java 这样的语言, 不辞辛苦地让写这样的不可维护代码如此容易. 例如, 要改变一个广泛使用的变量的类型几乎是不可能的, 因为所有的造型和转换都不能运行了. 与这些类型相联系的临时变量也都不对了. 更进一步, 如果这个变量要显示在屏幕上, 那么就必须追踪所有的显示和数据输入代码, 并且手动修改. Algol 类的语言, 包括 C 和Java, 认为把数据存储在一个数组, 散列表, 纯文本和数据库中是完全不同的语法. 而在象 Abundance 或者Smalltalk 语言的一些扩展中, 语法是一样的; 只是声明变了. 要利用 Java 的这个愚蠢的地方. 把你知道可能增长从而占用很多内存的数据暂时放入一个数组. 这样, 维护程序员稍后就需要惊人的工作量来把数组转换成文件访问. 同样可以把小文件放入数据库, 当需要进行性能调整的时候, 维护程序员就会享受把它们转换成数组访问的乐趣了.
2.
Java 造型(Cast)
: Java 的造型机制是与生俱来的, 你可以放心使用而不必愧疚, 因为这个语言本来就需要它. 每次你从一个 Collection 中取得一个对象, 你必须把它造型成它原来的类型. 所以变量的类型可能在多个地方指定. 如果类型后来改变了, 那么所有的造型都必须修改来使之符合. 如果这个倒霉的维护程序员没有找到全部造型(或者改得太多了), 编译器可能检测出来也可能不能. 同样, 如果一个变量的类型从 short 变成 int, 所有相应的造型都需要从 (short) 改成 (int). 已经有一种趋势, 创造一个通用的造型操作符 (cast) 和一个通用的转换操作符 (convert), 从而当变量类型变化的时候, 就不需要额外的维护. 一定不要让这个异端进入语言规范中. 在 RFE 114691 和泛型投票中投反对票. 因为它们将会消除许多造型操作.
3.
利用 Java 的冗余
: Java 始终让你必须两次指定变量的类型. Java 程序员已经习惯了这样的冗余, 如果你让这两种类型稍微有点不一样, 他们就不会注意到, 就象这个例子:
// note subtle spelling change
Bubblegum b = new Bubblegom();
不幸的是, ++ 操作符的流行, 使得象下面这样的伪冗余代码很难轻易得手:
// note subtle spelling change
swimmer = swimner + 1;
4.
绝不确认
: 绝不检查输入数据的正确性和偏差. 它会表明你绝对相信公司的设备, 以及你是一个完美的小组成员, 相信项目中所有的合作者和系统管理员. 始终返回合理的值, 甚至当输入数据有疑问或者是错误的时候.
5.
要有礼貌, 决不断言
: 避免 assert() 机制, 因为它能把三天的调试工作变成 10 分钟的.
6.
避免封装
: 由于效率的重要性, 避免封装. 一个方法的调用者, 需要所有能得到的外部线索来提醒它们, 方法内部是如何工作的.
7.
克隆&修改
: 以效率的名义, 使用剪切/拷贝/克隆/修改, 比使用很多可重用的小模块更快. 当以你写的代码行数来评定你进度的时候特别有用.
8.
使用静态数组
: 如果一个库中的模块需要一个数组来保存一张图片, 就只定义一个静态数组. 没有人的图片会大于 512x512, 所以一个固定大小的数组就可以了. 要达到最高的精度, 定义一个 double 的数组. 把这个超过 2M 的静态数组隐藏起来的额外好处, 就是可以使程序即使没有调用你的方法的时候, 也能超过客户机器的内存, 象疯了一样颠簸.
9.
虚假接口
: 写一个空接口叫做 "WrittenByMe" 或者其他什么, 让你所有的类都实现它. 然后, 对你使用的所有 Java 自带的类写一个包装器. 这个想法是用来保证你程序中的每个对象都实现了这个接口. 最后, 编写方法, 使得它们所有的参数和返回值都是 WrittenByMe. 这就几乎不可能看出某个方法是干什么的, 也引进了许多有趣的造型要求. 为了以后的扩展性, 让每个小组成员都有一个他/她自己的私人接口(比如, WrittenByJoe). 一个程序员正在编写的类都要实现他/她的这个接口. 然后你就可以用这么多数量的没有任何意义的接口来任意引用对象了.
10.
庞大的监听器
: 决不为每个 Component 创建单独的监听器. 项目中的每个按钮总是使用同一个监听器. 只是简单地使用一大堆 if...else 来检测按下了哪个按钮.
11.
大量使用一个好东西TM
: 疯狂使用包装和 oo (面向对象), 例如:
myPanel.add( getMyButton() );
private JButton getMyButton()
{
return myButton;
}
一个可能看不出有趣, 别着急, 总有一天会的.
12.
友好的友元
: 在 C++ 中尽可能频繁地使用友元声明. 把创建类的指针指向创建出来的类, 把这两种方法结合起来. 那么你就不需要浪费你的时间考虑接口了. 另外你应该使用 private 和 protected 关键字来证明你的类是经过很好包装的.
13.
使用三维数组
: 多多益善. 以旋转的方式在数组之间拷贝数据. 也就是说, 用 ArrayA 的行填充 ArrayB 的列. 拷贝的时候偏移为 1, 而不要给出明显的理由, 就很不错. 就是要让维护程序员着急.
14.
混合与竞争
: 同时使用访问方法(accessor method, 指 getter 和 setter)和公共变量(public vairable). 这样, 你就能改变一个对象的变量而省去了调用访问方法的开支, 但仍把这个类声明为一个"Java Bean". 还有一个额外的好处, 就是当维护程序员添加日志函数想找出谁正在修改值的时候, 能够阻挠他.
15.
包装, 包装, 包装
: 不论什么时候, 你不得不使用不属于你写的代码中的方法时, 要使用至少一层的包装把你的代码和那些烂代码隔离开来. 毕竟, 其他的作者可能在将来某个时候不顾所有的后果, 把每个方法都重新命名. 那时候你将会怎样? 当然, 如果他做了这样的事, 你可以用一层包装来把你的代码和这些修改隔离起来, 或者让 VAJ 来处理全局重命名. 无论无何, 这都是一个很完美的借口, 在他做这些蠢事之前, 用一个间接的包装层预先拦住他. Java 的一个失误就是: 如果不使用一些很简单的虚拟包装方法, 它们什么都不做, 只是调用具有相同或者非常相近的名字的另外一个方法, 就不可能解决一些简单问题. 这就意味着可能写出四层深的包装而没有做任何事, 并且几乎没有人注意到. 要隐藏地更深, 可以在每一层对方法进行重命名, 从字典中随机选一个同义词. 这就造成一些现象说明说做了一些事情. 进一步, 重命名对保证项目中术语的一致性有帮助. 要保证没有人把你的层数削减到一个合理的数字, 要在每一层中调一些你自己的代码来传递包装器.
16.
包装, 包装, 包装, 更多的包装
: 保证所有的 API 函数都经过 6-8 次的包装, 定义在不同的源文件中. 使用 #define 创建这些函数的快捷方式也行.
17.
没有秘密!
: 把每个方法和变量都声明为 public. 毕竟, 某些人某些时候可能想要使用. 一旦一个方法被声明为 public 以后, 就不能很好地撤回了, 不是吗? 这就使藏在表面下的一些工作以后很难改变. 也有一个令人欣喜的好处是可以隐藏一个类到底要做什么. 如果你的老板问你是不是疯了, 就告诉他你是在遵循透明接口的经典原则.
18.
Kama Sutra
: 这个技术增加的好处就是能让一个包的使用者或者文档编写者以及维护者精神错乱. 创建出同一方法的许多不同的重载变体, 仅仅在最细小的地方有所不同. 我认为是 Oscar Wilde 观察到在 Kama Sutra 中体位 47 和 115 是一模一样的, 除了在 115 中那个妇女的手指是交叉的. 这个包的使用者不得不仔细阅读一长串方法来找出要使用哪个变体. 这个技术也让文档膨胀, 这样就保证文档更可能过时. 如果老板问你为什么这么做, 解释说这仅仅是为了用户的方便. 再一次, 为了能达到最好的效果, 克隆那些公共的逻辑, 然后坐下等着看它们慢慢不再同步.
19.
变换, 为难
: 把一个叫做 drawRectangle(height, width) 的方法的参数顺序反过来, 变成 drawRectangle(width, height), 而对方法的名字没有任何改动. 接着在随后的一些发布中, 再改回来. 维护程序员仅仅靠很快地看一眼方法调用, 是不能判断出这些方法是否进行了调整的. 一般方法作为一个练习留给读者.
20.
主题和变体
: 不要在一个单独方法中使用参数, 而是你能创建多少分离的方法就创建多少. 例如 setAlignment(int alignment) 中, alignment 是一个枚举常量, 要为左中右创建三个方法, setLeftAlignment, setRightAlignment 以及 setCenterAlignment. 当然, 要达到最好的效果, 你必须要克隆公共的逻辑, 就使它们很难保持同步.
21.
静态的很不错
: 让你尽可能多的变量成为静态的. 如果你在程序中不需要这个类多于一个的实例, 那么其他的人也不会需要. 再一次, 如果项目中的其他编码人员抱怨的时候, 告诉他们你能得到的运行速度上的提高.
22.
Cargill 的窘境
: 利用 Cargill 的窘境(我认为是他的):"除了太多的迂回层次以外, 其他的设计问题都可以用增加另外一层迂回来解决." 直到几乎不可能找到能真正更新程序状态的方法的时候, 才要剖析 OO 程序. 更好一些, 把这些事情可以作为回调来激活, 可以通过遍历方法指针森林来做到, 已经知道这些方法指针森林包含了整个系统所用到的每个方法指针. 也可以把森林遍历作为副作用激活, 当通过深度拷贝来释放先前创建的引用计数对象, 而这些深度拷贝并不真的很深.
23.
Packratting
: 在你的代码中到处保留不用的或者是过时的方法. 毕竟, 如果你在 1976 年用过一次, 谁知道你以后是否会再用到呢? 当然程序从那时起就已经修改了, 但是也有可能很容易地改回去. 你"不想重新发明整个轮子"吧 (管理喜欢象这样说话). 如果你不动这些方法和变量的注释并且含义模糊, 任何维护这些代码的人都不敢碰它们.
24.
就是 Final
: 让你所有的叶子类都为 final. 毕竟, 你已经做完了这个项目 -- 当然, 没有其他人能通过扩展你的这些类来改善你的工作. 它可能甚至是安全上的瑕疵 -- 要知道, java.lang.String 难道不是仅仅为了这个原因而声明为 final 的吗? 如果项目中其他的编码人员向你抱怨, 告诉他们你所能获得的速度上的提高.
25.
避免接口
: 在用 Java 的时候, 鄙弃接口. 如果你的负责人抱怨的话, 就告诉他们, Java 的接口强迫你在用相同方法实现相同接口的不同类之间"剪切"代码, 他们就知道这将多么难维护. 相反, 象 Java AWT 的设计者那样做 -- 在你的类中放许许多多的功能, 只有继承他们的子类才能使用, 用许许多多的 "instanceof" 判断. 这样, 这样, 如果有人想要重用你的代码, 他不得不扩展你的类. 如果他们想重用来自两个类中的代码 -- 运气真坏呀, 他们不能同时扩展这两个类! 如果一个接口不可避免, 就创建一个万能的接口, 把它叫做 "ImplementableIface". 来自学术环境的另外一个精英, 会在实现这个接口的类的名字后面添上 "Impl". 这能带来很大的好处, e.g. 那些实现了 Runnable 接口的类.
26.
避免 Layout
: 绝不要使用 layout. 这样当维护程序员再增加一个域的时候, 他必须手工调整屏幕上显示出来的所有其他东西的绝对坐标. 如果你的老板强迫你使用一个 layout, 就只用一个巨大的 GridBagLayout, 把网格的绝对坐标进行硬编码.
27.
环境变量
: 如果你不得不写一些给其他程序员使用的类, 把环境检查(C++ 中是 getenv() / Java 中是 System.getProperty())的代码放到你的类里的无名静态初始化器中. 通过这种方式来把参数传递到你的类中, 而不是在构造函数中. 好处就是类的二进制程序一装载, 初始化方法马上就会调用, 甚至在任何这个类在进行实例化的时候, 所以它们通常会在 main() 程序之前运行. 换句话说, 其他的程序就不能在你的程序把这些参数读入之后修改它们 - 用户最好象你一样先设置好这些环境变量.
28.
表驱动的逻辑
: 避免任何形式的表驱动逻辑. 它本身就足够透明, 很快就会导致最终用户进行校对, 并且进行抖动, 甚至为他们自己修改这些表格.
29.
修改"母亲"的域值
: 在 Java 中, 所有作为参数传递的原始类型, 实际上都是只读的, 因为它们是传值的, 被调用者可以修改这些参数, 但是对于调用者的变量没有作用. 相反, 所有的对象都是可读写的. 引用是传值的, 就意味着对象本身是传引用的. 被调用者可以在你的对象中对域进行任何想做的事情. 绝对不要写出一个方法是否对传递过来的参数进行了修改. 让你的方法名暗示出只是查看域, 而实际上进行了修改.
30.
全局变量的魔力
: 让你的错误消息过程设置一个全局变量, 代替异常来进行错误处理. 然后确信系统中每个长时间运行的循环, 都检查这个全局标志, 如果出现错误就退出. 增加另外一个全局变量, 当用户按下"重置"按钮时候发信号. 当然系统中所有主要的循环都要检查这第二个标志. 隐藏那些不能按照要求退出的循环.
31.
全局变量, 我们怎么强调也不够
: 如果上帝不让我们使用全局变量, 他就不会发明出来. 与其让上帝失望, 不如让我们使用和设置尽可能多的全局变量. 每个函数至少使用和设置两个, 甚至没有理由这么做. 以后, 任何好的维护程序员很快就会发现, 这实在是一个侦探工作的练习. 她(God)会对这个练习很满意, 因为它把真正的维护程序员和业余的区分开了.
32.
全部变量, 再来一次, 孩子们
: 全局变量把你从必须指定函数的参数中解救出来. 要好好利用. 从这些全局变量中选出一个或者多个, 来指明对其他变量要做什么样的处理. 维护程序员会傻傻地假设 C 函数不会有副作用. 保证它们把结果以及内部状态信息存储在全局变量中.
33.
副作用
: 在 C 中, 函数应该是(没有副作用), 我希望这个提示已经足够了.
34.
回退
: 在循环体内, 假设循环动作是成功的, 并且理解更新所有指针变量. 如果后来在循环体内检测到异常, 把指针的更新进行回退, 作为对紧跟循环体的一个条件表达式的副作用.
35.
局部变量
: 绝不要使用局部变量. 无论何时你受到诱惑的时候, 要把它放入一个实例或者静态变量中, 并且无私地和其他类中的方法共享. 当你在以后其他方法中需要类似声明的时候, 这会省下你的工作. C++ 程序员可以通过全部声明为全局变量, 从而更近一步.
36.
缩减, 重用, 回收
: 如果你不得不为回调函数定义一个结构来保存数据, 始终把这个结构叫做 PRIVDATA. 每个模块都定义自己的 PRIVDATA. 在 VC++ 中有很大的好处, 能混淆调试器. 这样, 如果你有一个 PRIVDATA 变量, 需要在变量查看窗口中扩展开来, 它不知道你到底用的是哪个, 所以它就随便选一个.
37.
配置文件
: 这些配置文件通常是 keyword=value 的形式. 值在装载的时候赋值给 Java 变量. 最明显的迷惑技巧就是对于 keyword 和 Java 变量使用稍微有点不同的名字. 对于那些在运行时永远不变的常量也使用配置文件. 参数文件变量需要的代码量至少是单个简单变量的 5 倍.
38.
臃肿的类
: 要保证你的类限制在尽可能迟钝的范围内, 务必在每个类里面包含一些次要的, 不起眼的方法和属性. 举个例子吧, 一个定义了天体物理几何学的类中, 的确需要计算潮汐时间的方法以及构成 Crane 天气模型的一些属性. 这不仅使类定义过多, 并且使在一般的系统代码中寻找这些方法, 就象在垃圾中找一根吉他弦一样难.
39.
纵情于子类
: 面向对象编程本来就是用来编写不可维护代码的. 如果你有一个包含 10 个属性的类(变量/方法), 考虑一下带有一个属性的基类, 对它进行 9 层深的继承, 这样每个后代都只带有一个属性, 当你到达最后的子类的时候, 你就得到了所有 10 个属性. 如果可能的话, 把每个类声明放到不同的文件中. 这就让你的 INCLUDE 或者 USES 语句变得膨胀, 并且迫使维护程序员在他或者她的编辑器中打开更多的文件. 你要保证每个类你都至少创建了一个实例.
代码迷惑
Sedulously eschew obfuscatory hyperverbosity and prolixity.
1.
令人迷惑的 C
追随互联网上令人迷惑的 C 代码竞赛, 并且遵循获奖者的步伐.
2.
找一个 Forth 或者 APL 的高手
在那些世界里, 你的代码写得越简洁, 工作的方式越诡异, 你就能获得越多的尊敬.
3.
我要用一打
当你可以很轻易地使用两三个变量的时候, 那就决不要使用一个大而全的变量.
4.
越晦涩越好
始终寻找最模糊的方法来完成通常的任务. 比如, 不使用数组把一个整数转换成相应的字符串, 而使用类似下面的代码:
char *p;
switch (n)
{
case 1:
p = "one";
if (0)
case 2:
p = "two";
if (0)
case 3:
p = "three";
printf("%s", p);
break;
}
5.
愚蠢的一致性就是没脑子的怪物
当你需要一个字符常数的时候, 使用多种格式: ' ', 32, 0x20, 040. 利用 10 和 010 在 C 和 Java 中不是同一个数字这个事实, 尽情地自由使用.
6.
转型
把所有的参数都以 void * 来传递, 然后类型转换为适当的结构. 使用字节偏移而不是结构转型也很有意思.
7.
嵌套的 Switch
(switch 中的 switch) 对于人脑来说, 是最难的一种嵌套.
8.
利用隐式转换
记住编程语言中所有巧妙的隐式转换规则. 充分利用它们的优势. 决不使用 picture(COBOL 或者 PL/I 中)变量, 或者常用的转换函数(象 C 中的 sprintf). 确信使用浮点数作为数组的下标, 使用字符作为循环计数器, 以及对数字使用字符串函数. 毕竟所有这些操作都是定义明确的, 并且对代码的简洁性有贡献. 任何想要理解你代码的维护者都会对你非常感激, 因为他们不得不阅读和学习有关隐式数据类型转换的整章内容; 这一章是他们在对你的程序修改前必须全部掌握的一章.
9.
原始的整数
当使用 ComboBox 时, 对于可能的值来, 与其使用命名常数, 倒不如使用整数 case 的 switch 语句.
10.
分号!
始终在任何语法允许的情况下使用分号. 例如:
if ( a );
else;
{
int d;
d = c;
}
11.
使用八进制
象下面这样把八进制数藏在十进制数的列表里:
array = new int[]
{
111,
120,
013,
121,
};
12.
间接转换
无论你什么时候需要转换, Java 都提供了迷惑的很多机会. 举一个简单的例子, 如果你要把一个 double 转换成 String, 那么迂回地来做, 通过 Double 的 new Double(d).toString(), 而不用另外直接的方法 Double.toString(d). 当然, 你也可以比这迂回地更多! 避免任何在 Conversion Amanuensis 中推荐的转换技术. 在你转换之后, 每个扔进杂乱的堆中的临时对象都让你得到一些奖励的分数.
13.
嵌套
尽你所能地嵌套. 好的编码人员可以在一行内使用超过 10 层的 ( ), 一个方法内超过 20 个 { }. C++ 的编码人员利用预处理器的强大选项, 可以完全独立于下面代码的嵌套结构而进行嵌套. 不论何时, 一个块的开始和结束在打印列表中出现在不同页中, 你都可以得到额外的 Brownie 分数. 在任何可能的地方, 把嵌套的 if 语句转换成嵌套的 [? : ] 三元组. 如果它们跨越多个行, 那么越多越好.
14.
数值字面量
如果你有一个包含 100 个元素的数组, 在程序中尽可能多的地方对字面量 100 进行硬编码. 决不要使用一个 static final 命名常数来代替这个 100. 或者用 myArray.length 来提及. 为了让改变这个常数更困难, 用字面量 50 代替 100/2, 或者 99 代替 100 - 1. 还可以进一步伪装这个 100, 用 a == 101 代替 a > 100, 或者 a > 99 代替 a >= 100.
考虑一下其他的事情, 象页面的大小, 包含 x 行 header, y 行 body, z 行 footer, 你可以分别对这些进行迷惑处理并且对部分或者全部都可以.
这些历史悠久的技术, 对于那些包含两个不相关的数组恰好都有 100 个元素的程序特别有用. 如果维护程序员需要修改其中一个的长度时, 他不得不对程序中 100 的每一个用法进行解密, 来确定它用在了哪个数组上. 几乎可以确定他至少会犯一个错, 如果有希望的话, 这个错可能几年内都不会显现出来.
还有更多恶魔似的变体. 为了把维护程序员哄骗进对于安全性的一种虚假的感觉中去, 要负责任地创建一个命名常数, 但是非常"偶然地"恰好时候用了 100 这个字面量, 而不是这个命名常数. 其中最邪恶的是, 在字面量 100 或者正确的命名常数的位置, 偶尔使用了其他无关的命名常数, 恰巧它的值是 100. 那种把数组名字和它的大小常量联系起来的一致性命名规则, 如果没有说明必须避免使用的话, 那就是行得通的.
15.
C 对于数组古怪的看法
C 编译器把 myArray[i] 转换成 *(myArray + i), 相当于 *(i + myArray), 也相当于 i[myArray]. 只有内行才知道怎么好好使用. 要真正地进行伪装, 用一个函数来产生下标:
int myfunc(int q, int p) { return p%q; }
...
myfunc(6291, 8)[Array];
可惜的是, 这些技术只能用在本地 C 写的类中, 而不能用于 Java.
16.
很 长 的 代 码 行
试着把尽可能多的东西塞到一行中去. 这就节省了一些临时变量的开支, 也通过去除新行和空白来使源码文件小一点. 技巧: 把操作符旁边上的空白全都删去. 好的程序员总是可以在某种编辑器的帮助下打破行长为 255 个字符的限制. 使用很长的代码行一个额外的好处就是, 让那些不能在 6 point 字体大小下阅读的程序员, 不得不滚动才能查看源代码.
17.
异常
我要让大家看一个很少人知道的编码秘密. 异常是内部的痛苦. 写得好的代码从不出错, 所以异常实际上根本不必要. 不要在它们身上浪费时间. 子类异常是给那些无能者准备的, 他们知道他们的代码会出错. 你可以在整个应用程序(main 中)只使用一个 try/catch, 并且调用 System.exit(), 这样就能使你的程序极大地简化. 坚持把一个完美的异常集合放在每一个方法的头部, 不管它们是否真的抛出任何异常.
18.
什么时候使用异常
在非异常情况下使用异常. 通常用一个 ArrayIndexOutOfBoundsException 结束一个循环. 从一个异常中返回一个方法的标准结果.
19.
滥用线程
标题已经说明了一切.
20.
律师一样的代码
对于各种各样的编码技巧应该怎么做, 遵循律师们在新闻组中讨论时所用的语言. 比如, a=a++; 或者 f(a++,a++);, 然后把这些例子散布在你的代码中. C 中, 象下面例子中使用的前/后减的效果, 并没有在语言规范中定义:
*++b ? (*++b + *(b-1)) : 0 每种编译器可以自由地以不同的顺序来计算. 这就让它们加倍致命. 同样地, 可以把所有的空白去掉, 好好地利用 C 和 Java 中复杂的词法规则.
21.
提前返回
坚决遵循以下准则, 不用 goto, 不提前返回, 不用标号中断, 特别是当你使用的 if/else 的嵌套层次最少 5 层的时候.
22.
避免 {}
绝对不要在 if/else 块周围使用 { }, 除非句法上是必须的. 如果你把 if/else 语句和块很多层地嵌套混合在一起, 特别是当使用了令人迷惑的缩进后, 甚至就能使一个高手级别的维护程序员犯错误. 为了达到这个技术的最好效果, 就用 Perl 吧. 你可以在语句之后弄上这些代码, 并且加上格外的一些 if, 就可以得到让人惊奇的效果.
23.
来自地狱的 Tab
千万不要低估用 Tab 代替空格进行缩进所能造成的大破坏. 尤其是在对于一个 tab 代表多少缩进没有一个小组标准的情况下. 在 String 常量中嵌入 tab, 或者使用工具来帮你把空格转换成 tab.
24.
有魔力的矩阵位置
在矩阵的一定位置使用特殊的值作为标志. 一个好选择就是: 一个变换矩阵的 [3][0] 元素用的是齐次坐标系统.
25.
再看有魔力的数组
如果你需要给定类型的几个变量, 就直接定义一个数组, 然后用数字来访问它们. 选择一种编号的惯例, 只有你自己知道, 并且不要给它写文档. 也不要操心给这些下标定义 #define 的常数了. 每个人应该只知道全局变量 widget[15] 是取消按钮就行了. 这只是在汇编语言中使用绝对数字地址的一种新式变体.
26.
决不美化
决不要使用自动化的源码整理(美化)工具来整理自己的代码. 游说你的公司彻底抛弃这种工具, 因为它们在 PCVS/CVS(版本控制跟踪)中引起了虚假的差异信息, 或者游说每个程序员, 都应该在所有自己编写的模块中, 使用始终保持的一种缩进风格. 坚持让其他程序员在"他的"模块中遵循这个特殊的约定. 禁止使用美化器是相当简单的, 即使它能减少上百万次用于代码整理的击键和浪费在误解乱排列代码的那些时间. 坚持让每个人都使用同样的整理过的格式, 不仅仅是为了在通用仓库中保存, 也需要在编辑的时候使用. 可能这样就引起了一个 RWAR(Religious War), 而你的老板, 为了保持和睦, 就会禁止使用自动化的整理. 没有了自动化的整理, 现在你就可以自由地恰好没有把代码排列好, 就对循环或者 if 的语句产生一个错觉, 它们比实际上要短或者长一点, 或者 else 子句错配到另外一个 if 而不是那个正确的, 等等类似的情况. 例如:
// note misleading alignment
if ( a )
if ( b ) x=y;
else x=z;
27.
宏预处理器
它给迷惑提供了很多好机会. 关键技术就是把宏扩展到好几层深, 这样就你就不得不查看许多不同的 *.hpp 文件来寻找不同的部分. 把可执行代码放进宏中, 然后在每个 *.cpp 文件中包含这些宏(甚至是那些没有用到的宏), 这样一旦代码有变化的时候, 就把需要重新编译的文件数量最大化.
28.
利用精神分裂
Java 对于数组的声明是精神分裂的. 你可以用老式的 C 方法来做, String x[], (使用混合的前后标记), 或者新方法, String[] x, 只使用前缀标记. 如果你真的想让人迷惑, 就把这些标记混合起来吧, 例如:
byte[] rowvector, colvector, matrix[];
相当于:
byte[] rowvector;
byte[] colvector;
byte[][] matrix;
29.
隐藏错误恢复代码
使用嵌套把一个函数调用的错误恢复代码和调用代码隔得尽可能地远. 下面这个简单例子可以复杂到有 10 或者 12 层的嵌套:
if ( function_A() == OK )
{
if ( function_B() == OK )
{
/* Normal completion stuff */
}
else
{
/* some error recovery for Function_B */
}
}
else
{
/* some error recovery for Function_A */
}
30.
伪 C 代码
#define 的真正原因是帮助那些熟悉其他语言的程序员转到 C 来. 可能你会发现这样的声明, #define begin { " or " #define end } , 对写出有意思的代码很有帮助.
31.
把 import 混合起来
让维护程序员慢慢地猜你在使用的方法到底是哪个包里面的. 不用:
import com.mindprod.mypackage.Read;
import com.mindprod.mypackage.Write;
而使用:
import com.mindprod.mypackage.*;
不管多么隐晦, 都不要把类或者方法写完全. 让维护程序员去猜它属于哪个包或者哪个类. 当然, 你什么时候写全, 怎么样来 import, 导致的矛盾也能帮上很大忙.
32.
厕所管道
不管在任何情况下, 决不要让多于一个函数或者过程中的代码同时出现在屏幕上. 对于很短的函数, 可以使用下列简便的花招:
* 空行通常用于分离代码的逻辑块. 每一行本质上, 自然而然地是一个逻辑块. 在每行之间加上空行.
* 决不在行尾给自己的代码加注释. 把它放在上面一行. 如果你被迫要在行尾加注释, 那就在整个文件中挑出最长的一行, 加 10 个空格, 然后对所有的行尾注释都向左对齐到那一列.
* 过程顶部的注释应该使用至少 15 行的模板, 并且自由地使用空行. 下面是一个简便的例子:
/*
/* Procedure Name:
/*
/* Original procedure name:
/*
/* Author:
/*
/* Date of creation:
/*
/* Dates of modification:
/*
/* Modification authors:
/*
/* Original file name:
/*
/* Purpose:
/*
/* Intent:
/*
/* Designation:
/*
/* Classes used:
/*
/* Constants:
/*
/* Local variables:
/*
/* Parameters:
/*
/* Date of creation:
/*
/* Purpose:
*/
在文档中加入这么多的冗余信息, 几乎能够保证它很快就过时, 让那些足够笨的维护程序员相信它.
33.
包装细小的东西
创建一个完整的类或者方法来包装那些几乎不可能改变的细小部分, 但是需要复杂调用, 并且很小心地才能发现, 代码几乎什么也没做, 下面就是一个经典的例子:
view
人生的目的只有两件事:第一,得到你想要的;第二,得到之后就去享受它。
但是只有最聪明的人才能做到第二点。
我的话很重要,你们一定要反复理解,直到弄不明白为止!