介紹永遠(yuǎn)不要把無能歸咎于作惡 ------ 拿破侖 為了獲得在Java編程領(lǐng)域工作的機(jī)會(huì),我把這些來自于大師們關(guān)于如何編寫難以維護(hù)代碼的技巧傳達(dá)給大家。如果你使用這些技巧,那些后來繼承你工作的人即使做最簡單的改動(dòng)也要花費(fèi)幾年的時(shí)間。進(jìn)一步,如果你能遵守所有的這些規(guī)則,你將能保證一輩子都不會(huì)被解雇,因?yàn)槌四阋呀?jīng)沒有人能維護(hù)這些操蛋的代碼。甚至,如果你虔誠地遵守所有這些規(guī)則,連你自己也沒辦法維護(hù)這些代碼!你怕不怕? 你不需要做的太過火了。你的代碼不應(yīng)該看起來毫無維護(hù)的可能性,就讓它們那樣。否則,它們可能面臨著被重寫或者重構(gòu)的風(fēng)險(xiǎn)。 總則 為了打擊維護(hù)代碼的程序員,你應(yīng)該了解他是怎么想的。他有你偉大的源代碼。他沒有時(shí)間全部讀一遍,理解的部分就更少了。他希望快速找到哪里可以進(jìn)行他的修改,加入他的代碼,然后退出,并且不帶來任何的副作用。 他從一個(gè)衛(wèi)生紙卷筒中窺視你的代碼。他一次只能看到一小片你的代碼。你應(yīng)該確保他這樣做是不可能看到代碼的宏圖框架的。你應(yīng)該讓他難以找到他想要找的代碼。更重要的是,你應(yīng)該盡力讓他難以安全地忽視任何細(xì)節(jié)。 程序員總是被約定麻痹。偶爾違反下約定,你就能強(qiáng)迫得他們用一個(gè)放大鏡一行一行讀你的代碼。 可能你已經(jīng)得到讓所有編程語言變得不可維護(hù)的訣竅了。---- 除此之外,就是適當(dāng)?shù)臑E用一下這技巧。 命名 編寫不可維護(hù)代碼的一大技巧就是命名變量和函數(shù)的藝術(shù)。命名對(duì)編譯器沒有意義。但是給了你迷惑代碼維護(hù)程序員的巨大空間。 小孩名字的新用法買一本給小孩起名字的參考書,這樣你在給變量命名時(shí)絕對(duì)不會(huì)吃虧。Fred是一個(gè)非常不錯(cuò)的名字,也很容易輸入。如果你在找容易輸入的變量名字,試試asdf或者如果你用的DSK排列的鍵盤,aoeu也不錯(cuò)。 單字母變量如果你給你的變量命名為a,b,c,那么別人不可能在簡單的文本編輯器里搜索這些變量實(shí)例。甚至,沒人能夠猜測到這些變量是干什么的。如果有人喜歡打破從Fortran用i,j和k來用做遍歷變量的傳統(tǒng),改成用ii,jj和kk,我要警告他們想想西班牙宗教法庭對(duì)異教徒們做了什么。 充滿創(chuàng)意的錯(cuò)誤拼寫如果你必須描述性的變量和函數(shù)名,錯(cuò)誤地拼寫他們。有時(shí)錯(cuò)誤有時(shí)正確的拼寫(比如SetPintleOpening 和SetPintalClosing)我們可以高效的搞死grep命令或者IDE的搜索功能。這個(gè)非常的神奇。往不同的threater/theater添加國際范的tory或者tori拼寫也是非常神奇的一件事。 抽象一點(diǎn)在命名函數(shù)和變量時(shí),大量使用抽象的詞語,比如everything,data,handle,stuff,do,routine,perform和數(shù)字等。例如,routineX48,PerformDataFunction,DoIt,HandlerStuff和do_args_method。 使用首字母縮寫詞使用首字母縮寫詞來保持代碼簡潔。真正的人類從來不定義首字母縮寫詞,他們生來就知道首字母縮寫詞的意義。 詞匯替代為了打破無聊,用詞典盡可能多地查詢那些表示同一動(dòng)作的詞匯列表。比如,display,show,present。含糊地暗示這些動(dòng)作詞匯有細(xì)微的差別,但實(shí)際上根本沒有。當(dāng)然,如果真的有差別的函數(shù),就用同一個(gè)詞來描述(比如,用print同時(shí)表示“寫文件”,“打印到紙上”和“顯示在屏幕上”)。不管什么場景,都不要屈服于使用沒有歧義的特殊用途的專有詞匯。這樣做非常不專業(yè)地違反了結(jié)構(gòu)化設(shè)計(jì)準(zhǔn)則中的信息隱藏原則。(譯注:此處應(yīng)該是調(diào)侃信息隱藏,信息隱藏實(shí)際應(yīng)該指的是數(shù)據(jù)封裝時(shí)盡可能少暴露內(nèi)部信息) 使用其他語言的復(fù)數(shù)方式 用一個(gè)虛擬機(jī)腳本記錄不同計(jì)算機(jī)(vaxes故意寫成vaxen)數(shù)據(jù)(states故意寫成statii)。世界通用語,克林貢語和霍比特語被認(rèn)為是一種語言就是這么干的。對(duì)于偽世界語的復(fù)數(shù)形式,加上oj。你也可以為世界和平作出貢獻(xiàn)。(譯注:這一段有點(diǎn)難翻譯,大意是按照自己的想法隨意地加復(fù)數(shù)后綴,就像影視小說里面的異世界語言一樣。這會(huì)讓人很難理解) CapiTaliSation(字母大寫) 隨機(jī)大寫單詞中的音節(jié)字母。比如, 重用名字 只要語法規(guī)則允許,就給類,構(gòu)造函數(shù),函數(shù),變量,參數(shù)和局部變量起相同的名字。另外,給{}中的局部變量起相同的名字。目的就是讓維護(hù)者不得不仔細(xì)地檢查每個(gè)實(shí)例的作用域。特別對(duì)于Java,把普通函數(shù)偽裝成構(gòu)造函數(shù)。 使用重音字母 在變量名里使用重音符號(hào),比如 這里第二個(gè)int的i實(shí)際上是重音的i。在簡單的文本編輯器里,幾乎不可能分辨出這個(gè)傾斜的重音符號(hào)。 利用編譯器能識(shí)別的變量名長度限制 例如假設(shè)編譯器只識(shí)別前8個(gè)字符,那么var_unit_update和var_unit_setup都會(huì)被認(rèn)為和var_unit一樣。 下劃線,其實(shí)是一個(gè)朋友 用 '_' 和 '__' 作標(biāo)識(shí)符。 隨機(jī)點(diǎn)綴使用兩種語言(人類語言和計(jì)算機(jī)語言)。如果你的老板堅(jiān)持你使用他的語言,你就告訴他使用你自己的語言你能更好地組織你的想法?;蛘呷绻菢诱f不好使,就說這是語言歧視,然后威脅說要控告他們索賠一大筆錢。 擴(kuò)展ASCII字符 擴(kuò)展ASCII字符完全是合法的變量字符,包括 ?, D, 和 ? 字符。這些字符除了復(fù)制粘貼幾乎不可能在一個(gè)簡單的編輯器里編輯。 使用其他語言的名字 使用其他外國語言的字典作為變量起名的來源。例如,使用法語的punkt作為point。代碼維護(hù)者沒有你對(duì)法語的理解,將會(huì)非常享受這次多元文化秘密探索之旅。 用數(shù)學(xué)運(yùn)算符號(hào)做名字 使用偽裝成數(shù)學(xué)運(yùn)算符的變量名,例如: (譯注:單詞意思是‘左圓括號(hào)’ = (‘斜線’ + ‘星號(hào)’) / ‘等號(hào)’) 使用使人眼花繚亂的名字 選擇那些有不相關(guān)情感內(nèi)涵的詞,例如: (譯注:歡樂滿人間(電影名) = (超人 + 星際飛船)/ 上帝) 每個(gè)人對(duì)詞匯的情感內(nèi)涵都有不同的的理解,所以這樣會(huì)讓讀代碼的人很困惑。 重命名和重復(fù)使用 這個(gè)技巧在Ada語言中工作的尤其好。Ada是一種免除了現(xiàn)代模糊技術(shù)的語言。命名你現(xiàn)在使用的對(duì)象和包的那些人都是白癡。與其通知他們修改,不如按照自己的發(fā)明進(jìn)行重命名和子類型重命名所有的名字。當(dāng)然保留一些原來的名字,作為一個(gè)粗心的陷阱。 何時(shí)使用i 千萬不要把i用作最里層的循環(huán)變量。公平地把i用在任何其他地方,尤其是非整型變量的地方。類似的,用n做循環(huán)變量。 約定 忽略Sun公司的Java編碼約定。幸運(yùn)的是,當(dāng)你違反這些約定時(shí)編譯器也不會(huì)泄露你的秘密。目標(biāo)就是想出只在大小寫有細(xì)微差別的的命名。如果不得不準(zhǔn)守大小寫約定,你依然可以在一些模糊定義的地方顛覆這些約定。例如,同時(shí)用inputFilename 和 inputfileName。發(fā)明一套你自己的復(fù)雜到令人絕望的名字約定,然后嚴(yán)厲地斥責(zé)那些不遵守這個(gè)約定的人。 小寫的l很像數(shù)字1 用小寫的l來標(biāo)識(shí)long類型常數(shù)。例如10l 更容易被誤解成101。不使用那些能清楚分辨 重用全局名字做為私有變量名 在模塊A定義一個(gè)全局?jǐn)?shù)組,在模塊B的頭文件定義一個(gè)同樣名字的私有數(shù)組變量。這樣似乎你在模塊B用的是全局?jǐn)?shù)組,但其實(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會(huì)被當(dāng)成B,B別當(dāng)成A。等有更多的函數(shù)和全局變量,你就可以生成一張互相矛盾地使用相同名字的變量組成的大網(wǎng)。 回收利用變量 只要作用域允許,重復(fù)利用已經(jīng)存在的不相關(guān)的變量名。類似的,對(duì)于兩個(gè)不相關(guān)的用途使用相同的臨時(shí)變量(對(duì)外聲稱是節(jié)約??臻g)。就像惡魔變體一樣對(duì)變量進(jìn)行變化。例如,在一個(gè)很長的函數(shù)的前面給變量賦一個(gè)值,然后在中間某個(gè)地方再賦一個(gè)值,同時(shí)細(xì)微地改變變量的含義。比如,從以0為基準(zhǔn)改成以1為基準(zhǔn)。當(dāng)然,不要把這個(gè)改變寫進(jìn)文檔。 Cd wrttn wtht vwls s mch trsr(譯注:亂七八糟的縮寫)在變量和函數(shù)名里使用縮寫時(shí),對(duì)同一個(gè)單詞用不同變體打破無聊,甚至偶爾速記一把。這幫助了打敗那些使用文本搜索去理解你代碼某個(gè)方面的懶漢。例如,混合國際方式拼寫的colour和美國方式拼寫color以及花花公子口中的kulerz。如果你全拼這些名字,只有一種拼寫方式,這樣太容易讓代碼維護(hù)者記住了。因?yàn)橛泻芏嗖煌暮唽懲粋€(gè)單詞的方式,使用簡寫你可以有很多不同的變量表示同一個(gè)表面上的意思。作為一個(gè)附加的福利,代碼維護(hù)者可能甚至都沒注意到他們是不同的變量。 誤導(dǎo)人的命名 確保每個(gè)函數(shù)都比它的名字表達(dá)出的意思多做或者少做一點(diǎn)。舉個(gè)簡單的例子,一個(gè)名為isValid(x)的函數(shù)有個(gè)把x轉(zhuǎn)成二進(jìn)制且存進(jìn)數(shù)據(jù)庫的副作用。 m_ 一個(gè)來自C++的命名約定是在變量名前使用m_前綴。這個(gè)約定只要你“遺忘性地”在函數(shù)也加個(gè)m,就可以告訴他們這不是函數(shù)。 o_apple obj_apple 在每個(gè)類實(shí)例前加o或者obj前綴,以表明你想表示一個(gè)巨大的,多形態(tài)的宏圖。 匈牙利命名法匈牙利命名法是代碼混淆技術(shù)的戰(zhàn)術(shù)核武器, 使用它! 讓大量的源代碼被這種習(xí)語污染,沒有什么能比這種精心策劃的匈牙利命名法攻擊更快的干掉一名維護(hù)工程師了! 以下的提示將幫助你從本質(zhì)上腐蝕匈牙利命名法 :
再說匈牙利命名法 一個(gè)匈牙利命名法中后續(xù)的招數(shù)是“改變變量的類型但是不改變變量名”。這幾乎是從16位Windows時(shí)代一層不變地遷移來的: 到win32 這里的w都表明他們是單詞,但實(shí)際上是指long類型。這種命名法遷移到win64時(shí),意義會(huì)很明確,因?yàn)樵趙in64就是參數(shù)的位寬就是64位。但是以前的w和l前綴,就呵呵了。 減少,重用,再利用 如果你為回調(diào)函數(shù)定義一個(gè)結(jié)構(gòu)體來存儲(chǔ)數(shù)據(jù),把結(jié)構(gòu)體命名為PRIVDATA。每個(gè)模塊都可以定義它自己的PRIVDATA。在vc++里,這樣做有個(gè)好處就是迷惑代碼調(diào)試者。因?yàn)橛泻芏郟RIVATA展示在watch窗口,他不知道你指的是哪個(gè)PRIVATA,所以他只能任選一個(gè)。 晦澀地參考電影情節(jié) 用LancelotsFavouriteColour(譯注:Lancelot最喜歡的顏色)替代blue給常量命名,并且賦值十六進(jìn)制的$0204FB。這種顏色在屏幕上看起來和純藍(lán)色完全一樣,但是這樣命名會(huì)讓代碼維護(hù)者不得不執(zhí)行程序(或者使用某些圖像工具)才能知道是什么樣。只有那些熟悉“巨蟒劇團(tuán)”和《圣杯的故事》才知道Lancelot最喜歡的顏色是藍(lán)色。如果代碼維護(hù)者想不起來所有巨蟒劇團(tuán)的電影,那他或者她就沒資格做一個(gè)程序員。 偽裝編寫不可維護(hù)代碼一大部分技巧都是偽裝的藝術(shù)。很多技巧都基于代碼編譯器能夠比人眼和文本編輯器更好地分辨代碼這一事實(shí)上。這里列舉一些最佳偽裝技術(shù)。 代碼和注釋相互偽裝 來一些第一眼看上去是注釋的代碼。
如果沒有編輯器顏色區(qū)分代碼,你能注意到那三行已經(jīng)別注釋掉了嗎? 命名空間struct/union 和 typedef struct/union 在C(不是C++)中是不同的命名空間。在這兩個(gè)名字空間里給結(jié)構(gòu)體和枚舉使用相同的名字。如果可能的話,讓他們幾乎一模一樣。 隱藏宏定義把宏定義隱藏在垃圾注釋中間。程序員就會(huì)因?yàn)闊o聊放棄讀這些注釋,于是就發(fā)現(xiàn)不了這些宏。確保這些宏替換看起來像一個(gè)完全合法的賦值操作,例如: 繁忙假象 用define語句裝飾編造出來的函數(shù),給這些函數(shù)一堆參數(shù)注釋,例如: 用延長技術(shù)隱藏變量 把 中xy_z替代成2行的 這樣,全局搜索xy_z會(huì)什么也搜不到。對(duì)于C的預(yù)編譯器來說,行末的"\"符號(hào)會(huì)把這一行和下一行合并到一起。 偽裝成關(guān)鍵字的任意名字 寫文檔的時(shí)候,隨意用一個(gè)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)鍵字,編譯器或者處理器會(huì)報(bào)錯(cuò)。這個(gè)技巧如果用得好,程序員會(huì)在真正的關(guān)鍵字和你起的名字之間感到絕望般的困惑。但是你還是可以看起來非常無辜,你聲稱這樣命名是為了幫助他關(guān)聯(lián)每個(gè)變量的用途。 代碼命名不匹配跟屏幕顯示的名字 選擇跟顯示在屏幕上的名字完全沒有聯(lián)系的變量名。例如,屏幕上顯示標(biāo)簽名字是“Postal Code”,但是在代碼里,變量起名為zip。 不要改變命名 用多個(gè)TYPEDEF給同一個(gè)符號(hào)起別名的方式替代全局同步重命名兩個(gè)區(qū)塊的代碼。 如何隱藏被禁止的全局變量 由于全局變量被認(rèn)為是‘罪惡的’,你可以定義一個(gè)結(jié)構(gòu)體容納所有的全局內(nèi)容。機(jī)智地把這個(gè)結(jié)構(gòu)體命名為類似EverythingYoullEverNeed(你所需要的每件事)的名字。讓所有的函數(shù)都有一個(gè)指向這個(gè)結(jié)構(gòu)體的指針(命名為handle可以迷惑更多)。這樣會(huì)給別人你不是在使用全局變量的印象,你是通過一個(gè)handle還訪問每件事的。然后定義一個(gè)靜態(tài)實(shí)例,這樣所有的代碼就都是訪問同一個(gè)副本了。 用同義詞隱藏實(shí)例代碼維護(hù)者修改代碼之后,為了查看是否會(huì)有什么級(jí)聯(lián)影響,會(huì)用變量名進(jìn)行一次全局搜索。這可以通過同義詞輕松化解,例如 這些def語句應(yīng)該分散在不同的頭文件中。這些頭文件如果分散在不同目錄效果更佳。另外一種技術(shù)是在每個(gè)塊中重用變量名。編譯器可以把他們分辨開來,但是簡單的混合式文本搜索區(qū)分不了。不幸的在接下來幾年到來的SCID(譯注:Source Code in Database,一種將代碼預(yù)解析然后存在數(shù)據(jù)庫的技術(shù))將會(huì)使得這項(xiàng)簡單的技術(shù)不起作用,因?yàn)榫庉嬈鲗?huì)和編譯器一樣支持區(qū)塊規(guī)則。 長長的相象的變量名每個(gè)長長的變量或者類名都不相同,只差一個(gè)字符或者只有大小寫。一個(gè)完美的例子是swimmer和swimner。由于大多數(shù)字體的 發(fā)音類似拼寫類似的變量名只有大小寫或者下劃線差別的變量可以很好的讓那些用發(fā)音或者單詞拼寫來記憶變量而不是精確拼寫的人感到凌亂。 重載和混亂在C++中,用#define重載庫函數(shù)。這樣看起來就像是你在使用一個(gè)很熟悉的庫函數(shù),但是實(shí)際上是完全不相同的東西。 選擇最好的操作符重載例如,在C++中,把+,-,*,和/重載和加減完全不相關(guān)的操作。既然Stroustroup(C++作者)可以用移位操作進(jìn)行I/O操作,為什么你不能那樣充滿創(chuàng)意呢?如果你重載+,一定要重載成i = i + 5 和i += 5的意義完全不同。來個(gè)一個(gè)例子將符號(hào)重載混亂上升到一個(gè)更高的藝術(shù)。為類重載!操作符,但是這個(gè)重載跟取反或者取否完全沒關(guān)。讓這個(gè)操作符返回一個(gè)整數(shù)。這樣,為了或者一個(gè)布爾值,你必須用!!。而為了反轉(zhuǎn)這個(gè)布爾值,[此處應(yīng)有掌聲]你就必須用?。?!。不要混淆返回布爾0或者1的!操作符和按位取反的~邏輯操作符。 重載new重載 #defineC++自帶的 #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 當(dāng)傳遞 編譯器指令編譯器指令的設(shè)計(jì)目的是使同一代碼的行為完全不同。 反復(fù)和有力的打開和關(guān)閉布爾短路指令,以及長字符串指令。 文檔任何傻瓜都能說真話,但需要有些許理智的人,才懂得如何圓謊。 - 塞繆爾·巴特勒(1835 - 1902) 不正確的文檔通常比沒有文檔更糟糕。 - Bertrand Meyer 由于計(jì)算機(jī)忽略注釋和文檔,你可以肆無忌憚,盡你所能的來迷惑那些可憐的維護(hù)代碼的程序員。 在注釋中說謊你不必主動(dòng)說謊,只需使注釋與最新的代碼保持不一致就行了。 為那些顯而易見的代碼寫文檔為代碼添加一些類似于/ * add 1 to i * /的注釋,但是,絕不要為代碼記錄類似于包或方法的整體目的的東西。 記錄怎么做而不是為什么這么做僅記錄程序的細(xì)節(jié),而不是它試圖完成什么。 這樣,如果有一個(gè)錯(cuò)誤,改代碼的程序員將不知道代碼應(yīng)該做什么。 避免記錄“顯而易見的”例如,如果您正在編寫航空預(yù)訂系統(tǒng),如果您要添加另一家航空公司,請(qǐng)確保在代碼中至少有25個(gè)地方需要修改。 不要記錄他們?cè)谀膬焊膭?dòng)的。 那些在你之后接手代碼的人,在沒有徹底理解代碼的每一行之前是無法開展修改代碼的工作。 關(guān)于文檔模板的正確使用考慮用于允許代碼的自動(dòng)化文檔的函數(shù)文檔原型。 這些原型應(yīng)該從一個(gè)函數(shù)(或方法或類)復(fù)制到另一個(gè),但不會(huì)填充字段。 如果由于某種原因被迫填寫字段,請(qǐng)確保所有參數(shù)的名稱對(duì)于所有函數(shù)都是相同的,并且所有注意事項(xiàng)都是相同的,但是當(dāng)然不與當(dāng)前函數(shù)相關(guān)。 關(guān)于正確使用設(shè)計(jì)文檔當(dāng)實(shí)現(xiàn)一個(gè)非常復(fù)雜的算法,使用經(jīng)典的軟件工程原理做一個(gè)聲音設(shè)計(jì)編碼之前。 編寫一個(gè)非常詳細(xì)的設(shè)計(jì)文檔,描述一個(gè)非常復(fù)雜的算法中的每個(gè)步驟。 本文件越詳細(xì)越好。 事實(shí)上,設(shè)計(jì)文檔應(yīng)該將算法分解成結(jié)構(gòu)化步驟的層次結(jié)構(gòu),在文檔中自動(dòng)編號(hào)的單個(gè)段落的層次結(jié)構(gòu)中描述。 使用至少5級(jí)深度的標(biāo)題。 確保當(dāng)你完成后,你完全破壞了結(jié)構(gòu),有超過500個(gè)這樣的自動(dòng)編號(hào)的段落。 例如,一個(gè)段落可能是(這是一個(gè)真實(shí)的例子) 1.2.4.6.3.13 - 顯示所有可能的應(yīng)用(短偽代碼....), 然后... (這是在發(fā)牢騷) 當(dāng)你寫代碼時(shí),對(duì)每一個(gè)段落都寫一個(gè)對(duì)應(yīng)的全局函數(shù),就叫 Act1_2_4_6_3_13() 不用給這些函數(shù)專門寫文檔了。 畢竟,設(shè)計(jì)文檔里都有! 設(shè)計(jì)文檔自動(dòng)編號(hào)以來,就極難和代碼保持同步更新了, (當(dāng)然,因?yàn)楹瘮?shù)名是靜態(tài)的, 不是自動(dòng)編號(hào)的) 。其實(shí)這根本不是問題,因?yàn)槟銖臎]想過文檔同步更新這回事。事實(shí)上, 你還能做更多的事來摧毀設(shè)計(jì)文檔可能有的任何用途。 那些在你后面來的人應(yīng)該只能找到一兩個(gè)矛盾的早期草稿的設(shè)計(jì)文件,這些文件隱藏在靠近死機(jī)的286電腦附近一些灰塵的貨架上。 測量單位不要記錄任何變量,輸入,輸出或參數(shù)的測量單位。 例如 英尺,米,紙箱。 這在bean計(jì)數(shù)中不是那么重要,但它在工程工作中非常重要。 作為推論,從不記錄任何轉(zhuǎn)換常數(shù)的度量單位,或如何導(dǎo)出值。 這是溫和的作弊,但非常有效,在代碼中混合一些不正確的計(jì)量單位的注釋。 如果你感覺特別惡毒,補(bǔ)上自己的計(jì)量單位; 把它命名為你自己或一些模糊的人,永遠(yuǎn)不要定義它。 如果有人挑戰(zhàn)你,告訴他們你這樣做,以便可以使用整數(shù)而不是浮點(diǎn)運(yùn)算。 陷阱永遠(yuǎn)不要為陷阱代碼寫文檔。 如果你懷疑在一個(gè)類里可能有bug, 不要理睬它,自己知道就好。如果你對(duì)于這些代碼該如何重組或重寫的話, 聽我的,不要把他們寫出來。 記得在電影“小鹿斑比”有一句話: “如果你不能說什么好的, 就什么都不要說?!?嗎? 萬一那個(gè)寫這段代碼的人看到你的評(píng)論呢? 又或者如果公司的領(lǐng)導(dǎo)看到了呢? 或者是客戶看到了呢?你可能會(huì)因此丟掉工作的。 一個(gè)人匿名留下的評(píng)論“這需要被修復(fù)”可能會(huì)產(chǎn)生疑問, 特別是在當(dāng)這段評(píng)論所指的對(duì)象不明的情況下。 保持模糊,沒有人會(huì)覺得自己被批評(píng)。 為變量寫文檔永遠(yuǎn)不要 為一個(gè)變量聲明寫注釋。像關(guān)于這個(gè)變量如何使用,它的界限、合法值、隱式或者顯式的十進(jìn)制的值、度量單位、顯示格式、數(shù)據(jù)輸入規(guī)則(比如:都填入,必須輸入),以及什么值是可以信任的,諸如此類信息,都應(yīng)該可以通過程序代碼獲取到。 如果你的老板強(qiáng)制你寫注釋,只用給方法寫注釋就可以了,但是不要對(duì)一個(gè)變量聲明做注釋,即使是臨時(shí)的也不要寫! 在注釋中貶損他人不建議任何企圖使用外部維護(hù)承包商的行為,通過在你的代碼里帶有引用侮辱其他領(lǐng)先的軟件公司的言論,特別是任何被承包過來工作的人。比如: /* 內(nèi)循環(huán)優(yōu)化
對(duì)在軟件服務(wù)公司的笨蛋而言,這個(gè)員工太聰明了,他
通過利用<math.h>中垃圾一樣的功能節(jié)約了50倍的時(shí)間和內(nèi)存.
*/
class clever_SSInc
{
.. .
}如果可能的話,在代碼的重要區(qū)塊侮辱這個(gè)職員,調(diào)整注釋的位置,這樣管理者在急著把代碼發(fā)給他自己的管理者的時(shí)候,還想把這塊注釋刪掉的話,就會(huì)破壞代碼原有的功能。 評(píng)論穿孔卡片C?B?L拒絕接受科技設(shè)備競賽進(jìn)展。尤其是 SCIDs。有謠言說所有的函數(shù)和變量聲明一點(diǎn)就能出來,還說被Visual Studio 6.0開發(fā)出來的代碼是被維護(hù)工程師用 edlin 或者 vi 維護(hù)的,別信它們。堅(jiān)持嚴(yán)格的注釋規(guī)則也會(huì)埋葬代碼工作者樂趣。 蒙蒂的蟒蛇(指除了你在自己誰也不知道的代指)對(duì)一個(gè)叫makeSnafucated的函數(shù)寫個(gè) 程序設(shè)計(jì)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. 寫不可維護(hù)代碼的基本規(guī)則是把要素在盡可能多的地方用盡可能多的方式反復(fù)的說。 - Roedy Green 寫可維護(hù)代碼的關(guān)鍵是把程序的所有要素列在同一個(gè)地方。改變你的想法,應(yīng)該把它們列在同一個(gè)地方,并保證程序仍然工作。因此,寫不可維護(hù)性代碼的關(guān)鍵是在盡可能多的地方,盡可能多的方式一遍又一遍的列舉要素。歡呼吧,像Java那樣的語言,有自己的獨(dú)特方式,能讓編寫不可維護(hù)的代碼變得更加簡單。舉個(gè)例子,它幾乎不可能改變外部引用,因?yàn)樗械脑煨秃蛿?shù)據(jù)類型轉(zhuǎn)換都不工作,并且相關(guān)臨時(shí)變量也不合用, 進(jìn)一步, 如果變量顯示在屏幕上, 所有相關(guān)聯(lián)的變量和數(shù)據(jù)條目都要人工修飾并手動(dòng)跟蹤。Algol語言家族,包括C和Java,能用數(shù)組,哈希表,普通文件和不同類型的數(shù)據(jù)庫存儲(chǔ)數(shù)據(jù)。類似Abundance的語言,擴(kuò)展了Smalltalk的,語法都很獨(dú)特。僅僅是改變聲明,利用Java的遲鈍,把會(huì)慢慢溢出RAM的數(shù)據(jù),放到數(shù)組里。這樣維護(hù)工程師就有了一個(gè)把數(shù)組轉(zhuǎn)成文件的可怕任務(wù)。同樣的,在數(shù)據(jù)庫中放小文件,這樣維護(hù)工程師在性能調(diào)優(yōu)時(shí),就又有了把它們轉(zhuǎn)成數(shù)組的樂趣。 Java 類型轉(zhuǎn)換Java 的類型轉(zhuǎn)換方法算是上帝賜予你的禮物。你可以毫無內(nèi)疚的使用這個(gè)技術(shù),因?yàn)檎Z言本身需要它。每當(dāng)你從集合中獲取一個(gè)對(duì)象時(shí)你必須將之轉(zhuǎn)換成其原始的類型。雖然變量的類型可以在很多地方指定。如果類型在后來發(fā)生了變化,那么所有轉(zhuǎn)換的操作都必須與之匹配。而且編譯器無法幫助那個(gè)倒霉的維護(hù)程序員檢測到這些變化(因?yàn)閷?shí)在太多了)。同樣的,例如一個(gè)變量從 short 變成 int ,那么所有匹配的轉(zhuǎn)換需要相應(yīng)的改動(dòng)。不過越來越多的開發(fā)人員使用泛型來解決這種問題。而且可以確定的是這個(gè)異端將不會(huì)進(jìn)入語言的規(guī)范中。在 RFE 114691 中投票反對(duì)并在泛型上消除任意轉(zhuǎn)換的需要(編者注:最后這兩句話意思有點(diǎn)亂)。 利用 Java 的冗余Java 要求你必須為每個(gè)變量指定兩次的類型。Java 程序員已經(jīng)習(xí)慣了這種多余的操作,他們一般不會(huì)注意到這兩種類型略有不同。例如: Bubblegum b = new Bubblegom(); 不幸的是 ++ 操作的受歡迎程度使得你很難逃避如下代碼的偽冗余代碼: swimmer = swimner + 1; 從不做驗(yàn)證從不對(duì)任何輸入的數(shù)據(jù)進(jìn)行驗(yàn)證,不管數(shù)據(jù)是否正確或者是否有差異。這表明你絕對(duì)相信公司的設(shè)備,以及你正在一個(gè)完美的團(tuán)隊(duì)中工作。所有合作伙伴和系統(tǒng)運(yùn)營商都是可信任的,他們提供的數(shù)據(jù)都是合理的。 有禮貌,無斷言 避免使用 assert() 機(jī)制,因?yàn)樗赡馨讶斓膁ebug盛宴變成10分鐘的快餐。 避免封裝 為了提高效率,不要使用封裝。方法的調(diào)用者需要所有能得到的外部信息,以便了解方法的內(nèi)部是如何工作的。 復(fù)制粘貼修改 以效率的名義,使用 復(fù)制+粘貼+修改。這樣比寫成小型可復(fù)用模塊效率高得多。在用代碼行數(shù)衡量你的進(jìn)度的小作坊里,這招尤其管用。 使用靜態(tài)數(shù)組 如果一個(gè)庫里的模塊需要一個(gè)數(shù)組來存放圖片,就定義一個(gè)靜態(tài)數(shù)組。沒人會(huì)有比512 X 512 更大的圖片,所以固定大小的數(shù)組就可以了。為了最佳精度,就把它定義成 double 類型的數(shù)組。 傻瓜接口 編寫一個(gè)名為 “WrittenByMe” 之類的空接口,然后讓你的所有類都實(shí)現(xiàn)它。然后給所有你用到的Java 內(nèi)置類編寫包裝類。這里的思想是確保你程序里的每個(gè)對(duì)象都實(shí)現(xiàn)這個(gè)接口。最后,編寫所有的方法,讓它們的參數(shù)和返回類型都是這個(gè) WrittenByMe。這樣就幾乎不可能搞清楚某個(gè)方法的功能是什么,并且所有類型都需要好玩的造型方法。更出格的玩法是,讓每個(gè)團(tuán)隊(duì)成員編寫它們自己的接口(例如 WrittenByJoe),程序員用到的任何類都要實(shí)現(xiàn)他自己的接口。這樣你就可以在大量無意義接口中隨便找一個(gè)來引用對(duì)象了。 巨型監(jiān)聽器 永遠(yuǎn)不要為每個(gè)組件創(chuàng)建分開的監(jiān)聽器,而是對(duì)所有按鈕使用同一個(gè)監(jiān)聽器,然后用大量的 if…else 來判斷是哪一個(gè)按鈕被點(diǎn)擊就行了。 好事成堆TM 大膽使用封裝和 OO 思想。例如 myPanel.add( getMyButton() );
private JButton getMyButton()
{
return myButton;
}這段很可能看起來并不那么有趣。別擔(dān)心,只是還沒到嗨點(diǎn)。 好朋友 在 C++ 里盡量多使用友好聲明。再把創(chuàng)建類的指針傳遞給已創(chuàng)建類?,F(xiàn)在你不用浪費(fèi)時(shí)間去考慮接口。另外,你應(yīng)該用上關(guān)鍵字 private 和 protected 來表明你的類封裝得很好。 使用三維數(shù)組大量使用三維數(shù)組,然后用不一樣的方式在數(shù)組之間移動(dòng)數(shù)據(jù),比如,用 arrayA 的行去填充 arrayB 的列,再加上 1 的偏移值。這么做足以讓程序員抓狂。 混合與匹配存取方法和公共變量同時(shí)用上。這樣一來,你無需調(diào)用存取器的開銷就可以修改一個(gè)對(duì)象的變量,還能宣稱這個(gè)類是個(gè)”Java Bean”。程序員可能會(huì)試圖添加日志函數(shù)來找出改變值的源頭,但這一招會(huì)讓他迷惑不已。 包裝,包裝,再包裝無論什么時(shí)候,你使用別人寫的代碼,都要用至少一次的包裝器把別人的臟代碼與自己的隔離開。畢竟,其他作者 可能未來某個(gè)時(shí)間不顧一切地重命名了所有方法。到時(shí)候你怎么辦?當(dāng)然有辦法。 如果他這樣做,寫個(gè)包裝器就能將代碼與更改隔離了,或者也可以讓VAJ來處理全局性的重命名。然后,這是一個(gè)完美的借口,在別人做任何錯(cuò)事之前,先發(fā)制人的用一個(gè)中間層來切斷和他的聯(lián)系。Java的一個(gè)主要缺點(diǎn)是:如果不做愚蠢的方法包裝,除了調(diào)用同名方法之外,就做不了任何事,即使只是想解決一個(gè)小問題。這意味著完全可能寫一個(gè)四層深的包裝卻什么也不做,沒人會(huì)注意到這一點(diǎn)的。為了最大化混淆效果,每一層都重命名方法,找個(gè)同義詞典,挑點(diǎn)同義詞。這會(huì)讓人產(chǎn)生幻覺,以為你做了什么。 進(jìn)一步,重命名有助于確保項(xiàng)目術(shù)語缺乏一致性。為了確保沒有人試圖把你的包裝調(diào)整到一個(gè)合適的層次,在每一層都找點(diǎn)你自己的代碼調(diào)用一下。 包裝包裝,更多包裝在單獨(dú)源文件的函數(shù)定義中,確保所有 API 函數(shù)至少包裝了 6-8 次。 你也可以使用 #defines 的快捷方式來實(shí)現(xiàn)對(duì)這些函數(shù)的包裝。 不保密!把每個(gè)方法和變量都聲明為 public。畢竟總會(huì)有人有用得到它的時(shí)候。一旦方法被聲明為 public 了,就很難回退。這樣任何它覆蓋到的代碼都很難再修改。它還有個(gè)有趣的小功能,就是讓你看不清類的作用是什么。如果老板質(zhì)問你是不是瘋了,你就可以告訴他,你遵循的是經(jīng)典的透明接口原則。 愛經(jīng)這種技術(shù)具有驅(qū)動(dòng)程序包的任何用戶或記錄器以及維護(hù)程序員分心的附加優(yōu)點(diǎn)。 創(chuàng)建十幾個(gè)重載變量的同一方法,只有最細(xì)微的細(xì)節(jié)不同。 我認(rèn)為是奧斯卡王爾德,觀察到Kama Sutra的位置47和115是相同的,除了在115的地方女人的手指是交叉的。 包的用戶必須仔細(xì)地閱讀長列表的方法,以找出使用哪個(gè)變量。 該技術(shù)還使文檔膨脹化,從而確保其更可能過時(shí)。 如果老板問你為什么這樣做,解釋它只是為了方便用戶。 再次為了充分達(dá)到效果,克隆任何共同的邏輯,坐下來等待它的副本逐漸失去同步。 交換和掩飾交換參數(shù),把這樣的函數(shù) 主題和變體與其將參數(shù)用于單個(gè)方法,不如盡可能的創(chuàng)建多個(gè)單獨(dú)的方法 。舉個(gè)例子,有一個(gè)方法是 靜態(tài)真好盡可能的多寫靜態(tài)變量。如果在程序里,你不需要某個(gè)類多于一個(gè)的實(shí)例,別人也不會(huì)需要的。重復(fù),如果別人抱怨,告訴他們這樣存取速度更快。 嘉吉猜想學(xué)習(xí)嘉吉猜想(我覺得嘉吉是男的) "任何設(shè)計(jì)問題都可以通過增加中間層次解決,即使是中間層太多的問題"。 分解面向?qū)ο蟮某绦?,直到搞不清究竟哪個(gè)對(duì)象負(fù)責(zé)改變程序狀態(tài)為止。 更好的是,安排所有的回調(diào)事件激活都要通過指針森林,就是那個(gè)包含了程序可能用到的所有函數(shù)指針的森林。激活遍歷森林的一個(gè)副作用就是,在釋放對(duì)象引用計(jì)數(shù)指針之前就創(chuàng)造一個(gè)深拷貝,即使實(shí)際上不需要深拷貝。 全堆一塊 把你所有的沒用的和過時(shí)的方法和變量都留在代碼里。畢竟說起來,如果你在 1976 年用過一次,誰知道你啥時(shí)候會(huì)需要再用到呢?雖然程序是改了,但它也可能會(huì)改回來嘛,你就說”不想重新發(fā)明輪子”(領(lǐng)導(dǎo)們都會(huì)喜歡這樣的口氣)。如果你還原封不動(dòng)地留著這些方法和變量的注釋,而且注釋寫得又高深莫測,甭管維護(hù)代碼的是誰,恐怕都不敢對(duì)它輕舉妄動(dòng) 這就是 Final把所有的葉子類都聲明為 final。畢竟說起來,你在項(xiàng)目里的活兒都干完了,顯然不會(huì)有其他人會(huì)通過擴(kuò)展你的類來改進(jìn)你的代碼。這種情況甚至可能有安全漏洞。 java.lang.String 被定義成 final 也許就是這個(gè)原因吧?如果項(xiàng)目組其他程序員有意見,告訴他們這樣做能夠提高運(yùn)行速度。 不用接口鄙視Java的接口吧! 如果你的上級(jí)工程師抱怨,就告訴他們Java強(qiáng)迫你在兩個(gè)不同的類(同樣實(shí)現(xiàn)了這個(gè)接口)之間"剪切"代碼,這樣他們就知道有多難維護(hù)了。 而且,像Java AWT設(shè)計(jì)者一樣工作,寫一大堆功能,但只能被子類調(diào)用,再在方法里加上一大堆"instanceof"類型檢查. 這樣,如果有人想重用你的代碼,他們必須繼承你的類。如果他們想重用你的屬于兩個(gè)不同的類的代碼 - 真不走運(yùn),他們不能同時(shí)繼承兩個(gè)類! 如果非得用接口,起一個(gè)萬能的名字,比如 不用布局管理器永遠(yuǎn)不用布局管理器。 這樣當(dāng)維護(hù)程序員添加一個(gè)或更多組件時(shí),他必須手動(dòng)調(diào)整屏幕上每一個(gè)組件的絕對(duì)位置。 如果你老板強(qiáng)迫你使用布局管理器, 就用個(gè)大的 環(huán)境變量如果你必須為其他程序員編寫類,在你的類里添加環(huán)境檢查代碼( 表格驅(qū)動(dòng)邏輯避免任何形式的表驅(qū)動(dòng)邏輯。 開始看上去再簡單不過了, 但是最終使得用戶會(huì)對(duì)校對(duì)不寒而栗,甚至是簡單的修改表格。 改他媽的字段在Java里,所有私有數(shù)據(jù)類型是以傳值的方式傳參,所有傳遞的參數(shù)真的是只讀的。 調(diào)用者可以隨便修改參數(shù),被調(diào)用者不會(huì)受到任何影響。 相對(duì)的,被傳遞過來的對(duì)象是可讀寫的。引用是按值傳遞的,但被引用的對(duì)象是同一個(gè)。 調(diào)用者可以對(duì)傳過來的對(duì)象字段做任何想做的事。不用記錄方法事實(shí)上有沒有修改傳遞過來的對(duì)象。給你的方法起一個(gè)好名字,使它看上去僅僅是查看了對(duì)象的值,但實(shí)際上卻進(jìn)行了修改。 全局變量的魔力別使用異常來處理進(jìn)程錯(cuò)誤,把你的錯(cuò)誤信息存到一個(gè)全局變量里。確保系統(tǒng)的每一個(gè)長循環(huán)都檢查全局標(biāo)簽,并在發(fā)現(xiàn)異常時(shí)終止。再添加一個(gè)全局變量用來標(biāo)識(shí)用戶點(diǎn)擊了'重置'按鈕。當(dāng)然了,系統(tǒng)里的所有主要循環(huán)也得檢查第二個(gè)標(biāo)簽。 還有些不需要終止的循環(huán),隱藏起來吧。 全局變量,再強(qiáng)調(diào)也不為過!如果上帝不想讓我們使用全局變量,就不會(huì)發(fā)明它了。 別讓上帝失望,盡可能多的設(shè)置和使用全局變量吧。每個(gè)函數(shù)都應(yīng)該用上幾個(gè),即使實(shí)際上不需要也行。畢竟,任何好的維護(hù)工程師都會(huì)很快發(fā)現(xiàn)這是一次偵探鍛煉,他也會(huì)很開心的意識(shí)到,這個(gè)鍛煉能測試出一個(gè)人究竟是不是優(yōu)秀的維護(hù)工程師,抑或只是個(gè)新來的而已。 全局變量,要多用,親愛的全局變量完全把你從指定函數(shù)參數(shù)的工作中解救出來了。利用這個(gè)優(yōu)勢。 選擇這些全局變量中的一個(gè)或多個(gè)指定其他進(jìn)程要做什么。維護(hù)工程師居然愚蠢的認(rèn)為C函數(shù)沒有副作用。 確保他們想不到你在全局變量存儲(chǔ)結(jié)果和內(nèi)部狀態(tài)信息。 副作用在C里, 函數(shù)應(yīng)該是冪等的(沒有副作用)(冪等:同一個(gè)函數(shù)在參數(shù)相同的情況下執(zhí)行結(jié)果相同)。我希望這個(gè)提示足夠明顯。 回退在循環(huán)體里,假裝循環(huán)必定成功,而且會(huì)立即更新所有的指針變量。如果隨后在循環(huán)過程中出現(xiàn)了一個(gè)異常, 回退 被作為循環(huán)體條件并步進(jìn)的指針。 局部變量(這一段正文和上一段正文完全一樣,可能是文章上傳的時(shí)候就錯(cuò)了?) 在循環(huán)體里,假裝循環(huán)必定成功,而且會(huì)立即更新所有的指針變量。如果隨后在循環(huán)過程中出現(xiàn)了一個(gè)異常, 回退 被作為循環(huán)體條件并步進(jìn)的指針。 減少, 重用, 回收 ( Reduce, Reuse, Recycle,3R原則?)如果你需要定義一個(gè)結(jié)構(gòu)體來保存數(shù)據(jù)回調(diào),總把這個(gè)結(jié)構(gòu)體命名為 配置文件它們通常是鍵值對(duì)類型的。那些值將在啟動(dòng)時(shí)被加載。最棒的混淆技術(shù)是給java變量及鍵們使用只有輕微不同的名字 把那些運(yùn)行時(shí)也不改變的常量也放在配置文件里。參數(shù)文件變量至少需要比比一個(gè)簡單變量至少多5倍的維護(hù)代碼才行。 臃腫的類以最蠢的方法確保你的類有界,確保每個(gè)類都引用了外圍的,模糊不清的方法和屬性。舉個(gè)例子,一個(gè)定義天體軌道幾何的類當(dāng)然應(yīng)該有計(jì)算海洋潮汐時(shí)間表的方法,并包含起重機(jī)天氣模型的屬性。不僅在定義類的時(shí)候要這么干,而且當(dāng)然也得在系統(tǒng)代碼里找機(jī)會(huì)調(diào)用,比如需要在垃圾場里找吉他的時(shí)候。 該死的子類對(duì)于寫不可維護(hù)的代碼,面向?qū)ο缶幊毯喼笔巧咸焖蛠淼亩Y物。如果你想創(chuàng)建一個(gè)類,有10個(gè)成員(屬性/方法),那你應(yīng)該考慮這種寫法:寫一個(gè)基類,只有一個(gè)成員,繼承9層,每層添加一個(gè)成員。這樣,到最后一層時(shí),你就具有了全部的10個(gè)成員。如果可能,將每個(gè)類放到不同的文件中,這將更好的膨脹你的 代碼混淆Sedulously eschew obfuscatory hyperverbosity and prolixity. (堅(jiān)決避開混淆,冗長,和啰嗦) C的混淆參與網(wǎng)上的C混淆大賽并學(xué)習(xí)大師的一鱗半爪。 找到一個(gè)Forth或者APL大師在那個(gè)世界,代碼越簡潔,工作方式就越詭異,你就越受人尊重。 來一打能用兩三個(gè)輔助變量解決的問題就別用一個(gè)變量解決 尋找模糊永不停止尋找解決常規(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;
}一致性的小淘氣當(dāng)你需要一個(gè)字符常量的時(shí)候,可以用多種不同格式: ‘ ‘, 32, 0×20, 040。在C或Java里10和010是不同的數(shù)(0開頭的表示8進(jìn)制),你也可以充分利用這個(gè)特性 造型把所有數(shù)據(jù)都以 void * 形式傳遞,然后再造型為合適的結(jié)構(gòu)。不用結(jié)構(gòu)而是通過位移字節(jié)數(shù)來造型也很好玩。 嵌套 SwitchSwitch 里邊還有 Switch,這種嵌套方式是人類大腦難以破解的。 Exploit Implicit Conversion記住編程語言中所有微妙的隱式轉(zhuǎn)換規(guī)則。 充分利用它們。不使用圖片變量(picture variable) (in COBOL or PL/I) 也不用常規(guī)類型轉(zhuǎn)換 (比如 原生整數(shù)使用組合框時(shí),在switch條件中使用整數(shù)而不是定義好的常量 分號(hào)!只要語法允許,多用分號(hào)。 舉個(gè)例子: if(a);
else;
{
int d;
d = c;
}
;用八進(jìn)制在列好的十進(jìn)制的數(shù)字里夾雜一點(diǎn)八進(jìn)制數(shù),像這樣: array = new int []
{
111,
120,
013,
121,
};類型轉(zhuǎn)換在類型轉(zhuǎn)換時(shí)java提供了很多混淆機(jī)會(huì)。一個(gè)簡單的例子:如果你要將一個(gè)Double轉(zhuǎn)換為字符串,曲折一點(diǎn),使用 嵌套盡可能的嵌套。好的程序員能在一行寫10組(),能在一個(gè)方法里寫出20組 直接寫數(shù)字如果你有一個(gè)有100個(gè)元素的數(shù)組, 盡可能多地在程序中直接寫100。 別寫靜態(tài)常量,也別引用 這由來已久的技術(shù)在擁有兩個(gè)不相干的數(shù)組恰好都有100個(gè)元素的程序中尤其有效。如果維護(hù)程序員擅自改變其中一個(gè)數(shù)組的長度,他必須解讀程序中所有使用100進(jìn)行迭代的for循環(huán),以分辨出究竟哪個(gè)循環(huán)依賴了這個(gè)數(shù)組。幾乎可以肯定,他至少會(huì)造成一個(gè)錯(cuò)誤,希望多年后不會(huì)出現(xiàn)另一個(gè)。 還有更殘忍的變體。能讓維護(hù)程序員陷入一種虛假的安全感,看上去盡職盡責(zé)的命名了常量, 卻經(jīng)常偶爾地"意外"使用100這個(gè)值而不是定義好的常量。最最殘忍的是, 偶爾使用一些其他無關(guān)的,僅僅恰好值是100的常量?,F(xiàn)在, 毋庸置疑, 你應(yīng)該避免任何一致的命名方案,也不在聲明數(shù)組時(shí)用常量定義大小了。 C數(shù)組的新視角C編譯器 int myfunc(int q, int p) { return p%q; }
// ...
myfunc(6291, 8)[Array];不幸的是,這些技術(shù)只能用于本地化的C類,java不讓。 長行把盡可能多的代碼打包成單行。這節(jié)省了臨時(shí)變量的開銷,還能通過消除換行符和空格使源文件變短。提示:移除操作符周圍的所有空白。 好的程序員經(jīng)常會(huì)困擾于一些編輯器強(qiáng)加的255字符行長度限制。好處是長行讓那些不能閱讀6號(hào)字的程序員必須拖動(dòng)滾動(dòng)條才能看完這一整行。 異常我要讓你知道一個(gè)鮮為人知的秘密. 后臺(tái)異常的出現(xiàn)是一個(gè)錯(cuò)誤. 正確編寫的代碼永不失敗,所以異常實(shí)際上是不必要的。不要在它們身上浪費(fèi)時(shí)間。子類異常是無能的人知道他們的代碼將失敗才寫的。你可以大大簡化你的程序,在整個(gè)應(yīng)用程序 (或main方法) 只寫一個(gè) try/catch然后調(diào)用System.exit()就行了。有一個(gè)完美的標(biāo)準(zhǔn)值得堅(jiān)持,在每一個(gè)方法頭聲明Exception,不管實(shí)際上有沒有任何異常要拋。 善用異常在不需要異常的語句里使用異常。一般使用 放棄多線程標(biāo)題說明一切 (title says it all) 代碼標(biāo)準(zhǔn)了解語言在新聞組里各種各樣關(guān)于花式的位運(yùn)算魔法代碼的爭論 e.g. *++b ? (*++b + *(b-1)) 0 就不是由語言規(guī)范定義的,每個(gè)編譯器都可以自由地按不同的順序進(jìn)行解析. 這使它更加致命。同樣, 好好利用C和Java移除所有空格后復(fù)雜的解析規(guī)則. 盡早return完全遵守no goto,no early returns的建議,也別寫標(biāo)簽,這樣和至少5層深的 if/else 嵌套再搭配不過了。 不用{}除非語法要求,否則不用 來自地獄的制表符永遠(yuǎn)不要低估你可以通過制表符來造成多少破壞,只需要用制表符代替空格,尤其是公司沒規(guī)定一個(gè)制表符代表幾個(gè)空格的時(shí)候。 讓字里行間充滿制表符,或者使用工具來完成空格對(duì)制表符的轉(zhuǎn)換也很好。 魔法數(shù)組在某些數(shù)組位置使用特殊值作為標(biāo)簽。一個(gè)好的例子是 再探魔法數(shù)組如果需要給定類型的多個(gè)變量,定義一個(gè)數(shù)組就行了,然后用下標(biāo)訪問。只有你知道每個(gè)下標(biāo)的意義并且不在文檔里記錄。也不要費(fèi)心去用 不做美化不要使用自動(dòng)的代碼整潔(美化)工具保持你的代碼規(guī)整. 在你的公司用人們?cè)赑VCS/CVS (版本控制跟蹤工具) 創(chuàng)造了虛假的代碼行數(shù)的理由說服他們放棄 ,并且每個(gè)程序員都應(yīng)該有他自己的縮進(jìn)風(fēng)格,并在他寫的任何永遠(yuǎn)神圣不可侵犯的模塊中體現(xiàn)出來。 堅(jiān)持其他程序員在他們自己的模塊中也是這么干的。拒絕美化十分簡單,即使要耗費(fèi)數(shù)以百萬計(jì)的擊鍵來人工對(duì)齊,并消耗大量的時(shí)間來搞清楚那可憐的,個(gè)人風(fēng)格的,對(duì)齊。 只要堅(jiān)持每個(gè)人不只是在存儲(chǔ)在公共庫里時(shí),還在他們編輯時(shí), 都這么對(duì)齊 。這將讓老板為了實(shí)現(xiàn)RWAR,禁止自動(dòng)對(duì)齊。沒有自動(dòng)對(duì)齊,你可以自由的 意外 錯(cuò)位代碼,形成視覺錯(cuò)覺,讓看上去正常的方法體比實(shí)際上更短或者更長,或者代碼運(yùn)行時(shí)才發(fā)現(xiàn)else分支匹配了一個(gè)出乎意料的if。 e.g. if(a) if(b) x=y; else x=z; 宏預(yù)處理器它提供了巨大的混淆機(jī)會(huì),這項(xiàng)技術(shù)的關(guān)鍵點(diǎn)是幾層深的嵌套宏展開,你應(yīng)該把宏命令分散在各個(gè)不同文件中,將可執(zhí)行代碼換成宏然后分散到各個(gè) *.cpp 文件中 (即使是那些從來不用的宏),這將使代碼變更的編譯需求數(shù)量最大化 。 混合數(shù)組聲明Java 有兩種數(shù)組聲明方式。老版的C也能這么做,老式的 byte[ ] rowvector, colvector , matrix[ ]; 類似于: byte[ ] rowvector; byte[ ] colvector; byte[ ][] matrix; 隱藏錯(cuò)誤恢復(fù)代碼使用嵌套將函數(shù)的錯(cuò)誤恢復(fù)方法盡可能遠(yuǎn)離調(diào)用的位置。一個(gè)簡單的例子就是設(shè)置了 10 或者 12 級(jí)的嵌套: 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 */
}偽 C
混亂的導(dǎo)入讓維護(hù)程序員去猜測你使用的方法和類究竟來自那一個(gè)包。 不要這樣寫: import MyPackage.Read; import MyPackage.Write; 應(yīng)該用: import Mypackage. *; 無論多么晦澀也不完全限定任何方法或類。 讓維護(hù)程序員盡情猜測你使用的包/類的來源。當(dāng)然啦,對(duì)你幫助最大的導(dǎo)入方式肯定不是全限定導(dǎo)入。 廁所油管(又臭又長?)在任何情況下,決不能允許在屏幕上出現(xiàn)多于一個(gè)函數(shù)或過程的代碼。實(shí)現(xiàn)這一點(diǎn)很簡單,使用這個(gè)方便的技巧就行: 空行通常用于分離代碼的邏輯塊。每行本身都一個(gè)邏輯塊. 在每行之間放上空行。 不要在代碼的結(jié)尾寫注釋。 把它放在這一行的上面. 如果你被迫在行尾寫注釋,在整個(gè)文件中選擇最長的代碼行,加上10個(gè)空格,然后將所有其他行注釋的開頭與該列對(duì)齊。 方法頂部的注釋需要使用模板,而且最少包含 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: */ 在文檔中放置這么多冗余信息的這項(xiàng)技術(shù),可以讓它很快的過時(shí),并有助于負(fù)責(zé)維護(hù)的程序員傻到去相信它。 測試I don't need to test my programs. I have an error-correcting modem. (我不需要測試我的程序。我有一個(gè)錯(cuò)誤糾正調(diào)制解調(diào)器。)
把Bug留在程序里,留給新來的維護(hù)工程師,留給尋找有趣事情的后來者。一個(gè)完美的Bug從不留下任何可能解釋本身出現(xiàn)位置或原因的線索, 最懶,但也最有效的方法就是從來不測試你的代碼. 拒絕測試從不測試任何代碼,包括錯(cuò)誤處理,機(jī)器崩潰或系統(tǒng)故障。也不檢查操作系統(tǒng)的返回代碼。這種代碼從來不會(huì)被執(zhí)行,只會(huì)減慢測試時(shí)間而已。 而且, 你怎么可能測試到代碼對(duì)硬盤錯(cuò)誤,文件讀取錯(cuò)誤,系統(tǒng)崩潰或其他任何類型問題的處理方式呢。為什么?你怎么可能有一個(gè)令人難以置信的不可靠的計(jì)算機(jī)或能模仿這樣事情的測試腳手架呢? 現(xiàn)代硬件永不失敗, 誰想只是為了測試目編寫代碼呢?它沒有任何樂趣。如果用戶抱怨,,怪操作系統(tǒng)或硬件就行了。 他們不會(huì)知道真相的。 永不,絕不做任何性能測試伙計(jì),如果程序不夠快,告訴客戶買臺(tái)更快的機(jī)器就好了。如果你做了性能測試,就可能發(fā)現(xiàn)性能瓶頸,就可能需要改變算法啊, 就可能需要重構(gòu)整個(gè)產(chǎn)品. 誰想那么做? 而且, 在客戶現(xiàn)場上出現(xiàn)的性能問題意味著一次免費(fèi)體驗(yàn)異國情調(diào)的旅行,只要換個(gè)新鏡頭并準(zhǔn)備好護(hù)照就行了。 別寫測試用例拒絕執(zhí)行代碼覆蓋或路徑覆蓋測試,自動(dòng)化測試給窩囊廢用的。 自己找出哪些功能占你日常使用的90%,并分配90%測試到這些路徑。畢竟,這個(gè)技術(shù)可能只測試你源代碼的60%,直接節(jié)省了40%的測試工作量.這可以幫助你在項(xiàng)目后期補(bǔ)償計(jì)劃量表。很久以后才會(huì)有人注意到那些美妙的“營銷特性”不工作。 著名的大型軟件公司就是這樣測試代碼的; 你也應(yīng)該這樣. 如果看到這里你還沒走,請(qǐng)參閱下一項(xiàng). 懦夫才測試勇敢的程序員從不測試。太多的程序員害怕老板,害怕失去工作,害怕客戶的指責(zé)郵件,害怕被起訴了。這種恐懼麻痹了熱情,降低了工作效率. 研究表明,消除測試階段意味著管理人員可以提前確定上線日期,這對(duì)工作計(jì)劃有著明顯幫助。恐懼消失,創(chuàng)新和實(shí)驗(yàn)將開花結(jié)果。程序員的角色是生成代碼,debug是幫助臺(tái)和維護(hù)工程師的事,互相合作嘛。如果我們對(duì)我們的編碼能力有充分的信心,那么測試就沒有必要。從邏輯上看,傻瓜都知道測試解決不了任何技術(shù)問題,相對(duì)的,這只是一個(gè)情感信心的問題。對(duì)缺乏信心這個(gè)問題,一個(gè)更有效的解決方案是完全消除測試并送我們的程序員去上建立自信的課程. 畢竟,如果我們選擇做測試,我們就得測試程序的每個(gè)變化, 但現(xiàn)在我們只需要送我們的程序員去上一節(jié)課來建立自信就成了,這可是驚人的成本效益啊。 確保程序只在調(diào)試模式下工作如果你定義 TESTING 為 1 #define TESTING 1 這給了你一個(gè)很好的機(jī)會(huì),插入單獨(dú)的代碼段,如 #if TESTING==1 #endif 它可以包含一些不可或缺的代碼,比如 x = rt_val; 所以,如果有人把TESTING改成了0,這程序就將停止工作。只要再加上一小點(diǎn)想象力,不僅能做到邏輯混亂,還能讓編譯器也混亂。 語言的選擇Philosophy is a battle against the bewitchment of our intelligence by means of language.
電腦語言正在變得越來越愚蠢,使用那些新的靜態(tài)語言簡直反人類。 堅(jiān)持使用你能僥幸找到的最老的語言,如果可以的話,用八進(jìn)制的語言(像Hans und Frans一樣,我不是娘娘腔的男人;我很有男子氣概的,我用漆金線連接IBM單元記錄設(shè)備 (穿孔卡片), 并用手在紙帶上打孔實(shí)現(xiàn)編碼), 匯編不夠格,F(xiàn)ORTRAN和COBOL也不夠格, C和BASIC不不夠格, C++也不夠格.,全都不夠格。 FORTRAN用 FORTRAN寫代碼。如果你的老板質(zhì)疑你,你就告訴他FORTRAN上有很多有用的庫可以節(jié)約時(shí)間,即使用 FORTRAN寫的代碼的可維護(hù)性是0,這種編寫不可維護(hù)代碼的規(guī)則是很容易遵循的. 拒絕 Ada這些技術(shù)大約有20%不能用于 Ada,拒絕它吧。如果老板強(qiáng)迫你用,堅(jiān)持回答別人也不用,并且指出它會(huì)讓你的大量工具失效,比如 lint 和 plummer,這可都是能有效利用C缺陷的法寶啊。 使用 ASM把所有公共的效用函數(shù)轉(zhuǎn)換成asm. 使用 QBASIC確保所有重要的庫函數(shù)用QBASIC編寫 , 然后簡單的寫一個(gè)asm包裝器就能處理大- >中內(nèi)存模型映射。 寫點(diǎn)匯編在程序里隨機(jī)寫點(diǎn)匯編多有意思啊,幾乎沒有人懂,即使是幾行也能冷卻程序員的維護(hù)妄想。 匯編與C如果你有被C調(diào)用的匯編模塊,盡可能多的使用它,即使是最微不足道的目的,也要確保充分利用了goto,bcc或者其他迷人的匯編習(xí)俗。 不用維護(hù)工具不適用Abundance編碼, 也別用任何其他語言的編碼工具,它們從一開始就被設(shè)計(jì)的主要目就是使維護(hù)工程師的工作變簡單 。 同樣,避免使用 Eiffel 或者 Ada,它們從一開始就是為了 在項(xiàng)目進(jìn)入生產(chǎn)之前捕捉bug 而設(shè)計(jì)的 。 與人交流__Hell is other people._ _(地獄就是別人)
這里有很多靈光一現(xiàn)的點(diǎn)子,能讓那些喋喋不休的維護(hù)工程師變的沮喪,能擊潰你的老板阻止你寫不可維護(hù)代碼的妄想,甚至煽動(dòng)起一場關(guān)于每個(gè)人在庫中的代碼格式應(yīng)該是什么樣子的爭論. 老板全都懂如果你的老板認(rèn)為他/她自己20年的FORTRAN編程經(jīng)驗(yàn)足以指導(dǎo)現(xiàn)代編程,嚴(yán)格遵守他/她的所有建議。這樣,老板會(huì)信任你。這對(duì)你的職業(yè)生涯有益,而且你會(huì)學(xué)到很多新的方法來混淆程序代碼。 顛覆幫助臺(tái)確保代碼充滿bug的方法之一是確保維護(hù)人員從不了解幫助臺(tái). 這就需要顛覆幫助臺(tái). 不接電話. 使用自動(dòng)語音,回復(fù) "感謝你來售后服務(wù)熱線。 需要人工服務(wù)請(qǐng)按“1”或者留下語音留言",忽略請(qǐng)求幫助的郵件,而不是給他們一個(gè)可用的電話號(hào)碼。 對(duì)任何問題的標(biāo)準(zhǔn)回復(fù)是 " 我認(rèn)為你的帳戶被鎖定了,但是有權(quán)解鎖的人現(xiàn)在不在" 少說話不用在意下一個(gè)“千年蟲”。如果你發(fā)現(xiàn)了什么東西在悄悄的向著摧毀西半球的所有生命前進(jìn),別公開討論它,到我們處在最后四年,恐慌和機(jī)遇的關(guān)鍵事件窗口期再說。別把你的發(fā)現(xiàn)告訴朋友,同事或者任何其他有能力的人。 在任何情況下也不嘗試宣揚(yáng)這個(gè)信息也許暗示著新的或巨大盈利或威脅 。僅僅發(fā)送一個(gè)普通優(yōu)先級(jí),術(shù)語加密的備忘錄給你的上級(jí)來履行職責(zé)(to cover-your-a$$) . 如果可能的話,就把這段術(shù)語加密的信息作為一個(gè)不相干又帶著更緊迫的業(yè)務(wù)關(guān)注的的純文本備忘錄的附件。 放心,我們都看到了威脅。 安心的睡吧,很久之后,他們將用對(duì)數(shù)增長的時(shí)薪求提前退休的你回來 ! 瞎寫的微妙微妙是一件有趣的事, 有時(shí)錘子比別的工具更容易微妙。試試微妙的注釋,創(chuàng)造一個(gè)類,有著類似 在月俱樂部寫書加入寫電腦書的俱樂部,選擇那些總是忙于寫書以至于沒空寫代碼的作者,瀏覽本地書店尋找那些有很多圖卻沒有代碼實(shí)例的文章,瀏覽這些書,學(xué)習(xí)那些空洞又學(xué)術(shù)的語言, 可以拿來恐嚇那些追在你屁股后面的傻瓜們。你的代碼應(yīng)該富有特色。如果人們理解不了你的詞匯,他們就會(huì)認(rèn)為你極有才華而算法深?yuàn)W。千萬避免給你的算法任何易于理解的例子。 重造輪子你總是想寫系統(tǒng)級(jí)別的代碼,現(xiàn)在,機(jī)會(huì)來了。忽略標(biāo)準(zhǔn)庫,寫你自己的。放在簡歷上也好看。 自己的BNF總是使用你自己的,獨(dú)一無二的,BNF無關(guān)的命令語法。不要試圖在注釋里為合法和非法命令提供配套的解釋 .那被證明了完全缺乏學(xué)術(shù)嚴(yán)謹(jǐn)性,鐵路運(yùn)行時(shí)刻表從來都不準(zhǔn)確。確保沒有對(duì)從中間過渡符號(hào)(真的有合適的意思)到最終的符號(hào)(你最終鍵入的)演變做明顯的解釋。 永遠(yuǎn)也不使用字體,顏色,加粗或其他任何視覺線索來幫助閱讀者區(qū)分這兩者。在你自己的BNF規(guī)范里使用與程序完全相同的標(biāo)點(diǎn)符號(hào),這樣閱讀者就搞不清在你的BNF規(guī)范里 自己的內(nèi)存分配每個(gè)人都知道調(diào)試動(dòng)態(tài)存儲(chǔ)復(fù)雜而費(fèi)時(shí)。改造你的存儲(chǔ)分配器而不是確保每一個(gè)類都沒有內(nèi)存泄露。強(qiáng)迫你的用戶定期重啟系統(tǒng)來清理堆而不是釋放內(nèi)存。 系統(tǒng)重啟是一件很簡單的事 -- 比修復(fù)所有的內(nèi)存泄露漏洞簡單多了 。只要用戶記得定期重啟系統(tǒng),就不會(huì)發(fā)生運(yùn)行時(shí)堆空間溢出。想象一下,這能改變他們 “一次部署” 的策略! 另類語言的技巧讓你腦殘的基本編程方法
SQL 別名給數(shù)據(jù)庫表取一到二個(gè)字符長度的名稱。最好是跟它毫無關(guān)系的已有表的表名類似。 SQL 外聯(lián) Outer Join混合各種外部連接語法,讓大家看著頭皮發(fā)麻。 JavaScript Scope"優(yōu)化" JavaScript 代碼要充分利用一個(gè)函數(shù)可以在調(diào)用者的范圍內(nèi)訪問所有本地變量的特性。 Visual Basic 變量定義將如下定義: dim Count_num as string dim Color_var as string dim counter as integer 改為: Dim Count_num$, Color_var$, counter% Visual Basic 瘋狂如果從一個(gè)文本文件讀取 15 個(gè)字符,那么可以使用如下方法來讓事情變得復(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" Delphi/Pascal不準(zhǔn)使用函數(shù)和過程。使用 label/goto 語句,然后在代碼中亂跳。這足以讓他人在跟蹤代碼時(shí)發(fā)瘋。另外就是讓代碼在不經(jīng)意間來回跳躍,沒錯(cuò),就是這個(gè)竅門。 Perl在一行非常非常長的代碼結(jié)尾使用 if 和 unless 語句。 LispLISP 一個(gè)非常夢幻般的語言,用來寫不可維護(hù)代碼??纯聪旅娴倪@些令人困惑代碼片段: (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如果一個(gè)變量未定義就不用使用,除非你給它賦值。當(dāng)你檢查變量類型時(shí)候就會(huì)發(fā)生這種問題: lcx = TYPE('somevariable')lcx 的值將是 LOCAL lcx
lcx = TYPE('somevariable')lcx 的值現(xiàn)在是 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 雜七雜八的技術(shù)如果你給某人一段程序,你會(huì)讓他困惑一天;如果你教他們?nèi)绾尉幊?,你?huì)讓他困惑一輩子。
不要重編譯讓我們從一條可能是有史以來最友好的技巧開始:把代碼編譯成可執(zhí)行文件。如果它能用,就在源代碼里做一兩個(gè)微小的改動(dòng) — 每個(gè)模塊都照此辦理。但是不要費(fèi)勁巴拉地再編譯一次了。 你可以留著等以后有空而且需要調(diào)試的時(shí)候再說。多年以后,等可憐的維護(hù)代碼的程序員更改了代碼之后發(fā)現(xiàn)出錯(cuò)了,他會(huì)有一種錯(cuò)覺,覺得這些肯定是他自己最近修改的。這樣你就能讓他毫無頭緒地忙碌很長時(shí)間。 完敗調(diào)試工具對(duì)于試圖用行調(diào)試工具追蹤來看懂你的代碼的人,簡單的一招就能讓他狼狽不堪,那就是把每一行代碼都寫得很長。特別要把 then 語句 和 if 語句放在同一行里。他們無法設(shè)置斷點(diǎn)。他們也無法分清在看的分支是哪個(gè) if 里的。 公制和美制在工程方面有兩種編碼方式。一種是把所有輸入都轉(zhuǎn)換為公制(米制)計(jì)量單位,然后在輸出的時(shí)候自己換算回各種民用計(jì)量單位。另一種是從頭到尾都保持各種計(jì)量單位混合在一起。總是選擇第二種方式,這就是美國之道! CANICANI 是 Constant And Never-ending Improvement 的縮寫,持續(xù)改進(jìn)的意思。 要常常對(duì)你的代碼做出“改進(jìn)”,并強(qiáng)迫用戶經(jīng)常升級(jí) — 畢竟沒人愿意用一個(gè)過時(shí)的版本嘛。即便他們覺得他們對(duì)現(xiàn)有的程序滿意了,想想看,如果他們看到你又“完善“了它,他們會(huì)多么開心啊!不要告訴任何人版本之間的差別,除非你被逼無奈 — 畢竟,為什么要告訴他們本來永遠(yuǎn)也不會(huì)注意到的一些bug呢?
|
|
|