首頁 > 運動
如何一本正經地寫出別人無法維護的程式碼?
由 CSDN 發表于 運動2023-01-30
簡介My Client<
sales reps怎麼讀
作者 | 阿木
責編 | 伍杏玲
編寫除了自己沒人能看懂的程式碼,是一種怎樣的體驗?
下面由作為資深挖坑程式設計師的我,手把手教大家這是怎麼做到的?如果各位可以在接下來的時間多加練習,所謂青出於藍勝於藍,相信各位不但可以寫出別人無法維護的程式碼,還可能在有朝一日,甚至能技藝爐火純青地寫出自己都維護不了的程式碼。
編寫無法維護的程式碼說難其實並不難,核心要點就是和編碼規範反其道而行之,如果在此基礎上再新增一些自己琢磨出的心得的話那就更加完美了。
掌握了這個要點還不夠,還要注意一個原則:
不要讓我們的程式碼一眼看上去就無法維護
,格式之類的還是要注意些的,我們要追求的不是這種膚淺的表面上的無法維護,我們要的是實質是無法維護的。
要是別人一眼就能看出你的程式碼無法維護,那你的程式碼就存在需要重寫或者重構的風險了,那不成了前功盡棄親者痛,仇者快的事情了嘛。
瞭解清常規程式設計的思維方式再下手!
《孫子兵法》有云“知己知彼,百戰不殆”,假如我們要想從心理上徹底擊敗後續的程式碼維護人員,我們必須明白常規程式設計中的一些思維方式。
各位先想下,如果接手程式的是我們自己,而且程式碼量比較大,一般我們是沒有時間去從頭到尾一行一行地讀一遍的,更不要說能理解程式碼了。
為了能儘快地上線交差,程式設計師常見的做法是根據需求,先快速找到程式碼中需要改動的那一部分邏輯,然後對這部分的程式碼進行修改、測試。這種修改方式一次只能看到程式碼的一小部分,管中窺豹。
所以我們要做的是確保讓程式碼維護人員永遠看不到我們寫的程式碼的全貌,要儘量保證程式碼維護人員找不到他想要找到的那部分程式碼。
這還不是最關鍵的,最關鍵的是要讓修改者知道自己沒有忽略任何的東西。
每一個我們精心設計的這些小陷阱都會迫使程式碼維護者像用放大鏡似的,仔細地閱讀我們的每一行程式碼。
有些同學可能覺得這很簡單,認為只要按照上文中提到的反程式設計規範原則來進行即可。但是實際操作起來並沒有這麼簡單,還需要配合我們的精心誤用才可。下面我們就對常用的一些核心技能娓娓道來。
第一招:
一本正經地亂用註釋
這一部分我們先了解下注釋的正常用途:註釋是用來幫助開發者理解程式的,尤其是對於後來的開發者,透過註釋可以更快的瞭解程式碼的實際作用。
正常情況下程式碼註釋的原則一般是隻在需要註釋的地方進行註釋。這是一句很正確的廢話,解釋起來就是很明顯就能看懂的程式碼就不要去註釋的了,畢竟看註釋也是需要花費時間的。
另外一個原則就是在註釋中註明程式碼的作用需要和程式碼的實際作用是一致。
在實際工作中,在對程式碼進行修改後一定要連同程式碼的註釋也一起進行修改。關於註釋的其他的一些作用我們在此不再多說,光是這些就已經足夠我們用的了。
如何利用程式碼註釋寫出讓人無法理解的程式碼呢?
一、多整沒用的
這塊我分了兩種情況來描述,兩種情況對應兩種處理方式,實用性比較強。
明顯型註釋
讓維護者浪費時間看顯而易見的註釋。
這部分的原則是維護者看完註釋後覺得“程式碼比註釋容易讀多了”,目的就是誤導讀程式碼的人。
維護者在看程式碼時,上眼一看程式碼很清晰,但又一看竟然還有註釋
。
此時讀程式碼的人心裡肯定是要嘀咕下:看來這程式碼沒我想的這麼簡單。
然後我們的註釋要寫的長一些,
最後是要閱讀者看不懂,改的時候猶豫不決。
如果有餘力的話可以在註釋中教維護者怎麼程式設計
,這種一般殺傷力要比上面寫的會高一些,程式設計師最反感的可能就是你要教他怎麼程式設計了,尤其是教他這麼簡單的程式設計,殺傷力加倍。
下面看個例子:
publicclassProgram
{
staticvoidMain(string[] args)
{
/* This is a for loop that prints the
* words “I Rule!” to the console screen
* 1 million times, each on its own line。 It
* accomplishes this by starting at 0 and
* incrementing by 1。 If the value of the
* counter equals 1 million the for loop
* stops executing。*/
for (int i = 0; i < 1000000; i++)
{
Console。WriteLine(“I Rule!”);
}
}
}
廢棄程式碼註釋
字面意思已經很清楚了,正常情況下程式碼中不用的部分我們一般會註釋掉或者直接刪除掉,即使這段程式碼將來會使用到也不影響,可以從版本控制工具中再找回來。
針對性的做法就是
給刪掉的程式碼加個長長的註釋,寫明這段程式碼為什麼會被註釋起來
,也向維護者傳達了一個資訊,即這段程式碼不是被”廢棄”的,而是”臨時”先不用。
這樣做的殺傷點就在,如果只註釋了程式碼沒加註釋說明,根據實際經驗大家多數會直接略過被註釋的程式碼,而給程式碼加了註釋後看程式碼的人可能就要看看這個註釋了,不然會漏掉什麼關鍵資訊,畢竟程式碼不是他寫的。
樣板程式碼:
publicclassProgram
{
staticvoidMain(string[] args)
{
/* This block of code is no longer needed
* because we found out that Y2K was a hoax
* and our systems did not roll over to 1/1/1900 */
//DateTime today = DateTime。Today;
//if (today == new DateTime(1900, 1, 1))
//{
// today = today。AddYears(100);
// string message = “The date has been fixed for Y2K。”;
// Console。WriteLine(message);
//}
}
}
二、這個地方將來會修改
這種註釋就是我們經常提到的“TODO”型註釋。正常情況下TODO註釋並非一無是處,比如在初始化專案的時候TODO註釋還是非常有用的,到專案release 時一般是建議去掉的,如果必須要留著一般需要寫明在具體什麼日期會處理掉。一般是不推薦TODO型註釋長期存在於專案的程式碼中,正常的處理邏輯一般是遵循有Bug儘快Fix,無Bug則去掉註釋。
透過上面的描述相信大家已經知道這塊具體要怎麼應對了。個人建議是對於有待修改的多
寫點TODO註釋,且不註明更改的原因以及計劃更改的時間,
這樣後面的維護人員在看的時候可能連這塊到底是不是已經改過了都搞不清楚,所以殺傷效果也是有一些的。
樣板程式碼:
publicclassProgram
{
staticvoidMain(string[] args)
{
//TODO: I need to fix this someday – 07/24/1995 Bob
/* I know this error message is hard coded and
* I am relying on a Contains function, but
* someday I will make this code print a
* meaningful error message and exit gracefully。
* I just don’t have the time right now。
*/
string message = “An error has occurred”;
if(message。Contains(“error”))
{
thrownew Exception(message);
}
}
}
三、錯誤註釋資訊
這部分的意思是造成程式碼和註釋的不匹配,也就是註釋的資訊不正確。
我們要做的就是改完程式碼後不改註釋就行了
,此種方式比較省事,額外工作一點也不用多做,但是稍微有些代價,
需要注意的是最好是在此類註釋中加個特殊的標記,防止自己後續看的時候把自己也繞進去。
樣板例項這塊就不用加了吧,場景太多了,大家在自己的一畝三分地上耕作時臨場發揮即可。
四、講故事
簡單說來就是寫明這段程式碼為什麼要這樣寫,當然肯定不是單純的原因。除了原因一般建議在註釋中寫上當時的情況,比如某年某月和某人在某地討論了這個問題,某人說這個問題應該怎樣處理,你說這個問題不該這樣處理應該那樣處理,後來某某人又加入了討論,某某人對倆的討論做了某某的評價,最後決定要用現在的程式碼去實現這塊的功能。
總之,
原則就是把事情的細節描述清楚,越細越好。
有些同學可能會建議將
當天的天氣情況也寫上
,還有討論中那個氣死人的S*名字也要帶上,我個人認為天氣可以酌情新增,但寫上S*名字是不太鼓勵的,畢竟同事一場,要相互愛護的,大家按照自己公司的實際情況來選擇具體的處理方式吧。
樣板程式碼:
publicclassProgram
{
staticvoidMain(string[] args)
{
/* I discussed with Jim from Sales over coffee
* at the Starbucks on main street one day and he
* told me that Sales Reps receive commission
* based upon the following structure。
* Friday: 25%
* Wednesday: 15%
* All Other Days: 5%
* Did I mention that I ordered the Caramel Latte with
* a double shot of Espresso?
*/
double price = 5。00;
double commissionRate;
double commission;
if (DateTime。Today。DayOfWeek == DayOfWeek。Friday)
{
commissionRate = 。25;
}
elseif (DateTime。Today。DayOfWeek == DayOfWeek。Wednesday)
{
commissionRate = 。15;
}
else
{
commissionRate = 。05;
}
commission = price * commissionRate;
}
}
五、不要寫原因
按照註釋的規範,註釋時不但要解釋程式的表述的意思,更重要的是寫明為什麼寫,即程式碼這麼寫的原因是什麼。
這樣應對之策也已經顯而易見了,對於複雜程式,
比如一些特殊的邊界條件判斷,只寫下程式的字面意思,具體邊界值判斷為什麼要這樣寫,為什麼是這個值可以忽略掉,讓維護的人盡情去猜吧。
六、瑣碎
在這需要註明的是大部分程式註釋一般是用不到這種情況的,一般是推薦放在一些複雜演算法的解釋上,越是複雜的演算法越是推薦,原則就是把這部分應該寫到文件中的內容寫到程式碼中。
一定要把演算法的所有的詳細設計都寫上,註釋內容分段落,段落之間要分級,每個段落建議加上編號,這樣就基本可以保證程式碼的註釋和文件的內容保持一致。
後續的維護看到這樣的註釋的時候基本可以保證頭大一圈,如果此類註釋存在多處的話效果更佳。
鑑於樣板示例中註釋篇幅太長就不加示例了。
七、單位問題
單位這部分和具體的業務場景相關,比如時間相關的一般會有毫秒、秒、分鐘、小時、天、月、年等,涉及尺寸的場景如畫素、英寸等,涉及檔案大小的場景如位元組、KB、MB、GB等。
這一類的程式碼中我們的原則是
不對單位進行註釋,只管使用,如果可以在程式碼中各種單位混用
,那自然是更加優秀。
比如在關於檔案處理的場景中,KB、MB、GB多個單位混合使用,這樣後來的維護人員要想搞懂這部分程式碼中單位的真正含義就要下一番功夫了。
按照我們的正常邏輯,後面的人要想改這部分的程式碼的邏輯首先要先弄懂各個資料的單位,搞清楚之前肯定是不敢隨意修改的,一般這種情況只有一種解決辦法那就是一遍遍的除錯、測試程式來推算各個資料實際的單位,花費的時間自然是相當的多。
八、恐嚇
這一招可以說是殺手鐧級別的註釋,
可以在程式中加一部分可有可無的程式碼,而且是很明顯可有可無的那種
,然後給這段程式加個註釋,註釋中寫明“千萬不要註釋掉或者刪除這段程式碼,否則程式會出現異常!!!”,
需要注意的是不要解釋會出現什麼樣的異常。
這樣維護人員在看到這段程式碼的時候肯定首先會聯想到自己以前看過的一些文章,並堅信這段“廢話程式碼”肯定是不能刪除的。程式碼中如果存在多處這種註釋的話效果更佳。
障眼法篇
一、入門
讓你的程式碼和註釋交融在一起,算是入門級的程式碼偽裝術,主要目的是噁心後來的維護者,假使看程式碼的人剛好頭昏腦漲的話肯定會直接懵逼一會,懵逼完之後再一陣噁心。
如果程式碼和註釋的邏輯剛好是一脈相承下來的那自然更好,具體操作可以參考下面的樣板例項。
樣板示例:
publicclass Program
{
static void Main(string[] args)
{
for(j=0; j { total += array[j+0 ]; total += array[j+1 ]; total += array[j+2 ]; /* Main body of total += array[j+3]; * loopis unrolled total += array[j+4]; * for greater speed。 total += array[j+5]; */ total += array[j+6 ]; total += array[j+7 ]; } } } 二、同義詞 這種招式一般是建議用在C類程式的宏定義中,使用的原則也比較簡單, 即宏名稱和具體的值雜糅使用即可,造成一種你中有我,我中有你的既視感。 樣板示例: #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 三、命名不一致 這部分主要應用在前端開發中,舉個例子大家就清楚了,比如Web介面上郵政編碼顯示為postal code,程式碼中把變數名命名為zipcode,我相信不論誰看到這種情況都不敢直接改程式碼的,肯定要反覆確認一會postal code 對應的變數到底是不是zipcode。 四、宏定義隱藏 需要說明的是這裡的隱藏不是說將宏定義藏到找不到的地方,那肯定是不行的,說不定我們自己還要進行修改呢。 這裡說的宏定義隱藏是指將宏定義寫的不像宏定義,讓看程式碼的人一眼看去覺得這不是一個宏,然後略過去。 話不多說,上樣板: #define a=b a=0-b 五、變數名換行 這一招我只能用猥瑣來形容了,因為真的是猥瑣。產生的效果是即難閱讀也很難進行變數名的搜尋。 樣板示例: #define local_var xy\ _z // local_var OK 六、全域性變數 這裡說的是隱藏全域性變數,方法就是在函數里面使用全域性變數時不直接使用,而是以傳參的形式傳進來後進行使用,這樣很難分辨出這是一個全域性變數。 七、函式過載 正常過載後的函式,其功能應該和被過載的函式應該是接近的,我們要做的就是 讓過載後的函式和被過載函式的功能完全沒有關係 。 這個時候看程式碼的人如果基礎不牢的話,可能需要去溫習下函式過載的知識,是不是自己以前理解錯了。 八、運算子過載 運算子過載是一種很變態的招式,因為他會讓你的程式碼變的非常的詭異,只要按照下面描述的方式進行使用基本可以把程式碼的混亂程度直接拉昇到藝術的級別,藝術就是打破常規,所以一般只要不按照運算子過載推薦的使用方式去使用都能收到意想不到的效果。 樣板示例:類中過載 ! 運算子,過載後的功能不是取反而讓其返回一個整數,於是當使用!!時會先呼叫被過載後的函式,返回一個整數,然後再取反,最後返回個bool值,一臉懵逼。 九、混亂#define 這一招用上後我覺得看程式碼的人如果不是穩如老狗的老司機應該會抱頭痛哭的。不信可以看下面的這段樣板程式碼。 樣板示例: #ifndef DONE #ifdef TWICE // put stuff here to declare 3rd time around voidg(char* str); #define DONE #else// TWICE #ifdef ONCE // put stuff here to declare 2nd time around< voidg(void* str); #define TWICE #else// ONCE // put stuff here to declare 1st time around voidg(std::string str); #define ONCE #endif// ONCE #endif// TWICE #endif// DONE 變數命令篇 一、大小寫任意交替 這種基本上就是一種罵孃的命名方式,為啥會這麼容易引起怒火,看個例子就知道了:gGEtpRoDucTnaME,有沒有腦裂的感覺? 二、單字母變數 名稱上毫無邏輯可言。 樣板程式碼: publicclassProgram { staticvoidMain(string[] args) { private a = 0; private A = 0; private b = 0; } } 三、字母+數字很配 如果字母a – z 不夠使用,可以考慮字母+數字的組合,這樣一般就足夠使用了,畢竟數字是無限的。 樣板示例: publicclassProgram { staticvoidMain(string[] args) { private a1 = 0; private a2 = 0; 。。。 。。。 private a10 = 0; private A2 = 0; 。。。 。。。 private A10 = 0; private A1 = 0; private b1= 0; private b2 = 0; 。。。 。。。 private b10 = 0; } } 四、故意拼錯 變數名稱拼錯並不是隨意一個單詞就拼錯,此處指的是比較有創意的拼寫錯誤。 因為隨意的拼寫錯誤是很容易被發現的,高階的拼寫錯誤由於很難看出來,所以在進行變數搜尋的時候根本搜不出來。 比如:SetPintle、SetPintalClosing。 五、重複名稱 函式或者方法的內嵌結構中使用和函式或者方法層面中同名的變數名,變數名多的話可能會一陣眩暈。 進階篇 前面的註釋和變數命名可以說是本文的基礎篇,主要是較大家一些基本的程式設計技巧。這一篇作為進階的一篇,我會給大家介紹一下常見的一些稍微高階的程式設計技巧,廢話不多說,一起看下: 一、void* 不管什麼型別的指標一律都用宣告定義為void*,當實際用到時再轉換為需要的型別。 二、條件表示式 條件表示式這塊可以發揮的空間就比較大了,從實際編碼情況來看,每個簡單的條件表示式都可以進行拆分,看個例子就明白了。 10 == num,拆分為 num >= 99 && num <= 101 三、長程式碼 什麼,一行最多80個字元?不行,這才哪到哪,一定要跨行,而且要跨多行,不能因為換行影響了我們寫程式碼的那股激情。 原則就是越長越好,這樣後續閱讀程式碼的人就需要來來回回地讀,想想都覺得累。 四、巢狀 一個優秀的程式設計師必須能夠在一行程式碼中使用超過10個小括號(),如果覺得很難得話在一個函數里面使用超過5層的大括號也是可以的,還不行的話把巢狀的條件語句if … else 轉為[?:] 也是可以說明你是個優秀的程式設計師的。 五、不要break 不要在程式碼的迴圈中使用break,更不要使用goto,這樣可以保證一行break可以處理的程式碼最少要寫5層的if … else 來解決,一遇到break 就多出百十行程式碼,想想都過癮,一天下來光看看新增的程式碼行數就覺得充實。 六、儘量使用XML XML 的強大是無人能及的,不是JSON、Yaml這些所能及的。專案中使用XML 可以幫助我們將原來只需要10行的程式碼變為100行(可能還不止)。XML是無所不能的,哪怕是自己封裝自己也是可以做到的,信XML 得永生,信XML 的自信! 樣板程式碼: <!—— ED: soap envelope omitted for readability ——> <;CompanyGetConnector>; <;xs:schema xmlns:xs=“http://www。w3。org/2001/XMLSchema”>; <;xs:element name=“InitechGetConnector”>; <;xs:complexType>; <;xs:choice maxOccurs=“unbounded”>; <;xs:element name=“employees”>; <;xs:complexType>; <;xs:sequence>; <;xs:element name=“EmployerName” type=“xs:string” minOccurs=“0”/>; <;xs:element name=“Employee” type=“xs:string” minOccurs=“0”/>; <;xs:element name=“Firstname” type=“xs:string” minOccurs=“0”/>; <;xs:element name=“Prefix” type=“xs:string” minOccurs=“0”/>; <;xs:element name=“Lastname” type=“xs:string” minOccurs=“0”/>; <;xs:element name=“Org。_unit” type=“xs:string” minOccurs=“0”/>; <;xs:element name=“Function” type=“xs:string” minOccurs=“0”/>; <;xs:element name=“E-mail_work” type=“xs:string” minOccurs=“0”/>; <;xs:element name=“Telephone_work” type=“xs:string” minOccurs=“0”/>; <;xs:element name=“Mobile_work” type=“xs:string” minOccurs=“0”/>; <;xs:element name=“Birthdate” type=“xs:date” minOccurs=“0”/>; <;xs:element name=“Hired_since__irt。_yearsemployed_” type=“xs:date” minOccurs=“0”/>; <;xs:element name=“Image” type=“xs:base64Binary” minOccurs=“0”/>; <;/xs:sequence>; <;/xs:complexType>; <;/xs:element>; <;/xs:choice>; <;/xs:complexType>; <;/xs:element>; <;/xs:schema>; <;employees>; <;EmployerName>; My Client <;/EmployerName>; <;Employee>; 100001 <;/Employee>; <;/employees>; <;/CompanyGetConnector>; 七、測試 測試,不存在的。 一般建議不要測試,測試是一種懦夫的行為,作為一個優秀的程式設計師我們必須保持這種對自己程式碼的自信,再者測試會影響你的生產力,直接影響你寫程式碼的行數,所以測試這一步直接跳過就好啦。 作者簡介:阿木,目前就職於國內某知名網際網路公司,擔任雲計算技術部高階工程師,近3年雲計算從業經驗,愛讀書、愛寫作、愛技術。