XML
XML 的流行为代码伪装创造了丰富的机会。基本的技巧就是随机选一段代码,然后发明一种晦涩的方法,
将这段代码的逻辑用 XML 表示出来。然后用 XML 文件和 XML 解析器替换这段代码。
要保证选择的 XML 形式非常有限,除了原来的逻辑之外,其他任何事情都不能用它来表达。
没人会质疑 XML 的简单性。使用这个技巧,就能轻而易举地将十行简单的 Java 代码膨胀成一百行完美而不透明的 XML。
-
令人迷惑的 C
追随互联网上令人迷惑的 C 代码竞赛,并且遵循获奖者的步伐。
-
找一个 Forth 或者 APL 的高手
在那些世界里,代码写得越简洁,工作的方式越诡异,就能获得越多的尊敬。
-
我要一打
如果可以很轻易地使用两三个变量,那就决不要使用一个大而全的变量。
-
越晦涩越好
始终寻找最模糊的方法来完成通常的任务。比如,不使用数组把一个整数转换成相应的字符串,
而使用类似下面的代码:
char *p;
switch (n)
{
case 1:
p = "one";
if (0)
case 2:
p = "two";
if (0)
case 3:
p = "three";
printf("%s", p);
break;
}
-
愚蠢的一致性就是没脑子的怪物
需要一个字符常数的时候,使用多种格式:' '、32、0x20、040。利用 10 和 010 在 C 和 Java 中不是同一个数字这个事实,
尽情地自由使用。
-
造型
把所有的参数都以 void * 来传递,然后类型转换为适当的结构。使用字节偏移而不是结构转型也很有意思。
-
嵌套的 Switch
(switch 中的 switch)对于人脑来说,是最难解的一种嵌套。
-
利用隐式转换
记住编程语言中所有巧妙的隐式转换规则。充分利用它们的优势。决不使用 picture(COBOL 或者 PL/I 中)变量,
或者常用的转换函数(象 C 中的 sprintf)。确信使用浮点数作为数组的下标,使用字符作为循环计数器,
以及对数字使用字符串函数。毕竟所有这些操作都是定义明确的,并且只会对代码的简洁性有贡献。
任何想要理解你代码的维护者都会对你非常感激,因为他们不得不阅读和学习有关隐式数据类型转换的整章内容;
这一章是他们在对你的程序修改前必须全部掌握的一章。
-
整数常数
使用下拉框时,对于可能的值,与其使用命名常数,倒不如使用整数 case 的 switch 语句。
如果有一个包含 100 个元素的数组,在程序中尽可能多的地方对字面量 100 进行硬编码。
决不要使用静态不可更改(static final)命名常数来代替这个 100。或者用 myArray.length 来引用。
为了让改变这个常数更困难,用字面量 50 代替 100/2,或者 99 代替 100 - 1。还可以进一步伪装这个 100,
用 a == 101 代替 a > 100,
或者 a > 99 代替 a >= 100。
考虑一下其他的事情,象页面的大小,包含 x 行页眉,y 行正文, z 行页脚,
可以分别对这些进行迷惑处理并且对部分或者总和都可以。
这些历史悠久的技术,对于那些包含两个不相关的数组恰好都有 100 个元素的程序特别有用。
如果维护程序员需要修改其中一个的长度时,他不得不对程序中 100 的每一个用法进行解密,
来确定它用在了哪个数组上。几乎可以确定他至少会犯一个错,如果有希望的话,这个错可能几年内都不会显现出来。
还有更多恶魔似的变体。为了把维护程序员哄骗进对安全性虚假的感觉中去,
要负责任地创建一个命名常数,但是非常偶然地"无意中"用了 100 这个字面量,
而不是这个命名常数。其中最邪恶的是,在字面量 100 或者正确的命名常数的位置,
偶尔使用了其他无关的命名常数,恰巧它的值是 100。
那种把数组名字和它的大小常量联系起来的一致性命名规则,如果没有说明必须避免使用的话,
那就是行得通的。
-
分号!
始终在任何语法允许的情况下使用分号。例如:
if ( a );
else;
int d;
d = c;
}
-
使用八进制
象下面这样把八进制数藏在十进制数的列表里:
array = new int[]
{
111,
120,
013,
121,
};
-
间接转换
无论什么时候需要转换,Java 都提供了迷惑的很多机会。举一个简单的例子,
如果要把一个 double 转换成 String,
就迂回地来做,通过 Double 的 new Double(d).toString(),
而不用其他更为直接的方法 Double.toString(d)。当然,也可以比这迂回地更多!
避免任何在转换秘书中推荐的转换技术。
在转换之后,每个扔进堆中的临时对象都会得到一些奖励的分数。
-
嵌套
尽可能地嵌套。好的编码人员可以在一行内使用超过 10 层的 ( ),一个方法内超过 20 个 { }。
C++ 的编码人员利用预处理器的强大选项,可以完全独立于下面代码的嵌套结构而进行嵌套。
不论何时,块的开始和结束在打印列表中出现在不同页中,都可以得到额外的精灵分数。
在任何可能的地方,把嵌套的 if 语句转换成嵌套的 [? : ] 三元组。如果能跨越多行,
那就更好了。
-
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。
-
很 长 的 代 码 行
试着把尽可能多的东西塞到一行中去。这就节省了临时变量的开支,
也通过去除新行和空白来使源码文件小一点。技巧: 把操作符旁边上的空白全都删去。
好的程序员总是可以在某种编辑器的帮助下打破行长为 255 个字符的限制。
使用很长的代码行一个额外的好处就是,让那些不能在 6 point 字体大小下阅读的程序员,
不得不滚动才能查看源代码。
-
异常
我要让大家看一个很少人知道的编码秘密。异常是内部的痛苦。写得好的代码从不出错,
所以异常实际上根本不必要。不要在它们身上浪费时间。子类异常是给那些无能者准备的,
他们知道他们的代码会出错。可以在整个应用程序(main 中)只使用一个 try/catch,
并且调用 System.exit(),这样就能使程序极大地简化。坚持把一个完美的标准异常集合放在每一个方法的头部,
不管它们是否真的抛出任何异常。
-
何时使用异常
在非异常情况下使用异常。通常用 ArrayIndexOutOfBoundsException 结束循环。
从异常中返回方法的标准结果。
“高效”的异常
抛异常会有很高的代价。JVM 要扫描整个栈寻找可能在栈回溯中用到的海量信息。要避免这个代价,
可以创建一个异常(Exception)实例,然后多次抛出。
那么栈回溯就是针对异常创建时所在的代码,而不是抛出异常的地方。这会让他们一直在猜测错误到底在哪里。
-
滥用线程
标题已经说明了一切。
-
律师代码
遵循在新闻组中讨论各种各样的编码技巧应该怎么做时所使用的律师语言。比如,
a=a++; 或者 f(a++,a++);,然后把这些例子散布在你的代码中。
C 中,象下面例子中使用的前/后减的效果,并没有在语言规范中定义:
*++b ? (*++b + *(b-1)) : 0
每种编译器可以自由地以不同的顺序来计算。这就让它们加倍致命。同样地,可以把所有的空白去掉,
好好地利用 C 和 Java 中复杂的词法规则。
-
提前返回
坚决遵循以下准则,不用 goto,不提前返回,不用标号中断,特别是当使用的 if/else 的嵌套层次最少 5 层的时候。
-
避免 {}
绝对不要在 if/else 块周围使用 { },除非语法必须。如果把 if/else 语句和块很多层地嵌套混合在一起,
特别是当使用了误导的缩进之后,甚至能使一个高手级别的维护程序员犯错误。为了达到这个技术的最好效果,
就用 Perl 吧。可以在语句之后撒上格外的一些 if,就可以得到让人惊奇的效果。
-
来自地狱的制表符
千万不要低估用制表符代替空格进行缩进所能造成的大破坏。尤其是在对于一个制表符代表多少缩进没有一个共同标准的情况下。
在字符串常量中嵌入制表符,或者使用工具来帮忙把空格转换成制表符。
-
有魔力的矩阵位置
在矩阵的一定位置使用特殊的值作为标志。一个好选择就是: 一个变换矩阵的 [3][0] 元素用的是齐次坐标系统。
-
再看有魔力的数组
如果需要给定类型的几个变量,就定义一个数组,然后用数字来访问它们。选择一种编号的惯例,只有自己知道,
并且不要写下文档。也不要操心给这些下标定义 #define 的常数了。每个人应该只知道全局变量 widget[15] 是取消按钮就行了。
这只是在汇编语言中使用绝对数值地址的一种新式变体。
-
决不美化
决不要使用自动化的源码整理(美化)工具来整理自己的代码。游说公司彻底抛弃这种工具,
因为它们在 PCVS/CVS(版本控制跟踪)中引起了虚假的差异信息,或者游说每个程序员,
都应该在所有自己编写的模块中,永远保持自己神圣的缩进风格。坚持让其他程序员在"他的"模块中遵循这个特殊的约定。
禁止使用美化器相当简单,即使它能减少上百万次的击键来进行代码整理,以及浪费在误解排列混乱代码的那些时间。
坚持让每个人都使用同样的经过整理的格式,不仅仅是为了在公共仓库中保存,也需要在编辑的时候使用。
这样会引发一场宗教战争(RWAR,Religious War),而你的老板,为了保持和睦,就会禁止使用自动化的整理。
没有了自动化的整理,现在就可以自由地恰好没有把代码排列好,就对循环或者 if 语句产生一个错觉,
它们比实际上要短或者长一点,或者 else 子句错配到另外一个 if,等等类似的情况。例如:
-
宏预处理器
它给迷惑提供了很多好机会。关键技术就是把宏扩展到好几层深,这样就就不得不查看许多不同的 *.hpp 文件来寻找不同的部分。
把可执行代码放进宏中,然后在每个 *.cpp 文件(甚至是没有用到这些宏的文件)中包含这些宏,这样一旦代码有变化的时候,
就把需要重新编译的文件数量最大化。
-
利用精神分裂
Java 对于数组的声明是精神分裂的。可以用老式的 C 方法来做,String x[](使用混合的前后标记),或者新方法,
String[] x,只使用前缀标记。如果真的想让人迷惑,就把这些标记混合起来吧,例如:
byte[] rowvector, colvector, matrix[];
相当于:
byte[] rowvector;
byte[] colvector;
byte[][] matrix;
-
隐藏错误恢复代码
使用嵌套把一个函数调用的错误恢复代码和调用代码隔得尽可能地远。下面这个简单例子可以复杂到有 10 或者 12 层的嵌套:
-
伪 C 代码
#define 的真正原因是帮助那些熟悉其他语言的程序员转到 C 来。
可能你会发现这样的声明,#define begin { " or " #define end } ,
对写出更多有趣的代码很有帮助。
-
混合 import
让维护程序员慢慢地猜你在使用的方法到底是哪个包里面的。不用:
import com.mindprod.mypackage.Read;
import com.mindprod.mypackage.Write;
而使用:
import com.mindprod.mypackage.*;
不管多么隐晦,决不要把类或者方法写完全。让维护程序员去猜它属于哪个包或者哪个类。
当然,什么时候写全,怎么样来 import,不一致性也能帮上大忙。
-
不管在任何情况下,决不要让多于一个函数或者过程中的代码同时出现在屏幕上。对于很短的函数,
可以使用下列简便的花招:
-
空行通常用于分离代码的逻辑块。每一行本质上,自然而然地是一个逻辑块。在每行之间加上空行。
-
决不在行尾给自己的代码加注释。把它放在上面一行。如果被迫要在行尾加注释,
那就在整个文件中挑出最长的一行,加 10 个空格,然后对所有的行尾注释都向左对齐到那一列。
-
过程顶部的注释应该使用至少 15 行的模板,并且自由地使用空行。下面是一个简便的例子:
在文档中加入这么多的冗余信息,几乎能够保证它很快就过时,让那些足够笨的维护程序员相信它。
-
包装细小的东西
创建一个完整的类或者方法来包装那些几乎不可能改变的细小部分,但是需要复杂调用,并且很小心地才能发现,
代码几乎什么也没做,下面就是一个经典的例子:
循环
绝对不要使用循环的简陋正规用法:for
(int i=0; i<n; i++
)。始终随机将其伪装起来,例如这样:
- 用 while 或者 do
while 循环重新实现。
-
调换 i 和 n 变量的用法,
或者创造出一些花哨的名字,跟索引和计数没有任何关系。
- 将 < 换成 <=。
- 用 i-- 仅仅是为了改变步数。
很明显绝不应该使用紧凑的 for:each 循环。
在遍历 Collection 时,有许多方法来重新调整 Iterator 的内容,
这样每次维护程序员看着一个简单的 Iterator,就象全新的一个东西。