來源為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;