无法维护的代码.docx
- 文档编号:20122743
- 上传时间:2023-04-25
- 格式:DOCX
- 页数:28
- 大小:38.70KB
无法维护的代码.docx
《无法维护的代码.docx》由会员分享,可在线阅读,更多相关《无法维护的代码.docx(28页珍藏版)》请在冰豆网上搜索。
无法维护的代码
简介
永远不要(把自己遇到的问题)归因于(他人的)恶意,这恰恰说明了(你自己的)无能。
—拿破仑
为了造福大众,在Java编程领域创造就业机会,兄弟我在此传授大师们的秘籍。
这些大师写的代码极其难以维护,后继者就是想对它做最简单的修改都需要花上数年时间。
而且,如果你能对照秘籍潜心修炼,你甚至可以给自己弄个铁饭碗,因为除了你之外,没人能维护你写的代码。
再而且,如果你能练就秘籍中的全部招式,那么连你自己都无法维护你的代码了!
你不想练功过度走火入魔吧。
那就不要让你的代码一眼看去就完全无法维护,只要它实质上是那样就行了。
否则,你的代码就有被重写或重构的风险!
总体原则
Quidquidlatinedictumsit,altumsonatur.
(随便用拉丁文写点啥都会显得高大上。
)
想挫败维护代码的程序员,你必须先明白他的思维方式。
他接手了你的庞大程序,没有时间把它全部读一遍,更别说理解它了。
他无非是想快速找到修改代码的位置、改代码、编译,然后就能交差,并希望他的修改不会出现意外的副作用。
他查看你的代码不过是管中窥豹,一次只能看到一小段而已。
你要确保他永远看不到全貌。
要尽量让他难以找到他想找的代码。
但更重要的是,要让他不能有把握忽略任何东西。
程序员都被编程惯例洗脑了,还为此自鸣得意。
每一次你处心积虑地违背编程惯例,都会迫使他必须用放大镜去仔细阅读你的每一行代码。
你可能会觉得每个语言特性都可以用来让代码难以维护,其实不然。
你必须精心地误用它们才行。
命名
“当我使用一个单词的时候”HumptyDumpty曾经用一种轻蔑的口气说,“它就是我想表达的意思,不多也不少。
“
–LewisCarroll—《爱丽丝魔镜之旅》,第6章
编写无法维护代码的技巧的重中之重是变量和方法命名的艺术。
如何命名是和编译器无关的。
这就让你有巨大的自由度去利用它们迷惑维护代码的程序员。
妙用 宝宝起名大全
买本宝宝起名大全,你就永远不缺变量名了。
比如Fred就是个好名字,而且键盘输入它也省事。
如果你就想找一些容易输入的变量名,可以试试adsf或者aoeu之类。
单字母变量名
如果你给变量起名为a,b,c,用简单的文本编辑器就没法搜索它们的引用。
而且,没人能猜到它们的含义。
创造性的拼写错误
如果你必须使用描述性的变量和函数名,那就把它们都拼错。
还可以把某些函数和变量名拼错,再把其他的拼对(例如SetPintleOpening和SetPintalClosing),我们就能有效地将grep或IDE搜索技术玩弄于股掌之上。
这招超级管用。
还可以混淆不同语言(比如colour—英国英语,和color—美国英语)。
抽象
在命名函数和变量的时候,充分利用抽象单词,例如it,everything,data,handle,stuff,do,routine,perform和数字,像这样命名的好例子有routineX48,PerformDataFunction,DoIt,HandleStuff还有do_args_method。
首字母大写的缩写
用首字母大写缩写(比如GNU代表GNU’sNotUnix)使代码简洁难懂。
真正的汉子(无论男女)从来不说明这种缩写的含义,他们生下来就懂。
辞典大轮换
为了打破沉闷的编程气氛,你可以用一本辞典来查找尽量多的同义词。
例如display,show,present。
在注释里含糊其辞地暗示这些命名之间有细微的差别,其实根本没有。
不过,如果有两个命名相似的函数真的有重大差别,那倒是一定要确保它们用相同的单词来命名(例如,对于“写入文件”,“在纸上书写”和“屏幕显示”都用print来命名)。
在任何情况下都不要屈服于编写明确的项目词汇表这种无理要求。
你可以辩解说,这种要求是一种不专业的行为,它违反了结构化设计的信息隐藏原则。
首字母大写
随机地把单词中间某个音节的首字母大写。
例如ComputeReSult()。
重用命名
在语言规则允许的地方,尽量把类、构造器、方法、成员变量、参数和局部变量都命名成一样。
更高级的技巧是在{}块中重用局部变量。
这样做的目的是迫使维护代码的程序员认真检查每个实例的作用域。
特别是在Java代码中,可以把普通方法伪装成构造器。
使用非英语字母
在命名中偷偷使用不易察觉的非英语字母,例如
typedefstruct{inti;}ínt;
看上去没啥不对是吧?
嘿嘿嘿…这里的第二个ínt的 í 实际上是东北欧字母,并不是英语中的i。
在简单的文本编辑器里,想看出这一点点区别几乎是不可能的。
巧妙利用编译器对于命名长度的限制
如果编译器只区分命名的前几位,比如前8位,那么就把后面的字母写得不一样。
比如,其实是同一个变量,有时候写成var_unit_update(),有时候又写成var_unit_setup(),看起来是两个不同的函数调用。
而在编译的时候,它们其实是同一个变量var_unit。
下划线,真正的朋友
可以拿_和__作为标示符。
混合多语言
随机地混用两种语言(人类语言或计算机语言都行)。
如果老板要求使用他指定的语言,你就告诉他你用自己的语言更有利于组织你的思路,万一这招不管用,就去控诉这是语言歧视,并威胁起诉老板要求巨额精神损失赔偿。
扩展ASCII字符
扩展ASCII字符用于变量命名是完全合法的,包括 ß,Ð,和ñ 等。
在简单的文本编辑器里,除了拷贝/粘贴,基本上没法输入。
其他语言的命名
使用外语字典作为变量名的来源。
例如,可以用德语单词punkt代替point。
除非维护代码的程序员也像你一样熟练掌握了德语.不然他就只能尽情地在代码中享受异域风情了。
数学命名
用数学操作符的单词来命名变量。
例如:
openParen=(slash+asterix)/equals;
(左圆括号=(斜杠+星号)/等号;)
令人眩晕的命名
用带有完全不相关的感情色彩的单词来命名变量。
例如:
marypoppins=(superman+starship)/god;
(欢乐满人间=(超人+星河战队)/上帝;)
这一招可以让阅读代码的人陷入迷惑之中,因为他们在试图想清楚这些命名的逻辑时,会不自觉地联系到不同的感情场景里而无法自拔。
何时使用i
永远不要把i用作最内层的循环变量。
用什么命名都行,就是别用i。
把i用在其他地方就随便了,用作非整数变量尤其好。
惯例—明修栈道,暗度陈仓
忽视Java编码惯例,Sun自己就是这样做的。
幸运的是,你违反了它编译器也不会打小报告。
这一招的目的是搞出一些在某些特殊情况下有细微差别的名字来。
如果你被强迫遵循驼峰法命名,你还是可以在某些模棱两可的情况下颠覆它。
例如,inputFilename和inputfileName 两个命名都可以合法使用。
在此基础上自己发明一套复杂到变态的命名惯例,然后就可以对其他人反咬一口,说他们违反了惯例。
小写的l看上去很像数字1
用小写字母l标识long常数。
例如10l更容易被误认为是101而不是10L。
禁用所有能让人准确区分uvwwWgq92z5sil17|!
joO08`’”;,.mnnrn{[()]}的字体。
要做个有创造力的人。
把全局命名重用为私有
在A模块里声明一个全局数组,然后在B模块的头文件里再声明一个同名的私有数组,这样看起来你在B模块里引用的是那个全局数组,其实却不是。
不要在注释里提到这个重复的情况。
误导性的命名
让每个方法都和它的名字蕴含的功能有一些差异。
例如,一个叫isValid(x)的方法在判断完参数x的合法性之后,还顺带着把它转换成二进制并保存到数据库里。
伪装
当一个bug需要越长的时间才会暴露,它就越难被发现。
-RoedyGreen(本文作者)
编写无法维护代码的另一大秘诀就是伪装的艺术,即隐藏它或者让它看起来像其他东西。
很多招式有赖于这样一个事实:
编译器比肉眼或文本编辑器更有分辨能力。
下面是一些伪装的最佳招式。
把代码伪装成注释,反之亦然
下面包括了一些被注释掉的代码,但是一眼看去却像是正常代码。
for(j=0;j { total+=array[j+0]; total+=array[j+1]; total+=array[j+2];/*Mainbodyof total+=array[j+3];*loopisunrolled total+=array[j+4];*forgreaterspeed. total+=array[j+5];*/ total+=array[j+6]; total+=array[j+7]; } 如果不是用绿色标出来,你能注意到这三行代码被注释掉了么? 用连接符隐藏变量 对于下面的定义 #definelocal_varxy_z 可以把“xy_z”打散到两行里: #definelocal_varxy\ _z//local_varOK 这样全局搜索xy_z的操作在这个文件里就一无所获了。 对于C预处理器来说,第一行最后的“\”表示继续拼接下一行的内容。 文档 任何傻瓜都能说真话,而要把谎编圆则需要相当的智慧。 -SamuelButler(1835–1902) 不正确的文档往往比没有文档还糟糕。 -BertrandMeyer 既然计算机是忽略注释和文档的,你就可以在里边堂而皇之地编织弥天大谎,让可怜的维护代码的程序员彻底迷失。 在注释中撒谎 实际上你不需要主动地撒谎,只要没有及时保持注释和代码更新的一致性就可以了。 只记录显而易见的东西 往代码里掺进去类似于 /*给i加1*/ 这样的注释,但是永远不要记录包或者方法的整体设计这样的干货。 记录How而不是Why 只解释一个程序功能的细节,而不是它要完成的任务是什么。 这样的话,如果出现了一个bug,修复者就搞不清这里的代码应有的功能。 该写的别写 比如你在开发一套航班预定系统,那就要精心设计,让它在增加另一个航空公司的时候至少有25处代码需要修改。 永远不要在文档里说明要修改的位置。 后来的开发人员要想修改你的代码? 门都没有,除非他们能把每一行代码都读懂。 计量单位 永远不要在文档中说明任何变量、输入、输出或参数的计量单位,如英尺、米、加仑等。 计量单位对数豆子不是太重要,但在工程领域就相当重要了。 同理,永远不要说明任何转换常量的计量单位,或者是它的取值如何获得。 要想让代码更乱的话,你还可以在注释里写上错误的计量单位,这是赤裸裸的欺骗,但是非常有效。 如果你想做一个恶贯满盈的人,不妨自己发明一套计量单位,用自己或某个小人物的名字命名这套计量单位,但不要给出定义。 万一有人挑刺儿,你就告诉他们,你这么做是为了把浮点数运算凑成整数运算而进行的转换。 坑 永远不要记录代码中的坑。 如果你怀疑某个类里可能有bug,天知地知你知就好。 如果你想到了重构或重写代码的思路,看在老天爷的份上,千万别写出来。 切记电影《小鹿斑比》里那句台词“如果你不能说好听的话,那就什么也不要说。 ”。 万一这段代码的原作者看到你的注释怎么办? 万一老板看到了怎么办? 万一客户看到了怎么办? 搞不好最后你自己被解雇了。 一句”这里需要修改“的匿名注释就好多了,尤其是当看不清这句注释指的是哪里需要修改的情况下。 切记“难得糊涂”四个字,这样大家都不会感觉受到了批评。 说明变量 永远不要对变量声明加注释。 有关变量使用的方式、边界值、合法值、小数点后的位数、计量单位、显示格式、数据录入规则等等,后继者完全可以自己从程序代码中去理解和整理嘛。 如果老板强迫你写注释,就在方法体里胡乱多写点,但绝对不要对变量声明写注释,即使是临时变量! 在注释里挑拨离间 为了阻挠任何雇佣外部维护承包商的倾向,可以在代码中散布针对其他同行软件公司的攻击和抹黑,特别是可能接替你工作的其中任何一家。 例如: /*优化后的内层循环 这套技巧对于SSI软件服务公司的那帮蠢材来说太高深了,他们只会 用 */ classclever_SSInc { ... } 可能的话,除了注释之外,这些攻击抹黑的内容也要掺到代码里的重要语义部分,这样如果管理层想清理掉这些攻击性的言论然后发给外部承包商去维护,就会破坏代码结构。 程序设计 编写无法维护代码的基本规则就是: 在尽可能多的地方,以尽可能多的方式表述每一个事实。 -RoedyGreen 编写可维护代码的关键因素是只在一个地方表述应用里的一个事实。 如果你的想法变了,你也只在一个地方修改,这样就能保证整个程序正常工作。 所以,编写无法维护代码的关键因素就是反复地表述同一个事实,在尽可能多的地方,以尽可能多的方式进行。 令人高兴的是,像Java这样的语言让编写这种无法维护代码变得非常容易。 例如,改变一个被引用很多的变量的类型几乎是不可能的,因为所有造型和转换功能都会出错,而且关联的临时变量的类型也不合适了。 而且,如果变量值要在屏幕上显示,那么所有相关的显示和数据录入代码都必须一一找到并手工进行修改。 类似的还有很多,比如由C和Java组成的Algol语言系列,Abundance甚至Smalltalk对于数组等结构的处理,都是大有可为的。 Java造型 Java的造型机制是上帝的礼物。 你可以问心无愧地使用它,因为Java语言本身就需要它。 每次你从一个Collection里获取一个对象,你都必须把它造型为原始类型。 这样这个变量的类型就必须在无数地方表述。 如果后来类型变了,所有的造型都要修改才能匹配。 如果倒霉的维护代码的程序员没有找全(或者修改太多),编译器能不能检测到也不好说。 类似的,如果变量类型从short变成int,所有匹配的造型也都要从(short)改成(int)。 利用Java的冗余 Java要求你给每个变量的类型写两次表述。 Java程序员已经习惯了这种冗余,他们不会注意到你的两次表述有细微的差别,例如 Bubblegumb=newBubblegom(); 不幸的是++操作符的盛行让下面这种伪冗余代码得手的难度变大了: swimmer=swimner+1; 永远不做校验 永远不要对输入数据做任何的正确性或差异性检查。 这样能表现你对公司设备的绝对信任,以及你是一位信任所有项目伙伴和系统管理员的团队合作者。 总是返回合理的值,即使数据输入有问题或者错误。 有礼貌,无断言 避免使用assert()机制,因为它可能把三天的debug盛宴变成10分钟的快餐。 避免封装 为了提高效率,不要使用封装。 方法的调用者需要所有能得到的外部信息,以便了解方法的内部是如何工作的。 复制粘贴修改 以效率的名义,使用复制+粘贴+修改。 这样比写成小型可复用模块效率高得多。 在用代码行数衡量你的进度的小作坊里,这招尤其管用。 使用静态数组 如果一个库里的模块需要一个数组来存放图片,就定义一个静态数组。 没人会有比512X512更大的图片,所以固定大小的数组就可以了。 为了最佳精度,就把它定义成double类型的数组。 傻瓜接口 编写一个名为“WrittenByMe”之类的空接口,然后让你的所有类都实现它。 然后给所有你用到的Java内置类编写包装类。 这里的思想是确保你程序里的每个对象都实现这个接口。 最后,编写所有的方法,让它们的参数和返回类型都是这个WrittenByMe。 这样就几乎不可能搞清楚某个方法的功能是什么,并且所有类型都需要好玩的造型方法。 更出格的玩法是,让每个团队成员编写它们自己的接口(例如WrittenByJoe),程序员用到的任何类都要实现他自己的接口。 这样你就可以在大量无意义接口中随便找一个来引用对象了。 巨型监听器 永远不要为每个组件创建分开的监听器。 对所有按钮总是用同一个监听器,只要用大量的if…else来判断是哪一个按钮被点击就行了。 好事成堆TM 狂野地使用封装和OO思想。 例如 myPanel.add(getMyButton()); privateJButtongetMyButton() { returnmyButton; } 这段很可能看起来不怎么好笑。 别担心,只是时候未到而已。 友好的朋友 在C++里尽量多使用friend声明。 再把创建类的指针传递给已创建类。 现在你不用浪费时间去考虑接口了。 另外,你应该用上关键字private和protected来表明你的类封装得很好。 使用三维数组 大量使用它们。 用扭曲的方式在数组之间移动数据,比如,用arrayA里的行去填充arrayB的列。 这么做的时候,不管三七二十一再加上1的偏移值,这样很灵。 让维护代码的程序员抓狂去吧。 混合与匹配 存取方法和公共变量神马的都要给他用上。 这样的话,你无需调用存取器的开销就可以修改一个对象的变量,还能宣称这个类是个”JavaBean”。 对于那些试图添加日志函数来找出改变值的源头的维护代码的程序员,用这一招来迷惑他尤其有效。 没有秘密! 把每个方法和变量都声明为public。 毕竟某个人某天可能会需要用到它。 一旦方法被声明为public了,就很难缩回去。 对不? 这样任何它覆盖到的代码都很难修改了。 它还有个令人愉快的副作用,就是让你看不清类的作用是什么。 如果老板质问你是不是疯了,你就告诉他你遵循的是经典的透明接口原则。 全堆一块 把你所有的没用的和过时的方法和变量都留在代码里。 毕竟说起来,既然你在1976年用过一次,谁知道你啥时候会需要再用到呢? 当然程序是改了,但它也可能会改回来嘛,你”不想要重新发明轮子”(领导们都会喜欢这样的口气)。 如果你还原封不动地留着这些方法和变量的注释,而且注释写得又高深莫测,甭管维护代码的是谁,恐怕都不敢对它轻举妄动。 就是Final 把你所有的叶子类都声明为final。 毕竟说起来,你在项目里的活儿都干完了,显然不会有其他人会通过扩展你的类来改进你的代码。 这种情况甚至可能有安全漏洞。 java.lang.String被定义成final也许就是这个原因吧? 如果项目组其他程序员有意见,告诉他们这样做能够提高运行速度。 避免布局 永远不要用到布局。 当维护代码的程序员想增加一个字段,他必须手工调整屏幕上显示所有内容的绝对坐标值。 如果老板强迫你使用布局,那就写一个巨型的GridBagLayout并在里面用绝对坐标进行硬编码。 全局变量,怎么强调都不过分 如果上帝不愿意我们使用全局变量,他就不会发明出这个东西。 不要让上帝失望,尽量多使用全局变量。 每个函数最起码都要使用和设置其中的两个,即使没有理由也要这么做。 毕竟,任何优秀的维护代码的程序员都会很快搞清楚这是一种侦探工作测试,有利于让他们从笨蛋中脱颖而出。 再一次说说全局变量 全局变量让你可以省去在函数里描述参数的麻烦。 充分利用这一点。 在全局变量中选那么几个来表示对其他全局变量进行操作的类型。 局部变量 永远不要用局部变量。 在你感觉想要用的时候,把它改成一个实例或者静态变量,并无私地和其他方法分享它。 这样做的好处是,你以后在其他方法里写类似声明的时候会节省时间。 C++程序员可以百尺竿头更进一步,把所有变量都弄成全局的。 配置文件 配置文件通常是以关键字=值的形式出现。 在加载时这些值被放入Java变量中。 最明显的迷惑技术就是把有细微差别的名字用于关键字和Java变量.甚至可以在配置文件里定义运行时根本不会改变的常量。 参数文件变量和简单变量比,维护它的代码量起码是后者的5倍。 子类 对于编写无法维护代码的任务来说,面向对象编程的思想简直是天赐之宝。 如果你有一个类,里边有10个属性(成员/方法),可以考虑写一个基类,里面只有一个属性,然后产生9层的子类,每层增加一个属性。 等你访问到最终的子类时,你才能得到全部10个属性。 如果可能,把每个类的声明都放在不同的文件里。 编码迷局 迷惑C 从互联网上的各种混乱C语言竞赛中学习,追随大师们的脚步。 追求极致 总是追求用最迷惑的方式来做普通的任务。 例如,要用数组来把整数转换为相应的字符串,可以这么做: char*p; switch(n) { case1: p="one"; if(0) case2: p="two"; if(0) case3: p="three"; printf("%s",p); break; } 一致性的小淘气 当你需要一个字符常量的时候,可以用多种不同格式: ‘‘,32,0×20,040。 在C或Java里10和010是不同的数(0开头的表示8进制),你也可以充分利用这个特性。 造型 把所有数据都以void*形式传递,然后再造型为合适的结构。 不用结构而是通过位移字节数来造型也很好玩。 嵌套Switch Switch里边还有Switch,这种嵌套方式是人类大脑难以破解的。 利用隐式转化 牢记编程语言中所有的隐式转化细节。 充分利用它们。 数组的索引要用浮点变量,循环计数器用字符,对数字执行字符串函数调用。 不管怎么说,所有这些操作都是合法的,它们无非是让源代码更简洁而已。 任何尝试理解它们的维护者都会对你感激不尽,因为他们必须阅读和学习整个关于隐式数据类型转化的章节,而这个章节很可能是他们来维护你的代码之前完全忽略了的。 分号! 在所有语法允许的地方都加上分号,例如: if(a); else; { intd; d=c; } ; 使用八进制数 把八进制数混到十进制数列表里,就像这样: array=newint[] { 111, 120, 013, 121, }; 嵌套 尽可能深地嵌套。 优秀的程序员能在一行代码里写10层(),在一个方法里写20层{}。 C数组 C编译器会把myArray[i]转换成*(myArray+i),它等同于*(i+myArray)也等同于i[myArray]。 高手都知道怎么用好这个招。 可以用下面的函数来产生索引,这样就把代码搞乱了: intmyfunc(intq,intp){returnp%q;} ... myfunc(6291,8)[Array]; 遗憾的是,这一招只能在本地C类里用,Java还不行。 放长线钓大鱼 一行代码里堆的东西越多越好。 这样可以省下临时变量的开销,去掉换行和空格还可以缩短源文件大小。 记住,要去掉运算符两边的空格。 优秀的程序员总是能突破某些编辑器对于255个字符行宽的限制。 异常 在这里我要向你传授一个编程领域里鲜为人知的秘诀。 异常是个讨厌的东西。 良好的代码永远不会出错,所以异常实际上是不必要的。 不要把时间浪费在这上面。 子类异常是给那些知道自己代码会出错的低能儿用的。 在整个应用里,你只用在main()里放一个try/catch,里边直接调用System.exit(
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 无法 维护 代码
![提示](https://static.bdocx.com/images/bang_tan.gif)