編寫除了自己沒人能看懂的代碼,是一種怎樣的體驗?
下面由作為資深挖坑程序員的我,手把手教大家這是怎麼做到的?如果各位可以在接下來的時間多加練習,所謂青出于藍勝于藍,相信各位不但可以寫出别人無法維護的代碼,還可能在有朝一日,甚至能技藝爐火純青地寫出自己都維護不了的代碼。
編寫無法維護的代碼說難其實并不難,核心要點就是和編碼規範反其道而行之,如果在此基礎上再添加一些自己琢磨出的心得的話那就更加完美了。
掌握了這個要點還不夠,還要注意一個原則:不要讓我們的代碼一眼看上去就無法維護,格式之類的還是要注意些的,我們要追求的不是這種膚淺的表面上的無法維護,我們要的是實質是無法維護的。
要是别人一眼就能看出你的代碼無法維護,那你的代碼就存在需要重寫或者重構的風險了,那不成了前功盡棄親者痛,仇者快的事情了嘛。
1、了解清常規編程的思維方式再下手!
《孫子兵法》有雲“知己知彼,百戰不殆”,假如我們要想從心理上徹底擊敗後續的代碼維護人員,我們必須明白常規編程中的一些思維方式。
各位先想下,如果接手程序的是我們自己,而且代碼量比較大,一般我們是沒有時間去從頭到尾一行一行地讀一遍的,更不要說能理解代碼了。
為了能盡快地上線交差,程序員常見的做法是根據需求,先快速找到代碼中需要改動的那一部分邏輯,然後對這部分的代碼進行修改、測試。這種修改方式一次隻能看到代碼的一小部分,管中窺豹。
所以我們要做的是确保讓代碼維護人員永遠看不到我們寫的代碼的全貌,要盡量保證代碼維護人員找不到他想要找到的那部分代碼。這還不是最關鍵的,最關鍵的是要讓修改者知道自己沒有忽略任何的東西。
每一個我們精心設計的這些小陷阱都會迫使代碼維護者像用放大鏡似的,仔細地閱讀我們的每一行代碼。
有些同學可能覺得這很簡單,認為隻要按照上文中提到的反編程規範原則來進行即可。但是實際操作起來并沒有這麼簡單,還需要配合我們的精心誤用才可。下面我們就對常用的一些核心技能娓娓道來。
2、第一招:一本正經地亂用注釋
這一部分我們先了解下注釋的正常用途:注釋是用來幫助開發者理解程序的,尤其是對于後來的開發者,通過注釋可以更快的了解代碼的實際作用。
正常情況下代碼注釋的原則一般是隻在需要注釋的地方進行注釋。這是一句很正确的廢話,解釋起來就是很明顯就能看懂的代碼就不要去注釋的了,畢竟看注釋也是需要花費時間的。
另外一個原則就是在注釋中注明代碼的作用需要和代碼的實際作用是一緻。
在實際工作中,在對代碼進行修改後一定要連同代碼的注釋也一起進行修改。關于注釋的其他的一些作用我們在此不再多說,光是這些就已經足夠我們用的了。
如何利用代碼注釋寫出讓人無法理解的代碼呢?
一、多整沒用的
這塊我分了兩種情況來描述,兩種情況對應兩種處理方式,實用性比較強。
讓維護者浪費時間看顯而易見的注釋。
這部分的原則是維護者看完注釋後覺得“代碼比注釋容易讀多了”,目的就是誤導讀代碼的人。維護者在看代碼時,上眼一看代碼很清晰,但又一看竟然還有注釋。
此時讀代碼的人心裡肯定是要嘀咕下:看來這代碼沒我想的這麼簡單。
然後我們的注釋要寫的長一些,最後是要閱讀者看不懂,改的時候猶豫不決。
如果有餘力的話可以在注釋中教維護者怎麼編程,這種一般殺傷力要比上面寫的會高一些,程序員最反感的可能就是你要教他怎麼編程了,尤其是教他這麼簡單的編程,殺傷力加倍。
下面看個例子:
字面意思已經很清楚了,正常情況下代碼中不用的部分我們一般會注釋掉或者直接删除掉,即使這段代碼将來會使用到也不影響,可以從版本控制工具中再找回來。
針對性的做法就是給删掉的代碼加個長長的注釋,寫明這段代碼為什麼會被注釋起來,也向維護者傳達了一個信息,即這段代碼不是被”廢棄”的,而是”臨時”先不用。
這樣做的殺傷點就在,如果隻注釋了代碼沒加注釋說明,根據實際經驗大家多數會直接略過被注釋的代碼,而給代碼加了注釋後看代碼的人可能就要看看這個注釋了,不然會漏掉什麼關鍵信息,畢竟代碼不是他寫的。
樣闆代碼:
二、這個地方将來會修改
這種注釋就是我們經常提到的“TODO”型注釋。正常情況下TODO注釋并非一無是處,比如在初始化項目的時候TODO注釋還是非常有用的,到項目release 時一般是建議去掉的,如果必須要留着一般需要寫明在具體什麼日期會處理掉。一般是不推薦TODO型注釋長期存在于項目的代碼中,正常的處理邏輯一般是遵循有Bug盡快Fix,無Bug則去掉注釋。
通過上面的描述相信大家已經知道這塊具體要怎麼應對了。個人建議是對于有待修改的多寫點TODO注釋,且不注明更改的原因以及計劃更改的時間,這樣後面的維護人員在看的時候可能連這塊到底是不是已經改過了都搞不清楚,所以殺傷效果也是有一些的。
樣闆代碼:
三、錯誤注釋信息
這部分的意思是造成代碼和注釋的不匹配,也就是注釋的信息不正确。
我們要做的就是改完代碼後不改注釋就行了,此種方式比較省事,額外工作一點也不用多做,但是稍微有些代價,需要注意的是最好是在此類注釋中加個特殊的标記,防止自己後續看的時候把自己也繞進去。
樣闆實例這塊就不用加了吧,場景太多了,大家在自己的一畝三分地上耕作時臨場發揮即可。
四、講故事
簡單說來就是寫明這段代碼為什麼要這樣寫,當然肯定不是單純的原因。除了原因一般建議在注釋中寫上當時的情況,比如某年某月和某人在某地讨論了這個問題,某人說這個問題應該怎樣處理,你說這個問題不該這樣處理應該那樣處理,後來某某人又加入了讨論,某某人對倆的讨論做了某某的評價,最後決定要用現在的代碼去實現這塊的功能。
總之,原則就是把事情的細節描述清楚,越細越好。有些同學可能會建議将當天的天氣情況也寫上,還有讨論中那個氣死人的S*名字也要帶上,我個人認為天氣可以酌情添加,但寫上S*名字是不太鼓勵的,畢竟同事一場,要相互愛護的,大家按照自己公司的實際情況來選擇具體的處理方式吧。
樣闆代碼:
五、不要寫原因
按照注釋的規範,注釋時不但要解釋程序的表述的意思,更重要的是寫明為什麼寫,即代碼這麼寫的原因是什麼。
這樣應對之策也已經顯而易見了,對于複雜程序,比如一些特殊的邊界條件判斷,隻寫下程序的字面意思,具體邊界值判斷為什麼要這樣寫,為什麼是這個值可以忽略掉,讓維護的人盡情去猜吧。
六、瑣碎
在這需要注明的是大部分程序注釋一般是用不到這種情況的,一般是推薦放在一些複雜算法的解釋上,越是複雜的算法越是推薦,原則就是把這部分應該寫到文檔中的内容寫到代碼中。
一定要把算法的所有的詳細設計都寫上,注釋内容分段落,段落之間要分級,每個段落建議加上編号,這樣就基本可以保證代碼的注釋和文檔的内容保持一緻。後續的維護看到這樣的注釋的時候基本可以保證頭大一圈,如果此類注釋存在多處的話效果更佳。
鑒于樣闆示例中注釋篇幅太長就不加示例了。
七、單位問題
單位這部分和具體的業務場景相關,比如時間相關的一般會有毫秒、秒、分鐘、小時、天、月、年等,涉及尺寸的場景如像素、英寸等,涉及文件大小的場景如字節、KB、MB、GB等。
這一類的代碼中我們的原則是不對單位進行注釋,隻管使用,如果可以在代碼中各種單位混用,那自然是更加優秀。
比如在關于文件處理的場景中,KB、MB、GB多個單位混合使用,這樣後來的維護人員要想搞懂這部分代碼中單位的真正含義就要下一番功夫了。
按照我們的正常邏輯,後面的人要想改這部分的代碼的邏輯首先要先弄懂各個數據的單位,搞清楚之前肯定是不敢随意修改的,一般這種情況隻有一種解決辦法那就是一遍遍的調試、測試程序來推算各個數據實際的單位,花費的時間自然是相當的多。
八、恐吓
這一招可以說是殺手锏級别的注釋,可以在程序中加一部分可有可無的代碼,而且是很明顯可有可無的那種,然後給這段程序加個注釋,注釋中寫明“千萬不要注釋掉或者删除這段代碼,否則程序會出現異常!!!”,需要注意的是不要解釋會出現什麼樣的異常。
這樣維護人員在看到這段代碼的時候肯定首先會聯想到自己以前看過的一些文章,并堅信這段“廢話代碼”肯定是不能删除的。代碼中如果存在多處這種注釋的話效果更佳。
3、障眼法篇
一、入門
讓你的代碼和注釋交融在一起,算是入門級的代碼僞裝術,主要目的是惡心後來的維護者,假使看代碼的人剛好頭昏腦漲的話肯定會直接懵逼一會,懵逼完之後再一陣惡心。
如果代碼和注釋的邏輯剛好是一脈相承下來的那自然更好,具體操作可以參考下面的樣闆實例。
樣闆示例:
二、同義詞
這種招式一般是建議用在C類程序的宏定義中,使用的原則也比較簡單,即宏名稱和具體的值雜糅使用即可,造成一種你中有我,我中有你的既視感。
樣闆示例:
三、命名不一緻
這部分主要應用在前端開發中,舉個例子大家就清楚了,比如Web界面上郵政編碼顯示為postal code,代碼中把變量名命名為zipcode,我相信不論誰看到這種情況都不敢直接改代碼的,肯定要反複确認一會postal code 對應的變量到底是不是zipcode。
四、宏定義隐藏
需要說明的是這裡的隐藏不是說将宏定義藏到找不到的地方,那肯定是不行的,說不定我們自己還要進行修改呢。
這裡說的宏定義隐藏是指将宏定義寫的不像宏定義,讓看代碼的人一眼看去覺得這不是一個宏,然後略過去。
話不多說,上樣闆:
#define a=b a=0-b
五、變量名換行
這一招我隻能用猥瑣來形容了,因為真的是猥瑣。産生的效果是即難閱讀也很難進行變量名的搜索。
樣闆示例:
#define local_var xy\ _z // local_var OK
六、全局變量
這裡說的是隐藏全局變量,方法就是在函數裡面使用全局變量時不直接使用,而是以傳參的形式傳進來後進行使用,這樣很難分辨出這是一個全局變量。
七、函數重載
正常重載後的函數,其功能應該和被重載的函數應該是接近的,我們要做的就是讓重載後的函數和被重載函數的功能完全沒有關系。
這個時候看代碼的人如果基礎不牢的話,可能需要去溫習下函數重載的知識,是不是自己以前理解錯了。
八、操作符重載
操作符重載是一種很變态的招式,因為他會讓你的代碼變的非常的詭異,隻要按照下面描述的方式進行使用基本可以把代碼的混亂程度直接拉升到藝術的級别,藝術就是打破常規,所以一般隻要不按照操作符重載推薦的使用方式去使用都能收到意想不到的效果。
樣闆示例:類中重載 ! 操作符,重載後的功能不是取反而讓其返回一個整數,于是當使用!!時會先調用被重載後的函數,返回一個整數,然後再取反,最後返回個bool值,一臉懵逼。
九、混亂#define
這一招用上後我覺得看代碼的人如果不是穩如老狗的老司機應該會抱頭痛哭的。不信可以看下面的這段樣闆代碼。
樣闆示例:
4、變量命令篇
一、大小寫任意交替
這種基本上就是一種罵娘的命名方式,為啥會這麼容易引起怒火,看個例子就知道了:gGEtpRoDucTnaME,有沒有腦裂的感覺?
二、單字母變量
名稱上毫無邏輯可言。
樣闆代碼:
三、字母 數字很配
如果字母a – z 不夠使用,可以考慮字母 數字的組合,這樣一般就足夠使用了,畢竟數字是無限的。
樣闆示例:
四、故意拼錯
變量名稱拼錯并不是随意一個單詞就拼錯,此處指的是比較有創意的拼寫錯誤。
因為随意的拼寫錯誤是很容易被發現的,高級的拼寫錯誤由于很難看出來,所以在進行變量搜索的時候根本搜不出來。 比如:SetPintle、SetPintalClosing。
五、重複名稱
函數或者方法的内嵌結構中使用和函數或者方法層面中同名的變量名,變量名多的話可能會一陣眩暈。
5、進階篇
前面的注釋和變量命名可以說是本文的基礎篇,主要是較大家一些基本的編程技巧。這一篇作為進階的一篇,我會給大家介紹一下常見的一些稍微高端的編程技巧,廢話不多說,一起看下:
一、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 的自信!
樣闆代碼:
七、測試
測試,不存在的。
一般建議不要測試,測試是一種懦夫的行為,作為一個優秀的程序員我們必須保持這種對自己代碼的自信,再者測試會影響你的生産力,直接影響你寫代碼的行數,所以測試這一步直接跳過就好啦。
作者簡介:阿木,目前就職于國内某知名互聯網公司,擔任雲計算技術部高級工程師,近3年雲計算從業經驗,愛讀書、愛寫作、愛技術。
作者 | 阿木
責編 | 伍杏玲
出品 | 程序人生(ID:coder_life)
整理 |千鋒web前端
,更多精彩资讯请关注tft每日頭條,我们将持续为您更新最新资讯!