來源為https://github.com/Droogans/unmaintainable-code/
如何撰寫難以維護的程式碼
確保終身就業 ;-)
Roedy Green 加拿大心智產品
2015 年左右,經作者許可重製 (原文連結)
簡介
別把無能當作惡意。 - 拿破崙
為了在 Java 程式設計領域創造就業機會,我將從大師們那裡傳授如何撰寫難以維護的程式碼的技巧,這樣後來者將花費數年時間才能進行最簡單的更改。此外,如果您遵循所有這些規則,您甚至可以確保自己終身就業,因為沒有人能像您一樣希望維護這段程式碼。然而,如果您完全遵循所有這些規則,即使是您自己也無法維護這段程式碼!
您不希望過度做這件事。您的程式碼不應該看起來完全難以維護,只需是那樣。否則,它可能會被重寫或重構。
一般原則
Quidquid latine dictum sit, altum sonatur. - 拉丁文的任何話都聽起來很深奧。
為了阻礙維護程式設計師,您必須了解他的思維方式。他手上有您的龐大程式。他沒有時間全部閱讀,更不用說理解。他希望迅速找到進行更改的地方,進行更改並離開,並且不希望更改帶來任何意外的副作用。
他透過一根衛生紙筒觀看您的程式碼。他一次只能看到程式的一小部分。您要確保他無法從中獲得整體概念。您要盡可能讓他難以找到他正在尋找的程式碼。但更重要的是,您要讓他盡可能地難以安全地忽略任何內容。
程式設計師通常會被慣例所安撫。但偶爾,通過微妙地違反慣例,您迫使他用放大鏡閱讀您程式碼的每一行。
你可能會認為每種語言特性都會使代碼難以維護 – 這並非如此,只有在被適當地誤用時才會如此。
命名
“當我使用一個詞語時,” 頑皮的杜姆提說道,聲音中帶著一絲輕蔑,“它的意思就是我選擇的意思 - 既不多也不少。”
- 路易斯·卡羅爾 – 《鏡中奇遇》第6章
寫出難以維護的代碼的許多技巧在於命名變數和方法的藝術。對編譯器來說,它們一點都不重要。這給了你極大的自由度,可以用它們來迷惑維護程式設計師。
給 Names For Baby 新的用途
買一本嬰兒命名書,你將永遠不會為變數名稱感到困惑。Fred 是一個很棒的名字,而且容易打字。如果你正在尋找易於打字的變數名稱,可以嘗試使用 asdf
或者如果你使用 DSK 鍵盤,可以使用 aoeu
。
單字母變數名稱
如果你將變數命名為 a
、b
、c
,那麼使用簡單的文本編輯器將無法搜索到它們的實例。此外,沒有人能夠猜測它們的用途。如果有人甚至暗示要打破自從 FØRTRAN 以來一直受尊敬的使用 i
、j
和 k
作為索引變數的傳統,即將它們替換為 ii
、jj
和 kk
,請警告他們西班牙宗教裁判所對異端分子所做的事情。
創意拼寫錯誤
如果你必須使用描述性的變數和函數名稱,請拼寫錯誤。通過在一些函數和變數名稱中拼寫錯誤,並在其他地方拼寫正確(例如 SetPintleOpening
和 SetPintalClosing
),我們有效地消除了 grep 或 IDE 搜索技術的使用。這方法非常有效。通過在不同的劇院中拼寫 tory 或 tori,增加一種國際風味。
抽象化
在命名函數和變數時,大量使用抽象詞語,如 it、everything、data、handle、stuff、do、routine、perform 和數字,例如 routineX48
、PerformDataFunction
、DoIt
、HandleStuff
和 do_args_method
。
首字母縮略詞
使用首字母縮略詞使代碼簡潔。真正的男人從不定義首字母縮略詞;他們天生就理解它們。
同義詞替換
為了打破無聊,使用一本同義詞詞典,尋找盡可能多的替代詞彙來指代相同的動作,例如 display、show、present。模糊地暗示存在一些微妙的差異,即使實際上並不存在。然而,如果有兩個功能相似但有關鍵區別的函數,始終使用相同的詞來描述這兩個功能(例如 print 意為 “寫入文件”、“在紙上放墨水” 和 “在屏幕上顯示”)。切勿屈服於要求編寫具有特殊目的的專案詞彙詳細定義的要求。這樣做將是對 信息隱藏 結構化設計原則的專業違反。
使用其他語言的複數形式
一個 VMS 腳本跟蹤從各種 “Vaxen” 返回的 “statii”。世界語、克林貢語 和霍比特語都符合這些目的的語言。對於虛構的世界語複數形式,添加 oj。這將有助於實現世界和平。
大小寫混合
隨機大寫單詞中間音節的第一個字母。例如 ComputeRasterHistoGram()
。
重複使用名稱
在語言規則允許的地方,給予類、建構子、方法、成員變量、參數和局部變量相同的名稱。額外加分的是,在 {}
區塊內重複使用局部變量名稱。目標是迫使維護程序員仔細檢查每個實例的作用域。特別是在 Java 中,讓普通方法偽裝成建構子。
重音字母
在變量名稱中使用帶重音的字符。例如:
typedef struct { int i; } ínt;
其中第二個 ínt 的 í 實際上是 i-acute。僅使用簡單的文本編輯器,幾乎不可能區分重音符號的傾斜。
利用編譯器名稱長度限制
如果編譯器只區分名稱的前 8 個字符,則變化結尾,例如一種情況下是 var_unit_update()
,另一種情況下是 var_unit_setup()
。編譯器將將兩者都視為 var_unit
。
底線,真正的朋友
使用 _
和 __
作為識別符。
混合語言
隨機穿插兩種語言(人類或計算機)。如果你的老闆堅持讓你使用他的語言,告訴他你可以更好地組織自己的思緒,或者如果這樣不起作用,聲稱語言歧視並威脅向雇主提起訴訟索取巨額賠償。
擴展ASCII
擴展ASCII字符作為變量名是完全有效的,包括 ß、Ð 和 ñ 字符。在簡單的文本編輯器中幾乎不可能輸入,除非複製/粘貼。
其他語言的名稱
使用外語詞典作為變量名的來源。例如,使用德語中的 punkt 代表 point。沒有你對德語的牢固掌握,維護代碼的人將享受解讀含義的多元文化體驗。
數學名稱
選擇偽裝成數學運算符的變量名,例如:
openParen = (slash + asterix) / equals;
耀眼的名稱
選擇帶有無關情感內涵的變量名。例如:
marypoppins = (superman + starship) / god;
這會讓讀者困惑,因為他們很難將詞語的情感內涵與他們試圖思考的邏輯分開。
重新命名和重複使用
這個技巧在 Ada 語言中特別有效,該語言對許多標準的混淆技術免疫。最初為您使用的所有對象和包命名的人都是白癡。與其試圖說服他們改變,不如使用重命名和子類型將一切都重新命名為您自己設計的名稱。確保留下一些對舊名稱的引用,作為對不慎者的陷阱。
何時使用 i
永遠不要將 i
用於最內層的循環變量。除了 i
之外,任何其他變量都可以使用。尤其是對於非整數變量,應自由使用 i
。同樣,將 n
用作循環索引。
約定無關緊要
忽略 Sun Java Coding Conventions,畢竟,Sun 公司也是如此。幸運的是,當你違反這些約定時,編譯器不會告密。目標是提出只在大小寫上微妙不同的名稱。如果被迫使用大寫約定,仍然可以在選擇模糊的地方進行顛覆,例如同時使用 inputFilename 和 inputfileName。發明自己的複雜命名約定,然後責罵其他人不遵循它們。
小寫的 l 看起來很像數字 1
使用小寫的 l 來表示長常數。例如,10l
更容易被誤認為 101
而不是 10L
。禁止使用任何清楚區分 uvw
、wW
、gq9
、2z
、5s
、il17|!j
、oO08
、`` '"
、`;,.`、`m nn rn` 和 `{[()]}` 的字型。要有創意。
將全域名稱重複使用為私有名稱
在模組 A 中宣告一個全域陣列,在模組 B 的標頭檔中宣告一個同名的私有陣列,這樣看起來在模組 B 中使用的是全域陣列,但實際上不是。在註解中不要提及這種重複。
回收再利用
以混淆的方式使用作用域,通過以矛盾的方式重複使用變數名稱。例如,假設您有全域變數 A
和 B
,以及函數 foo
和 bar
。如果您知道變數 A
將經常傳遞給 foo
,而 B
則傳遞給 bar
,請確保將函數定義為 function foo(B)
和 function bar(A)
,這樣在函數內部 A
將始終被稱為 B
,反之亦然。通過更多函數和全域變數,您可以創建龐大的混亂網絡,相互矛盾地使用相同的名稱。
重複使用您的變數
在作用域規則允許的地方,重複使用現有的不相關變數名稱。同樣,將同一臨時變數用於兩個不相關的目的(聲稱節省堆疊空間)。對於一種狡猾的變體,變形變數,例如,在一個非常長的方法的頂部為變數賦值,然後在中間的某個地方以微妙的方式改變變數的含義,例如,將其從基於 0 的座標轉換為基於 1 的座標。請務必不要記錄這種含義變更。
Cd wrttn wtht vwls s mch trsr
在變數或方法名稱中使用縮寫時,通過為同一單詞提供幾種變體,甚至偶爾用全拼寫出來,來打破單調。這有助於打敗那些懶惰的人,他們只使用文本搜索來理解程序的某些方面。考慮變體拼寫作為詭計的一種變體,例如,混合使用國際 colour、美式 color 和酷哥語 kulerz。如果完全拼寫名稱,每個名稱只有一種可能的拼法。這對於維護程序員來說太容易記住了。由於縮寫單詞有很多不同的方式,使用縮審,您可以有幾個不同的變數,它們都有相同的表面目的。作為額外的獎勵,維護程序員甚至可能不會注意到它們是獨立的變數。
誤導性名稱
確保每個方法都比其名稱所暗示的多做一點(或少做一點)。舉個簡單的例子,一個名為 isValid(x)
的方法應該在副作用中將 x
轉換為二進制並將結果存儲在數據庫中。
m_
來自 C++ 領域的一種命名慣例是在成員前面使用 m_
。這應該幫助您將它們與方法區分開來,只要您忘記了 “method” 也以字母 “m” 開頭。
o_apple obj_apple
為每個類的每個實例使用 “o” 或 “obj” 前綴,以顯示您正在考慮整個多態圖。
匈牙利命名法
匈牙利命名法是源代碼混淆技術中的戰術核武器;使用它吧!由於大量源代碼被這種慣例污染,沒有什麼比一場精心策劃的匈牙利命名法攻擊更能迅速殺死一名維護工程師。以下提示將幫助您破壞匈牙利命名法的原始意圖:
-
堅持在 C++ 和其他直接強制變量的 const 性質的語言中使用 “c” 作為 const 的表示。
-
尋找並使用在當前語言以外的其他語言中具有意義的匈牙利瑕疵。例如,堅持使用 PowerBuilder 的
l_
和a_
(本地和引數)作用範圍前綴,並始終使用 VB 式風格,在編寫 C++ 時對每種控制類型都有一個匈牙利瑕疵。盡量忽視 MFC 源代碼中明顯可見的 megs 不使用控制類型的匈牙利瑕疵。 -
總是違反匈牙利命名法的原則,即最常用的變量應該攜帶最少的額外信息。通過上述技術和堅持每個類型都有自定義瑕疵前綴來實現這一目標。絕不允許任何人提醒您沒有瑕疵告訴您某物是一個類。如果您未能遵守這一原則,則源代碼可能會充斥著具有更高元音/輔音比率的較短變量名,最壞的情況下,這可能導致混淆的完全崩潰,並在代碼中自發出現英文命名法!
-
肆無忌憚地違反匈牙利式概念,即函式引數和其他高可見性符號必須給予有意義的名稱,但僅僅使用匈牙利式前綴作為臨時變數名稱。
-
堅持在匈牙利式前綴中攜帶完全正交的資訊。考慮這個現實世界的例子
a_crszkvc30LastNameCol
。一組維護工程師花了將近3天的時間才弄清楚這個龐大的變數名稱描述了一個常數、參考、從資料庫欄位中保存信息的函式引數,該欄位的類型為 Varchar[30],名稱為 “LastName”,並且是表的主鍵的一部分。當這種原則與 “所有變數應該是公開的” 結合時,這種技術可以立即使數千行源代碼變得過時! -
利用人類大腦同時僅能記住7個信息的原則。例如,按照上述標準編寫的代碼具有以下特點:
- 單個賦值語句包含14個類型和名稱信息。
- 單個函式調用傳遞三個參數並賦值結果包含29個類型和名稱信息。
- 努力改進這種優秀但過於簡潔的標準。通過建議在代碼中使用一個5個字母的星期幾前綴,以幫助區分在
星期一上午
和星期五下午
編寫的代碼,來給管理層和同事留下深刻印象。 - 即使是一個中等複雜的巢狀結構也很容易使短期記憶不堪重負,尤其是當維護程序員無法同時在屏幕上看到每個區塊的開始和結束時。
重新思考匈牙利式前綴
匈牙利式前綴中的一個後續技巧是 “更改變數的類型但保持變數名稱不變”。這幾乎總是在從 Win16 遷移到 Windows 應用程式時完成:
WndProc(HWND hW, WORD wMsg, WORD wParam, LONG lParam)
到 Win32
WndProc(HWND hW, UINT wMsg, WPARAM wParam, LPARAM lParam)
其中 w
值暗示它們是字,但實際上它們指的是長整數。這種方法的真正價值在於 Win64 遷移時變得清晰,當參數將是64位寬時,但舊的 w
和 l
前綴將永遠保留。
減少、重複使用、回收
如果您必須定義一個結構來保存回呼的數據,請始終將該結構命名為 PRIVDATA
。每個模組都可以定義自己的 PRIVDATA
。在 VC++ 中,這樣做的好處是會讓調試器感到困惑,因此如果您有一個 PRIVDATA
變數並嘗試在監視窗口中展開它,它不知道您指的是哪個 PRIVDATA
,所以它只會選擇一個。
模糊的電影參考
使用常數名稱像 LancelotsFavouriteColour
而不是 blue
,並將其分配為十六進制值 $0204FB
。該顏色在屏幕上看起來與純藍色相同,維護程序員必須計算 0204FB
(或使用一些圖形工具)才能知道它的外觀。只有那些對《蒙提·派森與聖杯》非常熟悉的人才會知道 Lancelot 的最喜歡的顏色是藍色。如果一個維護程序員無法從記憶中完整引用整部《蒙提·派森》電影的話,他或她就不應該成為一名程序員。
偽裝
一個 bug 浮現所需的時間越長,找到它就越困難。 - 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];
}
如果沒有顏色編碼,您會注意到三行代碼被註釋掉嗎?
命名空間
在 C 中,Struct/union 和 typedef struct/union 是不同的命名空間(在 C++ 中不是)。在這兩個命名空間中使用相同的名稱來定義結構或聯合體。如果可能的話,讓它們幾乎兼容。
typedef struct {
char* pTr;
size_t lEn;
} snafu;
struct snafu {
unsigned cNt
char* pTr;
size_t lEn;
} A;
隱藏巨集定義
在垃圾註釋中隱藏巨集定義。程序員會感到厭煩,不會完成閱讀註釋,因此永遠不會發現巨集。確保巨集將看起來像一個完全合法的賦值替換為一些奇怪的操作,一個簡單的例子:
#define a=b a=0-b
假裝忙碌
使用 define
陳述來創建虛構的函數,這些函數只是將其引數註釋掉,例如:
#define fastcopy(x,y,z) /*xyz*/
// ...
fastcopy(array1, array2, size); /* 什麼都不做 */
使用連續性來隱藏變數
不要使用
#define local_var xy_z
將 xy_z
分成兩行:
#define local_var xy\
_z // local_var OK
這樣,對於該檔案,全域搜尋 xy_z
將不會找到任何結果。對於 C 預處理器來說,行尾的 \
表示將此行與下一行連接在一起。
偽裝為關鍵字的任意名稱
在進行文件編寫時,如果需要一個任意名稱來代表文件名,請使用 “file”。絕不要使用明顯是任意名稱的名稱,例如 “Charlie.dat” 或 “Frodo.txt”。一般來說,在您的示例中,使用盡可能聽起來像保留關鍵字的任意名稱。例如,參數或變數的良好名稱可以是 bank
、blank
、class
、const
、constant
、input
、key
、keyword
、kind
、output
、parameter
、parm
、system
、type
、value
、var
和 variable
。如果您在示例中使用實際的保留字作為任意名稱,這些名稱將被您的命令處理器或編譯器拒絕,那就更好了。如果您做得好,用戶將在保留關鍵字和示例中的任意名稱之間感到困惑,但您可以聲稱您這樣做是為了幫助他們將每個變數與相應的目的關聯起來。
代碼名稱不得與屏幕名稱匹配
選擇變數名稱與在屏幕上顯示這些變數時使用的標籤完全無關。例如,在屏幕上標籤為 “郵政編碼” 的字段,但在代碼中將相關變數稱為 zip
。
不要更改名稱
不要全局重命名以使代碼的兩個部分同步,使用多個相同符號的 TYPEDEF。
如何隱藏禁止使用的全局變數
由於全局變量是 “邪惡” 的,定義一個結構來保存您將放入全局變量中的所有東西。將其稱為像 EverythingYoullEverNeed
這樣的巧妙名稱。使所有函數都接受指向此結構的指針(將其稱為 handle
以增加混淆)。這給人的印象是您並未使用全局變量,而是通過 “handle” 訪問所有內容。然後靜態聲明一個,以便所有代碼都使用同一個副本。
隱藏具有同義詞的實例
維護程式設計師為了查看他們所做的更改是否會有任何連鎖效應,會對命名的變數進行全局搜索。這可以通過使用同義詞來輕鬆擊敗,例如
#define xxx global_var // 在檔案 std.h 中
#define xy_z xxx // 在檔案 ..\other\substd.h 中
#define local_var xy_z // 在檔案 ..\codestd\inst.h 中
這些定義應該分散在不同的包含檔案中。如果這些包含檔案位於不同的目錄中,則尤其有效。另一種技術是在每個範圍中重複使用一個名稱。編譯器可以將它們區分開來,但一個簡單的文本搜索器卻辦不到。不幸的是,未來十年的 SCIDs 將使這種簡單的技術變得不可能,因為編輯器對範圍規則的理解程度與編譯器一樣。
長得相似的變數名稱
使用非常長的變數名稱或僅在一個字符或僅在大小寫不同的情況下有所不同的類別名稱。一個理想的變數名稱對是 swimmer
和 swimner
。利用大多數字型無法清楚區分 ilI1|
或 oO08
的失敗,使用識別符對如 parselnt
和 parseInt
或 D0Calc
和 DOCalc
。l
是一個非常好的變數名稱選擇,因為對於隨意一瞥的人來說,它會偽裝成常數 1
。在許多字型中,rn
看起來像 m
。那麼一個變數 swirnrner
如何?創建只在大小寫不同的情況下有所不同的變數名稱,例如 HashTable
和 Hashtable
。
聽起來相似看起來相似的變數名稱
變數與其他變數相似,除了大小寫和底線之外,具有混淆那些喜歡按聲音或字母拼寫記住名稱的人的優勢,而不是通過精確表示。
過載和迷惑
在 C++ 中,通過使用 #define
來過載庫函數。這樣看起來你正在使用一個熟悉的庫函數,而實際上你正在使用完全不同的東西。
選擇最佳的過載運算符
在 C++ 中,過載 +
、-
、*
和 /
來執行與加法、減法等完全無關的事情。畢竟,如果 Stroustroup 可以使用位移運算符來進行 I/O,為什麼你不能同樣具有創造力呢?如果你過載 +
,確保你以一種方式來做,使得 i = i + 5;
與 i += 5;
有完全不同的含義。這裡有一個將過載運算符混淆提升到一種高藝術的例子。為一個類過載 !
運算符,但過載與反轉或否定無關。讓它返回一個整數。然後,為了獲得它的邏輯值,你必須使用 ! !
。然而,這會反轉邏輯,所以 [鼓聲] 你必須使用 ! ! !
。不要混淆 !
運算符,它返回一個布爾值 0 或 1,與 ~
位元邏輯否定運算符不要混淆。
重載 new
重載 new
運算子 - 比重載 +-/*
更危險。如果將其重載以執行與原始功能不同的操作(但對物件的功能至關重要,因此很難更改),這可能會導致嚴重混亂。這應該確保嘗試創建動態實例的使用者會感到非常困惑。您還可以將此與區分大小寫的技巧結合,並且有一個名為 “New” 的成員函數和變數。
#define
在 C++ 中,#define
值得一篇專文來探索其豐富的混淆可能性。使用小寫 #define
變數,使它們偽裝成普通變數。永遠不要將參數用於預處理器函數。使用全局 #define
來完成所有操作。我聽說過預處理器最具想像力的用法之一是在編譯代碼之前需要通過 CPP 五次。通過巧妙地使用 defines
和 ifdefs
,混淆大師可以使標頭文件根據其被包含的次數聲明不同的內容。當一個標頭文件包含在另一個標頭文件中時,這尤其有趣。這是一個特別狡猾的例子:
#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()
。
編譯器指令
編譯器指令的設計目的是使相同的代碼表現完全不同。反覆且積極地打開和關閉布林短路指令,以及長字符串指令。
文件
任何傻瓜都能講真話,但需要有一定見識的人才知道如何說謊。 - Samuel Butler(1835 - 1902)
不正確的文件通常比沒有文件更糟。 - Bertrand Meyer
由於電腦忽略註釋和文件,您可以大膽說謊,並盡一切努力讓可憐的維護程序員感到困惑。
在註釋中說謊
您不必積極地說謊,只需不及時更新註釋與代碼的一致性。
記錄明顯的事項
在代碼中加入像 /* 將 i 加 1 */
這樣的註釋,但永遠不要記錄像套件或方法的整體目的這樣模糊的內容。
記錄如何而非為何
僅記錄程序執行的細節,而不是它試圖實現的目標。這樣,如果有 bug,修復者將不知道代碼應該做什麼。
避免記錄「明顯的」事項
例如,如果你正在編寫一個航空公司預訂系統,確保代碼中至少有 25 個地方需要修改,如果要添加另一家航空公司。永遠不要記錄它們的位置。跟在你後面的人沒有權利在不完全理解每一行代碼的情況下修改你的代碼。
關於文檔模板的正確使用
考慮使用函數文檔原型來允許代碼的自動文檔化。這些原型應該從一個函數(或方法或類)複製到另一個,但永遠不要填寫字段。如果由於某種原因你被迫填寫字段,請確保所有參數對於所有函數都是相同的命名,所有注意事項也是相同的,但當然與當前函數無關。
關於設計文檔的正確使用
在實現非常複雜的算法時,使用經典的軟體工程原則,在開始編碼之前進行良好的設計。撰寫一份極其詳細的設計文檔,描述非常複雜算法中的每一步。這份文檔越詳細,越好。 實際上,設計文檔應將算法分解為一個結構化步驟的層次結構,描述在文檔中的一個層次結構的自動編號段。使用至少 5 級標題。確保當完成時,你已經將結構分解得如此完全,以至於有超過 500 個這樣的自動編號段。例如,一個段落可能是(這是一個真實的例子)
1.2.4.6.3.13 - 顯示所有影響,以便應用所選的緩解措施的活動(省略了簡短的偽代碼)。
計量單位
永遠不要記錄任何變數、輸入、輸出或參數的計量單位。例如,英尺、公尺、箱子。這在點算方面並不重要,但在工程工作中非常重要。作為推論,永遠不要記錄任何轉換常數的計量單位,或者值是如何衍生的。在註釋中加入一些不正確的計量單位是一種輕微的欺騙手段,但非常有效。如果你感到特別惡意,可以捏造自己的計量單位;以自己的名字或某個不知名的人命名,並且永遠不要定義它。如果有人質疑你,告訴他們你這樣做是為了可以使用整數而不是浮點數運算。
注意事項
永遠不要在代碼中記錄注意事項。如果你懷疑某個類別中可能存在錯誤,請自己留著。如果你對代碼應如何重新組織或重寫有想法,千萬不要寫下來。記住《小鹿斑比》電影中 Thumper 的話:“如果你說不出好話,就不要說話”?如果寫那段代碼的程序員看到了你的評論怎麼辦?如果公司的老闆看到了呢?如果客戶看到了呢?你可能會被解雇。一個匿名評論說”這需要修復!“可以奇蹟般地起作用,尤其是如果不清楚評論指的是什麼。保持模糊,沒有人會感到受到個人批評。
變數文件
永遠不要在變數宣告上加上註釋。有關變數如何使用、其範圍、合法值、隱含/顯示的小數位數、度量單位、顯示格式、資料輸入規則(例如,必須填寫總數,必須輸入)以及何時可以信任其值等事實應從程序代碼中獲取。如果你的老闆強迫你寫註釋,可以在方法體中大量添加,但絕不要對變數宣告進行註釋,即使是臨時變數也不行!
在註釋中貶低
通過在代碼中散佈對其他領先軟件公司的侮辱性引用,尤其是可能被承包進行工作的公司,來阻止任何使用外部維護承包商的嘗試。例如:
/* The optimised inner loop.
This stuff is too clever for the dullard at Software Services Inc., who would
probably use 50 times as memory & time using the dumb routines in <math.h>.
*/
class clever_SSInc {
.. .
}
如果可能,將侮辱性內容放在代碼的語法上具有重要意義的部分,而不僅僅是註釋,這樣管理層在嘗試在發送維護之前對代碼進行清理時可能會破壞代碼。
將註釋撰寫得像在打孔卡片上的CØBØL
始終拒絕接受開發環境領域的進步,尤其是SCIDs。不要相信所有函式和變數宣告永遠只有一個點擊遠,並且始終假設在Visual Studio 6.0中開發的代碼將由使用edlin或vi的人員進行維護。堅持嚴格的註釋規則以掩蓋源代碼。
蒙提·派森式註釋
在名為makeSnafucated
的方法中,僅插入JavaDoc /* make snafucated */
。任何地方都不要定義什麼是“snafucated”。只有傻瓜才不知道“snafucated”是什麼意思,並且完全確定。要查看此技術的經典示例,請參考Sun AWT JavaDOC。
程式設計
撰寫難以維護代碼的基本規則是在盡可能多的地方以盡可能多的方式指定每個事實。 - Roedy Green
撰寫易於維護代碼的關鍵是只在一個地方指定有關應用程序的每個事實。要改變主意,您只需在一個地方進行更改,並且可以保證整個程序仍將正常運作。因此,撰寫難以維護代碼的關鍵是在盡可能多的地方以盡可能多種方式指定一個事實。令人高興的是,像Java這樣的語言竭盡全力使撰寫這種難以維護代碼變得容易。例如,幾乎不可能更改廣泛使用的變數的類型,因為所有的轉換和轉換函數將不再起作用,相關臨時變數的類型也將不再適用。此外,如果在屏幕上顯示變數,則必須跟踪所有相關的顯示和數據輸入代碼並手動修改。包括C和Java在內的Algol語言家族對將數據存儲在數組、Hashtable、平面文件和數據庫中具有完全不同的語法。在像Abundance和在某種程度上Smalltalk這樣的語言中,語法是相同的;只是聲明發生了變化。利用Java的笨拙。將您知道將來會變得過大以至於無法放入RAM的數據暫時放入數組中。這樣,維護程序員將需要進行艱鉅的任務,以便稍後從數組訪問轉換為文件訪問。同樣地,將微小文件放入數據庫中,以便維護程序員在進行性能調整時可以將其轉換為數組訪問。
Java 轉型
Java 的轉型方案是神的恩賜。您可以毫無罪惡感地使用它,因為語言要求這樣做。每次從集合中擷取物件時,您都必須將其轉型回原始類型。因此,變數的類型可能在數十個地方被指定。如果類型稍後更改,則必須更改所有轉型以匹配。編譯器可能會或可能不會檢測到不幸的維護程式設計師未能捕捉到所有問題(或更改了太多)。以類似的方式,如果變數的類型從 short
變為 int
,則所有匹配的轉型為 (short)
需要更改為 (int)
。目前有一個運動正在興起,試圖發明一個通用轉型運算子 (cast)
和一個通用轉換運算子 (convert)
,當變數類型更改時不需要進行任何維護。請確保這種異端邪說永遠不會出現在語言規範中。在 RFE 114691 和可能消除許多轉型需求的泛型性上投下反對票。
利用 Java 的冗餘性
Java 要求您兩次指定每個變數的類型。Java 程式設計師如此習慣這種冗餘性,以至於他們不會注意到如果您讓兩種類型稍有不同,就像這個例子中一樣:
Bubblegum b = new Bubblegom();
不幸的是,++
運算子的普及使得像這樣的虛假冗餘代碼更難以通過:
swimmer = swimner + 1;
永遠不要驗證
永遠不要檢查輸入數據的任何正確性或不一致性。這將表明您絕對信任公司的設備,以及您是一個完美的團隊合作者,信任所有專案合作夥伴和系統操作者。即使數據輸入有問題或錯誤,也應始終返回合理值。
請禮貌,永遠不要斷言
避免使用 assert()
機制,因為它可能會將一個為期三天的調試盛宴變成十分鐘的盛宴。
避免封裝
為了效率起見,避免封裝。方法的調用者需要盡可能多的外部線索,以提醒他們方法內部的運作方式。
複製和修改
這是翻譯好的內容,請檢查並告訴我是否需要進一步的修改。
使用靜態陣列
為了效率起見,使用剪下/貼上/複製/修改。這比使用許多小型可重複使用的模組要快得多。這在那些以你編寫的程式碼行數來衡量你進度的商店中尤其有用。
使用靜態陣列
如果庫中的模組需要一個陣列來保存圖像,只需定義一個靜態陣列。沒有人會有比 512 x 512 更大的圖像,因此固定大小的陣列是可以的。為了最佳精確度,將其設為雙精度陣列。對於隱藏一個 2 兆靜態陣列的獎勵效果,這會使程式超出客戶機的記憶體並且即使他們從未使用你的例程也會瘋狂地交換。
虛擬介面
編寫一個名為 WrittenByMe
的空介面,並讓所有的類別實作它。然後,為你使用的任何 Java 內建類別編寫包裝類別。這個想法是確保你程式中的每個物件都實作這個介面。最後,編寫所有方法,使其引數和返回類型都是 WrittenByMe
。這使得幾乎不可能弄清楚某些方法的作用,並引入各種有趣的轉型要求。進一步擴展,讓每個團隊成員都有自己的個人介面(例如 WrittenByJoe
);任何一位程式設計師工作過的類別都要實作他/她的介面。然後,你可以任意地用許多無意義的介面之一來參考物件!
巨大的監聽器
永遠不要為每個元件創建單獨的監聽器。在你的專案中,總是為每個按鈕只有一個監聽器,然後簡單地使用大量的 if…else 陳述來測試按下了哪個按鈕。
過度的好事TM
放縱於封裝和物件導向。例如:
myPanel.add( getMyButton() );
private JButton getMyButton() {
return myButton;
}
這個可能甚至看起來不好笑。別擔心。總有一天會好笑的。
友好的朋友
在 C++ 中盡可能多地使用友元宣告。結合將創建類別的指標傳遞給已創建類別。現在你不需要浪費時間思考介面。此外,你應該使用關鍵字 private 和 protected 來證明你的類別封裝得很好。
使用三維陣列
有很多。以錯綜複雜的方式在陣列之間移動資料,比如說,將arrayA
的列填入arrayB
的欄位。以偏移量為1進行操作,沒有明顯的理由,這樣做會讓維護程式設計師感到緊張。
混合搭配
同時使用取值器方法和公共變數。這樣一來,您可以在不調用取值器的情況下更改物件的變數,但仍然聲稱該類別是一個“Java Bean”。這有一個額外的優點,可以讓試圖添加記錄功能以找出誰更改了值的維護程式設計師感到沮喪。
包裝、包裝、包裝
每當您必須使用您未編寫的程式碼中的方法時,請至少通過一層包裝器將您的程式碼與其他骯髒程式碼隔離開來。畢竟,其他作者可能在未來的某個時候魯莽地重新命名每個方法。那時你會怎麼辦呢?當然,如果他這樣做了,您可以通過編寫一個包裝器來隔離您的程式碼免受更改的影響,或者您可以讓VAJ處理全局重命名。然而,這是一個完美的藉口,預先阻止他通過一層間接的包裝器層在他做任何蠢事之前。Java的一個主要缺點是沒有辦法解決許多簡單的問題,而不使用除了調用另一個同名方法或密切相關名稱的方法之外什麼都不做的虛擬包裝器方法。這意味著可以編寫四層深的包裝器,完全不做任何事情,幾乎沒有人會注意到。為了最大程度地模糊視線,在每個層級,重新命名方法,從同義詞詞典中隨機選擇隨機詞彙。這給人一種發生了重要事情的錯覺。此外,重新命名有助於確保一致的專案術語的缺乏。為了確保沒有人嘗試將您的層級削減到合理數量,請在每個層級繞過包裝器調用一些您的程式碼。
再次包裝包裝包裝
確保所有API函數至少被包裝6-8次,並在單獨的源文件中定義函數。使用#defines
來製作這些函數的方便快捷方式也是有幫助的。
沒有秘密!
將每個方法和變數都宣告為public
。畢竟,總有人在某個時候想要使用它。一旦一個方法被宣告為public
,它就無法被收回,對吧?這使得以後更改任何內部工作方式變得非常困難。這也有一個令人愉快的副作用,即模糊了類別的用途。如果老闆問你是不是瘋了,告訴他你正在遵循透明介面的經典原則。
卡瑪經
這種技巧的額外優勢是讓任何使用者或套件文檔的人分心,以及維護程式設計師。創建一打過載的相同方法變體,只有微小細節不同。我想是奧斯卡·王爾德觀察到卡瑪經的第47和115個姿勢是一樣的,只是在115中女人交叉了手指。然後套件的使用者必須仔細查看方法的長列表,才能弄清楚應該使用哪個變體。這種技巧還會膨脹文檔,因此更容易過時。如果老闆問你為什麼這樣做,解釋說這僅僅是為了使用者的方便。再次為了完整效果,複製任何常見邏輯,然後坐下等待逐漸不同步的副本。
排列和迷惑
將一個名為drawRectangle(height, width)
的方法的參數反轉為drawRectangle(width, height)
,而不對方法名稱做任何更改。然後幾個版本後再次反轉回來。維護程式設計師無法通過快速查看任何調用來判斷它是否已經調整過。一般化留給讀者作為練習。
主題和變化
不要使用單個方法的參數,盡可能創建多個獨立的方法。例如,不要使用setAlignment(int alignment)
這樣的參數,其中alignment
是一個列舉常數,代表左、右、中,而是創建三個方法setLeftAlignment
、setRightAlignment
和setCenterAlignment
。當然,為了完整效果,您必須複製共同邏輯,使其難以保持同步。
靜態是好的
盡可能將您的變數設為靜態。如果在此程式中您不需要多個類別實例,其他人也不會需要。再次,如果專案中的其他開發人員抱怨,告訴他們您正在獲得的執行速度改善。
卡吉爾的困境
利用卡吉爾的困境(我想這是他的)“任何設計問題都可以通過增加額外的間接層來解決,除了太多的間接層”。將面向對象程序分解,直到幾乎不可能找到實際更新程式狀態的方法。更好的是,安排所有這樣的發生在針對整個系統中使用的每個函數指針的指標森林中的回呼,通過釋放先前通過並非真正那麼深的深拷貝創建的引用計數對象來激活森林遍歷的副作用。如果您尚未觸及這些方法和變數的註釋,並且足夠神秘,任何維護代碼的人都會害怕觸及它們。
Packratting
在代碼中保留所有未使用和過時的方法和變數。畢竟 - 如果您在 1976 年曾需要使用它,誰知道您將來可能會再次使用它?當然,自那時以來程式已經改變,但它可能會輕易地改回來,您“不想重新發明輪子”(主管喜歡這樣的談話)。如果您尚未觸及這些方法和變數的註釋,並且足夠神秘,任何維護代碼的人都會害怕觸及它們。
就這樣
使您的所有葉子類別都是 final。畢竟,您已經完成了這個專案 - 當然沒有其他人可能通過擴展您的類別來改進您的工作。這甚至可能是一個安全漏洞 - 畢竟,java.lang.String 不就是為了這個原因而是 final 嗎?如果專案中的其他開發人員抱怨,告訴他們您正在獲得的執行速度改善。
避免接口
在 Java 中,輕視接口。如果您的主管抱怨,告訴他們 Java 接口強迫您在實現相同接口的不同類別之間“剪貼”代碼,他們知道這樣做有多難以維護。相反,像 Java AWT 設計者一樣 - 在您的類別中放入許多功能,只能被繼承自它們的類別使用,在您的方法中使用大量的“instanceof”檢查。這樣,如果有人想重用您的代碼,他們必須擴展您的類別。如果他們想從兩個不同的類別重用您的代碼 - 那就太糟糕了,他們無法同時擴展兩個類別!如果接口是不可避免的,創建一個通用的接口,並將其命名為像 ImplementableIface
這樣的名稱。學術界的另一個寶石是將實現接口的類別的名稱附加“Impl”。這可以被廣泛利用,例如實現 Runnable
的類別。
避免佈局
永遠不要使用佈局。這樣,當維護程序員添加一個字段時,他將不得不手動調整屏幕上顯示的每一個其他元素的絕對坐標。如果你的老闆強迫你使用佈局,請使用單個巨大的 GridBagLayout
,並在絕對網格坐標中硬編碼。
環境變數
如果你必須為其他程序員編寫類,請將環境檢查代碼(在 C++ 中使用 getenv()
/ 在 Java 中使用 System.getProperty()
)放在你的類的無名靜態初始化器中,並通過這種方式將所有引數傳遞給類,而不是在構造方法中。優點是初始化方法會在類程序二進制文件加載時立即調用,甚至在任何類被實例化之前,因此它們通常會在程序的 main()
之前被執行。換句話說,在這些參數被讀入你的類之前,程序的其餘部分沒有辦法修改這些參數 - 用戶最好將他們的環境變數設置得和你一樣!
表驅動邏輯
避免任何形式的表驅動邏輯。它起初看起來很無辜,但很快就會導致最終用戶校對,然後 噁心地,甚至修改表格。
修改 Mom 的字段
在 Java 中,所有作為參數傳遞的基本類型實際上是只讀的,因為它們是按值傳遞的。被調用者可以修改參數,但這對調用者的變量沒有影響。相比之下,所有傳遞的對象都是可讀寫的。引用是按值傳遞的,這意味著對象本身實際上是按引用傳遞的。被調用者可以對你對象中的字段做任何想做的事情。永遠不要記錄一個方法是否實際上修改了每個傳遞參數中的字段。命名你的方法以暗示它們只查看字段,當它們實際上改變它們時。
全局變量的魔力
不要使用異常來處理錯誤處理,讓你的錯誤消息例程設置一個全局變量。然後確保系統中的每個長時間運行的循環檢查這個全局標誌,如果發生錯誤就終止。再添加另一個全局變量來標誌當用戶按下 ‘重置’ 按鈕時。當然,系統中的所有主要循環也必須檢查這第二個標誌。隱藏一些不按需終止的循環。
全域變數,我們無法強調這些足夠了!
如果上帝不希望我們使用全域變數,他就不會發明它們。與其讓上帝失望,不如盡可能使用和設定許多全域變數。每個函數應該使用和設定至少兩個全域變數,即使沒有理由這樣做。畢竟,任何一位優秀的維護程式設計師很快就會發現這是一項偵探工作,她會為這種區分真正維護程式設計師和業餘者的練習感到高興。
全域變數,再說一次,夥計們
全域變數可以免去在函數中指定引數的麻煩。充分利用這一點。選擇一個或多個全域變數來指定對其他變數進行哪些類型的處理。維護程式設計師愚蠢地假設 C 函數不會產生副作用。確保它們將結果和內部狀態資訊保存在全域變數中。
副作用
在 C 語言中,函數應該是幂等的(無副作用)。希望這個提示足夠了。
回退
在迴圈的主體內,假設迴圈操作成功並立即更新所有指標變數。如果稍後檢測到該迴圈操作有異常,則在迴圈主體後面的條件表達式的副作用中回退指標進展。
區域變數
永遠不要使用區域變數。每當你感覺到使用一個的誘惑時,將其轉換為實例或靜態變數,以無私地與類別的所有其他方法共享。這將在其他方法需要類似聲明時為您節省工作。C++ 程式設計師可以進一步將所有變數設為全域。
組態檔案
這些通常具有關鍵字=值的形式。值在加載時加載到 Java 變數中。最明顯的混淆技術是使用略有不同的名稱來命名關鍵字和 Java 變數。即使是永遠不會在運行時更改的常數,也應使用組態檔案。參數檔案變數需要至少五倍的代碼來維護,而簡單變數只需要的代碼。
過度膨脹的類別
為了確保您的類別以最難理解的方式綁定,請確保在每個類別中包含外圍、晦澀的方法和屬性。例如,定義天體軌道幾何的類別應該有一個計算海洋潮汐時間表的方法和包含一個Crane氣象模型的屬性。這不僅過度定義了類別,還使得在一般系統代碼中尋找這些方法就像在垃圾場中尋找一個吉他撥片一樣困難。
放任地使用子類
面向對象編程對於編寫難以維護的代碼來說是一大福音。如果您有一個類別中有10個屬性(成員/方法),請考慮只有一個屬性的基類,然後將其子類化到9個層級,使每個後代添加一個屬性。當您到達最後一個後代類別時,您將擁有所有10個屬性。如果可能,請將每個類聲明放在單獨的文件中。這不僅會使您的INCLUDE
或USES
語句變得臃腫,還會迫使維護者在編輯器中打開更多文件。請確保至少創建每個子類的一個實例。
編碼混淆
切忌過度冗長和繁瑣的語言。
混淆的C語言
關注互聯網上的混淆C語言比賽,並坐在大師的蓮花腳下。
找一位Forth或APL大師
在那些領域中,您的代碼越簡潔,並且運作方式越奇怪,您就會越受尊敬。
我要十二個
當您可以輕鬆使用兩個或三個變數時,絕不要只使用一個。
神秘的裘德
總是尋找執行常見任務的最神秘方式。例如,不要使用陣列將整數轉換為對應的字符串,而是使用像這樣的代碼:
char *p;
switch (n) {
case 1:
p = "one";
if (0)
case 2:
p = "two";
if (0)
case 3:
p = "three";
printf("%s", p);
break;
}
愚蠢的一致性是小心靈的妖精
當您需要一個字符常量時,使用許多不同的格式 ' '
, 32
, 0x20
, 040
。充分利用在C或Java中10
和010
不是相同數字的事實。
強制轉換
將所有資料作為 void *
傳遞,然後將其類型轉換為適當的結構。使用數據的位移而不是結構轉換是一種有趣的方式。
巢狀開關
(一個開關內部有另一個開關)是人類思維最難理解的巢狀類型。
利用隱式轉換
記住編程語言中所有微妙的隱式轉換規則。充分利用它們。永遠不要使用圖片變數(在 COBOL 或 PL/I 中)或一般轉換例程(例如在 C 中的 sprintf
)。確保將浮點變數用作陣列的索引,將字符用作循環計數器,並對數字執行字符串函數。畢竟,所有這些操作都是明確定義的,並且只會增加您源代碼的簡潔性。任何試圖理解它們的維護人員將對您非常感激,因為他們將不得不閱讀並學習有關隱式數據類型轉換的整個章節;這可能是他們在處理您的程序之前完全忽略的一章。
原始整數
在使用 ComboBoxes 時,使用帶有整數 case 的開關語句,而不是可能值的命名常量。
分號!
只要在語法上允許,就始終使用分號。例如:
if(a);
else;
{
int d;
d = c;
}
;
使用八進制
將八進制文字直接嵌入到十進制數字列表中,如下所示:
array = new int [] {
111,
120,
013,
121,
};
間接轉換
Java 在必須轉換時提供了一個很好的混淆機會。舉個簡單的例子,如果必須將 double 轉換為 String,可以繞道而行,通過 new Double(d).toString()
而不是更直接的 Double.toString(d)
。當然,您可以比這更迂迴!避免使用轉換技術建議的 Conversion Amanuensis。在轉換後,每留下一個額外的臨時對象在堆中,您都會獲得額外的積分。
嵌套
盡可能深度嵌套。優秀的程式設計師可以在單行上達到 10 層 ( )
和在單個方法中達到 20 { }
。C++ 程式設計師還有一個額外強大的選項,即預處理器嵌套,完全獨立於底層代碼的巢狀結構。每當區塊的開始和結束出現在打印清單的不同頁面上時,您都會獲得額外的 Brownie 積分。在可能的情況下,將嵌套的 if 轉換為嵌套的 [?:] 三元運算符。如果它們跨越多行,那就更好。
數值文字
如果您有一個包含100個元素的陣列,在程式中盡可能多次硬編碼文字100。永遠不要使用靜態常數myArray.length
來代替100,也不要使用靜態常數來表示100,或者引用它。為了使更改這個常數變得更加困難,使用文字50代替100的一半,或者使用99代替100減1。您可以進一步掩蓋100,例如檢查a == 101
而不是a > 100
,或者a > 99
而不是a >= 100
。
考慮像頁面大小這樣的事情,其中包含x個標頭行、y個正文行和z個頁腳行,您可以對這些以及它們的部分或總和獨立應用這些混淆技巧。
這些古老的技巧在一個具有兩個無關的陣列的程式中特別有效,這兩個陣列碰巧都有100個元素。如果維護程式設計師必須更改其中一個的長度,他將不得不解讀程式中文字100的每個用法,以確定它適用於哪個陣列。他幾乎肯定會至少犯一個錯誤,希望這個錯誤不會在幾年後顯現。
還有更狡猾的變體。為了讓維護程式設計師產生一種虛假的安全感,誠實地創建命名常數,但非常偶爾“意外地”使用文字100的值而不是命名常數。最狡猾的是,在文字100或正確的命名常數的位置,偶爾使用一些其他無關的命名常數,這些命名常數碰巧具有值100,目前是這樣。幾乎不用說,您應該避免任何將陣列名稱與其大小常數關聯起來的一致命名方案。
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 點字體的程式設計師必須滾動查看它們。
例外情況
我將告訴你一個鮮為人知的編碼秘訣。例外情況很煩人。寫得好的程式碼永遠不會失敗,因此例外情況實際上是不必要的。不要浪費時間在它們上面。對於知道他們的程式碼會失敗的無能者來說,子類化例外情況是必要的。通過在整個應用程序中僅有一個 try/catch(在主函數中)並調用 System.exit(),你可以大大簡化你的程式。只需在每個方法標頭上放置一組完全標準的 throws,無論它們是否實際上可能拋出任何例外。
何時使用例外情況
對於非例外情況使用例外情況。通常使用 ArrayIndexOutOfBoundsException
來終止迴圈。在例外情況中從方法中傳回標準結果。
盡情使用執行緒
標題說明了一切。
律師代碼
在新聞組中遵循語言律師對於各種棘手代碼應該如何執行的討論,例如 a=a++;
或 f(a++,a++);
,然後在你的代碼中大量使用這些示例。在 C 語言中,像
*++b ? (*++b + *(b-1)) : 0
這樣的前/後遞減代碼的效果並不由語言規範定義。每個編譯器都可以按不同的順序進行評估。這使它們變得更加致命。同樣地,通過刪除所有空格,利用 C 和 Java 的複雜標記規則。
早期返回
嚴格遵循有關不使用 goto、不使用早期返回和不使用標記中斷的指南,尤其是當你可以將 if/else 的巢狀深度增加至少 5 級時。
避免 {}
除非在語法上是必需的,否則永遠不要在 if/else 區塊周圍放入 { }
。如果你有一個深度嵌套的 if/else 陳述和區塊混合,尤其是具有誤導性縮排,即使是專家維護程式設計師也可能出錯。對於這種技術的最佳效果,使用 Perl。你可以在陳述之後的地方添加額外的 if,產生驚人的效果。
地獄中的定位製表符
永遠不要低估使用製表符而不是空格進行縮排可能造成的混亂程度,尤其是當公司沒有關於製表符代表多少縮排的標準時。將製表符嵌入字串文字中,或使用工具將空格轉換為製表符,讓工具為您執行此操作。
魔法矩陣位置
在某些矩陣位置使用特殊值作為標誌。一個不錯的選擇是在與齊次座標系統一起使用的變換矩陣中的 [3][0]
元素。
重新訪問魔法陣列插槽
如果您需要多個給定類型的變數,只需定義一個它們的陣列,然後按編號訪問它們。選擇一個只有您知道並且不要記錄的編號慣例。也不要麻煩定義 #define
常數來表示索引。每個人都應該知道全域變數 widget[15]
是取消按鈕。這只是在組合語言代碼中使用絕對數值地址的最新變體。
永不美化
永遠不要使用自動源代碼整理工具(美化器)來保持代碼對齊。遊說將它們從公司禁止,理由是它們在 PVCS/CVS(版本控制追踪)中創建虛假增量,或者每個程序員應該擁有他為任何模塊編寫的縮排風格,這樣風格永遠不會被改變。堅持其他程序員遵守這些古怪的慣例在“他的”模塊中。禁止美化器非常容易,即使它們節省了數百萬次手動對齊和浪費時間解釋對齊不良的代碼。只需堅持每個人使用相同的整潔格式,不僅用於存儲在共同存儲庫中,而且在編輯時也使用。這將引發一場爭吵,老闆為了保持和平,將禁止自動整理。沒有自動整理,您現在可以 意外地 將代碼對齊不良,以產生視覺上的錯覺,使循環和 if 的主體看起來比實際更長或更短,或者 else 子句與實際不匹配的 if 匹配。例如:
if(a)
if(b) x=y;
else x=z;
宏預處理器
它提供了很好的混淆機會。 關鍵技術是將巨集展開嵌套多層,以便您必須在許多不同的 *.hpp 檔案中發現所有各個部分。 將可執行代碼放入巨集中,然後在每個 *.cpp 檔案中包含這些巨集(即使從不使用這些巨集的檔案)將最大程度地增加重新編譯所需的量,如果該代碼發生變化。
利用精神分裂
Java 在陣列聲明方面存在精神分裂。 您可以按照舊的 C 方式 String x[]
進行聲明(使用混合的前置後置表示法),或者按照新的方式 String[] x
進行聲明,該方式使用純前置表示法。 如果您想要真正混淆人們,混合使用表示法,例如:
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 */
}
伪 C
#define
的真正原因是幫助熟悉其他編程語言的程序員轉換到 C。 也許您會發現像 #define begin {
或 #define end }
這樣的聲明對於撰寫更有趣的代碼很有用。
令人困惑的導入
讓維護程序員猜測您正在使用的方法位於哪些套件中。 與其:
import MyPackage.Read;
import MyPackage.Write;
使用:
import Mypackage.*;
永遠不要完全限定任何方法或類,無論多麼晦澀。 讓維護程序員猜測它屬於哪個套件/類。 當然,在何時完全限定以及如何進行導入的一致性對幫助最大。
馬桶管道
絕不允許來自多個函數或程序的代碼同時出現在屏幕上。 為了實現這一點,對於短例程,使用以下方便的技巧: 空行通常用於分隔邏輯代碼塊。 每行本身就是一個邏輯塊。 在每行之間放置空行。 永遠不要在代碼行的末尾進行註釋。 將其放在上一行。 如果被迫在行尾進行註釋,選擇整個文件中最長的代碼行,添加 10 個空格,並將所有行尾註釋左對齊到該列。
/*
/* 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:
*/
測試
我不需要測試我的程式。我有一個能自動修正錯誤的調制解調器。
- Om I. Baud
在程式中留下錯誤可以讓後來的維護程式設計師有趣的事情可做。一個完美的 bug 應該絲毫不留痕跡,不讓人知道它是何時引入的或在哪裡。最懶惰的方法就是從不測試你的程式碼。
永遠不要測試
永遠不要測試任何處理錯誤情況、機器當機或作業系統故障的程式碼。永遠不要檢查作業系統的返回代碼。那段程式碼永遠不會執行,而且會拖慢你的測試時間。此外,你怎麼可能測試你的程式碼來處理磁碟錯誤、檔案讀取錯誤、作業系統當機等所有這些事件呢?你需要一台非常不可靠的電腦,或者一個模擬這種情況的測試支架。現代硬體從不會出錯,而且誰想要為了測試而寫程式碼呢?這樣一點樂趣都沒有。如果用戶抱怨,就怪作業系統或硬體。他們永遠不會知道。
永遠不要進行任何性能測試
嘿,如果速度不夠快,就告訴客戶買一台更快的機器。如果你真的進行了性能測試,你可能會發現瓶頸,這可能導致算法的更改,進而導致產品的完全重新設計。誰想要那樣呢?此外,在客戶現場出現的性能問題意味著你可以免費前往一些異國風情的地方。只需保持疫苗接種最新和護照隨身攜帶。
永遠不要撰寫任何測試案例
永遠不要進行程式碼覆蓋率或路徑覆蓋率測試。自動化測試是給懦夫的。找出哪些功能佔你例程使用的 90%,並將 90% 的測試分配給這些路徑。畢竟,這種技術可能僅測試了你源代碼的約 60%,你已經節省了 40% 的測試工作。這可以幫助你在專案後期補足進度。當人們注意到所有那些漂亮的「行銷功能」都不起作用時,你早已離開。大型知名軟體公司都是這樣測試程式碼的;你也應該這樣做。如果出於某種原因,你仍然在這裡,請參見下一項。
測試是給懦夫的
一位勇敢的程式設計師將會跳過這一步驟。太多的程式設計師害怕老闆,害怕失去工作,害怕客戶的憎恨郵件,害怕被起訴。這種恐懼會使行動癱瘓,並降低生產力。研究顯示,消除測試階段意味著管理者可以提前設定交貨日期,這在規劃過程中是一個明顯的幫助。當恐懼消失時,創新和實驗可以蓬勃發展。程式設計師的角色是產生程式碼,而除錯可以通過幫助台和遺留維護小組的合作努力來完成。 如果我們對自己的編碼能力充滿信心,那麼測試將是不必要的。從邏輯上看,任何傻瓜都可以認識到,測試甚至不試圖解決技術問題,而是一個情感信心的問題。對於這種缺乏信心問題,一個更有效的解決方案是完全消除測試,並將我們的程式設計師送去自尊心課程。畢竟,如果我們選擇進行測試,那麼我們必須測試每個程式更改,但我們只需要讓程式設計師參加一個建立自尊心的課程。成本效益之驚人與明顯同樣。
確保僅在偵錯模式下運作
如果您將 TESTING 定義為 1
#define TESTING 1
這將為您提供絕佳的機會,可以有單獨的程式碼部分,例如
#if TESTING==1
#endif
其中可以包含不可或缺的小提示,如
x = rt_val;
這樣,如果有人將 TESTING 重置為 0,該程式將無法運作。再加上一點想像力,它不僅會困惑邏輯,還會使編譯器困惑。
語言的選擇
哲學是一場對抗語言通過智慧的迷惑的戰鬥。
- 路德維希·維特根斯坦
電腦語言逐漸演變為更加防呆。使用最先進的語言是不男人的。堅持使用您可以使用的最古老的語言,如果可以的話使用八進制機器語言(就像漢斯和弗朗斯一樣,我不是娘娘腔;我是如此有男子氣概,以至於我曾經通過將金頂線插入 IBM 單位記錄設備(打孔卡)的插板,或者用手動打孔器在紙帶上打孔),如果不行,使用組合語言,如果不行使用 FORTRAN 或 COBOL,如果不行使用 C,和 BASIC,如果不行使用 C++。
FØRTRAN
將所有代碼都寫在FORTRAN中。如果老闆問為什麼,您可以回答說有很多非常有用的庫可以使用,從而節省時間。然而,在FORTRAN中編寫可維護的代碼的機會為零,因此遵循不可維護的編碼指南會更容易。
避免Ada
大約有20%的技術在Ada中無法使用。拒絕使用Ada。如果您的經理向您施壓,堅持說沒有其他人使用它,並指出它與您的大量工具套件(如lint和plummer)不兼容,這些工具套件是為了解決C的缺陷而設計的。
使用ASM
將所有常見的實用功能轉換為asm。
使用QBASIC
將所有重要的庫函數寫成QBASIC,然後只需編寫一個asm包裝器來處理大型->中型內存模型映射。
內聯組合語言
在代碼中加入一些內聯組合語言片段,僅僅是為了好玩。幾乎沒有人再了解組合語言。即使是幾行組合語言也可能讓維護程序員束手無策。
MASM調用C
如果您有從C調用的組件模塊,請嘗試盡可能經常地從組件調用C,即使僅用於微不足道的目的,並確保充分利用goto、bcc和其他組件的迷人混淆。
避免可維護性工具
避免在Abundance中編碼,或將其原則混入其他語言中。它從頭開始設計的主要目標是使維護程序員的工作更輕鬆。同樣,避免Eiffel或Ada,因為它們旨在在程序進入生產階段之前捕獲錯誤。
與他人打交道
地獄就是別人。
- 讓-保羅·薩特(Jean-Paul Sartre),《無出口》(No Exit),1934年
在上述提示中,有許多提示散佈在其中,告訴您如何通過挫敗維護程序員的努力,如何阻止老闆阻止您編寫不可維護的代碼,甚至如何在庫中格式化代碼的話題上引發一場RWAR,讓每個人都參與其中。
老闆最懂
如果您的老闆認為他或她的20年FORTRAN經驗是當代編程的絕佳指南,請嚴格遵循他或她的所有建議。結果,老闆會信任您。這可能有助於您的職業生涯。您將學習許多新的方法來混淆程序代碼。
顛覆幫助台
確保程式碼充滿錯誤的一種方法是確保維護程式設計師永遠不會聽說它們。這需要顛覆幫助台。永遠不要接電話。使用自動語音說”感謝您撥打幫助熱線。要聯絡真人,請按“1”,或留言等待提示音。” 電子郵件求助請求應該被忽略,除非分配追蹤編號。對任何問題的標準回應是”我認為您的帳戶被鎖定了。能夠授權恢復的人現在不在。”
保持緘默
永遠警惕下一個Y2K。如果您發現可能在固定截止日期前潛在威脅並摧毀西半球所有生命的事情,絕對不要公開討論,直到我們處於關鍵的4年事件窗口之下。不要告訴朋友、同事或其他能幹的人您的發現。在任何情況下都不要嘗試發布可能暗示這個新且極具利潤威脅的任何內容。請發送一份普通優先級、用行話加密的備忘錄給高級管理層以自保。如果可能的話,請將行話加密信息作為附件附加在與更緊迫的業務關注無關的純文本備忘錄上。請放心,我們都看到了這個威脅。安心入眠,知道即使在您被迫提前退休後,您將被懇求以將薪酬按對數增加的時薪回來!
用廢話迷惑他們
微妙是一件美好的事情,儘管有時候一把大錘比其他工具更微妙。因此,對於誤導性評論的改進創建了像FooFactory
這樣的類,其中包含對GoF創建模式的引用的註釋(理想情況下帶有指向虛假UML設計文件的http鏈接),而這與對象創建無關。利用維護者對能力的錯覺。更微妙的是,創建具有受保護構造函數和像Foo f = Foo.newInstance()
這樣的方法的Java類,它返回實際的新實例,而不是預期的單例。造成的副作用機會是無窮的。
本月之書俱樂部
加入一個電腦書籍之月俱樂部。選擇那些看起來太忙於寫書,以至於根本沒有時間寫任何程式碼的作者。在當地書店尋找那些擁有大量雲圖且沒有編碼範例的書籍標題。略讀這些書籍,學習一些冷僻的詞彙,以用來威嚇那些跟在你後面的年輕人。你的程式碼應該令人印象深刻。如果人們無法理解你的詞彙,他們必須認為你非常聰明,且你的演算法非常深奧。在解釋演算法時避免使用任何樸實無華的類比。
自行打造
你一直想寫系統層級的程式碼。現在是你的機會。忽略標準庫,自己寫。這將會讓你的履歷看起來很棒。
自行打造 BNF
始終使用你自己獨特的未記錄品牌的 BNF 表示法來記錄你的命令語法。永遠不要透過提供一套帶有註釋的範例有效和無效命令來解釋語法。這將展示出完全缺乏學術嚴謹性。鐵路圖幾乎一樣俗氣。確保沒有明顯的方法可以區分終端符號(實際上您會輸入的內容)和中間符號(代表語法中的片語)。永遠不要使用字體、顏色、大寫或任何其他視覺線索來幫助讀者區分兩者。在你的 BNF 表示法中使用完全相同的標點符號字形,這樣讀者永遠無法確定 (...)
, [...]
, {...}
或 "..."
是您實際上作為命令的一部分輸入,還是用於提供關於 BNF 表示法中哪些語法元素是必需的、可重複的或可選的線索。畢竟,如果他們太笨無法理解你的 BNF 變體,他們就沒有使用你的程式的業務。
自行打造配置器
每個人都知道調試動態存儲是複雜且耗時的。與其確保每個類別都沒有存儲洩漏,不如重新發明您自己的存儲配置器。它只是從一個大型區域中分配空間。而不是釋放存儲,強迫您的使用者定期執行清除堆的系統重置。系統在重置時只需要跟踪幾件事情 - 比起修補所有存儲洩漏要容易得多;只要使用者記得定期重置系統,他們永遠不會用盡堆空間。想像一下當他們嘗試在部署後更改這種策略!
奇特語言的技巧
在 Basic 中編程會導致腦損傷。
- Edsger Wybe Dijkstra
SQL 別名
將表名別名為一個或兩個字母。更好的做法是將它們別名為其他不相關的現有表的名稱。
SQL 外部連接
混合各種外部連接語法的風格,只是為了讓每個人都保持警惕。
JavaScript 作用域
“優化” JavaScript 代碼,利用函數可以訪問調用者作用域中的所有局部變量這一事實。
Visual Basic 声明
不要這樣做:
dim Count_num as string
dim Color_var as string
dim counter as integer
使用:
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 專用
不要使用函數和過程。使用標籤/goto語句,然後在代碼中大量跳轉。這將使他們瘋狂,試圖跟踪這些跳轉。另一個想法只是為了熟悉它,並且在代碼中跳來跳去,以某種雜亂的方式。
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
這個專門針對 Visual Foxpro。除非為其分配一個值,否則變量未定義並且無法使用。這是當您檢查變量類型時發生的情況:
lcx = TYPE('somevariable')
lcx
的值將是 'U'
或 undefined
。但是,如果為變量分配作用域,它會定義它並使其成為邏輯 FALSE
。很巧妙,對吧!?
LOCAL lcx
lcx = TYPE('somevariable')
lcx
的值現在是 'L'
或邏輯。它進一步定義了 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
其他技巧
如果你給某人一個程式,你會讓他們感到挫敗一天;如果你教他們如何寫程式,你會讓他們終身感到挫敗。
- 匿名
不要重新編譯
讓我們從可能是有史以來設計的最狡猾技巧開始:將程式碼編譯成可執行檔。如果它運作正常,然後只需在每個模組中做一兩個小小的變更在原始碼中…但不要打擾重新編譯這些。 你可以在以後有更多時間時再進行,也可以在有時間進行除錯時再進行。當幾年後不幸的維護程式設計師做了一個更改並且程式碼不再運作時,她將錯誤地假設這一定是她最近更改的某些東西。你將讓她陷入一場持續數週的狂野追逐。
擋住除錯器
一個非常簡單的方式來困惑試圖用行除錯器追蹤你的程式碼的人,就是使行變得很長。特別是,將 then 子句放在與 if 相同的行上。他們無法設置斷點。他們無法知道 if 的哪個分支被執行。
S.I. vs 美國度量
在工程工作中有兩種編碼方式。一種是將所有輸入轉換為 S.I.(公制)度量單位,然後進行計算,然後再轉換回各種民用度量單位進行輸出。另一種是在整個程式中保留各種混合度量系統。永遠選擇第二種。這是美國的方式!
CANI
Constant And Never-ending Improvement。經常對你的程式碼進行“改進”,並強迫用戶經常升級 - 畢竟,沒有人想運行過時的版本。僅僅因為他們認為他們對目前的程式很滿意,想想當你“修復”後他們會有多麼快樂!除非被迫,否則不要告訴任何人版本之間的差異 - 畢竟,為什麼告訴某人舊版本中的錯誤,否則他們可能永遠不會注意到?
關於框
關於框應該僅包含程式的名稱、程式設計者的名字和以法律術語撰寫的版權聲明。理想情況下,它應該連結到數兆的程式碼,以產生一個有趣的動畫顯示。然而,它絕對不應該包含程式用途的描述、次要版本號、最近程式碼修訂的日期、獲取更新的網站,或作者的電子郵件地址。這樣所有用戶很快就會運行不同版本,並會嘗試在安裝版本 N+1 之前安裝版本 N+2。
這是原始 Markdown 內容的翻譯,請確保符合您的要求。
變化變化變化
版本之間的變化越多越好,您不希望用戶每年都對相同的 API 或用戶界面感到厭倦。最後,如果您可以在用戶不注意到的情況下進行這些更改,那就更好了 - 這將使他們保持警惕,避免變得自滿。
將 C 原型放在單獨的文件中
而不是共同的標頭文件。這有雙重好處,需要在每個文件中維護參數數據類型的更改,並且避免編譯器或鏈接器檢測到類型不匹配的機會。當從 32 -> 64 位平台移植時,這將尤其有幫助。
無需技能
您無需具備高超技能來編寫難以維護的代碼。只需著手開始編碼。請記住,即使以後必須刪除大部分代碼,管理仍然會以代碼行數來衡量生產力。
只攜帶一把錘子
堅持使用您熟悉的工具並輕裝上陣;如果您只攜帶一把錘子,那麼所有問題都是釘子。
標準?什麼標準?
盡可能忽略目標語言和環境中數千開發人員當前使用的編碼標準。例如,在編寫基於 MFC 的應用程序時,堅持使用 STL 風格的編碼標準。
顛倒通常的真假慣例
顛倒通常的真和假的定義。聽起來非常明顯,但效果非常好。您可以將以下代碼隱藏在程式的深處,以便從一個再也沒有人查看的文件中挖掘出來:
#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
。
第三方函式庫
在專案中包含功能強大的第三方函式庫,然後不使用它們。透過實踐,您可以完全忽略好工具並將未使用的工具添加到您的履歷的「其他工具」部分。
避免使用函式庫
假裝對直接包含在您的開發工具中的函式庫一無所知。如果在 Visual C++ 中編碼,忽略 MFC 或 STL 的存在,並手動編碼所有字元串和陣列;這有助於保持您的指標技能獲得鍛煉,並自動阻止任何擴展程式碼的嘗試。
創建建置順序
製作一個如此複雜的建置順序,以至於任何維護者都無法使他們的修正編譯。保密 SmartJ,它幾乎使 make
腳本過時。同樣地,保密 javac
編譯器也可用作一個類別。切勿透露撰寫和維護一個快速小型自訂 Java 程式以查找檔案並執行直接調用 sun.tools.javac.Main
編譯類別有多麼容易,否則將受到死刑的威脅。
更多有趣的 make
應用
讓 makefile 生成的批次檔案從多個目錄複製源文件,並具有未記錄的覆蓋規則。這允許代碼分支而無需任何花俏的原始碼控制系統,並阻止您的後繼者找出哪個版本的 DoUsefulWork()
是他們應該編輯的版本。
收集編碼標準
尋找所有有關編寫可維護代碼的提示,例如方塊建議,並公然違反它們。
使用 IDE,不是我!
將所有程式碼放在 makefile 中。您的後繼者將對您如何設法編寫一個生成批次檔案的 makefile,該批次檔案生成一些標頭文件,然後構建應用程序感到印象深刻,以至於他們永遠無法確定更改會產生什麼影響,或無法遷移到現代 IDE。為了最大效果,使用過時的 make 工具,例如早期的 NMAKE 的無依賴概念的簡陋版本。
繞過公司編碼標準
一些公司有嚴格的政策,不允許使用數字文字;必須使用命名常數。破壞這項政策的意圖相當容易。例如,一位聰明的 C++ 程式設計師寫道:
#define K_ONE 1
#define K_TWO 2
#define K_THOUSAND 999
編譯器警告
請確保留一些編譯器警告。在 make 中使用方便的 “-” 前綴來抑制 make 因任何編譯器錯誤而失敗。這樣,如果一位維護程式設計師粗心地在您的原始碼中插入錯誤,make 工具仍然會嘗試重新構建整個套件;甚至可能成功!而任何手動編譯您的程式碼的程式設計師將認為他們已經破壞了某些現有的程式碼或標頭,而實際上只是碰巧遇到了您無害的警告。他們將再次感謝您,因為他們將不得不遵循的過程讓他們發現錯誤一直存在。額外的獎勵分數確保您的程式絕對無法使用任何編譯器錯誤檢查診斷功能進行編譯。當然,編譯器可能能夠進行下標範圍檢查,但真正的程式設計師不使用此功能,您也不應該使用。為什麼讓編譯器檢查錯誤,當您可以利用自己有利可圖且有益的時間來找出這些微妙的錯誤呢?
將錯誤修復與升級結合
永遠不要發布僅包含「錯誤修復」的版本。請確保將錯誤修復與資料庫格式更改、複雜的使用者介面更改以及管理介面的完全重寫結合在一起。這樣,升級將變得如此困難,以至於人們會習慣這些錯誤並開始稱之為功能。而真正希望這些「功能」以不同方式運作的人將有動機升級到新版本。這將在長遠節省您的維護工作,並從客戶那裡獲得更多收入。
每次產品發布都更改檔案格式
是的,您的客戶將要求向上相容性,所以請繼續這樣做。但請確保沒有向下相容性。這將防止客戶回退到更新版本,再加上合理的錯誤修復政策(見上文),將確保一旦升級到更新版本,他們將留在那裡。額外的獎勵分數是找出如何使舊版本甚至無法識別新版本創建的檔案。這樣,他們不僅無法讀取這些檔案,甚至會否認這些檔案是由同一應用程式創建的!提示:個人電腦文字處理器提供了這種複雜行為的有用範例。
補償錯誤
不必擔心在程式碼中找到錯誤的根本原因。只需在高層級例程中放入補償代碼。這是一個很棒的智力鍛煉,類似於3D 西洋棋,將使未來的程式碼維護人員在嘗試弄清楚問題是在生成數據的低層例程還是在改變各種情況的高層例程時,娛樂了數小時。這種技術對於編譯器非常有用,因為它們本質上是多通過程序。您可以通過使後續通過更複雜來完全避免在早期通過中修復問題。幸運的話,您將永遠不必與據說維護編譯器前端的那個小傢伙交談。額外的獎勵點確保後端在前端生成正確數據時崩潰。
使用自旋鎖
避免實際同步原語,而是使用各種自旋鎖 - 反覆睡眠然後測試(非易失性)全局變量,直到滿足您的標準。自旋鎖更容易使用,比系統對象更“通用”和“靈活”。
大量添加同步代碼
在不需要的地方添加一些系統同步原語。我在一段程式碼中遇到了一個關鍵部分,其中不可能有第二個線程。我向原始開發人員提出挑戰,他表示這有助於說明該程式碼是“關鍵的!”
優雅降級
如果您的系統包含一個 NT 設備驅動程式,要求應用程序為任何交易分配 I/O 緩衝區並將其鎖定在內存中,然後在之後釋放/解鎖它們。這將導致應用程序在提前鎖定該緩衝區的情況下在 NT 上崩潰。但是客戶端站點的任何人可能無法更改設備驅動程式,因此他們將無法選擇。
自定義腳本語言
將一種在運行時進行字節編譯的腳本命令語言納入您的客戶端/伺服器應用程序中。
編譯器相依代碼
如果您發現編譯器或解釋器中的錯誤,請確保將該行為視為代碼正常運作所必需的。畢竟,您不會使用其他編譯器,其他人也不應該使用!
真實案例
這是一位大師寫的真實案例。讓我們看看這個單一 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;
}
- 重新發明標準庫中的簡單函數。
- 單詞 Realocate 拼寫不正確。別低估創意拼寫的力量。
- 無實際原因為輸入緩衝區製作臨時副本。
- 無原因進行類型轉換。
memcpy()
接受(void*)
,所以即使指針已經是(void*)
,也要對其進行類型轉換。更棒的是,您無論如何都可以傳遞任何內容。 - 從緩衝區複製比必要更多的內容。這只會在 Unix 上導致核心轉儲,而不是在 Windows 上。
- 應該明顯
os
和ns
代表 “old size” 和 “new size”。 - 在分配
buf
後,將其用0\
來初始化。不要使用calloc()
,因為有人可能重新編寫 ANSI 規範,使calloc()
將緩衝區填充為非0\
的內容。(不要理會我們將要將完全相同數據複製到buf
的事實。)
如何修復未使用變數錯誤
如果您的編譯器發出 “未使用的本地變數” 警告,請勿刪除該變數。相反,找到一種巧妙的方式來使用它。我最喜歡的是…
i = i;
大小很重要
幾乎可以說,函數越大,越好。跳轉和 GOTO 越多越好。這樣,任何更改都必須通過許多情景進行分析。它將維護程序員纏在其中的糾結中。如果函數真的非常龐大,它將成為維護程序員的哥斯拉,無情地將他們踩在地上,讓他們對發生了什麼毫無頭緒。
一張圖片勝過千言萬語;一個函式勝過千行代碼
讓每個方法的內容盡可能長 - 希望你永遠不會寫出少於一千行代碼的方法或函式,當然是深度嵌套的。
一個遺失的檔案
確保一個或多個關鍵檔案遺失。最好是通過包含其他檔案來實現。例如,在你的主模組中,你有
#include <stdcode.h>
stdcode.h
是可用的。但在 stdcode.h
中,有一個引用
#include "a:\\refcode.h"
而 refcode.h
卻無處可尋。
到處寫,從不讀
至少應該在每個地方設置一個變數,但幾乎不使用。不幸的是,現代編譯器通常會阻止你做相反的事情,即到處讀取,卻從不寫入,但你仍然可以在 C 或 C++ 中實現這一點。
哲學
設計語言的人是編寫編譯器和系統類別的人。他們自然而然地設計語言,使他們的工作變得輕鬆和數學上優雅。然而,每個編譯器作者面前有 10,000 名維護程序員。這些基層維護程序員對語言的設計絲毫無權。然而,他們撰寫的代碼總量遠遠超過編譯器中的代碼。
這種精英主義思維的結果之一是 JDBC 介面。它讓 JDBC 實現者的生活變得輕鬆,但對於維護程序員來說卻是一場噩夢。它比三十年前隨 SQL 推出的 FORTRAN 介面要笨拙得多。
如果有人諮詢維護程序員,他們會要求一些方法來隱藏瑣碎的細節,這樣他們就可以看到全局。他們會要求各種捷徑,這樣他們就不必輸入那麼多,並且可以在屏幕上一次看到更多的程式。他們會大聲抱怨編譯器要求他們執行的無數瑣碎浪費時間的任務。
在這方面有一些努力 NetRexx、Bali 和視覺編輯器(例如 IBM 的 Visual Age 是一個開始),可以折疊與當前目的無關的細節。
鞋匠沒有鞋
想像一下,如果您的客戶是一位堅持使用文字處理器來維護其總帳的會計師。您會盡力說服他,讓他的數據應該是結構化的。他需要通過交叉字段檢查進行驗證。您會說服他,將數據存儲在數據庫中可以做更多事情,包括控制同時更新。
想像一下,如果您的客戶是一位軟體開發人員。他堅持使用文本編輯器來維護所有數據(源代碼)。他甚至還沒有利用文字處理器的顏色、字體大小或字體。
想像一下,如果我們開始將源代碼存儲為結構化數據會發生什麼。我們可以以許多不同的方式查看相同的源代碼,例如作為Java、作為NextRex、作為決策表、作為流程圖、作為循環結構骨架(去除細節)、作為Java並刪除不同級別的細節或註釋、作為Java並突出顯示當前感興趣的變量和方法調用,或作為Java並生成有關引數名稱和/或類型的註釋。我們可以以TeX和數學家的方式將複雜的算術表達式顯示為2D。您可以看到代碼中有額外或更少的括號(取決於您對優先順序規則的熟悉程度)。括號嵌套可以使用不同大小和顏色來幫助眼睛匹配。通過透明覆蓋集合的更改,您可以選擇性地移除或應用,您可以實時觀看您團隊中其他程序員在不同國家工作時修改您正在工作的類中的代碼。
您可以利用現代屏幕的全彩能力給出潛移默化的提示,例如通過自動將光譜的一部分分配給每個套件/類,使用柔和的色調作為該類的任何方法或變量的背景。您可以將任何標識符的定義加粗以突出顯示。
您可以詢問哪些方法/構造函數將生成類型為X的對象?哪些方法將接受類型為X的對象作為參數?在代碼的這一點上,有哪些變量是可訪問的?通過點擊方法調用或變量引用,您可以查看其定義,幫助澄清實際將調用的給定方法的版本。您可以要求全局訪問對給定方法或變量的所有引用,並在處理每個引用時進行標記。您可以通過點擊進行相當多的代碼編寫。
一些想法可能不會實現。但在實踐中找出哪些是有價值的最好方法是嘗試。一旦我們有了基本工具,我們就可以嘗試數百個類似的想法,以使維護程式設計師的生活更輕鬆。
我在 SCID 學生項目中進一步討論了這個問題。
這篇文章的早期版本出現在《Java Developers’ Journal》(第2期第6期)。我也在1997年11月在科羅拉多高峰會議上就這個主題發表過演講。從那時起,它一直在逐漸增長。
這篇文章是一個玩笑!如果有人認真對待這篇文章,我感到抱歉。加拿大人認為用 :-) 標記笑話很俗氣。當我一直強調如何撰寫__可維護的代碼時,人們沒有注意。我發現人們更樂意聽到人們經常做的愚蠢事情,以避免搞砸。檢查不可維護的設計模式是一種迅速防範惡意或不慎的粗心大意的方法。
原文發表在Roedy Green’s Mindproducts 網站上。