分享

如何编写不可维护的代码 ;

 123xyz123 2018-04-27

介绍

永远不要把无能归咎于作恶                ------ 拿破仑

为了获得在Java编程领域工作的机会,我把这些来自于大师们关于如何编写难以维护代码的技巧传达给大家。如果你使用这些技巧,那些后来继承你工作的人即使做最简单的改动也要花费几年的时间。进一步,如果你能遵守所有的这些规则,你将能保证一辈子都不会被解雇,因为除了你已经没有人能维护这些操蛋的代码。甚至,如果你虔诚地遵守所有这些规则,连你自己也没办法维护这些代码!你怕不怕?

第 1 段(可获 2 积分)

你不需要做的太过火了。你的代码不应该看起来毫无维护的可能性,就让它们那样。否则,它们可能面临着被重写或者重构的风险。

总则

Quidquid latine dictum sit, altum sonatur.- Whatever is said in Latin sounds profound.

为了打击维护代码的程序员,你应该了解他是怎么想的。他有你伟大的源代码。他没有时间全部读一遍,理解的部分就更少了。他希望快速找到哪里可以进行他的修改,加入他的代码,然后退出,并且不带来任何的副作用。

他从一个卫生纸卷筒中窥视你的代码。他一次只能看到一小片你的代码。你应该确保他这样做是不可能看到代码的宏图框架的。你应该让他难以找到他想要找的代码。更重要的是,你应该尽力让他难以安全地忽视任何细节。

第 2 段(可获 2 积分)

程序员总是被约定麻痹。偶尔违反下约定,你就能强迫得他们用一个放大镜一行一行读你的代码。

可能你已经得到让所有编程语言变得不可维护的诀窍了。---- 除此之外,就是适当的滥用一下这技巧。

命名

"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."

Lewis Carroll -- Through the Looking Glass, Chapter 6

编写不可维护代码的一大技巧就是命名变量和函数的艺术。命名对编译器没有意义。但是给了你迷惑代码维护程序员的巨大空间。

第 3 段(可获 2 积分)

小孩名字的新用法

买一本给小孩起名字的参考书,这样你在给变量命名时绝对不会吃亏。Fred是一个非常不错的名字,也很容易输入。如果你在找容易输入的变量名字,试试asdf或者如果你用的DSK排列的键盘,aoeu也不错。

单字母变量

如果你给你的变量命名为a,b,c,那么别人不可能在简单的文本编辑器里搜索这些变量实例。甚至,没人能够猜测到这些变量是干什么的。如果有人喜欢打破从Fortran用i,j和k来用做遍历变量的传统,改成用ii,jj和kk,我要警告他们想想西班牙宗教法庭对异教徒们做了什么。

第 4 段(可获 2 积分)

充满创意的错误拼写

如果你必须描述性的变量和函数名,错误地拼写他们。有时错误有时正确的拼写(比如SetPintleOpening 和SetPintalClosing)我们可以高效的搞死grep命令或者IDE的搜索功能。这个非常的神奇。往不同的threater/theater添加国际范的tory或者tori拼写也是非常神奇的一件事。

抽象一点

在命名函数和变量时,大量使用抽象的词语,比如everything,data,handle,stuff,do,routine,perform和数字等。例如,routineX48,PerformDataFunction,DoIt,HandlerStuff和do_args_method。

第 5 段(可获 2 积分)

使用首字母缩写词

使用首字母缩写词来保持代码简洁。真正的人类从来不定义首字母缩写词,他们生来就知道首字母缩写词的意义。

词汇替代

为了打破无聊,用词典尽可能多地查询那些表示同一动作的词汇列表。比如,display,show,present。含糊地暗示这些动作词汇有细微的差别,但实际上根本没有。当然,如果真的有差别的函数,就用同一个词来描述(比如,用print同时表示“写文件”,“打印到纸上”和“显示在屏幕上”)。不管什么场景,都不要屈服于使用没有歧义的特殊用途的专有词汇。这样做非常不专业地违反了结构化设计准则中的信息隐藏原则。(译注:此处应该是调侃信息隐藏,信息隐藏实际应该指的是数据封装时尽可能少暴露内部信息)

第 6 段(可获 2 积分)

 使用其他语言的复数方式

用一个虚拟机脚本记录不同计算机(vaxes故意写成vaxen)数据(states故意写成statii)。世界通用语,克林贡语和霍比特语被认为是一种语言就是这么干的。对于伪世界语的复数形式,加上oj。你也可以为世界和平作出贡献。(译注:这一段有点难翻译,大意是按照自己的想法随意地加复数后缀,就像影视小说里面的异世界语言一样。这会让人很难理解)

CapiTaliSation(字母大写)

随机大写单词中的音节字母。比如,ComputeRasterHistoGram().

重用名字

只要语法规则允许,就给类,构造函数,函数,变量,参数和局部变量起相同的名字。另外,给{}中的局部变量起相同的名字。目的就是让维护者不得不仔细地检查每个实例的作用域。特别对于Java,把普通函数伪装成构造函数。

第 7 段(可获 2 积分)

使用重音字母

在变量名里使用重音符号,比如

typedef struct { int i; } ínt;

这里第二个int的i实际上是重音的i。在简单的文本编辑器里,几乎不可能分辨出这个倾斜的重音符号。

利用编译器能识别的变量名长度限制

例如假设编译器只识别前8个字符,那么var_unit_update和var_unit_setup都会被认为和var_unit一样。

下划线,其实是一个朋友

用 '_' 和 '__' 作标识符。

随机点缀使用两种语言(人类语言和计算机语言)。如果你的老板坚持你使用他的语言,你就告诉他使用你自己的语言你能更好地组织你的想法。或者如果那样说不好使,就说这是语言歧视,然后威胁说要控告他们索赔一大笔钱。

第 8 段(可获 2 积分)

扩展ASCII字符

扩展ASCII字符完全是合法的变量字符,包括 ß, Ð, 和 ñ 字符。这些字符除了复制粘贴几乎不可能在一个简单的编辑器里编辑。

使用其他语言的名字

使用其他外国语言的字典作为变量起名的来源。例如,使用法语的punkt作为point。代码维护者没有你对法语的理解,将会非常享受这次多元文化秘密探索之旅。

用数学运算符号做名字

使用伪装成数学运算符的变量名,例如:

openParen = (slash + asterix) / equals;

(译注:单词意思是‘左圆括号’ = (‘斜线’ + ‘星号’) / ‘等号’)

第 9 段(可获 2 积分)

使用使人眼花缭乱的名字

选择那些有不相关情感内涵的词,例如:

marypoppins = (superman + starship) / god;

(译注:欢乐满人间(电影名) = (超人 + 星际飞船)/ 上帝)

每个人对词汇的情感内涵都有不同的的理解,所以这样会让读代码的人很困惑。

重命名和重复使用

这个技巧在Ada语言中工作的尤其好。Ada是一种免除了现代模糊技术的语言。命名你现在使用的对象和包的那些人都是白痴。与其通知他们修改,不如按照自己的发明进行重命名和子类型重命名所有的名字。当然保留一些原来的名字,作为一个粗心的陷阱。

第 10 段(可获 2 积分)

何时使用i

千万不要把i用作最里层的循环变量。公平地把i用在任何其他地方,尤其是非整型变量的地方。类似的,用n做循环变量。

约定

忽略Sun公司的Java编码约定。幸运的是,当你违反这些约定时编译器也不会泄露你的秘密。目标就是想出只在大小写有细微差别的的命名。如果不得不准守大小写约定,你依然可以在一些模糊定义的地方颠覆这些约定。例如,同时用inputFilename 和 inputfileName。发明一套你自己的复杂到令人绝望的名字约定,然后严厉地斥责那些不遵守这个约定的人。

第 11 段(可获 2 积分)

小写的l很像数字1

用小写的l来标识long类型常数。例如10l 更容易被误解成101。不使用那些能清楚分辨 uvwwW,gq92z5sil17|!joO08`'";,.m nn rn, and {[()]} 的字体。发挥你创造性。

重用全局名字做为私有变量名

在模块A定义一个全局数组,在模块B的头文件定义一个同样名字的私有数组变量。这样似乎你在模块B用的是全局数组,但其实不是。不要在注释里提及这里的复用。

循环访问

第 12 段(可获 2 积分)

用一种矛盾的方式尽可能迷惑地循环使用变量名作用域。例如,假设有全局变量A和B,和函数foo和bar。A一般传递给foo,B传递给bar。那么把foo函数定义成foo(B)和bar函数定义成bar(A)。这样在函数内部,A会被当成B,B别当成A。等有更多的函数和全局变量,你就可以生成一张互相矛盾地使用相同名字的变量组成的大网。

回收利用变量

只要作用域允许,重复利用已经存在的不相关的变量名。类似的,对于两个不相关的用途使用相同的临时变量(对外声称是节约栈空间)。就像恶魔变体一样对变量进行变化。例如,在一个很长的函数的前面给变量赋一个值,然后在中间某个地方再赋一个值,同时细微地改变变量的含义。比如,从以0为基准改成以1为基准。当然,不要把这个改变写进文档。

第 13 段(可获 2 积分)

Cd wrttn wtht vwls s mch trsr(译注:乱七八糟的缩写)

在变量和函数名里使用缩写时,对同一个单词用不同变体打破无聊,甚至偶尔速记一把。这帮助了打败那些使用文本搜索去理解你代码某个方面的懒汉。例如,混合国际方式拼写的colour和美国方式拼写color以及花花公子口中的kulerz。如果你全拼这些名字,只有一种拼写方式,这样太容易让代码维护者记住了。因为有很多不同的简写同一个单词的方式,使用简写你可以有很多不同的变量表示同一个表面上的意思。作为一个附加的福利,代码维护者可能甚至都没注意到他们是不同的变量。

第 14 段(可获 2 积分)

误导人的命名

确保每个函数都比它的名字表达出的意思多做或者少做一点。举个简单的例子,一个名为isValid(x)的函数有个把x转成二进制且存进数据库的副作用。

m_

一个来自C++的命名约定是在变量名前使用m_前缀。这个约定只要你“遗忘性地”在函数也加个m,就可以告诉他们这不是函数。

o_apple obj_apple

在每个类实例前加o或者obj前缀,以表明你想表示一个巨大的,多形态的宏图。

第 15 段(可获 2 积分)

匈牙利命名法

匈牙利命名法是代码混淆技术的战术核武器, 使用它! 让大量的源代码被这种习语污染,没有什么能比这种精心策划的匈牙利命名法攻击更快的干掉一名维护工程师了!  以下的提示将帮助你从本质上腐蚀匈牙利命名法 :

  • 在C++或其他允许使用常量性变量的语言中坚持使用 "c" 作为常量

  • 寻找并使用那些只在你使用的语言里没有特殊意义的有(keng)趣(die)字符(匈牙利毒瘤/ Hungarian warts )。比如说坚持在PowerBuilder上使用l_ and a_ {local and argument} 做变量名前缀,比如说坚持在写给C++的控件类型上显现出一种有趣的VB风格,坚持忽略数以兆计的MFC开放代码从来不使用那些有(keng)趣(die)字符做控件类型的事实。

  • 最违反匈牙利原则的是最常用的变量总是随身携带最少的信息. 很好的贯彻上述技术的方法是坚持为每一个类加上一个自定义的前缀。也不要让任何人告诉你,一般没有前缀的才是类!这条规则的重要性,毫不夸张的说,如果你不能坚持的话,源代码很快就会被具有较高元音/辅音比的短变量名淹没。 最坏的情况,将导致代码充满自重复英语符号和各种模糊的崩溃!

  • 悍然违反匈牙利风格的设计理念要求函数参数和其他常见符号必须被赋予一个有意义的名字,但是真正的匈牙利毒瘤风格完全由自己临时创造一个完美的变量名字。

  • 坚持给你自己的有(keng)趣(die)字符携带一些完全无关的信息,考虑这个真实的例子,a_crszkvc30LastNameCol。 它让一整组维护工程师花了三天才发现这个大家伙是(a const, reference, function argument that was holding information from a database column of type Varchar[30] named "LastName" which was part of the table's primary key)的缩写!当适当的结合“所有的变量应该是公共的”这一原则,这种技术有能力使数千行的代码立即作废!

  • 为了完美利用人脑只能同时容纳七条信息的原则,你可以写出具有以下特性的代码:

    • 在一条赋值语句中写出了14种不同的类型和变量名。
    • 让只含有三个参数并声明一个结果的单个函数调用包含29种不同的类型和变量名。
    • 这种优秀又简洁明了的习惯,感染了管理者和同事,推荐对一周的每一天使用代表性的5个字母的前缀来区分写在周一上午(Monam)和周五下午(FriPM)的代码(译者:例如周一上午使用Monam_apk,周五下午使用FriPM_pqd)
    • 即使是一个中等复杂的嵌套结构,也很容易使短期记忆混乱,特别是当维护程序员不能在屏幕上同时看到每个块的开始和结束时。
第 16 段(可获 2 积分)

再说匈牙利命名法

一个匈牙利命名法中后续的招数是“改变变量的类型但是不改变变量名”。这几乎是从16位Windows时代一层不变地迁移来的:

WndProc(HWND hW, WORD wMsg, WORD wParam, LONG lParam)

到win32

WndProc(HWND hW, UINT wMsg, WPARAM wParam, LPARAM lParam)

这里的w都表明他们是单词,但实际上是指long类型。这种命名法迁移到win64时,意义会很明确,因为在win64就是参数的位宽就是64位。但是以前的w和l前缀,就呵呵了。

第 17 段(可获 2 积分)

减少,重用,再利用

如果你为回调函数定义一个结构体来存储数据,把结构体命名为PRIVDATA。每个模块都可以定义它自己的PRIVDATA。在vc++里,这样做有个好处就是迷惑代码调试者。因为有很多PRIVATA展示在watch窗口,他不知道你指的是哪个PRIVATA,所以他只能任选一个。

 晦涩地参考电影情节

用LancelotsFavouriteColour(译注:Lancelot最喜欢的颜色)替代blue给常量命名,并且赋值十六进制的$0204FB。这种颜色在屏幕上看起来和纯蓝色完全一样,但是这样命名会让代码维护者不得不执行程序(或者使用某些图像工具)才能知道是什么样。只有那些熟悉“巨蟒剧团”和《圣杯的故事》才知道Lancelot最喜欢的颜色是蓝色。如果代码维护者想不起来所有巨蟒剧团的电影,那他或者她就没资格做一个程序员。

第 18 段(可获 2 积分)

伪装

The longer it takes for a bug to surface, the harder it is to find. - Roedy Green

编写不可维护代码一大部分技巧都是伪装的艺术。很多技巧都基于代码编译器能够比人眼和文本编辑器更好地分辨代码这一事实上。这里列举一些最佳伪装技术。

代码和注释相互伪装

来一些第一眼看上去是注释的代码。

for(j=0; j<array_len; j+ =8)
{
total += array[j+0 ];
total += array[j+1 ];
total += array[j+2 ]; /* Main body of
total += array[j+3]; * loop is unrolled
total += array[j+4]; * for greater speed.
total += array[j+5]; */
total += array[j+6 ];
total += array[j+7 ];
}

 

第 19 段(可获 2 积分)

如果没有编辑器颜色区分代码,你能注意到那三行已经别注释掉了吗?

命名空间

struct/union 和 typedef struct/union 在C(不是C++)中是不同的命名空间。在这两个名字空间里给结构体和枚举使用相同的名字。如果可能的话,让他们几乎一模一样。

typedef struct {
char* pTr;
size_t lEn;
} snafu;

struct snafu {
unsigned cNt
char* pTr;
size_t lEn;
} A;

隐藏宏定义

把宏定义隐藏在垃圾注释中间。程序员就会因为无聊放弃读这些注释,于是就发现不了这些宏。确保这些宏替换看起来像一个完全合法的赋值操作,例如:

第 20 段(可获 2 积分)
#define a=b a=0-b

繁忙假象

用define语句装饰编造出来的函数,给这些函数一堆参数注释,例如:

#define fastcopy(x,y,z) /*xyz*/
// ...
fastcopy(array1, array2, size); /* does nothing */

用延长技术隐藏变量

#define local_var xy_z

中xy_z替代成2行的

#define local_var xy_z // local_var OK

这样,全局搜索xy_z会什么也搜不到。对于C的预编译器来说,行末的"\"符号会把这一行和下一行合并到一起。

伪装成关键字的任意名字

第 21 段(可获 2 积分)

写文档的时候,随意用一个file之外的词来重复文件名。不要用那些看起来就特别随意的名字,比如“Charlie.dat”或者“Frodo.txt”。一般情况下,用那些看起来更像保留的关键字的词。例如,给参数和变量起的比较好的名字有bank,blank, class, const, constant, input, key, keyword, kind, output, parameter,parm, system, type, value, var 和 variable。如果你用真正的保留关键字,编译器或者处理器会报错。这个技巧如果用得好,程序员会在真正的关键字和你起的名字之间感到绝望般的困惑。但是你还是可以看起来非常无辜,你声称这样命名是为了帮助他关联每个变量的用途。

第 22 段(可获 2 积分)

代码命名不匹配跟屏幕显示的名字

选择跟显示在屏幕上的名字完全没有联系的变量名。例如,屏幕上显示标签名字是“Postal Code”,但是在代码里,变量起名为zip。

不要改变命名

用多个TYPEDEF给同一个符号起别名的方式替代全局同步重命名两个区块的代码。

如何隐藏被禁止的全局变量

由于全局变量被认为是‘罪恶的’,你可以定义一个结构体容纳所有的全局内容。机智地把这个结构体命名为类似EverythingYoullEverNeed(你所需要的每件事)的名字。让所有的函数都有一个指向这个结构体的指针(命名为handle可以迷惑更多)。这样会给别人你不是在使用全局变量的印象,你是通过一个handle还访问每件事的。然后定义一个静态实例,这样所有的代码就都是访问同一个副本了。

第 23 段(可获 2 积分)

用同义词隐藏实例

代码维护者修改代码之后,为了查看是否会有什么级联影响,会用变量名进行一次全局搜索。这可以通过同义词轻松化解,例如

#define xxx global_var // in file std.h
#define xy_z xxx // in file ..\other\substd.h
#define local_var xy_z // in file ..\codestd\inst.h

这些def语句应该分散在不同的头文件中。这些头文件如果分散在不同目录效果更佳。另外一种技术是在每个块中重用变量名。编译器可以把他们分辨开来,但是简单的混合式文本搜索区分不了。不幸的在接下来几年到来的SCID(译注:Source Code in Database,一种将代码预解析然后存在数据库的技术)将会使得这项简单的技术不起作用,因为编辑器将会和编译器一样支持区块规则。

第 24 段(可获 2 积分)

长长的相象的变量名

每个长长的变量或者类名都不相同,只差一个字符或者只有大小写。一个完美的例子是swimmer和swimner。由于大多数字体的 ilI1| 或者 oO08 差别很小,利用这点,使用标识符例如parselnt 和 parseInt 或者 D0Calc 和 DOCalc 做变量名是一个绝好的主意。因为随便一眼看去,都像是常量1.在很多字体里,rn看起来很像一个m。所以你知道变量swirnrner可以怎么做了。也可以制造一些名字仅仅差在大小写的变量名,例如HashTable 和 Hashtable。

第 25 段(可获 2 积分)

发音类似拼写类似的变量名

只有大小写或者下划线差别的变量可以很好的让那些用发音或者单词拼写来记忆变量而不是精确拼写的人感到凌乱。

重载和混乱

在C++中,用#define重载库函数。这样看起来就像是你在使用一个很熟悉的库函数,但是实际上是完全不相同的东西。

选择最好的操作符重载

例如,在C++中,把+,-,*,和/重载和加减完全不相关的操作。既然Stroustroup(C++作者)可以用移位操作进行I/O操作,为什么你不能那样充满创意呢?如果你重载+,一定要重载成i = i + 5 和i += 5的意义完全不同。来个一个例子将符号重载混乱上升到一个更高的艺术。为类重载!操作符,但是这个重载跟取反或者取否完全没关。让这个操作符返回一个整数。这样,为了或者一个布尔值,你必须用!!。而为了反转这个布尔值,[此处应有掌声]你就必须用!!!。不要混淆返回布尔0或者1的!操作符和按位取反的~逻辑操作符。

第 26 段(可获 2 积分)

重载new

重载 new 操作符 - 比重载 +-/* 危险多了。这可能会造成巨大浩劫如果重载后的东西做了一些与它本来的功能完全不同的事情, (但是重要的对象功能很难改变)。确保用户创建动态实例难于登天。你还能表演一下这个有趣的戏法,不仅有个成员功能,还有个成员变量,都叫"New"。

#define

C++自带的#define给混淆带来了丰富的可能性,甚至值得写篇文章来赞美一下。 使用小写 #define 变量就能与原生变量开一场假面舞会。你的预处理器函数从不用参数,全局的#defines就能完成一切嘛。我听过的其中预处理器的最富有想象力的使用方法是这样的, 在代码编译之前导入五次CPP。通过巧妙运用 definesifdefs,能形成一个大师级的混淆方式,就是根据头文件的导入次数做完全不同的事。一个头文件包含另一个头文件时就更有意思了。这是一个特别狡猾的例子:

第 27 段(可获 2 积分)
#ifndef DONE

#ifdef TWICE

// put stuff here to declare 3rd time around
void g(char* str);
#define DONE

#else // TWICE
#ifdef ONCE

// put stuff here to declare 2nd time around
void g(void* str);
#define TWICE

#else // ONCE

// put stuff here to declare 1st time around
void g(std::string str);
#define ONCE

#endif // ONCE
#endif // TWICE
#endif // DONE

当传递 g()一个char*时会非常有趣,因为调用不同版本的g()将取决于包含头的次数。

编译器指令

编译器指令的设计目的是使同一代码的行为完全不同。 反复和有力的打开和关闭布尔短路指令,以及长字符串指令。

第 28 段(可获 2 积分)

文档

任何傻瓜都能说真话,但需要有些许理智的人,才懂得如何圆谎。 - 塞缪尔·巴特勒(1835 - 1902)

不正确的文档通常比没有文档更糟糕。 - Bertrand Meyer

由于计算机忽略注释和文档,你可以肆无忌惮,尽你所能的来迷惑那些可怜的维护代码的程序员。

在注释中说谎

你不必主动说谎,只需使注释与最新的代码保持不一致就行了。

为那些显而易见的代码写文档

为代码添加一些类似于/ * add 1 to i * /的注释,但是,绝不要为代码记录类似于包或方法的整体目的的东西。

第 29 段(可获 2 积分)

记录怎么做而不是为什么这么做

仅记录程序的细节,而不是它试图完成什么。 这样,如果有一个错误,改代码的程序员将不知道代码应该做什么。

避免记录“显而易见的”

例如,如果您正在编写航空预订系统,如果您要添加另一家航空公司,请确保在代码中至少有25个地方需要修改。 不要记录他们在哪儿改动的。 那些在你之后接手代码的人,在没有彻底理解代码的每一行之前是无法开展修改代码的工作。

关于文档模板的正确使用

第 30 段(可获 2 积分)

考虑用于允许代码的自动化文档的函数文档原型。 这些原型应该从一个函数(或方法或类)复制到另一个,但不会填充字段。 如果由于某种原因被迫填写字段,请确保所有参数的名称对于所有函数都是相同的,并且所有注意事项都是相同的,但是当然不与当前函数相关。

关于正确使用设计文档

当实现一个非常复杂的算法,使用经典的软件工程原理做一个声音设计编码之前。 编写一个非常详细的设计文档,描述一个非常复杂的算法中的每个步骤。 本文件越详细越好。 事实上,设计文档应该将算法分解成结构化步骤的层次结构,在文档中自动编号的单个段落的层次结构中描述。 使用至少5级深度的标题。 确保当你完成后,你完全破坏了结构,有超过500个这样的自动编号的段落。 例如,一个段落可能是(这是一个真实的例子)

第 31 段(可获 2 积分)

1.2.4.6.3.13 - 显示所有可能的应用(短伪代码....),

然后... (这是在发牢骚) 当你写代码时,对每一个段落都写一个对应的全局函数,就叫

Act1_2_4_6_3_13()

不用给这些函数专门写文档了。 毕竟,设计文档里都有!

设计文档自动编号以来,就极难和代码保持同步更新了, (当然,因为函数名是静态的, 不是自动编号的) 。其实这根本不是问题,因为你从没想过文档同步更新这回事。事实上, 你还能做更多的事来摧毁设计文档可能有的任何用途。

第 32 段(可获 2 积分)

那些在你后面来的人应该只能找到一两个矛盾的早期草稿的设计文件,这些文件隐藏在靠近死机的286电脑附近一些灰尘的货架上。

测量单位

不要记录任何变量,输入,输出或参数的测量单位。 例如 英尺,米,纸箱。 这在bean计数中不是那么重要,但它在工程工作中非常重要。 作为推论,从不记录任何转换常数的度量单位,或如何导出值。 这是温和的作弊,但非常有效,在代码中混合一些不正确的计量单位的注释。 如果你感觉特别恶毒,补上自己的计量单位; 把它命名为你自己或一些模糊的人,永远不要定义它。 如果有人挑战你,告诉他们你这样做,以便可以使用整数而不是浮点运算。

第 33 段(可获 2 积分)

陷阱

永远不要为陷阱代码写文档。 如果你怀疑在一个类里可能有bug, 不要理睬它,自己知道就好。如果你对于这些代码该如何重组或重写的话, 听我的,不要把他们写出来。 记得在电影“小鹿斑比”有一句话: “如果你不能说什么好的, 就什么都不要说。” 吗? 万一那个写这段代码的人看到你的评论呢? 又或者如果公司的领导看到了呢? 或者是客户看到了呢?你可能会因此丢掉工作的。 一个人匿名留下的评论“这需要被修复”可能会产生疑问, 特别是在当这段评论所指的对象不明的情况下。 保持模糊,没有人会觉得自己被批评。

第 34 段(可获 2 积分)

为变量写文档

永远不要 为一个变量声明写注释。像关于这个变量如何使用,它的界限、合法值、隐式或者显式的十进制的值、度量单位、显示格式、数据输入规则(比如:都填入,必须输入),以及什么值是可以信任的,诸如此类信息,都应该可以通过程序代码获取到。 如果你的老板强制你写注释,只用给方法写注释就可以了,但是不要对一个变量声明做注释,即使是临时的也不要写!

在注释中贬损他人

不建议任何企图使用外部维护承包商的行为,通过在你的代码里带有引用侮辱其他领先的软件公司的言论,特别是任何被承包过来工作的人。比如:

第 35 段(可获 2 积分)
/* 内循环优化
对在软件服务公司的笨蛋而言,这个员工太聪明了,他
通过利用<math.h>中垃圾一样的功能节约了50倍的时间和内存.
*/
class clever_SSInc
    {
    .. .
    }

如果可能的话,在代码的重要区块侮辱这个职员,调整注释的位置,这样管理者在急着把代码发给他自己的管理者的时候,还想把这块注释删掉的话,就会破坏代码原有的功能。

评论穿孔卡片CØBØL

拒绝接受科技设备竞赛进展。尤其是 SCIDs。有谣言说所有的函数和变量声明一点就能出来,还说被Visual Studio 6.0开发出来的代码是被维护工程师用 edlin 或者 vi 维护的,别信它们。坚持严格的注释规则也会埋葬代码工作者乐趣。

第 36 段(可获 2 积分)

蒙蒂的蟒蛇(指除了你在自己谁也不知道的代指)

对一个叫makeSnafucated的函数写个/* make snafucated */就行了。别告诉别人snafucatedanywhere的意思. 这还用说嘛,笨蛋才不知道snafucated的意思呢。 Sun的AWT技术文档,就是这种技术的经典例子。

程序设计

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 Green

写可维护代码的关键是把程序的所有要素列在同一个地方。改变你的想法,应该把它们列在同一个地方,并保证程序仍然工作。因此,写不可维护性代码的关键是在尽可能多的地方,尽可能多的方式一遍又一遍的列举要素。欢呼吧,像Java那样的语言,有自己的独特方式,能让编写不可维护的代码变得更加简单。举个例子,它几乎不可能改变外部引用,因为所有的造型和数据类型转换都不工作,并且相关临时变量也不合用, 进一步, 如果变量显示在屏幕上, 所有相关联的变量和数据条目都要人工修饰并手动跟踪。Algol语言家族,包括C和Java,能用数组,哈希表,普通文件和不同类型的数据库存储数据。类似Abundance的语言,扩展了Smalltalk的,语法都很独特。仅仅是改变声明,利用Java的迟钝,把会慢慢溢出RAM的数据,放到数组里。这样维护工程师就有了一个把数组转成文件的可怕任务。同样的,在数据库中放小文件,这样维护工程师在性能调优时,就又有了把它们转成数组的乐趣。

第 37 段(可获 2 积分)

Java 类型转换

Java 的类型转换方法算是上帝赐予你的礼物。你可以毫无内疚的使用这个技术,因为语言本身需要它。每当你从集合中获取一个对象时你必须将之转换成其原始的类型。虽然变量的类型可以在很多地方指定。如果类型在后来发生了变化,那么所有转换的操作都必须与之匹配。而且编译器无法帮助那个倒霉的维护程序员检测到这些变化(因为实在太多了)。同样的,例如一个变量从 short 变成 int ,那么所有匹配的转换需要相应的改动。不过越来越多的开发人员使用泛型来解决这种问题。而且可以确定的是这个异端将不会进入语言的规范中。在 RFE 114691 中投票反对并在泛型上消除任意转换的需要(编者注:最后这两句话意思有点乱)。

第 38 段(可获 2 积分)

利用 Java 的冗余

Java 要求你必须为每个变量指定两次的类型。Java 程序员已经习惯了这种多余的操作,他们一般不会注意到这两种类型略有不同。例如:

Bubblegum b = new Bubblegom();

不幸的是 ++ 操作的受欢迎程度使得你很难逃避如下代码的伪冗余代码:

swimmer = swimner + 1;

从不做验证

从不对任何输入的数据进行验证,不管数据是否正确或者是否有差异。这表明你绝对相信公司的设备,以及你正在一个完美的团队中工作。所有合作伙伴和系统运营商都是可信任的,他们提供的数据都是合理的。

第 39 段(可获 2 积分)

有礼貌,无断言

避免使用 assert() 机制,因为它可能把三天的debug盛宴变成10分钟的快餐。

避免封装

为了提高效率,不要使用封装。方法的调用者需要所有能得到的外部信息,以便了解方法的内部是如何工作的。

复制粘贴修改

以效率的名义,使用 复制+粘贴+修改。这样比写成小型可复用模块效率高得多。在用代码行数衡量你的进度的小作坊里,这招尤其管用。

使用静态数组

如果一个库里的模块需要一个数组来存放图片,就定义一个静态数组。没人会有比512 X 512 更大的图片,所以固定大小的数组就可以了。为了最佳精度,就把它定义成 double 类型的数组。

第 40 段(可获 2 积分)

傻瓜接口

编写一个名为 “WrittenByMe” 之类的空接口,然后让你的所有类都实现它。然后给所有你用到的Java 内置类编写包装类。这里的思想是确保你程序里的每个对象都实现这个接口。最后,编写所有的方法,让它们的参数和返回类型都是这个 WrittenByMe。这样就几乎不可能搞清楚某个方法的功能是什么,并且所有类型都需要好玩的造型方法。更出格的玩法是,让每个团队成员编写它们自己的接口(例如 WrittenByJoe),程序员用到的任何类都要实现他自己的接口。这样你就可以在大量无意义接口中随便找一个来引用对象了。

第 41 段(可获 2 积分)

巨型监听器

永远不要为每个组件创建分开的监听器,而是对所有按钮使用同一个监听器,然后用大量的 if…else 来判断是哪一个按钮被点击就行了。

好事成堆TM

大胆使用封装和 OO 思想。例如

myPanel.add( getMyButton() );
private JButton getMyButton()
    {
    return myButton;
    }

这段很可能看起来并不那么有趣。别担心,只是还没到嗨点。

好朋友

在 C++ 里尽量多使用友好声明。再把创建类的指针传递给已创建类。现在你不用浪费时间去考虑接口。另外,你应该用上关键字 private 和 protected 来表明你的类封装得很好。

第 42 段(可获 2 积分)

使用三维数组

大量使用三维数组,然后用不一样的方式在数组之间移动数据,比如,用 arrayA 的行去填充 arrayB 的列,再加上 1 的偏移值。这么做足以让程序员抓狂。

混合与匹配

存取方法和公共变量同时用上。这样一来,你无需调用存取器的开销就可以修改一个对象的变量,还能宣称这个类是个”Java Bean”。程序员可能会试图添加日志函数来找出改变值的源头,但这一招会让他迷惑不已。

第 43 段(可获 2 积分)

包装,包装,再包装

无论什么时候,你使用别人写的代码,都要用至少一次的包装器把别人的脏代码与自己的隔离开。毕竟,其他作者 可能未来某个时间不顾一切地重命名了所有方法。到时候你怎么办?当然有办法。 如果他这样做,写个包装器就能将代码与更改隔离了,或者也可以让VAJ来处理全局性的重命名。然后,这是一个完美的借口,在别人做任何错事之前,先发制人的用一个中间层来切断和他的联系。Java的一个主要缺点是:如果不做愚蠢的方法包装,除了调用同名方法之外,就做不了任何事,即使只是想解决一个小问题。这意味着完全可能写一个四层深的包装却什么也不做,没人会注意到这一点的。为了最大化混淆效果,每一层都重命名方法,找个同义词典,挑点同义词。这会让人产生幻觉,以为你做了什么。 进一步,重命名有助于确保项目术语缺乏一致性。为了确保没有人试图把你的包装调整到一个合适的层次,在每一层都找点你自己的代码调用一下。

第 44 段(可获 2 积分)

包装包装,更多包装

在单独源文件的函数定义中,确保所有 API 函数至少包装了 6-8 次。 你也可以使用 #defines 的快捷方式来实现对这些函数的包装。

不保密!

把每个方法和变量都声明为 public。毕竟总会有人有用得到它的时候。一旦方法被声明为 public 了,就很难回退。这样任何它覆盖到的代码都很难再修改。它还有个有趣的小功能,就是让你看不清类的作用是什么。如果老板质问你是不是疯了,你就可以告诉他,你遵循的是经典的透明接口原则。

第 45 段(可获 2 积分)

爱经

这种技术具有驱动程序包的任何用户或记录器以及维护程序员分心的附加优点。 创建十几个重载变量的同一方法,只有最细微的细节不同。 我认为是奥斯卡王尔德,观察到Kama Sutra的位置47和115是相同的,除了在115的地方女人的手指是交叉的。 包的用户必须仔细地阅读长列表的方法,以找出使用哪个变量。 该技术还使文档膨胀化,从而确保其更可能过时。 如果老板问你为什么这样做,解释它只是为了方便用户。 再次为了充分达到效果,克隆任何共同的逻辑,坐下来等待它的副本逐渐失去同步。

第 46 段(可获 2 积分)

交换和掩饰

交换参数,把这样的函数 drawRectangle(height, width) ,变成 drawRectangle(width, height) ,并且除了名字之外不做任何改变。几个版本后,再换回来。这样维护人员根本不能通过普通的函数调用就快速发现方法已经被改变了。一般而言这是是留给读者作练习用的。

主题和变体

与其将参数用于单个方法,不如尽可能的创建多个单独的方法 。举个例子,有一个方法是 setAlignment(int alignment) ,如果 alignment是枚举常量, 有着 left, right, center三个实例,那就创建三个方法 setLeftAlignment, setRightAlignment, 和 setCenterAlignment. 当然,为了效果最大化,你必须拷贝这些相同的代码逻辑使得它们难以同步。

第 47 段(可获 2 积分)

静态真好

尽可能的多写静态变量。如果在程序里,你不需要某个类多于一个的实例,别人也不会需要的。重复,如果别人抱怨,告诉他们这样存取速度更快。

嘉吉猜想

学习嘉吉猜想(我觉得嘉吉是男的) "任何设计问题都可以通过增加中间层次解决,即使是中间层太多的问题"。 分解面向对象的程序,直到搞不清究竟哪个对象负责改变程序状态为止。 更好的是,安排所有的回调事件激活都要通过指针森林,就是那个包含了程序可能用到的所有函数指针的森林。激活遍历森林的一个副作用就是,在释放对象引用计数指针之前就创造一个深拷贝,即使实际上不需要深拷贝。

第 48 段(可获 2 积分)

全堆一块

把你所有的没用的和过时的方法和变量都留在代码里。毕竟说起来,如果你在 1976 年用过一次,谁知道你啥时候会需要再用到呢?虽然程序是改了,但它也可能会改回来嘛,你就说”不想重新发明轮子”(领导们都会喜欢这样的口气)。如果你还原封不动地留着这些方法和变量的注释,而且注释写得又高深莫测,甭管维护代码的是谁,恐怕都不敢对它轻举妄动

这就是 Final

把所有的叶子类都声明为 final。毕竟说起来,你在项目里的活儿都干完了,显然不会有其他人会通过扩展你的类来改进你的代码。这种情况甚至可能有安全漏洞。 java.lang.String 被定义成 final 也许就是这个原因吧?如果项目组其他程序员有意见,告诉他们这样做能够提高运行速度。

第 49 段(可获 2 积分)

不用接口

鄙视Java的接口吧! 如果你的上级工程师抱怨,就告诉他们Java强迫你在两个不同的类(同样实现了这个接口)之间"剪切"代码,这样他们就知道有多难维护了。 而且,像Java AWT设计者一样工作,写一大堆功能,但只能被子类调用,再在方法里加上一大堆"instanceof"类型检查. 这样,如果有人想重用你的代码,他们必须继承你的类。如果他们想重用你的属于两个不同的类的代码 - 真不走运,他们不能同时继承两个类! 如果非得用接口,起一个万能的名字,比如ImplementableIface。另一个学术界珍珠是给类命名为被实现接口的名称+“Impl”。这也能提供巨大的帮助。 e.g. 让类实现 Runnable接口。

第 50 段(可获 2 积分)

不用布局管理器

永远不用布局管理器。 这样当维护程序员添加一个或更多组件时,他必须手动调整屏幕上每一个组件的绝对位置。  如果你老板强迫你使用布局管理器, 就用个大的 GridBagLayout, 然后硬编码网格坐标。

环境变量

如果你必须为其他程序员编写类,在你的类里添加环境检查代码(getenv() in C++ / System.getProperty() in Java),放在静态代码块里,然后搞到所有需要的参数,而不是通过构造器接收。这种方法的优点是初始化工作发生的尽可能早,甚至与类文件被加载一样早,甚至比类实例化更早,甚至在main()之前就被执行了。换句话说,在你的类文件读取之前,程序的其余部分根本不能更改这些参数 - 就是说用户最好把他们自己的环境变量设置的和你设置的一样!

第 51 段(可获 2 积分)

表格驱动逻辑

避免任何形式的表驱动逻辑。 开始看上去再简单不过了, 但是最终使得用户会对校对不寒而栗,甚至是简单的修改表格。

改他妈的字段

在Java里,所有私有数据类型是以传值的方式传参,所有传递的参数真的是只读的。 调用者可以随便修改参数,被调用者不会受到任何影响。 相对的,被传递过来的对象是可读写的。引用是按值传递的,但被引用的对象是同一个。 调用者可以对传过来的对象字段做任何想做的事。不用记录方法事实上有没有修改传递过来的对象。给你的方法起一个好名字,使它看上去仅仅是查看了对象的值,但实际上却进行了修改。

第 52 段(可获 2 积分)

全局变量的魔力

别使用异常来处理进程错误,把你的错误信息存到一个全局变量里。确保系统的每一个长循环都检查全局标签,并在发现异常时终止。再添加一个全局变量用来标识用户点击了'重置'按钮。当然了,系统里的所有主要循环也得检查第二个标签。 还有些需要终止的循环,隐藏起来吧。

全局变量,再强调也不为过!

如果上帝不想让我们使用全局变量,就不会发明它了。 别让上帝失望,尽可能多的设置和使用全局变量吧。每个函数都应该用上几个,即使实际上不需要也行。毕竟,任何好的维护工程师都会很快发现这是一次侦探锻炼,他也会很开心的意识到,这个锻炼能测试出一个人究竟是不是优秀的维护工程师,抑或只是个新来的而已。

第 53 段(可获 2 积分)

全局变量,要多用,亲爱的

全局变量完全把你从指定函数参数的工作中解救出来了。利用这个优势。 选择这些全局变量中的一个或多个指定其他进程要做什么。维护工程师居然愚蠢的认为C函数没有副作用。 确保他们想不到你在全局变量存储结果和内部状态信息。

副作用

在C里, 函数应该是幂等的(没有副作用)(幂等:同一个函数在参数相同的情况下执行结果相同)。我希望这个提示足够明显。

回退

在循环体里,假装循环必定成功,而且会立即更新所有的指针变量。如果随后在循环过程中出现了一个异常, 回退 被作为循环体条件并步进的指针。

第 54 段(可获 2 积分)

局部变量

(这一段正文和上一段正文完全一样,可能是文章上传的时候就错了?)

在循环体里,假装循环必定成功,而且会立即更新所有的指针变量。如果随后在循环过程中出现了一个异常, 回退 被作为循环体条件并步进的指针。

减少, 重用, 回收 ( Reduce, Reuse, Recycle,3R原则?)

如果你需要定义一个结构体来保存数据回调,总把这个结构体命名为PRIVDATA。每个模块都应该定义它自己的PRIVDATA。在 VC++, 这有着让调试器混乱的优点,如果你跟踪一个叫 PRIVDATA 的变量,还想放在调试窗里观察,调试器会不知道你想看哪一个PRIVDATA,只好随便挑一个。

第 55 段(可获 2 积分)

配置文件

它们通常是键值对类型的。那些值将在启动时被加载。最棒的混淆技术是给java变量及键们使用只有轻微不同的名字 把那些运行时也不改变的常量也放在配置文件里。参数文件变量至少需要比比一个简单变量至少多5倍的维护代码才行。

臃肿的类

以最蠢的方法确保你的类有界,确保每个类都引用了外围的,模糊不清的方法和属性。举个例子,一个定义天体轨道几何的类当然应该有计算海洋潮汐时间表的方法,并包含起重机天气模型的属性。不仅在定义类的时候要这么干,而且当然也得在系统代码里找机会调用,比如需要在垃圾场里找吉他的时候。

第 56 段(可获 2 积分)

该死的子类

对于写不可维护的代码,面向对象编程简直是上天送来的礼物。如果你想创建一个类,有10个成员(属性/方法),那你应该考虑这种写法:写一个基类,只有一个成员,继承9层,每层添加一个成员。这样,到最后一层时,你就具有了全部的10个成员。如果可能,将每个类放到不同的文件中,这将更好的膨胀你的 INCLUDEUSES 语句,这样维护人员就不得不用他的编辑器同时打开比10个还多的文件。当然啦,每一次继承都得有对应的实例创建才行。

第 57 段(可获 2 积分)

代码混淆

Sedulously eschew obfuscatory hyperverbosity and prolixity.

(坚决避开混淆,冗长,和啰嗦)

C的混淆

参与网上的C混淆大赛并学习大师的一鳞半爪。

找到一个Forth或者APL大师

在那个世界,代码越简洁,工作方式就越诡异,你就越受人尊重。

来一打

能用两三个辅助变量解决的问题就别用一个变量解决

寻找模糊

永不停止寻找解决常规问题的更模糊的方法。例如,别用数组将整数转换为相应的字符串,要使用类似这样的代码:

第 58 段(可获 2 积分)
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, 0×20, 040。在C或Java里10和010是不同的数(0开头的表示8进制),你也可以充分利用这个特性

造型

把所有数据都以 void * 形式传递,然后再造型为合适的结构。不用结构而是通过位移字节数来造型也很好玩。

嵌套 Switch

Switch 里边还有 Switch,这种嵌套方式是人类大脑难以破解的。

第 59 段(可获 2 积分)

Exploit Implicit Conversion

记住编程语言中所有微妙的隐式转换规则。 充分利用它们。不使用图片变量(picture variable) (in COBOL or PL/I) 也不用常规类型转换 (比如 sprintf in C)。确保使用浮点变量作为下标访问数组,字符作为循环计数器 并对数字使用字符串函数。毕竟,所有这些操作都没问题而且还让你的代码显得很简洁。 任何试图理解他们的维护者都会非常感激你,因为他们必须阅读和学习关于隐式数据类型转换的整个章节, 一篇在遇到你的程序之前他们可能完全忽视的章节。

第 60 段(可获 2 积分)

原生整数

使用组合框时,在switch条件中使用整数而不是定义好的常量

分号!

只要语法允许,多用分号。 举个例子:

if(a);
else;
{
int d;
d = c;
}
;

用八进制

在列好的十进制的数字里夹杂一点八进制数,像这样:

array = new int []
{
111,
120,
013,
121,
};

类型转换

在类型转换时java提供了很多混淆机会。一个简单的例子:如果你要将一个Double转换为字符串,曲折一点,使用 new Double(d).toString() 就比直接 Double.toString(d)要好. 你可以的,当然可以,更曲折一点!避免使用任何大受好评的类型转换方式。转换过程中在堆里再扔点临时对象就更好了。

第 61 段(可获 2 积分)

嵌套

尽可能的嵌套。好的程序员能在一行写10组(),能在一个方法里写出20组{ }。C++ 程序员还有更强大的能力,除了底层代码嵌套之外,还能做预处理嵌套。如果程序印刷出来开始和结尾都不在一页上那就更属于额外的餐后甜点了。 只要有可能, 就把ifs嵌套转换成 [? ]三元表达式。如果它们跨越几行,就更完美了。

直接写数字

如果你有一个有100个元素的数组, 尽可能多地在程序中直接写100。 别写静态常量,也别引用 myArray.length. 为了使改变这个常数更加困难,直接写50而不是100/2,或者99而不是100-1. 你可以进一步伪装100, 把a > 100换成a == 101,把 a >= 100换成a > 99。考虑页面大小, 总行数由of x行 header, y行 body, and z行 footer 组成,通过时而组装,时而分写的方法,你可以更好的利用这些东西来制作陷阱。

第 62 段(可获 2 积分)

这由来已久的技术在拥有两个不相干的数组恰好都有100个元素的程序中尤其有效。如果维护程序员擅自改变其中一个数组的长度,他必须解读程序中所有使用100进行迭代的for循环,以分辨出究竟哪个循环依赖了这个数组。几乎可以肯定,他至少会造成一个错误,希望多年后不会出现另一个。

还有更残忍的变体。能让维护程序员陷入一种虚假的安全感,看上去尽职尽责的命名了常量, 却经常偶尔地"意外"使用100这个值而不是定义好的常量。最最残忍的是, 偶尔使用一些其他无关的,仅仅恰好值是100的常量。现在, 毋庸置疑, 你应该避免任何一致的命名方案,也不在声明数组时用常量定义大小了。

第 63 段(可获 2 积分)

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号字的程序员必须拖动滚动条才能看完这一整行。

第 64 段(可获 2 积分)

异常

我要让你知道一个鲜为人知的秘密. 后台异常的出现是一个错误. 正确编写的代码永不失败,所以异常实际上是不必要的。不要在它们身上浪费时间。子类异常是无能的人知道他们的代码将失败才写的。你可以大大简化你的程序,在整个应用程序 (或main方法) 只写一个 try/catch然后调用System.exit()就行了。有一个完美的标准值得坚持,在每一个方法头声明Exception,不管实际上有没有任何异常要抛。

善用异常

第 65 段(可获 2 积分)

在不需要异常的语句里使用异常。一般使用 ArrayIndexOutOfBoundsException来终止循环就很好。用异常还能帮助你避免方法正常返回结果。

放弃多线程

标题说明一切 (title says it all)

代码标准

了解语言在新闻组里各种各样关于花式的位运算魔法代码的争论 e.g. a=a++; or f(a++,a++); 在你的库里随便放点吧. 在C里, 后果就是前/后自减代码,比如

*++b ? (*++b + *(b-1)) 0

就不是由语言规范定义的,每个编译器都可以自由地按不同的顺序进行解析. 这使它更加致命。同样, 好好利用C和Java移除所有空格后复杂的解析规则.

第 66 段(可获 2 积分)

尽早return

完全遵守no goto,no early returns的建议,也别写标签,这样和至少5层深的 if/else 嵌套再搭配不过了。

不用{}

除非语法要求,否则不用 { } 包围你的 if/else 块。如果你有多层混杂的if/else,再配上误导性的缩进,甚至专家级维修程序员都会中招。为了让这项技术效果最大化,用 Perl. 你甚至还能在代码 行尾 加上ifs, 简直神乎其技。

第 67 段(可获 2 积分)

来自地狱的制表符

永远不要低估你可以通过制表符来造成多少破坏,只需要用制表符代替空格,尤其是公司没规定一个制表符代表几个空格的时候。 让字里行间充满制表符,或者使用工具来完成空格对制表符的转换也很好。

魔法数组

在某些数组位置使用特殊值作为标签。一个好的例子是某个数组的[3][0]元素被用于产生一个齐次坐标系统。

再探魔法数组

如果需要给定类型的多个变量,定义一个数组就行了,然后用下标访问。只有你知道每个下标的意义并且不在文档里记录。也不要费心去用 #define 把下标定义成常数。每个人都应该知道全局变量 widget[15] 是取消按钮. 这种方法一种最新的变体是在汇编中使用绝对地址。

第 68 段(可获 2 积分)

不做美化

不要使用自动的代码整洁(美化)工具保持你的代码规整. 在你的公司用人们在PVCS/CVS (版本控制跟踪工具) 创造了虚假的代码行数的理由说服他们放弃 ,并且每个程序员都应该有他自己的缩进风格,并在他写的任何永远神圣不可侵犯的模块中体现出来。 坚持其他程序员在他们自己的模块中也是这么干的。拒绝美化十分简单,即使要耗费数以百万计的击键来人工对齐,并消耗大量的时间来搞清楚那可怜的,个人风格的,对齐。 只要坚持每个人不只是在存储在公共库里时,还在他们编辑时,这么对齐 。这将让老板为了实现RWAR,禁止自动对齐。没有自动对齐,你可以自由的 意外 错位代码,形成视觉错觉,让看上去正常的方法体比实际上更短或者更长,或者代码运行时才发现else分支匹配了一个出乎意料的if。 e.g.

第 69 段(可获 2 积分)
if(a)
  if(b) x=y;
else x=z;

宏预处理器

它提供了巨大的混淆机会,这项技术的关键点是几层深的嵌套宏展开,你应该把宏命令分散在各个不同文件中,将可执行代码换成宏然后分散到各个 *.cpp 文件中 (即使是那些从来不用的宏),这将使代码变更的编译需求数量最大化 。

混合数组声明

Java 有两种数组声明方式。老版的C也能这么做,老式的 String x[] (混合前后缀表示法) 和新式的 String[] x (纯前缀表示法),如果你想使人迷惑,混用声明e.g.

第 70 段(可获 2 积分)
byte[ ] rowvector, colvector , matrix[ ];

类似于:

byte[ ] rowvector;
byte[ ] colvector;
byte[ ][] matrix;

隐藏错误恢复代码

使用嵌套将函数的错误恢复方法尽可能远离调用的位置。一个简单的例子就是设置了 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 */
        }
第 71 段(可获 2 积分)

伪 C

#define被使用真正的理由是帮助那些熟悉其他类型程序的程序员更好的适应C语言,可能你会这样声明:#define begin { " 或者 " #define end } ,这样就能写出来更有趣的代码了。

混乱的导入

让维护程序员去猜测你使用的方法和类究竟来自那一个包。 不要这样写:

import MyPackage.Read;
import MyPackage.Write;

应该用:

import Mypackage. *;

无论多么晦涩也不完全限定任何方法或类。 让维护程序员尽情猜测你使用的包/类的来源。当然啦,对你帮助最大的导入方式肯定不是全限定导入。

第 72 段(可获 2 积分)

厕所油管(又臭又长?)

在任何情况下,决不能允许在屏幕上出现多于一个函数或过程的代码。实现这一点很简单,使用这个方便的技巧就行: 空行通常用于分离代码的逻辑块。每行本身都一个逻辑块. 在每行之间放上空行。 不要在代码的结尾写注释。 把它放在这一行的上面. 如果你被迫在行尾写注释,在整个文件中选择最长的代码行,加上10个空格,然后将所有其他行注释的开头与该列对齐。

第 73 段(可获 2 积分)

方法顶部的注释需要使用模板,而且最少包含 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:
*/

在文档中放置这么多冗余信息的这项技术,可以让它很快的过时,并有助于负责维护的程序员傻到去相信它。

第 74 段(可获 2 积分)

测试

I don't need to test my programs. I have an error-correcting modem.

(我不需要测试我的程序。我有一个错误纠正调制解调器。)

  • Om I. Baud

把Bug留在程序里,留给新来的维护工程师,留给寻找有趣事情的后来者。一个完美的Bug从不留下任何可能解释本身出现位置或原因的线索, 最懒,但也最有效的方法就是从来不测试你的代码.

拒绝测试

从不测试任何代码,包括错误处理,机器崩溃或系统故障。也不检查操作系统的返回代码。这种代码从来不会被执行,只会减慢测试时间而已。 而且, 你怎么可能测试到代码对硬盘错误,文件读取错误,系统崩溃或其他任何类型问题的处理方式呢。为什么?你怎么可能有一个令人难以置信的不可靠的计算机或能模仿这样事情的测试脚手架呢? 现代硬件永不失败, 谁想只是为了测试目编写代码呢?它没有任何乐趣。如果用户抱怨,,怪操作系统或硬件就行了。 他们不会知道真相的。

第 75 段(可获 2 积分)

永不,绝不做任何性能测试

伙计,如果程序不够快,告诉客户买台更快的机器就好了。如果你做了性能测试,就可能发现性能瓶颈,就可能需要改变算法啊, 就可能需要重构整个产品. 谁想那么做? 而且, 在客户现场上出现的性能问题意味着一次免费体验异国情调的旅行,只要换个新镜头并准备好护照就行了。

别写测试用例

拒绝执行代码覆盖或路径覆盖测试,自动化测试给窝囊废用的。 自己找出哪些功能占你日常使用的90%,并分配90%测试到这些路径。毕竟,这个技术可能只测试你源代码的60%,直接节省了40%的测试工作量.这可以帮助你在项目后期补偿计划量表。很久以后才会有人注意到那些美妙的“营销特性”不工作。 著名的大型软件公司就是这样测试代码的; 你也应该这样. 如果看到这里你还没走,请参阅下一项.

第 76 段(可获 2 积分)

懦夫才测试

勇敢的程序员从不测试。太多的程序员害怕老板,害怕失去工作,害怕客户的指责邮件,害怕被起诉了。这种恐惧麻痹了热情,降低了工作效率. 研究表明,消除测试阶段意味着管理人员可以提前确定上线日期,这对工作计划有着明显帮助。恐惧消失,创新和实验将开花结果。程序员的角色是生成代码,debug是帮助台和维护工程师的事,互相合作嘛。如果我们对我们的编码能力有充分的信心,那么测试就没有必要。从逻辑上看,傻瓜都知道测试解决不了任何技术问题,相对的,这只是一个情感信心的问题。对缺乏信心这个问题,一个更有效的解决方案是完全消除测试并送我们的程序员去上建立自信的课程. 毕竟,如果我们选择做测试,我们就得测试程序的每个变化, 但现在我们只需要送我们的程序员去上一节课来建立自信就成了,这可是惊人的成本效益啊。

第 77 段(可获 2 积分)

确保程序只在调试模式下工作

如果你定义 TESTING 为 1

#define TESTING 1

这给了你一个很好的机会,插入单独的代码段,如

#if TESTING==1
#endif

它可以包含一些不可或缺的代码,比如

x = rt_val;

所以,如果有人把TESTING改成了0,这程序就将停止工作。只要再加上一小点想象力,不仅能做到逻辑混乱,还能让编译器也混乱。

语言的选择

Philosophy is a battle against the bewitchment of our intelligence by means of language.

  • Ludwig Wittgenstein

电脑语言正在变得越来越愚蠢,使用那些新的静态语言简直反人类。 坚持使用你能侥幸找到的最老的语言,如果可以的话,用八进制的语言(像Hans und Frans一样,我不是娘娘腔的男人;我很有男子气概的,我用漆金线连接IBM单元记录设备 (穿孔卡片), 并用手在纸带上打孔实现编码), 汇编不够格,FORTRAN和COBOL也不够格, C和BASIC不不够格, C++也不够格.,全都不够格。

第 78 段(可获 2 积分)

FORTRAN

用 FORTRAN写代码。如果你的老板质疑你,你就告诉他FORTRAN上有很多有用的库可以节约时间,即使用 FORTRAN写的代码的可维护性是0,这种编写不可维护代码的规则是很容易遵循的.

拒绝 Ada

这些技术大约有20%不能用于 Ada,拒绝它吧。如果老板强迫你用,坚持回答别人也不用,并且指出它会让你的大量工具失效,比如 lint 和 plummer,这可都是能有效利用C缺陷的法宝啊。

第 79 段(可获 2 积分)

使用 ASM

把所有公共的效用函数转换成asm.

使用 QBASIC

确保所有重要的库函数用QBASIC编写 , 然后简单的写一个asm包装器就能处理大- >中内存模型映射。

写点汇编

在程序里随机写点汇编多有意思啊,几乎没有人懂,即使是几行也能冷却程序员的维护妄想。

汇编与C

如果你有被C调用的汇编模块,尽可能多的使用它,即使是最微不足道的目的,也要确保充分利用了goto,bcc或者其他迷人的汇编习俗。

第 80 段(可获 2 积分)

不用维护工具

不适用Abundance编码, 也别用任何其他语言的编码工具,它们从一开始就被设计的主要目就是使维护工程师的工作变简单 。 同样,避免使用 Eiffel 或者 Ada,它们从一开始就是为了 在项目进入生产之前捕捉bug 而设计的 。

与人交流

__Hell is other people._ _(地狱就是别人)

  • Jean-Paul Sartre, No Exit, 1934

这里有很多灵光一现的点子,能让那些喋喋不休的维护工程师变的沮丧,能击溃你的老板阻止你写不可维护代码的妄想,甚至煽动起一场关于每个人在库中的代码格式应该是什么样子的争论.

第 81 段(可获 2 积分)

老板全都懂

如果你的老板认为他/她自己20年的FORTRAN编程经验足以指导现代编程,严格遵守他/她的所有建议。这样,老板会信任你。这对你的职业生涯有益,而且你会学到很多新的方法来混淆程序代码。

颠覆帮助台

确保代码充满bug的方法之一是确保维护人员从不了解帮助台. 这就需要颠覆帮助台. 不接电话. 使用自动语音,回复 "感谢你来售后服务热线。 需要人工服务请按“1”或者留下语音留言",忽略请求帮助的邮件,而不是给他们一个可用的电话号码。 对任何问题的标准回复是 " 我认为你的帐户被锁定了,但是有权解锁的人现在不在"

第 82 段(可获 2 积分)

少说话

不用在意下一个“千年虫”。如果你发现了什么东西在悄悄的向着摧毁西半球的所有生命前进,公开讨论它,到我们处在最后四年,恐慌和机遇的关键事件窗口期再说。别把你的发现告诉朋友,同事或者任何其他有能力的人。 在任何情况下也不尝试宣扬这个信息也许暗示着新的或巨大盈利或威胁 。仅仅发送一个普通优先级,术语加密的备忘录给你的上级来履行职责(to cover-your-a$$) . 如果可能的话,就把这段术语加密的信息作为一个不相干又带着更紧迫的业务关注的的纯文本备忘录的附件。 放心,我们都看到了威胁。 安心的睡吧,很久之后,他们将用对数增长的时薪求提前退休的你回来 !

第 83 段(可获 2 积分)

瞎写的微妙

微妙是一件有趣的事, 有时锤子比别的工具更容易微妙。试试微妙的注释,创造一个类,有着类似FooFactory的名字,注释包含对GoF创造模式的引用(最好超链接到假的UML设计文档),但是却在对象创造的过程中什么也不做。就这样尽情的玩弄维护者的预期吧。更微妙的是,让java类有一个受保护的构造器和类似于 Foo f = Foo.newInstance()的方法, 返回新实例,而不是别人预期的单例. 这样微妙的机会是无限的。

第 84 段(可获 2 积分)

在月俱乐部写书

加入写电脑书的俱乐部,选择那些总是忙于写书以至于没空写代码的作者,浏览本地书店寻找那些有很多图却没有代码实例的文章,浏览这些书,学习那些空洞又学术的语言, 可以拿来恐吓那些追在你屁股后面的傻瓜们。你的代码应该富有特色。如果人们理解不了你的词汇,他们就会认为你极有才华而算法深奥。千万避免给你的算法任何易于理解的例子。

第 85 段(可获 2 积分)

重造轮子

你总是想写系统级别的代码,现在,机会来了。忽略标准库,写你自己的。放在简历上也好看。

自己的BNF

总是使用你自己的,独一无二的,BNF无关的命令语法。不要试图在注释里为合法和非法命令提供配套的解释 .那被证明了完全缺乏学术严谨性,铁路运行时刻表从来都不准确。确保没有对从中间过渡符号(真的有合适的意思)到最终的符号(你最终键入的)演变做明显的解释。 永远也不使用字体,颜色,加粗或其他任何视觉线索来帮助阅读者区分这两者。在你自己的BNF规范里使用与程序完全相同的标点符号,这样阅读者就搞不清在你的BNF规范里(...), [...], {...} 或者 "..."究竟是命令的一部分还是提供线索表明那些语法元素是强制性的,可重复的还是可选的。最终,如果他们太愚蠢以至于理解不了你的BNF规范,那他们当然就对你的程序无计可施了。

第 86 段(可获 2 积分)

自己的内存分配

每个人都知道调试动态存储复杂而费时。改造你的存储分配器而不是确保每一个类都没有内存泄露。强迫你的用户定期重启系统来清理堆而不是释放内存。 系统重启是一件很简单的事 -- 比修复所有的内存泄露漏洞简单多了 。只要用户记得定期重启系统,就不会发生运行时堆空间溢出。想象一下,这能改变他们 “一次部署” 的策略!

第 87 段(可获 2 积分)

另类语言的技巧

让你脑残的基本编程方法

  • Edsger Wybe Dijkstra  (译者注:艾兹格·W·迪科斯彻 (Edsger Wybe Dijkstra,1930年5月11日~2002年8月6日)荷兰人。 计算机科学家,毕业就职于荷兰Leiden大学,早年钻研物理及数学,而后转为计算学。曾在1972年获得过素有计算机科学界的诺贝尔奖之称的图灵奖,之后,他还获得过1974年 AFIPS Harry Goode Memorial Award、1989年ACM SIGCSE计算机科学教育教学杰出贡献奖、以及2002年ACM PODC最具影响力论文奖。)

SQL 别名

给数据库表取一到二个字符长度的名称。最好是跟它毫无关系的已有表的表名类似。

SQL 外联 Outer Join

混合各种外部连接语法,让大家看着头皮发麻。

JavaScript Scope

"优化" JavaScript 代码要充分利用一个函数可以在调用者的范围内访问所有本地变量的特性。

Visual Basic 变量定义

将如下定义:

dim Count_num as string
dim Color_var as string
dim counter as integer
第 88 段(可获 2 积分)

改为:

Dim Count_num$, Color_var$, counter%

Visual Basic 疯狂

如果从一个文本文件读取 15 个字符,那么可以使用如下方法来让事情变得复杂:

ReadChars = .ReadChars (29,0)
ReadChar = trim(left(mid(ReadChar,len(ReadChar)-15,len(ReadChar)-5),7))
If ReadChars = "alongsentancewithoutanyspaces"
Mid,14,24 = "withoutanys"
and left,5 = "without"

Delphi/Pascal

不准使用函数和过程。使用 label/goto 语句,然后在代码中乱跳。这足以让他人在跟踪代码时发疯。另外就是让代码在不经意间来回跳跃,没错,就是这个窍门。

第 89 段(可获 2 积分)

Perl

在一行非常非常长的代码结尾使用 if 和 unless 语句。

Lisp

LISP 一个非常梦幻般的语言,用来写不可维护代码。看看下面的这些令人困惑代码片段:

(lambda (*<8-]= *<8-[= ) (or *<8-]= *<8-[= ))
(defun :-] (<) (= < 2))

(defun !(!)(if(and(funcall(lambda(!)(if(and '(< 0)(< ! 2))1 nil))(1+ !))
(not(null '(lambda(!)(if(< 1 !)t nil)))))1(* !(!(1- !)))))

Visual Foxpro

如果一个变量未定义就不用使用,除非你给它赋值。当你检查变量类型时候就会发生这种问题:

第 90 段(可获 2 积分)
lcx = TYPE('somevariable')

lcx 的值将是 'U' 或者 undefined. 但是!如果你给这个变量一个范围并在之后进行赋值,就会产生一个逻辑错误,嗯?!

LOCAL lcx
lcx = TYPE('somevariable')

lcx 的值现在是 'L' 或者 logical. 而后被定义为 FALSE 的值。想想一下把这个东西用来写不可维护的代码!

LOCAL lc_one, lc_two, lc_three... , lc_n
IF lc_one
DO some_incredibly_complex_operation_that_will_neverbe_executed WITH
make_sure_to_pass_parameters
ENDIF

IF lc_two
DO some_incredibly_complex_operation_that_will_neverbe_executed WITH
make_sure_to_pass_parameters
ENDIF

PROCEDURE some_incredibly_complex_oper....
* put tons of code here that will never be executed
* why not cut and paste your main procedure!
ENDIF
第 91 段(可获 2 积分)

杂七杂八的技术

如果你给某人一段程序,你会让他困惑一天;如果你教他们如何编程,你会让他困惑一辈子。

  • Anonymous

不要重编译

让我们从一条可能是有史以来最友好的技巧开始:把代码编译成可执行文件。如果它能用,就在源代码里做一两个微小的改动 — 每个模块都照此办理。但是不要费劲巴拉地再编译一次了。 你可以留着等以后有空而且需要调试的时候再说。多年以后,等可怜的维护代码的程序员更改了代码之后发现出错了,他会有一种错觉,觉得这些肯定是他自己最近修改的。这样你就能让他毫无头绪地忙碌很长时间。

第 92 段(可获 2 积分)

完败调试工具

对于试图用行调试工具追踪来看懂你的代码的人,简单的一招就能让他狼狈不堪,那就是把每一行代码都写得很长。特别要把 then 语句 和 if 语句放在同一行里。他们无法设置断点。他们也无法分清在看的分支是哪个 if 里的。

公制和美制

在工程方面有两种编码方式。一种是把所有输入都转换为公制(米制)计量单位,然后在输出的时候自己换算回各种民用计量单位。另一种是从头到尾都保持各种计量单位混合在一起。总是选择第二种方式,这就是美国之道!

第 93 段(可获 2 积分)

CANI

CANI 是 Constant And Never-ending Improvement 的缩写,持续改进的意思。 要常常对你的代码做出“改进”,并强迫用户经常升级 — 毕竟没人愿意用一个过时的版本嘛。即便他们觉得他们对现有的程序满意了,想想看,如果他们看到你又“完善“了它,他们会多么开心啊!不要告诉任何人版本之间的差别,除非你被逼无奈 — 毕竟,为什么要告诉他们本来永远也不会注意到的一些bug呢?


“关于” 栏 (About Box)

”关于“一栏应该只包含程序名、程序员姓名和一份用法律用语写的版权声明。理想情况下,它还应该链接到几 MB 的代码,产生有趣的动画效果。但是,里边永远不要包含程序用途的描述、它的版本号、或最新代码修改日期、或获取更新的网站地址、或作者的email地址等。这样,所有的用户很快就会运行在各种不同的版本上,在安装N+1版之前就试图安装N+2版。

第 94 段(可获 2 积分)

超级变变变

在两个版本之间,你能做的变更自然是多多益善。你不会希望用户年复一年地面对同一套老的接口或用户界面,这样会很无聊。最后,如果你能在用户不注意的情况下做出这些变更,那就更好了 — 这会让他们保持警惕,戒骄戒躁。 .

将 C 原型放在单个文件中

与常规 header 不同。 这具有双重优点,它要求在每个文件中维持的参数数据类型的改变,并且避免编译器或链接器将检测到类型不匹配的情况。 当从 32 - > 64 位平台移植时,这非常有用。

第 95 段(可获 2 积分)

无需技能

写无法维护代码不需要多高的技术水平。喊破嗓子不如甩开膀子,不管三七二十一开始写代码就行了。记住,管理层还在按代码行数考核生产率,即使以后这些代码里的大部分都得删掉。

只带一把锤子

一招鲜吃遍天,会干什么就吆喝什么,轻装前进。如果你手头只有一把锤子,那么所有的问题都是钉子。

规范体系

有可能的话,忽略当前你的项目所用语言和环境中被普罗大众所接受的编程规范。比如,编写基于MFC 的应用时,就坚持使用STL 编码风格。

第 96 段(可获 2 积分)

翻转通常的 True False 惯例

把常用的 true 和 false 的定义反过来用。这一招听起来平淡无奇,但是往往收获奇效。你可以先藏好下面的定义 :

#define TRUE 0
#define FALSE 1

把这个定义深深地藏在代码中某个没人会再去看的文件里不易被发现的地方,然后让程序做下面这样的比较 :

if ( var == TRUE )
if ( var != FALSE )

某些人肯定会迫不及待地跳出来“修正”这种明显的冗余,并且在其他地方照着常规去使用变量var :

if ( var )

还有一招是为 TRUE 和 FALSE赋予相同的值,虽然大部分人可能会看穿这种骗局。给它们分别赋值 1 和 2 或者 -1 和 0 是让他们瞎忙乎的方式里更精巧的,而且这样做看起来也不失对他们的尊重。你在Java 里也可以用这一招,定义一个叫 TRUE 的静态常量。在这种情况下,其他程序员更有可能怀疑你干的不是好事,因为Java里已经有了内建的标识符 true。

第 97 段(可获 2 积分)

第三方的类库

在你的项目里引用了功能强大的第三方类库,但是却没有去使用他们。虽然有了实践,你却完全不理会那些好的工具,却把那些没用的工具写在你的简历中的“其他工具”部分。

避免使用类库

假装不知道那些在你的开发工具中直接引用的类库。在Visual C++ 的代码中忽略MFC或者STL的存在,却使用手工编码实现所有的字符串和数组;这将有助于保持您的指针技能很牛逼,并且将会自动使你尝试扩展代码成为不可能。

创建一个构建顺序

第 98 段(可获 2 积分)

保证它是如此的精巧以至于没有任何维护人员能让自己的修复通过编译。 保密使用 SmartJ来编译脚本已经过时了的事实。同样的,对javac 编译器也是可用类的事实保密。即使面临死亡的痛苦,也别告诉别人编写和维护一个小而快的java程序来寻找文件并调用sun.tools.javac.Main完成编译是如此的简单。

为编译增添乐趣

用脚本复制法在无序的复数文件夹中完成代码复制就行了! 用不着任何花哨的源代码控制系统来控制代码分支, 这样让你的继任者即使只想找出自己需要的是哪个版本的DoUsefulWork()也变得很难。

第 99 段(可获 2 积分)

收集代码规范

Square Box Suggestions或其他的网站上,尽你所能的找到所有你可能能找到的关于编写可维护代码的建议,然后 ...  ...  对着干!

IDE?我拒绝!

把所有的代码放到makefile里,你的继任者一定会为你的壮举留下深刻印象,因为你只用一个makefile就能生成批处理文件来完成所有头文件的导入和工程构建!这样他们就弄不清任何改动会带来什么影响,也搞不懂怎样才能把工程迁移到IDE中。使用过时的工具来让效果最大化, 比如早就完蛋的根本没有依赖概念的NMAKE。

第 100 段(可获 2 积分)

规避公司的编码规则

某些公司有严格的规定,不允许使用数字标识符,你必须使用预先命名的常量。要挫败这种规定背后的意图太容易了。比如,一位聪明的 C++ 程序员是这么写的:

#define K_ONE 1
#define K_TWO 2
#define K_THOUSAND 999

编译器警告

一定要保留一些编译器警告。在 make 里使用 “-” 前缀强制执行,忽视任何编译器报告的错误。这样,即使维护代码的程序员不小心在你的源代码里造成了一个语法错误,make 工具还是会重新把整个包build 一遍,甚至可能会成功!而任何程序员要是手工编译你的代码,看到屏幕上冒出一堆其实无关紧要的警告,他们肯定会觉得是自己搞坏了代码。同样,他们一定会感谢你让他们有找错的机会。学有余力的同学可以做点手脚让编译器在打开编译错误诊断工具时就没法编译你的程序。当然了,编译器也许能做一些脚本边界检查,但是真正的程序员是不用这些特性的,所以你也不该用。既然你用自己的宝贵时间就能找到这些精巧的bug,何必还多此一举让编译器来检查错误呢?

第 101 段(可获 2 积分)

把 bug 修复和升级混在一起

永远不要发布什么“bug 修复”版本。一定要把 bug 修复和数据库结构变更、复杂的用户界面修改,还有管理界面重写等混在一起。那样的话,升级就变成一件非常困难的事情,人们会慢慢习惯 bug 的存在并开始称他们为特性。那些真心希望改变这些”特性“的人们就会有动力升级到新版本。这样从长期来说可以节省你的维护工作量,并从你的客户那里获得更多收入。

在你的产品发布每个新版本的时候都改变文件结构

第 102 段(可获 2 积分)

没错,你的客户会要求向上兼容性,所以你大可以放心这么做。但是请确保不会有向下兼容性问题。因为这样将会导致客户无法接受新版本,再加上一个合理的Bug修复政策 (见上文),这也会导致一旦升级到一个新的版本,他们就不会再想做任何变动了。如果能想办法让老版本不能识别由新版本创建的文件,那你就会得到额外的加分(这里是反语)。 那样的话,他们不仅不能读取,而且还会拒绝它,认为它不是由同一个应用所创建的文件! 提示:PC版本的文字处理器就是一个很有用的例子,能让我们看清楚这种复杂的行为。

第 103 段(可获 2 积分)

掩饰Bug

不要探究bug产生的深层原因,简单的掩饰一下推到高级线程就好,这是一个很好的智力练习,类似于三维国际象棋,能让未来的维护工程师花好几个小时来辨别究竟是低级线程产生数据时的问题还是高级线程更改变量时产生的问题。对编译器而言,这是一种很好的技术,“多路通行”!你完全可以避免在早期测试中简简单单的修复这个问题,这样在后期测试中就会变得很难修复了。幸运的话,你也用不着向那些负责前端维护的傻蛋们做什么解释。如果前端产生正确的数据反而会让后端崩溃,就更完美了。

第 104 段(可获 2 积分)

使用死锁

为了使用死锁要精巧的避开同步许可。多睡眠(sleep),多测试,多利用那些(没有被volatile修饰)的全局变量。 死锁比系统对象更加容易使用,而且还常见而灵活。

随心所欲的使用代码同步

随机的使用同步控制,即使瞎选的地方不需要。我曾经在一段完全不可能被第二个线程调用的代码块中发现了同步控制(critical)代码块,我批评了代码作者,可他居然信誓旦旦的告诉我critical能证明这段代码,wow,关键!(critical用于同步控制,但英文意思是关键)

第 105 段(可获 2 积分)

优雅降级

如果你的系统包含NT设备驱动,在活动期间让应用程序申请I/O缓冲区并加锁,事后再解锁。如果过早终止该缓冲区锁定,程序将导致 NT 崩溃 . 但是在客户端站点没有人能改变设备驱动程序,所以他们没有选择。

自定义脚本语言

将脚本命令语言加入到您运行时编译字节的客户机/服务器应用程序中

编译器依赖的代码

第 106 段(可获 2 积分)

如果你发现了编译器或者解释器的 Bug,请确保这种行为对让你的代码正常工作有必要。你不应该换另外一个编译器,其他人也不应该。

一个真实的例子

下面是一个研究生写的真实的例子,我们来看看他将不同的实现记录打包到同一个 C 函数中:

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;
}
第 107 段(可获 2 积分)
  • 重写标准库已有的简单功能
  • Realocate这个单词拼写不正确,永远不要低估创造性拼写的力量
  • 毫无理由的创造输入缓冲区的临时副本.
  • 毫无理由的强转. memcpy() 要求 (void*), 所以要强转指针,即使它本身就是 (void*) 。作为奖励就能拿着它到处用了。
  • 从不考虑释放临时内存(temp)。这将造成缓慢的内存泄露,而且只有程序运行多天才会显现出来。
  • 为了以防万一,从缓冲区复制超出必要的长度,这会导致UNIX上的核心转储,但windows没事。
  • 显而易见, os and ns 代表 "old size" 和 "new size".
  • 获取 buf指向的内存后, 填充0\. 不用 calloc(), 因为有人可能重写ANSI规范,这样calloc()就有可能用其它的什么东西而不是0\来填充了。 (从不考虑马上就要用相同长度的数据把 buf 覆盖的事实)
第 108 段(可获 2 积分)

如何修复“未使用的变量”警告

如果编译器发出“未使用的本地变量”警告,不要删除变量,相对的,要使用一种聪明的方法来使用它。我最喜欢的方法是这样:

i = i;

合理的代码行数

不证自明,方法越大越好。 当然,跳转和GOTO也是越多越好。这样,任何代码变更都需要检查大量相关代码。对维护人员而言,它们就是纠缠着一起的意大利面条。. 如果函数真正足够庞大,它将成为维护程序员的哥斯拉怪兽, 无情的踩踏之前没人知道将会发生什么。

第 109 段(可获 2 积分)

1000 个单词的长句,超过 1000 行的函数

让每个方法的代码尽可能的多,希望你从来没有写过任何少于 1000 行的代码,当然也可以使用深度的嵌套:)

丢失关键文件

确保让他们找不到你的某个关键文件,这个最佳做法是在 include 语句中,例如,在你的主模块里有这样的代码:

#include <stdcode.h>

stdcode.h 这个文件当然必须得有,但是在这个文件里我们可以再玩一下:

#include "a:\\refcode.h"

然后 refcode.h 就找不到罗。。。

到处写,没地方读

你至少得有一个变量,到处都有对这个变量进行赋值,但是却从来不会被用到。不幸的是,如果反过来做的话,很多先进的编译器会阻止你这样做,但你仍然可以在 C 或者 C++ 代码中这样玩。

第 110 段(可获 2 积分)

哲学

设计语言的人是编写编译器和系统类的人。 他们设计时自然使他们的工作容易和数学上显得优雅。 然而,每个编译器有10,000个维护程序员。 日常维护程序员在语言设计上绝对没有话语权。 然而,他们编写的代码的总量使编译器中的代码质量变差。

这种精英思想结果的一个例子是JDBC接口。 它使得JDBC实现者工作容易,但是对于维护程序员来说是一个噩梦。 它比30年前SQL出来的FORTRAN接口更笨拙。

第 111 段(可获 2 积分)

如果有人曾经咨询维护程序员,他们会要求隐藏管理细节的方法,以便他们可以看到整个森林的树木。 他们会要求各种快捷方式,所以他们不必键入太多,所以他们可以在屏幕上一次看到更多的程序。 他们会大声抱怨无数微小的时间 - 编译器要求造成的浪费任务。

在这个方向有一些努力如NetRexx,Bali和视觉编辑器(例如IBM的视觉时代是一个开始),可以折叠与当前无关的细节。

第 112 段(可获 2 积分)

鞋匠没有鞋子

想象一个会计师作为客户坚持使用word程序处理他的总账目。 你最好尽力去说服他,他的数据应该结构化。 他需要通过跨域检查进行验证。 如果数据存储在数据库,包括受控同步更新,他可以处理更多的数据,这样你就可以说服他。

想象一下,软件开发者是一个客户。 他坚持用文本编辑器维护他的所有数据(源代码)。 他还没有利用字处理器的颜色,类型大小或字体。

第 113 段(可获 2 积分)

想想如果我们开始将源代码存储为结构化数据可能会发生什么。我们可以以许多替代方式查看相同的源代码,例如作为Java,作为NextRex,作为决策表,作为流程图,作为循环结构骨架(删除细节),作为具有各种级别的细节或删除注释的Java,作为变量和方法调用的高亮显示的Java或者作为具有关于参数名称和/或类型的生成的注释的Java。我们可以在2D中显示复杂的算术表达式,TeX和数学家的方式。您可以看到带有更多或更少括号的代码(取决于您对优先规则的感觉)。括号巢可以使用不同的大小和颜色来帮助眼睛匹配它们。透明覆盖集的更改,您可以选择删除或应用。您可以实时观察您的团队中的其他程序员,在不同的国家工作,修改您也在使用的类中的代码。

第 114 段(可获 2 积分)

你可以利用现代屏幕全彩色的能力得到潜意识的线索, 例如通过自动给每一个包或类分配一部分频谱,使用柔和的色调作为任何引用该类的方法或变量的背景。 你可以给任何标识符的定义加粗显示,使其脱颖而出。

你也许会问什么方法或构造函数会产生一个X类型的对象? 什么方法将接受一个X类型的对象作为一个参数? 在代码中的这一点上,哪些变量是可访问的? 通过单击方法调用或变量引用, 你可以看到它的定义,从而帮助找出一个给定方法的哪个版本实际上会被调用。 你可以要求全局访问所有对一个给定的方法或变量的引用, 每一个被处理后,给他们打勾。 你可以通过点击和按下按钮就写出大量的代码来。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多