永遠不要把無能歸咎于作惡 ------ 拿破侖
為了獲得在Java編程領(lǐng)域工作的機會,我把這些來自于大師們關(guān)于如何編寫難以維護代碼的技巧傳達給大家。如果你使用這些技巧,那些后來繼承你工作的人即使做最簡單的改動也要花費幾年的時間。進一步,如果你能遵守所有的這些規(guī)則,你將能保證一輩子都不會被解雇,因為除了你已經(jīng)沒有人能維護這些操蛋的代碼。甚至,如果你虔誠地遵守所有這些規(guī)則,連你自己也沒辦法維護這些代碼!你怕不怕?
你不需要做的太過火了。你的代碼不應(yīng)該看起來毫無維護的可能性,就讓它們那樣。否則,它們可能面臨著被重寫或者重構(gòu)的風險。
總則
Quidquid latine dictum sit, altum sonatur.- Whatever is said in Latin sounds profound.
為了打擊維護代碼的程序員,你應(yīng)該了解他是怎么想的。他有你偉大的源代碼。他沒有時間全部讀一遍,理解的部分就更少了。他希望快速找到哪里可以進行他的修改,加入他的代碼,然后退出,并且不帶來任何的副作用。
他從一個衛(wèi)生紙卷筒中窺視你的代碼。他一次只能看到一小片你的代碼。你應(yīng)該確保他這樣做是不可能看到代碼的宏圖框架的。你應(yīng)該讓他難以找到他想要找的代碼。更重要的是,你應(yīng)該盡力讓他難以安全地忽視任何細節(jié)。
程序員總是被約定麻痹。偶爾違反下約定,你就能強迫得他們用一個放大鏡一行一行讀你的代碼。
可能你已經(jīng)得到讓所有編程語言變得不可維護的訣竅了。---- 除此之外,就是適當?shù)臑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."Lewis Carroll -- Through the Looking Glass, Chapter 6
編寫不可維護代碼的一大技巧就是命名變量和函數(shù)的藝術(shù)。命名對編譯器沒有意義。但是給了你迷惑代碼維護程序員的巨大空間。
買一本給小孩起名字的參考書,這樣你在給變量命名時絕對不會吃虧。Fred是一個非常不錯的名字,也很容易輸入。如果你在找容易輸入的變量名字,試試asdf或者如果你用的DSK排列的鍵盤,aoeu也不錯。
如果你給你的變量命名為a,b,c,那么別人不可能在簡單的文本編輯器里搜索這些變量實例。甚至,沒人能夠猜測到這些變量是干什么的。如果有人喜歡打破從Fortran用i,j和k來用做遍歷變量的傳統(tǒng),改成用ii,jj和kk,我要警告他們想想西班牙宗教法庭對異教徒們做了什么。
如果你必須描述性的變量和函數(shù)名,錯誤地拼寫他們。有時錯誤有時正確的拼寫(比如SetPintleOpening 和SetPintalClosing)我們可以高效的搞死grep命令或者IDE的搜索功能。這個非常的神奇。往不同的threater/theater添加國際范的tory或者tori拼寫也是非常神奇的一件事。
在命名函數(shù)和變量時,大量使用抽象的詞語,比如everything,data,handle,stuff,do,routine,perform和數(shù)字等。例如,routineX48,PerformDataFunction,DoIt,HandlerStuff和do_args_method。
使用首字母縮寫詞來保持代碼簡潔。真正的人類從來不定義首字母縮寫詞,他們生來就知道首字母縮寫詞的意義。
為了打破無聊,用詞典盡可能多地查詢那些表示同一動作的詞匯列表。比如,display,show,present。含糊地暗示這些動作詞匯有細微的差別,但實際上根本沒有。當然,如果真的有差別的函數(shù),就用同一個詞來描述(比如,用print同時表示“寫文件”,“打印到紙上”和“顯示在屏幕上”)。不管什么場景,都不要屈服于使用沒有歧義的特殊用途的專有詞匯。這樣做非常不專業(yè)地違反了結(jié)構(gòu)化設(shè)計準則中的信息隱藏原則。(譯注:此處應(yīng)該是調(diào)侃信息隱藏,信息隱藏實際應(yīng)該指的是數(shù)據(jù)封裝時盡可能少暴露內(nèi)部信息)
使用其他語言的復(fù)數(shù)方式
用一個虛擬機腳本記錄不同計算機(vaxes故意寫成vaxen)數(shù)據(jù)(states故意寫成statii)。世界通用語,克林貢語和霍比特語被認為是一種語言就是這么干的。對于偽世界語的復(fù)數(shù)形式,加上oj。你也可以為世界和平作出貢獻。(譯注:這一段有點難翻譯,大意是按照自己的想法隨意地加復(fù)數(shù)后綴,就像影視小說里面的異世界語言一樣。這會讓人很難理解)
CapiTaliSation(字母大寫)
隨機大寫單詞中的音節(jié)字母。比如,ComputeRasterHistoGram()
.
重用名字
只要語法規(guī)則允許,就給類,構(gòu)造函數(shù),函數(shù),變量,參數(shù)和局部變量起相同的名字。另外,給{}中的局部變量起相同的名字。目的就是讓維護者不得不仔細地檢查每個實例的作用域。特別對于Java,把普通函數(shù)偽裝成構(gòu)造函數(shù)。
使用重音字母
在變量名里使用重音符號,比如
typedef struct { int i; } ínt;
這里第二個int的i實際上是重音的i。在簡單的文本編輯器里,幾乎不可能分辨出這個傾斜的重音符號。
利用編譯器能識別的變量名長度限制
例如假設(shè)編譯器只識別前8個字符,那么var_unit_update和var_unit_setup都會被認為和var_unit一樣。
下劃線,其實是一個朋友
用 '_' 和 '__' 作標識符。
隨機點綴使用兩種語言(人類語言和計算機語言)。如果你的老板堅持你使用他的語言,你就告訴他使用你自己的語言你能更好地組織你的想法?;蛘呷绻菢诱f不好使,就說這是語言歧視,然后威脅說要控告他們索賠一大筆錢。
擴展ASCII字符
擴展ASCII字符完全是合法的變量字符,包括 ?, D, 和 ? 字符。這些字符除了復(fù)制粘貼幾乎不可能在一個簡單的編輯器里編輯。
使用其他語言的名字
使用其他外國語言的字典作為變量起名的來源。例如,使用法語的punkt作為point。代碼維護者沒有你對法語的理解,將會非常享受這次多元文化秘密探索之旅。
用數(shù)學(xué)運算符號做名字
使用偽裝成數(shù)學(xué)運算符的變量名,例如:
openParen = (slash + asterix) / equals;
(譯注:單詞意思是‘左圓括號’ = (‘斜線’ + ‘星號’) / ‘等號’)
使用使人眼花繚亂的名字
選擇那些有不相關(guān)情感內(nèi)涵的詞,例如:
marypoppins = (superman + starship) / god;
(譯注:歡樂滿人間(電影名) = (超人 + 星際飛船)/ 上帝)
每個人對詞匯的情感內(nèi)涵都有不同的的理解,所以這樣會讓讀代碼的人很困惑。
重命名和重復(fù)使用
這個技巧在Ada語言中工作的尤其好。Ada是一種免除了現(xiàn)代模糊技術(shù)的語言。命名你現(xiàn)在使用的對象和包的那些人都是白癡。與其通知他們修改,不如按照自己的發(fā)明進行重命名和子類型重命名所有的名字。當然保留一些原來的名字,作為一個粗心的陷阱。
何時使用i
千萬不要把i用作最里層的循環(huán)變量。公平地把i用在任何其他地方,尤其是非整型變量的地方。類似的,用n做循環(huán)變量。
約定
忽略Sun公司的Java編碼約定。幸運的是,當你違反這些約定時編譯器也不會泄露你的秘密。目標就是想出只在大小寫有細微差別的的命名。如果不得不準守大小寫約定,你依然可以在一些模糊定義的地方顛覆這些約定。例如,同時用inputFilename 和 inputfileName。發(fā)明一套你自己的復(fù)雜到令人絕望的名字約定,然后嚴厲地斥責那些不遵守這個約定的人。
小寫的l很像數(shù)字1
用小寫的l來標識long類型常數(shù)。例如10l 更容易被誤解成101。不使用那些能清楚分辨 uvw
, wW
,gq9
, 2z
, 5s
, il17|!j
, oO08
, `'"
, ;,.
, m nn rn
, and {[()]} 的字體。發(fā)揮你創(chuàng)造性。
重用全局名字做為私有變量名
在模塊A定義一個全局數(shù)組,在模塊B的頭文件定義一個同樣名字的私有數(shù)組變量。這樣似乎你在模塊B用的是全局數(shù)組,但其實不是。不要在注釋里提及這里的復(fù)用。
循環(huán)訪問
用一種矛盾的方式盡可能迷惑地循環(huán)使用變量名作用域。例如,假設(shè)有全局變量A和B,和函數(shù)foo和bar。A一般傳遞給foo,B傳遞給bar。那么把foo函數(shù)定義成foo(B)和bar函數(shù)定義成bar(A)。這樣在函數(shù)內(nèi)部,A會被當成B,B別當成A。等有更多的函數(shù)和全局變量,你就可以生成一張互相矛盾地使用相同名字的變量組成的大網(wǎng)。
回收利用變量
只要作用域允許,重復(fù)利用已經(jīng)存在的不相關(guān)的變量名。類似的,對于兩個不相關(guān)的用途使用相同的臨時變量(對外聲稱是節(jié)約??臻g)。就像惡魔變體一樣對變量進行變化。例如,在一個很長的函數(shù)的前面給變量賦一個值,然后在中間某個地方再賦一個值,同時細微地改變變量的含義。比如,從以0為基準改成以1為基準。當然,不要把這個改變寫進文檔。
在變量和函數(shù)名里使用縮寫時,對同一個單詞用不同變體打破無聊,甚至偶爾速記一把。這幫助了打敗那些使用文本搜索去理解你代碼某個方面的懶漢。例如,混合國際方式拼寫的colour和美國方式拼寫color以及花花公子口中的kulerz。如果你全拼這些名字,只有一種拼寫方式,這樣太容易讓代碼維護者記住了。因為有很多不同的簡寫同一個單詞的方式,使用簡寫你可以有很多不同的變量表示同一個表面上的意思。作為一個附加的福利,代碼維護者可能甚至都沒注意到他們是不同的變量。
誤導(dǎo)人的命名
確保每個函數(shù)都比它的名字表達出的意思多做或者少做一點。舉個簡單的例子,一個名為isValid(x)的函數(shù)有個把x轉(zhuǎn)成二進制且存進數(shù)據(jù)庫的副作用。
m_
一個來自C++的命名約定是在變量名前使用m_前綴。這個約定只要你“遺忘性地”在函數(shù)也加個m,就可以告訴他們這不是函數(shù)。
o_apple obj_apple
在每個類實例前加o或者obj前綴,以表明你想表示一個巨大的,多形態(tài)的宏圖。
匈牙利命名法是代碼混淆技術(shù)的戰(zhàn)術(shù)核武器, 使用它! 讓大量的源代碼被這種習(xí)語污染,沒有什么能比這種精心策劃的匈牙利命名法攻擊更快的干掉一名維護工程師了! 以下的提示將幫助你從本質(zhì)上腐蝕匈牙利命名法 :
在C++或其他允許使用常量性變量的語言中堅持使用 "c" 作為常量
尋找并使用那些只在你使用的語言里沒有特殊意義的有(keng)趣(die)字符(匈牙利毒瘤/ Hungarian warts )。比如說堅持在PowerBuilder上使用l_
and a_
{local and argument} 做變量名前綴,比如說堅持在寫給C++的控件類型上顯現(xiàn)出一種有趣的VB風格,堅持忽略數(shù)以兆計的MFC開放代碼從來不使用那些有(keng)趣(die)字符做控件類型的事實。
最違反匈牙利原則的是最常用的變量總是隨身攜帶最少的信息. 很好的貫徹上述技術(shù)的方法是堅持為每一個類加上一個自定義的前綴。也不要讓任何人告訴你,一般沒有前綴的才是類!這條規(guī)則的重要性,毫不夸張的說,如果你不能堅持的話,源代碼很快就會被具有較高元音/輔音比的短變量名淹沒。 最壞的情況,將導(dǎo)致代碼充滿自重復(fù)英語符號和各種模糊的崩潰!
悍然違反匈牙利風格的設(shè)計理念要求函數(shù)參數(shù)和其他常見符號必須被賦予一個有意義的名字,但是真正的匈牙利毒瘤風格完全由自己臨時創(chuàng)造一個完美的變量名字。
堅持給你自己的有(keng)趣(die)字符攜帶一些完全無關(guān)的信息,考慮這個真實的例子,a_crszkvc30LastNameCol。
它讓一整組維護工程師花了三天才發(fā)現(xiàn)這個大家伙是(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)的縮寫!當適當?shù)慕Y(jié)合“所有的變量應(yīng)該是公共的”這一原則,這種技術(shù)有能力使數(shù)千行的代碼立即作廢!
為了完美利用人腦只能同時容納七條信息的原則,你可以寫出具有以下特性的代碼:
Monam
)和周五下午(FriPM
)的代碼(譯者:例如周一上午使用Monam_apk,周五下午使用FriPM_pqd
)再說匈牙利命名法
一個匈牙利命名法中后續(xù)的招數(shù)是“改變變量的類型但是不改變變量名”。這幾乎是從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就是參數(shù)的位寬就是64位。但是以前的w和l前綴,就呵呵了。
減少,重用,再利用
如果你為回調(diào)函數(shù)定義一個結(jié)構(gòu)體來存儲數(shù)據(jù),把結(jié)構(gòu)體命名為PRIVDATA。每個模塊都可以定義它自己的PRIVDATA。在vc++里,這樣做有個好處就是迷惑代碼調(diào)試者。因為有很多PRIVATA展示在watch窗口,他不知道你指的是哪個PRIVATA,所以他只能任選一個。
晦澀地參考電影情節(jié)
用LancelotsFavouriteColour(譯注:Lancelot最喜歡的顏色)替代blue給常量命名,并且賦值十六進制的$0204FB。這種顏色在屏幕上看起來和純藍色完全一樣,但是這樣命名會讓代碼維護者不得不執(zhí)行程序(或者使用某些圖像工具)才能知道是什么樣。只有那些熟悉“巨蟒劇團”和《圣杯的故事》才知道Lancelot最喜歡的顏色是藍色。如果代碼維護者想不起來所有巨蟒劇團的電影,那他或者她就沒資格做一個程序員。
The longer it takes for a bug to surface, the harder it is to find. - Roedy Green
編寫不可維護代碼一大部分技巧都是偽裝的藝術(shù)。很多技巧都基于代碼編譯器能夠比人眼和文本編輯器更好地分辨代碼這一事實上。這里列舉一些最佳偽裝技術(shù)。
代碼和注釋相互偽裝
來一些第一眼看上去是注釋的代碼。
for(j=0; j<array_len; j+ =8){total += array[j+0 ];total += array[j+1 ];total += array[j+2 ]; /* Main body oftotal += array[j+3]; * loop is unrolledtotal += array[j+4]; * for greater speed.total += array[j+5]; */total += array[j+6 ];total += array[j+7 ];}
如果沒有編輯器顏色區(qū)分代碼,你能注意到那三行已經(jīng)別注釋掉了嗎?
struct/union 和 typedef struct/union 在C(不是C++)中是不同的命名空間。在這兩個名字空間里給結(jié)構(gòu)體和枚舉使用相同的名字。如果可能的話,讓他們幾乎一模一樣。
typedef struct {char* pTr;size_t lEn;} snafu;struct snafu {unsigned cNtchar* pTr;size_t lEn;} A;
把宏定義隱藏在垃圾注釋中間。程序員就會因為無聊放棄讀這些注釋,于是就發(fā)現(xiàn)不了這些宏。確保這些宏替換看起來像一個完全合法的賦值操作,例如:
#define a=b a=0-b
繁忙假象
用define語句裝飾編造出來的函數(shù),給這些函數(shù)一堆參數(shù)注釋,例如:
#define fastcopy(x,y,z) /*xyz*/// ...fastcopy(array1, array2, size); /* does nothing */
用延長技術(shù)隱藏變量
把
#define local_var xy_z
中xy_z替代成2行的
#define local_var xy_z // local_var OK
這樣,全局搜索xy_z會什么也搜不到。對于C的預(yù)編譯器來說,行末的"\"符號會把這一行和下一行合并到一起。
偽裝成關(guān)鍵字的任意名字
寫文檔的時候,隨意用一個file之外的詞來重復(fù)文件名。不要用那些看起來就特別隨意的名字,比如“Charlie.dat”或者“Frodo.txt”。一般情況下,用那些看起來更像保留的關(guān)鍵字的詞。例如,給參數(shù)和變量起的比較好的名字有bank,blank, class, const, constant, input, key, keyword, kind, output, parameter,parm, system, type, value, var 和 variable。如果你用真正的保留關(guān)鍵字,編譯器或者處理器會報錯。這個技巧如果用得好,程序員會在真正的關(guān)鍵字和你起的名字之間感到絕望般的困惑。但是你還是可以看起來非常無辜,你聲稱這樣命名是為了幫助他關(guān)聯(lián)每個變量的用途。
代碼命名不匹配跟屏幕顯示的名字
選擇跟顯示在屏幕上的名字完全沒有聯(lián)系的變量名。例如,屏幕上顯示標簽名字是“Postal Code”,但是在代碼里,變量起名為zip。
不要改變命名
用多個TYPEDEF給同一個符號起別名的方式替代全局同步重命名兩個區(qū)塊的代碼。
如何隱藏被禁止的全局變量
由于全局變量被認為是‘罪惡的’,你可以定義一個結(jié)構(gòu)體容納所有的全局內(nèi)容。機智地把這個結(jié)構(gòu)體命名為類似EverythingYoullEverNeed(你所需要的每件事)的名字。讓所有的函數(shù)都有一個指向這個結(jié)構(gòu)體的指針(命名為handle可以迷惑更多)。這樣會給別人你不是在使用全局變量的印象,你是通過一個handle還訪問每件事的。然后定義一個靜態(tài)實例,這樣所有的代碼就都是訪問同一個副本了。
代碼維護者修改代碼之后,為了查看是否會有什么級聯(lián)影響,會用變量名進行一次全局搜索。這可以通過同義詞輕松化解,例如
#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語句應(yīng)該分散在不同的頭文件中。這些頭文件如果分散在不同目錄效果更佳。另外一種技術(shù)是在每個塊中重用變量名。編譯器可以把他們分辨開來,但是簡單的混合式文本搜索區(qū)分不了。不幸的在接下來幾年到來的SCID(譯注:Source Code in Database,一種將代碼預(yù)解析然后存在數(shù)據(jù)庫的技術(shù))將會使得這項簡單的技術(shù)不起作用,因為編輯器將會和編譯器一樣支持區(qū)塊規(guī)則。
每個長長的變量或者類名都不相同,只差一個字符或者只有大小寫。一個完美的例子是swimmer和swimner。由于大多數(shù)字體的 ilI1|
或者 oO08 差別很小,利用這點,使用標識符例如parselnt
和 parseInt
或者 D0Calc
和 DOCalc 做變量名是一個絕好的主意。因為隨便一眼看去,都像是常量1.在很多字體里,rn看起來很像一個m。所以你知道變量swirnrner可以怎么做了。也可以制造一些名字僅僅差在大小寫的變量名,例如HashTable
和 Hashtable。
只有大小寫或者下劃線差別的變量可以很好的讓那些用發(fā)音或者單詞拼寫來記憶變量而不是精確拼寫的人感到凌亂。
在C++中,用#define重載庫函數(shù)。這樣看起來就像是你在使用一個很熟悉的庫函數(shù),但是實際上是完全不相同的東西。
例如,在C++中,把+,-,*,和/重載和加減完全不相關(guān)的操作。既然Stroustroup(C++作者)可以用移位操作進行I/O操作,為什么你不能那樣充滿創(chuàng)意呢?如果你重載+,一定要重載成i = i + 5 和i += 5的意義完全不同。來個一個例子將符號重載混亂上升到一個更高的藝術(shù)。為類重載!操作符,但是這個重載跟取反或者取否完全沒關(guān)。讓這個操作符返回一個整數(shù)。這樣,為了或者一個布爾值,你必須用??!。而為了反轉(zhuǎn)這個布爾值,[此處應(yīng)有掌聲]你就必須用!??!。不要混淆返回布爾0或者1的!操作符和按位取反的~邏輯操作符。
重載 new
操作符 - 比重載 +-/* 危險多了。這可能會造成巨大浩劫
如果重載后的東西做了一些與它本來的功能完全不同的事情, (但是重要的對象功能很難改變)。確保用戶創(chuàng)建動態(tài)實例難于登天。你還能表演一下這個有趣的戲法,不僅有個成員功能,還有個成員變量,都叫"New"。
C++自帶的#define
給混淆帶來了豐富的可能性,甚至值得寫篇文章來贊美一下。 使用小寫 #define
變量就能與原生變量開一場假面舞會。你的預(yù)處理器函數(shù)從不用參數(shù),全局的#defines
就能完成一切嘛。我聽過的其中預(yù)處理器的最富有想象力的使用方法是這樣的, 在代碼編譯之前導(dǎo)入五次CPP。通過巧妙運用 defines
和 ifdefs
,能形成一個大師級的混淆方式,就是根據(jù)頭文件的導(dǎo)入次數(shù)做完全不同的事。一個頭文件包含另一個頭文件時就更有意思了。這是一個特別狡猾的例子:
#ifndef DONE#ifdef TWICE// put stuff here to declare 3rd time aroundvoid g(char* str);#define DONE#else // TWICE#ifdef ONCE// put stuff here to declare 2nd time aroundvoid g(void* str);#define TWICE#else // ONCE// put stuff here to declare 1st time aroundvoid g(std::string str);#define ONCE#endif // ONCE#endif // TWICE#endif // DONE
當傳遞 g()
一個char*時會非常有趣,因為調(diào)用不同版本的g()
將取決于包含頭的次數(shù)。
編譯器指令的設(shè)計目的是使同一代碼的行為完全不同。 反復(fù)和有力的打開和關(guān)閉布爾短路指令,以及長字符串指令。
任何傻瓜都能說真話,但需要有些許理智的人,才懂得如何圓謊。 - 塞繆爾·巴特勒(1835 - 1902)
不正確的文檔通常比沒有文檔更糟糕。 - Bertrand Meyer
由于計算機忽略注釋和文檔,你可以肆無忌憚,盡你所能的來迷惑那些可憐的維護代碼的程序員。
你不必主動說謊,只需使注釋與最新的代碼保持不一致就行了。
為代碼添加一些類似于/ * add 1 to i * /的注釋,但是,絕不要為代碼記錄類似于包或方法的整體目的的東西。
僅記錄程序的細節(jié),而不是它試圖完成什么。 這樣,如果有一個錯誤,改代碼的程序員將不知道代碼應(yīng)該做什么。
例如,如果您正在編寫航空預(yù)訂系統(tǒng),如果您要添加另一家航空公司,請確保在代碼中至少有25個地方需要修改。 不要記錄他們在哪兒改動的。 那些在你之后接手代碼的人,在沒有徹底理解代碼的每一行之前是無法開展修改代碼的工作。
考慮用于允許代碼的自動化文檔的函數(shù)文檔原型。 這些原型應(yīng)該從一個函數(shù)(或方法或類)復(fù)制到另一個,但不會填充字段。 如果由于某種原因被迫填寫字段,請確保所有參數(shù)的名稱對于所有函數(shù)都是相同的,并且所有注意事項都是相同的,但是當然不與當前函數(shù)相關(guān)。
當實現(xiàn)一個非常復(fù)雜的算法,使用經(jīng)典的軟件工程原理做一個聲音設(shè)計編碼之前。 編寫一個非常詳細的設(shè)計文檔,描述一個非常復(fù)雜的算法中的每個步驟。 本文件越詳細越好。 事實上,設(shè)計文檔應(yīng)該將算法分解成結(jié)構(gòu)化步驟的層次結(jié)構(gòu),在文檔中自動編號的單個段落的層次結(jié)構(gòu)中描述。 使用至少5級深度的標題。 確保當你完成后,你完全破壞了結(jié)構(gòu),有超過500個這樣的自動編號的段落。 例如,一個段落可能是(這是一個真實的例子)
1.2.4.6.3.13 - 顯示所有可能的應(yīng)用(短偽代碼....),
然后... (這是在發(fā)牢騷) 當你寫代碼時,對每一個段落都寫一個對應(yīng)的全局函數(shù),就叫
Act1_2_4_6_3_13()
不用給這些函數(shù)專門寫文檔了。 畢竟,設(shè)計文檔里都有!
設(shè)計文檔自動編號以來,就極難和代碼保持同步更新了, (當然,因為函數(shù)名是靜態(tài)的, 不是自動編號的) 。其實這根本不是問題,因為你從沒想過文檔同步更新這回事。事實上, 你還能做更多的事來摧毀設(shè)計文檔可能有的任何用途。
那些在你后面來的人應(yīng)該只能找到一兩個矛盾的早期草稿的設(shè)計文件,這些文件隱藏在靠近死機的286電腦附近一些灰塵的貨架上。
不要記錄任何變量,輸入,輸出或參數(shù)的測量單位。 例如 英尺,米,紙箱。 這在bean計數(shù)中不是那么重要,但它在工程工作中非常重要。 作為推論,從不記錄任何轉(zhuǎn)換常數(shù)的度量單位,或如何導(dǎo)出值。 這是溫和的作弊,但非常有效,在代碼中混合一些不正確的計量單位的注釋。 如果你感覺特別惡毒,補上自己的計量單位; 把它命名為你自己或一些模糊的人,永遠不要定義它。 如果有人挑戰(zhàn)你,告訴他們你這樣做,以便可以使用整數(shù)而不是浮點運算。
永遠不要為陷阱代碼寫文檔。 如果你懷疑在一個類里可能有bug, 不要理睬它,自己知道就好。如果你對于這些代碼該如何重組或重寫的話, 聽我的,不要把他們寫出來。 記得在電影“小鹿斑比”有一句話: “如果你不能說什么好的, 就什么都不要說。” 嗎? 萬一那個寫這段代碼的人看到你的評論呢? 又或者如果公司的領(lǐng)導(dǎo)看到了呢? 或者是客戶看到了呢?你可能會因此丟掉工作的。 一個人匿名留下的評論“這需要被修復(fù)”可能會產(chǎn)生疑問, 特別是在當這段評論所指的對象不明的情況下。 保持模糊,沒有人會覺得自己被批評。
永遠不要 為一個變量聲明寫注釋。像關(guān)于這個變量如何使用,它的界限、合法值、隱式或者顯式的十進制的值、度量單位、顯示格式、數(shù)據(jù)輸入規(guī)則(比如:都填入,必須輸入),以及什么值是可以信任的,諸如此類信息,都應(yīng)該可以通過程序代碼獲取到。 如果你的老板強制你寫注釋,只用給方法寫注釋就可以了,但是不要對一個變量聲明做注釋,即使是臨時的也不要寫!
不建議任何企圖使用外部維護承包商的行為,通過在你的代碼里帶有引用侮辱其他領(lǐng)先的軟件公司的言論,特別是任何被承包過來工作的人。比如:
/* 內(nèi)循環(huán)優(yōu)化對在軟件服務(wù)公司的笨蛋而言,這個員工太聰明了,他通過利用<math.h>中垃圾一樣的功能節(jié)約了50倍的時間和內(nèi)存.*/class clever_SSInc { .. . }
如果可能的話,在代碼的重要區(qū)塊侮辱這個職員,調(diào)整注釋的位置,這樣管理者在急著把代碼發(fā)給他自己的管理者的時候,還想把這塊注釋刪掉的話,就會破壞代碼原有的功能。
拒絕接受科技設(shè)備競賽進展。尤其是 SCIDs。有謠言說所有的函數(shù)和變量聲明一點就能出來,還說被Visual Studio 6.0開發(fā)出來的代碼是被維護工程師用 edlin 或者 vi 維護的,別信它們。堅持嚴格的注釋規(guī)則也會埋葬代碼工作者樂趣。
對一個叫makeSnafucated的函數(shù)寫個/* make snafucated */
就行了。別告訴別人snafucated是anywhere的意思. 這還用說嘛,笨蛋才不知道snafucated的意思呢。 Sun的AWT技術(shù)文檔,就是這種技術(shù)的經(jīng)典例子。
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.
寫不可維護代碼的基本規(guī)則是把要素在盡可能多的地方用盡可能多的方式反復(fù)的說。
- Roedy Green
寫可維護代碼的關(guān)鍵是把程序的所有要素列在同一個地方。改變你的想法,應(yīng)該把它們列在同一個地方,并保證程序仍然工作。因此,寫不可維護性代碼的關(guān)鍵是在盡可能多的地方,盡可能多的方式一遍又一遍的列舉要素。歡呼吧,像Java那樣的語言,有自己的獨特方式,能讓編寫不可維護的代碼變得更加簡單。舉個例子,它幾乎不可能改變外部引用,因為所有的造型和數(shù)據(jù)類型轉(zhuǎn)換都不工作,并且相關(guān)臨時變量也不合用, 進一步, 如果變量顯示在屏幕上, 所有相關(guān)聯(lián)的變量和數(shù)據(jù)條目都要人工修飾并手動跟蹤。Algol語言家族,包括C和Java,能用數(shù)組,哈希表,普通文件和不同類型的數(shù)據(jù)庫存儲數(shù)據(jù)。類似Abundance的語言,擴展了Smalltalk的,語法都很獨特。僅僅是改變聲明,利用Java的遲鈍,把會慢慢溢出RAM的數(shù)據(jù),放到數(shù)組里。這樣維護工程師就有了一個把數(shù)組轉(zhuǎn)成文件的可怕任務(wù)。同樣的,在數(shù)據(jù)庫中放小文件,這樣維護工程師在性能調(diào)優(yōu)時,就又有了把它們轉(zhuǎn)成數(shù)組的樂趣。
Java 的類型轉(zhuǎn)換方法算是上帝賜予你的禮物。你可以毫無內(nèi)疚的使用這個技術(shù),因為語言本身需要它。每當你從集合中獲取一個對象時你必須將之轉(zhuǎn)換成其原始的類型。雖然變量的類型可以在很多地方指定。如果類型在后來發(fā)生了變化,那么所有轉(zhuǎn)換的操作都必須與之匹配。而且編譯器無法幫助那個倒霉的維護程序員檢測到這些變化(因為實在太多了)。同樣的,例如一個變量從 short 變成 int ,那么所有匹配的轉(zhuǎn)換需要相應(yīng)的改動。不過越來越多的開發(fā)人員使用泛型來解決這種問題。而且可以確定的是這個異端將不會進入語言的規(guī)范中。在 RFE 114691 中投票反對并在泛型上消除任意轉(zhuǎn)換的需要(編者注:最后這兩句話意思有點亂)。
Java 要求你必須為每個變量指定兩次的類型。Java 程序員已經(jīng)習(xí)慣了這種多余的操作,他們一般不會注意到這兩種類型略有不同。例如:
Bubblegum b = new Bubblegom();
不幸的是 ++ 操作的受歡迎程度使得你很難逃避如下代碼的偽冗余代碼:
swimmer = swimner + 1;
從不對任何輸入的數(shù)據(jù)進行驗證,不管數(shù)據(jù)是否正確或者是否有差異。這表明你絕對相信公司的設(shè)備,以及你正在一個完美的團隊中工作。所有合作伙伴和系統(tǒng)運營商都是可信任的,他們提供的數(shù)據(jù)都是合理的。
有禮貌,無斷言
避免使用 assert() 機制,因為它可能把三天的debug盛宴變成10分鐘的快餐。
避免封裝
為了提高效率,不要使用封裝。方法的調(diào)用者需要所有能得到的外部信息,以便了解方法的內(nèi)部是如何工作的。
復(fù)制粘貼修改
以效率的名義,使用 復(fù)制+粘貼+修改。這樣比寫成小型可復(fù)用模塊效率高得多。在用代碼行數(shù)衡量你的進度的小作坊里,這招尤其管用。
使用靜態(tài)數(shù)組
如果一個庫里的模塊需要一個數(shù)組來存放圖片,就定義一個靜態(tài)數(shù)組。沒人會有比512 X 512 更大的圖片,所以固定大小的數(shù)組就可以了。為了最佳精度,就把它定義成 double 類型的數(shù)組。
傻瓜接口
編寫一個名為 “WrittenByMe” 之類的空接口,然后讓你的所有類都實現(xiàn)它。然后給所有你用到的Java 內(nèi)置類編寫包裝類。這里的思想是確保你程序里的每個對象都實現(xiàn)這個接口。最后,編寫所有的方法,讓它們的參數(shù)和返回類型都是這個 WrittenByMe。這樣就幾乎不可能搞清楚某個方法的功能是什么,并且所有類型都需要好玩的造型方法。更出格的玩法是,讓每個團隊成員編寫它們自己的接口(例如 WrittenByJoe),程序員用到的任何類都要實現(xiàn)他自己的接口。這樣你就可以在大量無意義接口中隨便找一個來引用對象了。
巨型監(jiān)聽器
永遠不要為每個組件創(chuàng)建分開的監(jiān)聽器,而是對所有按鈕使用同一個監(jiān)聽器,然后用大量的 if…else 來判斷是哪一個按鈕被點擊就行了。
好事成堆TM
大膽使用封裝和 OO 思想。例如
myPanel.add( getMyButton() );private JButton getMyButton() { return myButton; }
這段很可能看起來并不那么有趣。別擔心,只是還沒到嗨點。
好朋友
在 C++ 里盡量多使用友好聲明。再把創(chuàng)建類的指針傳遞給已創(chuàng)建類?,F(xiàn)在你不用浪費時間去考慮接口。另外,你應(yīng)該用上關(guān)鍵字 private 和 protected 來表明你的類封裝得很好。
大量使用三維數(shù)組,然后用不一樣的方式在數(shù)組之間移動數(shù)據(jù),比如,用 arrayA 的行去填充 arrayB 的列,再加上 1 的偏移值。這么做足以讓程序員抓狂。
存取方法和公共變量同時用上。這樣一來,你無需調(diào)用存取器的開銷就可以修改一個對象的變量,還能宣稱這個類是個”Java Bean”。程序員可能會試圖添加日志函數(shù)來找出改變值的源頭,但這一招會讓他迷惑不已。
無論什么時候,你使用別人寫的代碼,都要用至少一次的包裝器把別人的臟代碼與自己的隔離開。畢竟,其他作者 可能未來某個時間不顧一切地重命名了所有方法。到時候你怎么辦?當然有辦法。 如果他這樣做,寫個包裝器就能將代碼與更改隔離了,或者也可以讓VAJ來處理全局性的重命名。然后,這是一個完美的借口,在別人做任何錯事之前,先發(fā)制人的用一個中間層來切斷和他的聯(lián)系。Java的一個主要缺點是:如果不做愚蠢的方法包裝,除了調(diào)用同名方法之外,就做不了任何事,即使只是想解決一個小問題。這意味著完全可能寫一個四層深的包裝卻什么也不做,沒人會注意到這一點的。為了最大化混淆效果,每一層都重命名方法,找個同義詞典,挑點同義詞。這會讓人產(chǎn)生幻覺,以為你做了什么。 進一步,重命名有助于確保項目術(shù)語缺乏一致性。為了確保沒有人試圖把你的包裝調(diào)整到一個合適的層次,在每一層都找點你自己的代碼調(diào)用一下。
在單獨源文件的函數(shù)定義中,確保所有 API 函數(shù)至少包裝了 6-8 次。 你也可以使用 #defines 的快捷方式來實現(xiàn)對這些函數(shù)的包裝。
把每個方法和變量都聲明為 public。畢竟總會有人有用得到它的時候。一旦方法被聲明為 public 了,就很難回退。這樣任何它覆蓋到的代碼都很難再修改。它還有個有趣的小功能,就是讓你看不清類的作用是什么。如果老板質(zhì)問你是不是瘋了,你就可以告訴他,你遵循的是經(jīng)典的透明接口原則。
這種技術(shù)具有驅(qū)動程序包的任何用戶或記錄器以及維護程序員分心的附加優(yōu)點。 創(chuàng)建十幾個重載變量的同一方法,只有最細微的細節(jié)不同。 我認為是奧斯卡王爾德,觀察到Kama Sutra的位置47和115是相同的,除了在115的地方女人的手指是交叉的。 包的用戶必須仔細地閱讀長列表的方法,以找出使用哪個變量。 該技術(shù)還使文檔膨脹化,從而確保其更可能過時。 如果老板問你為什么這樣做,解釋它只是為了方便用戶。 再次為了充分達到效果,克隆任何共同的邏輯,坐下來等待它的副本逐漸失去同步。
交換參數(shù),把這樣的函數(shù) drawRectangle(height, width)
,變成 drawRectangle(width, height)
,并且除了名字之外不做任何改變。幾個版本后,再換回來。這樣維護人員根本不能通過普通的函數(shù)調(diào)用就快速發(fā)現(xiàn)方法已經(jīng)被改變了。一般而言這是是留給讀者作練習(xí)用的。
與其將參數(shù)用于單個方法,不如盡可能的創(chuàng)建多個單獨的方法 。舉個例子,有一個方法是 setAlignment(int alignment)
,如果 alignment
是枚舉常量, 有著 left, right, center三個實例,那就創(chuàng)建三個方法 setLeftAlignment
, setRightAlignment
, 和 setCenterAlignment
. 當然,為了效果最大化,你必須拷貝這些相同的代碼邏輯使得它們難以同步。
盡可能的多寫靜態(tài)變量。如果在程序里,你不需要某個類多于一個的實例,別人也不會需要的。重復(fù),如果別人抱怨,告訴他們這樣存取速度更快。
學(xué)習(xí)嘉吉猜想(我覺得嘉吉是男的) "任何設(shè)計問題都可以通過增加中間層次解決,即使是中間層太多的問題"。 分解面向?qū)ο蟮某绦?,直到搞不清究竟哪個對象負責改變程序狀態(tài)為止。 更好的是,安排所有的回調(diào)事件激活都要通過指針森林,就是那個包含了程序可能用到的所有函數(shù)指針的森林。激活遍歷森林的一個副作用就是,在釋放對象引用計數(shù)指針之前就創(chuàng)造一個深拷貝,即使實際上不需要深拷貝。
全堆一塊
把你所有的沒用的和過時的方法和變量都留在代碼里。畢竟說起來,如果你在 1976 年用過一次,誰知道你啥時候會需要再用到呢?雖然程序是改了,但它也可能會改回來嘛,你就說”不想重新發(fā)明輪子”(領(lǐng)導(dǎo)們都會喜歡這樣的口氣)。如果你還原封不動地留著這些方法和變量的注釋,而且注釋寫得又高深莫測,甭管維護代碼的是誰,恐怕都不敢對它輕舉妄動
把所有的葉子類都聲明為 final。畢竟說起來,你在項目里的活兒都干完了,顯然不會有其他人會通過擴展你的類來改進你的代碼。這種情況甚至可能有安全漏洞。 java.lang.String 被定義成 final 也許就是這個原因吧?如果項目組其他程序員有意見,告訴他們這樣做能夠提高運行速度。
鄙視Java的接口吧! 如果你的上級工程師抱怨,就告訴他們Java強迫你在兩個不同的類(同樣實現(xiàn)了這個接口)之間"剪切"代碼,這樣他們就知道有多難維護了。 而且,像Java AWT設(shè)計者一樣工作,寫一大堆功能,但只能被子類調(diào)用,再在方法里加上一大堆"instanceof"類型檢查. 這樣,如果有人想重用你的代碼,他們必須繼承你的類。如果他們想重用你的屬于兩個不同的類的代碼 - 真不走運,他們不能同時繼承兩個類! 如果非得用接口,起一個萬能的名字,比如ImplementableIface
。另一個學(xué)術(shù)界珍珠是給類命名為被實現(xiàn)接口的名稱+“Impl”。這也能提供巨大的幫助。 e.g. 讓類實現(xiàn) Runnable
接口。
永遠不用布局管理器。 這樣當維護程序員添加一個或更多組件時,他必須手動調(diào)整屏幕上每一個組件的絕對位置。 如果你老板強迫你使用布局管理器, 就用個大的 GridBagLayout
, 然后硬編碼網(wǎng)格坐標。
如果你必須為其他程序員編寫類,在你的類里添加環(huán)境檢查代碼(getenv()
in C++ / System.getProperty()
in Java),放在靜態(tài)代碼塊里,然后搞到所有需要的參數(shù),而不是通過構(gòu)造器接收。這種方法的優(yōu)點是初始化工作發(fā)生的盡可能早,甚至與類文件被加載一樣早,甚至比類實例化更早,甚至在main()
之前就被執(zhí)行了。換句話說,在你的類文件讀取之前,程序的其余部分根本不能更改這些參數(shù) - 就是說用戶最好把他們自己的環(huán)境變量設(shè)置的和你設(shè)置的一樣!
避免任何形式的表驅(qū)動邏輯。 開始看上去再簡單不過了, 但是最終使得用戶會對校對不寒而栗,甚至是簡單的修改表格。
在Java里,所有私有數(shù)據(jù)類型是以傳值的方式傳參,所有傳遞的參數(shù)真的是只讀的。 調(diào)用者可以隨便修改參數(shù),被調(diào)用者不會受到任何影響。 相對的,被傳遞過來的對象是可讀寫的。引用是按值傳遞的,但被引用的對象是同一個。 調(diào)用者可以對傳過來的對象字段做任何想做的事。不用記錄方法事實上有沒有修改傳遞過來的對象。給你的方法起一個好名字,使它看上去僅僅是查看了對象的值,但實際上卻進行了修改。
別使用異常來處理進程錯誤,把你的錯誤信息存到一個全局變量里。確保系統(tǒng)的每一個長循環(huán)都檢查全局標簽,并在發(fā)現(xiàn)異常時終止。再添加一個全局變量用來標識用戶點擊了'重置'按鈕。當然了,系統(tǒng)里的所有主要循環(huán)也得檢查第二個標簽。 還有些不需要終止的循環(huán),隱藏起來吧。
如果上帝不想讓我們使用全局變量,就不會發(fā)明它了。 別讓上帝失望,盡可能多的設(shè)置和使用全局變量吧。每個函數(shù)都應(yīng)該用上幾個,即使實際上不需要也行。畢竟,任何好的維護工程師都會很快發(fā)現(xiàn)這是一次偵探鍛煉,他也會很開心的意識到,這個鍛煉能測試出一個人究竟是不是優(yōu)秀的維護工程師,抑或只是個新來的而已。
全局變量完全把你從指定函數(shù)參數(shù)的工作中解救出來了。利用這個優(yōu)勢。 選擇這些全局變量中的一個或多個指定其他進程要做什么。維護工程師居然愚蠢的認為C函數(shù)沒有副作用。 確保他們想不到你在全局變量存儲結(jié)果和內(nèi)部狀態(tài)信息。
在C里, 函數(shù)應(yīng)該是冪等的(沒有副作用)(冪等:同一個函數(shù)在參數(shù)相同的情況下執(zhí)行結(jié)果相同)。我希望這個提示足夠明顯。
在循環(huán)體里,假裝循環(huán)必定成功,而且會立即更新所有的指針變量。如果隨后在循環(huán)過程中出現(xiàn)了一個異常, 回退 被作為循環(huán)體條件并步進的指針。
(這一段正文和上一段正文完全一樣,可能是文章上傳的時候就錯了?)
在循環(huán)體里,假裝循環(huán)必定成功,而且會立即更新所有的指針變量。如果隨后在循環(huán)過程中出現(xiàn)了一個異常, 回退 被作為循環(huán)體條件并步進的指針。
如果你需要定義一個結(jié)構(gòu)體來保存數(shù)據(jù)回調(diào),總把這個結(jié)構(gòu)體命名為PRIVDATA
。每個模塊都應(yīng)該定義它自己的PRIVDATA
。在 VC++, 這有著讓調(diào)試器混亂的優(yōu)點,如果你跟蹤一個叫 PRIVDATA
的變量,還想放在調(diào)試窗里觀察,調(diào)試器會不知道你想看哪一個PRIVDATA
,只好隨便挑一個。
它們通常是鍵值對類型的。那些值將在啟動時被加載。最棒的混淆技術(shù)是給java變量及鍵們使用只有輕微不同的名字 把那些運行時也不改變的常量也放在配置文件里。參數(shù)文件變量至少需要比比一個簡單變量至少多5倍的維護代碼才行。
以最蠢的方法確保你的類有界,確保每個類都引用了外圍的,模糊不清的方法和屬性。舉個例子,一個定義天體軌道幾何的類當然應(yīng)該有計算海洋潮汐時間表的方法,并包含起重機天氣模型的屬性。不僅在定義類的時候要這么干,而且當然也得在系統(tǒng)代碼里找機會調(diào)用,比如需要在垃圾場里找吉他的時候。
對于寫不可維護的代碼,面向?qū)ο缶幊毯喼笔巧咸焖蛠淼亩Y物。如果你想創(chuàng)建一個類,有10個成員(屬性/方法),那你應(yīng)該考慮這種寫法:寫一個基類,只有一個成員,繼承9層,每層添加一個成員。這樣,到最后一層時,你就具有了全部的10個成員。如果可能,將每個類放到不同的文件中,這將更好的膨脹你的 INCLUDE
和 USES
語句,這樣維護人員就不得不用他的編輯器同時打開比10個還多的文件。當然啦,每一次繼承都得有對應(yīng)的實例創(chuàng)建才行。
Sedulously eschew obfuscatory hyperverbosity and prolixity.
(堅決避開混淆,冗長,和啰嗦)
參與網(wǎng)上的C混淆大賽并學(xué)習(xí)大師的一鱗半爪。
在那個世界,代碼越簡潔,工作方式就越詭異,你就越受人尊重。
能用兩三個輔助變量解決的問題就別用一個變量解決
永不停止尋找解決常規(guī)問題的更模糊的方法。例如,別用數(shù)組將整數(shù)轉(zhuǎn)換為相應(yīng)的字符串,要使用類似這樣的代碼:
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是不同的數(shù)(0開頭的表示8進制),你也可以充分利用這個特性
把所有數(shù)據(jù)都以 void * 形式傳遞,然后再造型為合適的結(jié)構(gòu)。不用結(jié)構(gòu)而是通過位移字節(jié)數(shù)來造型也很好玩。
Switch 里邊還有 Switch,這種嵌套方式是人類大腦難以破解的。
記住編程語言中所有微妙的隱式轉(zhuǎn)換規(guī)則。 充分利用它們。不使用圖片變量(picture variable) (in COBOL or PL/I) 也不用常規(guī)類型轉(zhuǎn)換 (比如 sprintf
in C)。確保使用浮點變量作為下標訪問數(shù)組,字符作為循環(huán)計數(shù)器 并對數(shù)字使用字符串函數(shù)。畢竟,所有這些操作都沒問題而且還讓你的代碼顯得很簡潔。 任何試圖理解他們的維護者都會非常感激你,因為他們必須閱讀和學(xué)習(xí)關(guān)于隱式數(shù)據(jù)類型轉(zhuǎn)換的整個章節(jié), 一篇在遇到你的程序之前他們可能完全忽視的章節(jié)。
使用組合框時,在switch條件中使用整數(shù)而不是定義好的常量
只要語法允許,多用分號。 舉個例子:
if(a);else;{int d;d = c;};
在列好的十進制的數(shù)字里夾雜一點八進制數(shù),像這樣:
array = new int []{111,120,013,121,};
在類型轉(zhuǎn)換時java提供了很多混淆機會。一個簡單的例子:如果你要將一個Double轉(zhuǎn)換為字符串,曲折一點,使用 new Double(d).toString()
就比直接 Double.toString(d)
要好. 你可以的,當然可以,更曲折一點!避免使用任何大受好評的類型轉(zhuǎn)換方式。轉(zhuǎn)換過程中在堆里再扔點臨時對象就更好了。
盡可能的嵌套。好的程序員能在一行寫10組(),能在一個方法里寫出20組{ }
。C++ 程序員還有更強大的能力,除了底層代碼嵌套之外,還能做預(yù)處理嵌套。如果程序印刷出來開始和結(jié)尾都不在一頁上那就更屬于額外的餐后甜點了。 只要有可能, 就把ifs嵌套轉(zhuǎn)換成 [? ]三元表達式。如果它們跨越幾行,就更完美了。
如果你有一個有100個元素的數(shù)組, 盡可能多地在程序中直接寫100。 別寫靜態(tài)常量,也別引用 myArray.length
. 為了使改變這個常數(shù)更加困難,直接寫50而不是100/2,或者99而不是100-1. 你可以進一步偽裝100, 把a > 100
換成a == 101
,把 a >= 100
換成a > 99
。考慮頁面大小, 總行數(shù)由of x行 header, y行 body, and z行 footer 組成,通過時而組裝,時而分寫的方法,你可以更好的利用這些東西來制作陷阱。
這由來已久的技術(shù)在擁有兩個不相干的數(shù)組恰好都有100個元素的程序中尤其有效。如果維護程序員擅自改變其中一個數(shù)組的長度,他必須解讀程序中所有使用100進行迭代的for循環(huán),以分辨出究竟哪個循環(huán)依賴了這個數(shù)組。幾乎可以肯定,他至少會造成一個錯誤,希望多年后不會出現(xiàn)另一個。
還有更殘忍的變體。能讓維護程序員陷入一種虛假的安全感,看上去盡職盡責的命名了常量, 卻經(jīng)常偶爾地"意外"使用100這個值而不是定義好的常量。最最殘忍的是, 偶爾使用一些其他無關(guān)的,僅僅恰好值是100的常量?,F(xiàn)在, 毋庸置疑, 你應(yīng)該避免任何一致的命名方案,也不在聲明數(shù)組時用常量定義大小了。
C編譯器會把myArray[i]
轉(zhuǎn)換成 *(myArray + i),這當然
*(i + myArray)等價,也就與
i[myArray]等價。
專家知道怎么向好的方面使用這項技術(shù)。但對我們而言真正值得利用的,是用函數(shù)生成下標的時候:
int myfunc(int q, int p) { return p%q; }// ...myfunc(6291, 8)[Array];
不幸的是,這些技術(shù)只能用于本地化的C類,java不讓。
把盡可能多的代碼打包成單行。這節(jié)省了臨時變量的開銷,還能通過消除換行符和空格使源文件變短。提示:移除操作符周圍的所有空白。 好的程序員經(jīng)常會困擾于一些編輯器強加的255字符行長度限制。好處是長行讓那些不能閱讀6號字的程序員必須拖動滾動條才能看完這一整行。
我要讓你知道一個鮮為人知的秘密. 后臺異常的出現(xiàn)是一個錯誤. 正確編寫的代碼永不失敗,所以異常實際上是不必要的。不要在它們身上浪費時間。子類異常是無能的人知道他們的代碼將失敗才寫的。你可以大大簡化你的程序,在整個應(yīng)用程序 (或main方法) 只寫一個 try/catch然后調(diào)用System.exit()就行了。有一個完美的標準值得堅持,在每一個方法頭聲明Exception,不管實際上有沒有任何異常要拋。
在不需要異常的語句里使用異常。一般使用 ArrayIndexOutOfBoundsException
來終止循環(huán)就很好。用異常還能幫助你避免方法正常返回結(jié)果。
標題說明一切 (title says it all)
了解語言在新聞組里各種各樣關(guān)于花式的位運算魔法代碼的爭論 e.g. a=a++;
or f(a++,a++);
在你的庫里隨便放點吧. 在C里, 后果就是前/后自減代碼,比如
*++b ? (*++b + *(b-1)) 0
就不是由語言規(guī)范定義的,每個編譯器都可以自由地按不同的順序進行解析. 這使它更加致命。同樣, 好好利用C和Java移除所有空格后復(fù)雜的解析規(guī)則.
完全遵守no goto,no early returns的建議,也別寫標簽,這樣和至少5層深的 if/else 嵌套再搭配不過了。
除非語法要求,否則不用 { }
包圍你的 if/else 塊。如果你有多層混雜的if/else,再配上誤導(dǎo)性的縮進,甚至專家級維修程序員都會中招。為了讓這項技術(shù)效果最大化,用 Perl. 你甚至還能在代碼 行尾 加上ifs, 簡直神乎其技。
永遠不要低估你可以通過制表符來造成多少破壞,只需要用制表符代替空格,尤其是公司沒規(guī)定一個制表符代表幾個空格的時候。 讓字里行間充滿制表符,或者使用工具來完成空格對制表符的轉(zhuǎn)換也很好。
在某些數(shù)組位置使用特殊值作為標簽。一個好的例子是某個
數(shù)組的[3][0]
元素被用于產(chǎn)生一個齊次坐標系統(tǒng)。
如果需要給定類型的多個變量,定義一個數(shù)組就行了,然后用下標訪問。只有你知道每個下標的意義并且不在文檔里記錄。也不要費心去用 #define
把下標定義成常數(shù)。每個人都應(yīng)該知道全局變量 widget[15]
是取消按鈕. 這種方法一種最新的變體是在匯編中使用絕對地址。
不要使用自動的代碼整潔(美化)工具保持你的代碼規(guī)整. 在你的公司用人們在PVCS/CVS (版本控制跟蹤工具) 創(chuàng)造了虛假的代碼行數(shù)的理由說服他們放棄 ,并且每個程序員都應(yīng)該有他自己的縮進風格,并在他寫的任何永遠神圣不可侵犯的模塊中體現(xiàn)出來。 堅持其他程序員在他們自己的模塊中也是這么干的。拒絕美化十分簡單,即使要耗費數(shù)以百萬計的擊鍵來人工對齊,并消耗大量的時間來搞清楚那可憐的,個人風格的,對齊。 只要堅持每個人不只是在存儲在公共庫里時,還在他們編輯時, 都這么對齊 。這將讓老板為了實現(xiàn)RWAR,禁止自動對齊。沒有自動對齊,你可以自由的 意外 錯位代碼,形成視覺錯覺,讓看上去正常的方法體比實際上更短或者更長,或者代碼運行時才發(fā)現(xiàn)else分支匹配了一個出乎意料的if。 e.g.
if(a) if(b) x=y;else x=z;
它提供了巨大的混淆機會,這項技術(shù)的關(guān)鍵點是幾層深的嵌套宏展開,你應(yīng)該把宏命令分散在各個不同文件中,將可執(zhí)行代碼換成宏然后分散到各個 *.cpp 文件中 (即使是那些從來不用的宏),這將使代碼變更的編譯需求數(shù)量最大化 。
Java 有兩種數(shù)組聲明方式。老版的C也能這么做,老式的 String x[]
(混合前后綴表示法) 和新式的 String[] x
(純前綴表示法),如果你想使人迷惑,混用聲明e.g.
byte[ ] rowvector, colvector , matrix[ ];
類似于:
byte[ ] rowvector;byte[ ] colvector;byte[ ][] matrix;
使用嵌套將函數(shù)的錯誤恢復(fù)方法盡可能遠離調(diào)用的位置。一個簡單的例子就是設(shè)置了 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 */ }
#define
被使用真正的理由是幫助那些熟悉其他類型程序的程序員更好的適應(yīng)C語言,可能你會這樣聲明:#define begin { "
或者 " #define end }
,這樣就能寫出來更有趣的代碼了。
讓維護程序員去猜測你使用的方法和類究竟來自那一個包。 不要這樣寫:
import MyPackage.Read;import MyPackage.Write;
應(yīng)該用:
import Mypackage. *;
無論多么晦澀也不完全限定任何方法或類。 讓維護程序員盡情猜測你使用的包/類的來源。當然啦,對你幫助最大的導(dǎo)入方式肯定不是全限定導(dǎo)入。
在任何情況下,決不能允許在屏幕上出現(xiàn)多于一個函數(shù)或過程的代碼。實現(xiàn)這一點很簡單,使用這個方便的技巧就行: 空行通常用于分離代碼的邏輯塊。每行本身都一個邏輯塊. 在每行之間放上空行。 不要在代碼的結(jié)尾寫注釋。 把它放在這一行的上面. 如果你被迫在行尾寫注釋,在整個文件中選擇最長的代碼行,加上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:*/
在文檔中放置這么多冗余信息的這項技術(shù),可以讓它很快的過時,并有助于負責維護的程序員傻到去相信它。
I don't need to test my programs. I have an error-correcting modem.
(我不需要測試我的程序。我有一個錯誤糾正調(diào)制解調(diào)器。)
把Bug留在程序里,留給新來的維護工程師,留給尋找有趣事情的后來者。一個完美的Bug從不留下任何可能解釋本身出現(xiàn)位置或原因的線索, 最懶,但也最有效的方法就是從來不測試你的代碼.
從不測試任何代碼,包括錯誤處理,機器崩潰或系統(tǒng)故障。也不檢查操作系統(tǒng)的返回代碼。這種代碼從來不會被執(zhí)行,只會減慢測試時間而已。 而且, 你怎么可能測試到代碼對硬盤錯誤,文件讀取錯誤,系統(tǒng)崩潰或其他任何類型問題的處理方式呢。為什么?你怎么可能有一個令人難以置信的不可靠的計算機或能模仿這樣事情的測試腳手架呢? 現(xiàn)代硬件永不失敗, 誰想只是為了測試目編寫代碼呢?它沒有任何樂趣。如果用戶抱怨,,怪操作系統(tǒng)或硬件就行了。 他們不會知道真相的。
伙計,如果程序不夠快,告訴客戶買臺更快的機器就好了。如果你做了性能測試,就可能發(fā)現(xiàn)性能瓶頸,就可能需要改變算法啊, 就可能需要重構(gòu)整個產(chǎn)品. 誰想那么做? 而且, 在客戶現(xiàn)場上出現(xiàn)的性能問題意味著一次免費體驗異國情調(diào)的旅行,只要換個新鏡頭并準備好護照就行了。
拒絕執(zhí)行代碼覆蓋或路徑覆蓋測試,自動化測試給窩囊廢用的。 自己找出哪些功能占你日常使用的90%,并分配90%測試到這些路徑。畢竟,這個技術(shù)可能只測試你源代碼的60%,直接節(jié)省了40%的測試工作量.這可以幫助你在項目后期補償計劃量表。很久以后才會有人注意到那些美妙的“營銷特性”不工作。 著名的大型軟件公司就是這樣測試代碼的; 你也應(yīng)該這樣. 如果看到這里你還沒走,請參閱下一項.
勇敢的程序員從不測試。太多的程序員害怕老板,害怕失去工作,害怕客戶的指責郵件,害怕被起訴了。這種恐懼麻痹了熱情,降低了工作效率. 研究表明,消除測試階段意味著管理人員可以提前確定上線日期,這對工作計劃有著明顯幫助??謶窒?,創(chuàng)新和實驗將開花結(jié)果。程序員的角色是生成代碼,debug是幫助臺和維護工程師的事,互相合作嘛。如果我們對我們的編碼能力有充分的信心,那么測試就沒有必要。從邏輯上看,傻瓜都知道測試解決不了任何技術(shù)問題,相對的,這只是一個情感信心的問題。對缺乏信心這個問題,一個更有效的解決方案是完全消除測試并送我們的程序員去上建立自信的課程. 畢竟,如果我們選擇做測試,我們就得測試程序的每個變化, 但現(xiàn)在我們只需要送我們的程序員去上一節(jié)課來建立自信就成了,這可是驚人的成本效益啊。
如果你定義 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.
電腦語言正在變得越來越愚蠢,使用那些新的靜態(tài)語言簡直反人類。 堅持使用你能僥幸找到的最老的語言,如果可以的話,用八進制的語言(像Hans und Frans一樣,我不是娘娘腔的男人;我很有男子氣概的,我用漆金線連接IBM單元記錄設(shè)備 (穿孔卡片), 并用手在紙帶上打孔實現(xiàn)編碼), 匯編不夠格,F(xiàn)ORTRAN和COBOL也不夠格, C和BASIC不不夠格, C++也不夠格.,全都不夠格。
用 FORTRAN寫代碼。如果你的老板質(zhì)疑你,你就告訴他FORTRAN上有很多有用的庫可以節(jié)約時間,即使用 FORTRAN寫的代碼的可維護性是0,這種編寫不可維護代碼的規(guī)則是很容易遵循的.
這些技術(shù)大約有20%不能用于 Ada,拒絕它吧。如果老板強迫你用,堅持回答別人也不用,并且指出它會讓你的大量工具失效,比如 lint 和 plummer,這可都是能有效利用C缺陷的法寶啊。
把所有公共的效用函數(shù)轉(zhuǎn)換成asm.
確保所有重要的庫函數(shù)用QBASIC編寫 , 然后簡單的寫一個asm包裝器就能處理大- >中內(nèi)存模型映射。
在程序里隨機寫點匯編多有意思啊,幾乎沒有人懂,即使是幾行也能冷卻程序員的維護妄想。
如果你有被C調(diào)用的匯編模塊,盡可能多的使用它,即使是最微不足道的目的,也要確保充分利用了goto,bcc或者其他迷人的匯編習(xí)俗。
不適用Abundance編碼, 也別用任何其他語言的編碼工具,它們從一開始就被設(shè)計的主要目就是使維護工程師的工作變簡單 。 同樣,避免使用 Eiffel 或者 Ada,它們從一開始就是為了 在項目進入生產(chǎn)之前捕捉bug 而設(shè)計的 。
__Hell is other people._ _(地獄就是別人)
這里有很多靈光一現(xiàn)的點子,能讓那些喋喋不休的維護工程師變的沮喪,能擊潰你的老板阻止你寫不可維護代碼的妄想,甚至煽動起一場關(guān)于每個人在庫中的代碼格式應(yīng)該是什么樣子的爭論.
如果你的老板認為他/她自己20年的FORTRAN編程經(jīng)驗足以指導(dǎo)現(xiàn)代編程,嚴格遵守他/她的所有建議。這樣,老板會信任你。這對你的職業(yè)生涯有益,而且你會學(xué)到很多新的方法來混淆程序代碼。
確保代碼充滿bug的方法之一是確保維護人員從不了解幫助臺. 這就需要顛覆幫助臺. 不接電話. 使用自動語音,回復(fù) "感謝你來售后服務(wù)熱線。 需要人工服務(wù)請按“1”或者留下語音留言",忽略請求幫助的郵件,而不是給他們一個可用的電話號碼。 對任何問題的標準回復(fù)是 " 我認為你的帳戶被鎖定了,但是有權(quán)解鎖的人現(xiàn)在不在"
不用在意下一個“千年蟲”。如果你發(fā)現(xiàn)了什么東西在悄悄的向著摧毀西半球的所有生命前進,別公開討論它,到我們處在最后四年,恐慌和機遇的關(guān)鍵事件窗口期再說。別把你的發(fā)現(xiàn)告訴朋友,同事或者任何其他有能力的人。 在任何情況下也不嘗試宣揚這個信息也許暗示著新的或巨大盈利或威脅 。僅僅發(fā)送一個普通優(yōu)先級,術(shù)語加密的備忘錄給你的上級來履行職責(to cover-your-a$$) . 如果可能的話,就把這段術(shù)語加密的信息作為一個不相干又帶著更緊迫的業(yè)務(wù)關(guān)注的的純文本備忘錄的附件。 放心,我們都看到了威脅。 安心的睡吧,很久之后,他們將用對數(shù)增長的時薪求提前退休的你回來 !
微妙是一件有趣的事, 有時錘子比別的工具更容易微妙。試試微妙的注釋,創(chuàng)造一個類,有著類似FooFactory
的名字,注釋包含對GoF創(chuàng)造模式的引用(最好超鏈接到假的UML設(shè)計文檔),但是卻在對象創(chuàng)造的過程中什么也不做。就這樣盡情的玩弄維護者的預(yù)期吧。更微妙的是,讓java
類有一個受保護的構(gòu)造器和類似于 Foo f = Foo.newInstance()
的方法, 返回新實例,而不是別人預(yù)期的單例. 這樣微妙的機會是無限的。
加入寫電腦書的俱樂部,選擇那些總是忙于寫書以至于沒空寫代碼的作者,瀏覽本地書店尋找那些有很多圖卻沒有代碼實例的文章,瀏覽這些書,學(xué)習(xí)那些空洞又學(xué)術(shù)的語言, 可以拿來恐嚇那些追在你屁股后面的傻瓜們。你的代碼應(yīng)該富有特色。如果人們理解不了你的詞匯,他們就會認為你極有才華而算法深奧。千萬避免給你的算法任何易于理解的例子。
你總是想寫系統(tǒng)級別的代碼,現(xiàn)在,機會來了。忽略標準庫,寫你自己的。放在簡歷上也好看。
總是使用你自己的,獨一無二的,BNF無關(guān)的命令語法。不要試圖在注釋里為合法和非法命令提供配套的解釋 .那被證明了完全缺乏學(xué)術(shù)嚴謹性,鐵路運行時刻表從來都不準確。確保沒有對從中間過渡符號(真的有合適的意思)到最終的符號(你最終鍵入的)演變做明顯的解釋。 永遠也不使用字體,顏色,加粗或其他任何視覺線索來幫助閱讀者區(qū)分這兩者。在你自己的BNF規(guī)范里使用與程序完全相同的標點符號,這樣閱讀者就搞不清在你的BNF規(guī)范里(...)
, [...]
, {...}
或者 "..."
究竟是命令的一部分還是提供線索表明那些語法元素是強制性的,可重復(fù)的還是可選的。最終,如果他們太愚蠢以至于理解不了你的BNF規(guī)范,那他們當然就對你的程序無計可施了。
每個人都知道調(diào)試動態(tài)存儲復(fù)雜而費時。改造你的存儲分配器而不是確保每一個類都沒有內(nèi)存泄露。強迫你的用戶定期重啟系統(tǒng)來清理堆而不是釋放內(nèi)存。 系統(tǒng)重啟是一件很簡單的事 -- 比修復(fù)所有的內(nèi)存泄露漏洞簡單多了 。只要用戶記得定期重啟系統(tǒng),就不會發(fā)生運行時堆空間溢出。想象一下,這能改變他們 “一次部署” 的策略!
讓你腦殘的基本編程方法
給數(shù)據(jù)庫表取一到二個字符長度的名稱。最好是跟它毫無關(guān)系的已有表的表名類似。
混合各種外部連接語法,讓大家看著頭皮發(fā)麻。
"優(yōu)化" JavaScript 代碼要充分利用一個函數(shù)可以在調(diào)用者的范圍內(nèi)訪問所有本地變量的特性。
將如下定義:
dim Count_num as stringdim Color_var as stringdim counter as integer
改為:
Dim Count_num$, Color_var$, counter%
如果從一個文本文件讀取 15 個字符,那么可以使用如下方法來讓事情變得復(fù)雜:
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"
不準使用函數(shù)和過程。使用 label/goto 語句,然后在代碼中亂跳。這足以讓他人在跟蹤代碼時發(fā)瘋。另外就是讓代碼在不經(jīng)意間來回跳躍,沒錯,就是這個竅門。
在一行非常非常長的代碼結(jié)尾使用 if 和 unless 語句。
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- !)))))
如果一個變量未定義就不用使用,除非你給它賦值。當你檢查變量類型時候就會發(fā)生這種問題:
lcx = TYPE('somevariable')
lcx 的值將是 'U'
或者 undefined
. 但是!如果你給這個變量一個范圍并在之后進行賦值,就會產(chǎn)生一個邏輯錯誤,嗯?!
LOCAL lcxlcx = TYPE('somevariable')
lcx 的值現(xiàn)在是 'L'
或者 logical. 而后被定義為 FALSE 的值。想想一下把這個東西用來寫不可維護的代碼!
LOCAL lc_one, lc_two, lc_three... , lc_nIF lc_oneDO some_incredibly_complex_operation_that_will_neverbe_executed WITHmake_sure_to_pass_parametersENDIFIF lc_twoDO some_incredibly_complex_operation_that_will_neverbe_executed WITHmake_sure_to_pass_parametersENDIFPROCEDURE some_incredibly_complex_oper....* put tons of code here that will never be executed* why not cut and paste your main procedure!ENDIF
如果你給某人一段程序,你會讓他困惑一天;如果你教他們?nèi)绾尉幊?,你會讓他困惑一輩子?/p>
讓我們從一條可能是有史以來最友好的技巧開始:把代碼編譯成可執(zhí)行文件。如果它能用,就在源代碼里做一兩個微小的改動 — 每個模塊都照此辦理。但是不要費勁巴拉地再編譯一次了。 你可以留著等以后有空而且需要調(diào)試的時候再說。多年以后,等可憐的維護代碼的程序員更改了代碼之后發(fā)現(xiàn)出錯了,他會有一種錯覺,覺得這些肯定是他自己最近修改的。這樣你就能讓他毫無頭緒地忙碌很長時間。
對于試圖用行調(diào)試工具追蹤來看懂你的代碼的人,簡單的一招就能讓他狼狽不堪,那就是把每一行代碼都寫得很長。特別要把 then 語句 和 if 語句放在同一行里。他們無法設(shè)置斷點。他們也無法分清在看的分支是哪個 if 里的。
在工程方面有兩種編碼方式。一種是把所有輸入都轉(zhuǎn)換為公制(米制)計量單位,然后在輸出的時候自己換算回各種民用計量單位。另一種是從頭到尾都保持各種計量單位混合在一起??偸沁x擇第二種方式,這就是美國之道!
CANI 是 Constant And Never-ending Improvement 的縮寫,持續(xù)改進的意思。 要常常對你的代碼做出“改進”,并強迫用戶經(jīng)常升級 — 畢竟沒人愿意用一個過時的版本嘛。即便他們覺得他們對現(xiàn)有的程序滿意了,想想看,如果他們看到你又“完善“了它,他們會多么開心?。〔灰嬖V任何人版本之間的差別,除非你被逼無奈 — 畢竟,為什么要告訴他們本來永遠也不會注意到的一些bug呢?
”關(guān)于“一欄應(yīng)該只包含程序名、程序員姓名和一份用法律用語寫的版權(quán)聲明。理想情況下,它還應(yīng)該鏈接到幾 MB 的代碼,產(chǎn)生有趣的動畫效果。但是,里邊永遠不要包含程序用途的描述、它的版本號、或最新代碼修改日期、或獲取更新的網(wǎng)站地址、或作者的email地址等。這樣,所有的用戶很快就會運行在各種不同的版本上,在安裝N+1版之前就試圖安裝N+2版。
在兩個版本之間,你能做的變更自然是多多益善。你不會希望用戶年復(fù)一年地面對同一套老的接口或用戶界面,這樣會很無聊。最后,如果你能在用戶不注意的情況下做出這些變更,那就更好了 — 這會讓他們保持警惕,戒驕戒躁。 .
與常規(guī) header 不同。 這具有雙重優(yōu)點,它要求在每個文件中維持的參數(shù)數(shù)據(jù)類型的改變,并且避免編譯器或鏈接器將檢測到類型不匹配的情況。 當從 32 - > 64 位平臺移植時,這非常有用。
寫無法維護代碼不需要多高的技術(shù)水平。喊破嗓子不如甩開膀子,不管三七二十一開始寫代碼就行了。記住,管理層還在按代碼行數(shù)考核生產(chǎn)率,即使以后這些代碼里的大部分都得刪掉。
一招鮮吃遍天,會干什么就吆喝什么,輕裝前進。如果你手頭只有一把錘子,那么所有的問題都是釘子。
有可能的話,忽略當前你的項目所用語言和環(huán)境中被普羅大眾所接受的編程規(guī)范。比如,編寫基于MFC 的應(yīng)用時,就堅持使用STL 編碼風格。
把常用的 true 和 false 的定義反過來用。這一招聽起來平淡無奇,但是往往收獲奇效。你可以先藏好下面的定義 :
#define TRUE 0#define FALSE 1
把這個定義深深地藏在代碼中某個沒人會再去看的文件里不易被發(fā)現(xiàn)的地方,然后讓程序做下面這樣的比較 :
if ( var == TRUE )if ( var != FALSE )
某些人肯定會迫不及待地跳出來“修正”這種明顯的冗余,并且在其他地方照著常規(guī)去使用變量var :
if ( var )
還有一招是為 TRUE 和 FALSE賦予相同的值,雖然大部分人可能會看穿這種騙局。給它們分別賦值 1 和 2 或者 -1 和 0 是讓他們瞎忙乎的方式里更精巧的,而且這樣做看起來也不失對他們的尊重。你在Java 里也可以用這一招,定義一個叫 TRUE 的靜態(tài)常量。在這種情況下,其他程序員更有可能懷疑你干的不是好事,因為Java里已經(jīng)有了內(nèi)建的標識符 true。
在你的項目里引用了功能強大的第三方類庫,但是卻沒有去使用他們。雖然有了實踐,你卻完全不理會那些好的工具,卻把那些沒用的工具寫在你的簡歷中的“其他工具”部分。
假裝不知道那些在你的開發(fā)工具中直接引用的類庫。在Visual C++ 的代碼中忽略MFC或者STL的存在,卻使用手工編碼實現(xiàn)所有的字符串和數(shù)組;這將有助于保持您的指針技能很牛逼,并且將會自動使你嘗試擴展代碼成為不可能。
保證它是如此的精巧以至于沒有任何維護人員能讓自己的修復(fù)通過編譯。 保密使用 SmartJ來編譯腳本已經(jīng)過時了的事實。同樣的,對javac
編譯器也是可用類的事實保密。即使面臨死亡的痛苦,也別告訴別人編寫和維護一個小而快的java程序來尋找文件并調(diào)用sun.tools.javac.Main
完成編譯是如此的簡單。
用腳本復(fù)制法在無序的復(fù)數(shù)文件夾中完成代碼復(fù)制就行了! 用不著任何花哨的源代碼控制系統(tǒng)來控制代碼分支, 這樣讓你的繼任者即使只想找出自己需要的是
哪個版本的DoUsefulWork()
也變得很難。
在Square Box Suggestions或其他的網(wǎng)站上,盡你所能的找到所有你可能能找到的關(guān)于編寫可維護代碼的建議,然后 ... ... 對著干!
把所有的代碼放到makefile里,你的繼任者一定會為你的壯舉留下深刻印象,因為你只用一個makefile就能生成批處理文件來完成所有頭文件的導(dǎo)入和工程構(gòu)建!這樣他們就弄不清任何改動會帶來什么影響,也搞不懂怎樣才能把工程遷移到IDE中。使用過時的工具來讓效果最大化, 比如早就完蛋的根本沒有依賴概念的NMAKE。
某些公司有嚴格的規(guī)定,不允許使用數(shù)字標識符,你必須使用預(yù)先命名的常量。要挫敗這種規(guī)定背后的意圖太容易了。比如,一位聰明的 C++ 程序員是這么寫的:
#define K_ONE 1#define K_TWO 2#define K_THOUSAND 999
一定要保留一些編譯器警告。在 make 里使用 “-” 前綴強制執(zhí)行,忽視任何編譯器報告的錯誤。這樣,即使維護代碼的程序員不小心在你的源代碼里造成了一個語法錯誤,make 工具還是會重新把整個包build 一遍,甚至可能會成功!而任何程序員要是手工編譯你的代碼,看到屏幕上冒出一堆其實無關(guān)緊要的警告,他們肯定會覺得是自己搞壞了代碼。同樣,他們一定會感謝你讓他們有找錯的機會。學(xué)有余力的同學(xué)可以做點手腳讓編譯器在打開編譯錯誤診斷工具時就沒法編譯你的程序。當然了,編譯器也許能做一些腳本邊界檢查,但是真正的程序員是不用這些特性的,所以你也不該用。既然你用自己的寶貴時間就能找到這些精巧的bug,何必還多此一舉讓編譯器來檢查錯誤呢?
永遠不要發(fā)布什么“bug 修復(fù)”版本。一定要把 bug 修復(fù)和數(shù)據(jù)庫結(jié)構(gòu)變更、復(fù)雜的用戶界面修改,還有管理界面重寫等混在一起。那樣的話,升級就變成一件非常困難的事情,人們會慢慢習(xí)慣 bug 的存在并開始稱他們?yōu)樘匦?。那些真心希望改變這些”特性“的人們就會有動力升級到新版本。這樣從長期來說可以節(jié)省你的維護工作量,并從你的客戶那里獲得更多收入。
沒錯,你的客戶會要求向上兼容性,所以你大可以放心這么做。但是請確保不會有向下兼容性問題。因為這樣將會導(dǎo)致客戶無法接受新版本,再加上一個合理的Bug修復(fù)政策 (見上文),這也會導(dǎo)致一旦升級到一個新的版本,他們就不會再想做任何變動了。如果能想辦法讓老版本不能識別由新版本創(chuàng)建的文件,那你就會得到額外的加分(這里是反語)。 那樣的話,他們不僅不能讀取,而且還會拒絕它,認為它不是由同一個應(yīng)用所創(chuàng)建的文件! 提示:PC版本的文字處理器就是一個很有用的例子,能讓我們看清楚這種復(fù)雜的行為。
不要探究bug產(chǎn)生的深層原因,簡單的掩飾一下推到高級線程就好,這是一個很好的智力練習(xí),類似于三維國際象棋,能讓未來的維護工程師花好幾個小時來辨別究竟是低級線程產(chǎn)生數(shù)據(jù)時的問題還是高級線程更改變量時產(chǎn)生的問題。對編譯器而言,這是一種很好的技術(shù),“多路通行”!你完全可以避免在早期測試中簡簡單單的修復(fù)這個問題,這樣在后期測試中就會變得很難修復(fù)了。幸運的話,你也用不著向那些負責前端維護的傻蛋們做什么解釋。如果前端產(chǎn)生正確的數(shù)據(jù)反而會讓后端崩潰,就更完美了。
為了使用死鎖要精巧的避開同步許可。多睡眠(sleep),多測試,多利用那些(沒有被volatile修飾)的全局變量。 死鎖比系統(tǒng)對象更加容易使用,而且還常見而靈活。
隨機的使用同步控制,即使瞎選的地方不需要。我曾經(jīng)在一段完全不可能被第二個線程調(diào)用的代碼塊中發(fā)現(xiàn)了同步控制(critical)代碼塊,我批評了代碼作者,可他居然信誓旦旦的告訴我critical能證明這段代碼,wow,關(guān)鍵!(critical用于同步控制,但英文意思是關(guān)鍵)
如果你的系統(tǒng)包含NT設(shè)備驅(qū)動,在活動期間讓應(yīng)用程序申請I/O緩沖區(qū)并加鎖,事后再解鎖。如果過早終止該緩沖區(qū)鎖定,程序?qū)?dǎo)致 NT 崩潰 . 但是在客戶端站點沒有人能改變設(shè)備驅(qū)動程序,所以他們沒有選擇。
將腳本命令語言加入到您運行時編譯字節(jié)的客戶機/服務(wù)器應(yīng)用程序中
如果你發(fā)現(xiàn)了編譯器或者解釋器的 Bug,請確保這種行為對讓你的代碼正常工作有必要。你不應(yīng)該換另外一個編譯器,其他人也不應(yīng)該。
下面是一個研究生寫的真實的例子,我們來看看他將不同的實現(xiàn)記錄打包到同一個 C 函數(shù)中:
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;}
memcpy()
要求 (void*)
, 所以要強轉(zhuǎn)指針,即使它本身就是 (void*)
。作為獎勵就能拿著它到處用了。temp
)。這將造成緩慢的內(nèi)存泄露,而且只有程序運行多天才會顯現(xiàn)出來。os
and ns
代表 "old size" 和 "new size".buf指向的內(nèi)存后
, 填充0\
. 不用 calloc(),
因為有人可能重寫ANSI規(guī)范,這樣calloc()
就有可能用其它的什么東西而不是0\來填充了。 (從不考慮馬上就要用相同長度的數(shù)據(jù)把 buf
覆蓋的事實)如果編譯器發(fā)出“未使用的本地變量”警告,不要刪除變量,相對的,要使用一種聰明的方法來使用它。我最喜歡的方法是這樣:
i = i;
不證自明,方法越大越好。 當然,跳轉(zhuǎn)和GOTO也是越多越好。這樣,任何代碼變更都需要檢查大量相關(guān)代碼。對維護人員而言,它們就是糾纏著一起的意大利面條。. 如果函數(shù)真正足夠龐大,它將成為維護程序員的哥斯拉怪獸, 無情的踩踏之前沒人知道將會發(fā)生什么。
讓每個方法的代碼盡可能的多,希望你從來沒有寫過任何少于 1000 行的代碼,當然也可以使用深度的嵌套:)
確保讓他們找不到你的某個關(guān)鍵文件,這個最佳做法是在 include 語句中,例如,在你的主模塊里有這樣的代碼:
#include <stdcode.h>
stdcode.h
這個文件當然必須得有,但是在這個文件里我們可以再玩一下:
#include "a:\\refcode.h"
然后 refcode.h
就找不到羅。。。
你至少得有一個變量,到處都有對這個變量進行賦值,但是卻從來不會被用到。不幸的是,如果反過來做的話,很多先進的編譯器會阻止你這樣做,但你仍然可以在 C 或者 C++ 代碼中這樣玩。
設(shè)計語言的人是編寫編譯器和系統(tǒng)類的人。 他們設(shè)計時自然使他們的工作容易和數(shù)學(xué)上顯得優(yōu)雅。 然而,每個編譯器有10,000個維護程序員。 日常維護程序員在語言設(shè)計上絕對沒有話語權(quán)。 然而,他們編寫的代碼的總量使編譯器中的代碼質(zhì)量變差。
這種精英思想結(jié)果的一個例子是JDBC接口。 它使得JDBC實現(xiàn)者工作容易,但是對于維護程序員來說是一個噩夢。 它比30年前SQL出來的FORTRAN接口更笨拙。
如果有人曾經(jīng)咨詢維護程序員,他們會要求隱藏管理細節(jié)的方法,以便他們可以看到整個森林的樹木。 他們會要求各種快捷方式,所以他們不必鍵入太多,所以他們可以在屏幕上一次看到更多的程序。 他們會大聲抱怨無數(shù)微小的時間 - 編譯器要求造成的浪費任務(wù)。
在這個方向有一些努力如NetRexx,Bali和視覺編輯器(例如IBM的視覺時代是一個開始),可以折疊與當前無關(guān)的細節(jié)。
想象一個會計師作為客戶堅持使用word程序處理他的總賬目。 你最好盡力去說服他,他的數(shù)據(jù)應(yīng)該結(jié)構(gòu)化。 他需要通過跨域檢查進行驗證。 如果數(shù)據(jù)存儲在數(shù)據(jù)庫,包括受控同步更新,他可以處理更多的數(shù)據(jù),這樣你就可以說服他。
想象一下,軟件開發(fā)者是一個客戶。 他堅持用文本編輯器維護他的所有數(shù)據(jù)(源代碼)。 他還沒有利用字處理器的顏色,類型大小或字體。
想想如果我們開始將源代碼存儲為結(jié)構(gòu)化數(shù)據(jù)可能會發(fā)生什么。我們可以以許多替代方式查看相同的源代碼,例如作為Java,作為NextRex,作為決策表,作為流程圖,作為循環(huán)結(jié)構(gòu)骨架(刪除細節(jié)),作為具有各種級別的細節(jié)或刪除注釋的Java,作為變量和方法調(diào)用的高亮顯示的Java或者作為具有關(guān)于參數(shù)名稱和/或類型的生成的注釋的Java。我們可以在2D中顯示復(fù)雜的算術(shù)表達式,TeX和數(shù)學(xué)家的方式。您可以看到帶有更多或更少括號的代碼(取決于您對優(yōu)先規(guī)則的感覺)。括號巢可以使用不同的大小和顏色來幫助眼睛匹配它們。透明覆蓋集的更改,您可以選擇刪除或應(yīng)用。您可以實時觀察您的團隊中的其他程序員,在不同的國家工作,修改您也在使用的類中的代碼。
你可以利用現(xiàn)代屏幕全彩色的能力得到潛意識的線索, 例如通過自動給每一個包或類分配一部分頻譜,使用柔和的色調(diào)作為任何引用該類的方法或變量的背景。 你可以給任何標識符的定義加粗顯示,使其脫穎而出。
你也許會問什么方法或構(gòu)造函數(shù)會產(chǎn)生一個X類型的對象? 什么方法將接受一個X類型的對象作為一個參數(shù)? 在代碼中的這一點上,哪些變量是可訪問的? 通過單擊方法調(diào)用或變量引用, 你可以看到它的定義,從而幫助找出一個給定方法的哪個版本實際上會被調(diào)用。 你可以要求全局訪問所有對一個給定的方法或變量的引用, 每一個被處理后,給他們打勾。 你可以通過點擊和按下按鈕就寫出大量的代碼來。