程序員:改完這9段屎一樣的代碼,還挺香

我們總是很容易就能寫出滿足某個特定功能的代碼,卻很難寫出優雅代碼

。又最欣賞那些優雅的代碼,因為優雅代碼更能體現一個開發者的積累。

就像寫一篇散文,有的就像初學者不得其門而入,遣詞造句都非常困難,然後糾糾結結,最終不了了之。或者囉哩吧嗦,看起來說了一堆,其實就像是村婦閒聊,毫無重點,不過是口水文而已。

好代碼應該是這樣的,如涓涓細流、如同一首詩,一篇優美的故事,將作者編寫代碼時的情感慢慢鋪墊開來,或是高潮迭起,此起彼伏,或是平鋪直述,卻蘊含道理。我始終相信優秀的代碼是有靈魂的,代碼的靈魂就是作者的邏輯思維。

編寫整潔代碼 or 非整潔代碼,就像平時生活中是否注意愛護環境的一點點小習慣,一旦壞味道代碼沒有及時處理,就會成為破窗效應,然後逐漸的代碼越寫越爛,最終這些代碼要麼以重構收場,要麼就被拋棄。

我們見過太多沒有毫無質量可言的代碼,許多時候開發者們由於能力原因、或者時間有限,寫了許多能夠滿足當前工作的代碼,然後就棄置高閣,不再理會。於是,代碼寫之前的只有自己和上帝能理解代碼的意思,而寫完了之後,只有上帝能懂了;還有一些開發者說:我只會寫代碼,不會優化代碼,他們彷彿特別勤奮,每天都會比其他人都熱衷於熬工時,但是寫出的代碼,實際上是一個個難以維護的技術債。而且許多代碼的作者總喜歡找各種藉口來抵賴,例如喜歡說代碼出了問題都是底層框架太垃圾了、或者別人的代碼封裝得太差。他們總是抱怨這抱怨那,但是即便有優秀的框架、技術,就一定能寫出優秀的代碼麼?

在這裡筆者列舉了平時看到過一些自認為不太整潔的代碼,以及與代碼整潔之道中相對應的範例,歡迎大家一起來拍磚。

01命名規則

1.1 變量命名和方法命名

在我們剛剛開始學習寫代碼的古老時代,或許會有下面這種習慣。

程序員:改完這9段屎一樣的代碼,還挺香

這是一個喜歡用自己的姓名來命名類和方法的作者,在他的代碼中,經常可以看到這樣奇怪的對象定義,而且他還喜歡用a,b,c,d,e,f或者s1,s2這樣的命名,彷彿他的代碼自帶混淆特效。這樣的代碼嗅起來會不會覺得充斥著奇怪的味道?

如果一個項目中,有十幾個地方都出現了這個 GetData() 方法,那種感覺一定非常難受。

1.2 Model、Dto 傻傻分不清楚

隨著技能的增長,或許我們會學到一些新的代碼概念,例如,Model、DTO 是經常容易弄混淆的一種概念,但是在某些代碼中,出現了下面的命名方式就有點令人窒息了。

這位大概是一位對概念嚴重消化不良的資深開發者,居然同時把 Model 和 DTO 複用在一個對象上,

程序員:改完這9段屎一樣的代碼,還挺香

ps:當然,一個開發者定義變量的背後一定有他的動機。

他到底是想要的是用來在 MVC 模式解決數據傳輸和對象綁定的模型對象?還是用於傳輸數據的 DTO 呢?

其實他定義這個對象,是為了定義存儲數據對象的實體( Entity )。

1.3特殊情況術語和字段對照表非常重要

近年來開發者素質越來越高,所以許多優秀開發者會傾向於使用翻譯軟件來翻譯變量名,然後用英語來命名,但是即便如此,許多政務項目總是能嗅出一些奇怪的味道。

例如前不久看到一條這樣的短信:(原圖已經消失)

xxx公積金中心提醒您:您於{TQSJ}日進行了{TQCZ}操作,賬上剩餘金額為{SYJE}元。

這是個bug將xxx公積金中心的某些秘密透露在大家面前。作為一個嚴謹的項目,居然使用中文首字母大寫命名法,這讓習慣於大駝峰、小駝峰的我看了之後尷尬癌犯了,很不舒服。但是這也是許多政務信息化項目的中字段命名的規範,而且在這種情況下,往往會輸出一份非常規範的數據庫字段對照表,確保中文和首字母的語義不讓人產生歧義。

所以特定語境下,變量和方法本身沒有嚴格的規定,但是一定要使用恰當的語境概念,對於這樣的特定場景,儘量維護一份實時更新的術語表吧。

02狀態碼返回值

2.1業務邏輯狀態碼

似乎在對外提供接口時,使用下列接口狀態碼是一種比較常見的慣例。提供統一格式的 code 狀態碼以及返回的消息和成功返回結果時的填充數據,能夠讓開發者高效的完成接口對接,無需關心http狀態碼背後的含義。

程序員:改完這9段屎一樣的代碼,還挺香


2.2用 http 狀態碼為什麼不夠?

上面這是一種經典的流派,還有一種流派則會使用http狀態碼來返回指定的數據,事實上 http 協議本身已經提供了許多狀態碼,例如下面的這些大家都非常熟悉的狀態碼。

但是這些狀態碼為啥不夠?主要是為了減少前後端、服務上下游之間接口對接的難度,也是一種提高效率的方式。但是 http 狀態碼是一種通用的格式,應儘量使用這種方式,而不應該通過解析正常響應後的 json 來判斷是否正確操作。

程序員:改完這9段屎一樣的代碼,還挺香

03switch 語句與判斷語句

3.1 面向過程式或面向對象式 我曾經跟小組中一位大佬交流他的一段代碼,他的這段代碼大概是這樣的。

程序員:改完這9段屎一樣的代碼,還挺香

讀者卒...

且不說這位大佬的代碼是寫得好或者不好,僅僅就這200多行代碼的4個大switch讀起來大概會讓人便秘難受吧。於是在我讀完這段代碼之後,我冒死向他請教這麼寫代碼的原因,他說我這個流程處理就是一個簡單的用例場景,哪裡還有什麼可以優化的餘地?

我跟他介紹了20分鐘代碼封裝的必要性,於是,他把代碼寫成了這樣。

程序員:改完這9段屎一樣的代碼,還挺香

這酸爽令人簡直難以置信。(事實上這個新鮮出爐的遺留應用,正是這樣一點點堆積了許多總代碼行超過千行的類文件)

Robert 大叔給出了這樣的建議:

對於switch 語句,我的規矩是如果只出現一次,用於創建多態對象,而且隱藏在某個集成關係中,在系統中其他部分看不到,就還能容忍。當然也要就事論事,有時我也會部分或全部違反這條規矩。

上文我給出的示例,有點像面向過程的代碼風格,而 Robert 大叔在他的書中寫下的示例是這樣的(抽象工廠模式的示例)。

這清爽的感覺,讓人很舒服啊。

3.2 孰優孰劣?

當然,原示例是一個流程處理的例子,似乎大家的流程處理代碼都習慣於使用這種面向過程風格的寫法,反正要加判定條件,就加一個 case 就可以了。

而在某些特定情況下,甚至用 if / else 來寫邏輯判斷更簡單,於是我們經常在某些銷量很好的快速開發平臺中,看到這樣的例子。

這些典型的面向過程風格的代碼,確實讀起來似乎更加簡單、而且也易於實現。

Robert 大叔是這樣說的:過程式代碼(使用數據結構的代碼)便於在不改動既有數據結構的前提下添加新函數,面向對象代碼便於在不改動既有函數的前提下添加新類。

反過來講也說得通:過程式代碼難以添加新數據結構,因為必須修改所有函數,面向對象代碼難以添加新函數,因為必須修改所有類。

所以究竟是使用面向過程式代碼,還是面向對象式代碼?沒有萬試萬靈的靈丹妙藥。

04奧卡姆剃刀定律、得墨忒耳律

4.1“如非必要,勿增實體”

一旦開始初步掌握面向對象開發的基本原則,於是我們就會新建許多各種不同的模型對象。尤其是在webapi接口開發過程中,更是如此。

切勿浪費較多東西去做,用較少的東西,同樣可以做好的事情。

4.2 得墨忒耳律

假設有一段代碼是這樣的。

程序員:改完這9段屎一樣的代碼,還挺香

會不會為了獲得某些數據,而寫出這樣的代碼呢?

程序員:改完這9段屎一樣的代碼,還挺香

這樣就是典型的對得墨忒耳律的違背。這個原則指出:

模塊不應瞭解它所操作對象的內部情形。

更準確的說,得墨忒耳律認為,類C的方法f只應該調用以下對象的方法:

C(本身)

由方法f創建的對象。

作為參數傳遞給f的對象;

由C的實體變量持有的對象。

對象不應調用由任何函數返回的對象的方法。換言之,只跟朋友說話,不與陌生人說話。

祖父只跟自己的親兒子(Father)說話,而不跟孫子(Me)說話。

05圈複雜度

在軟件測試的概念裡,圈複雜度用來衡量一個模塊判定結構的複雜程度,數量上表現為線性無關的路徑條數,即合理的預防錯誤所需測試的最少路徑條數。圈複雜度大說明程序代碼可能質量低且難於測試和維護,根據經驗,程序的可能錯誤和高的圈複雜度有著很大關係。

據說在Oracle數據庫中有一些屎山代碼,是通過一堆標識量來判斷某些特定邏輯的,大概是這樣的。

程序員:改完這9段屎一樣的代碼,還挺香

這是一個圈複雜度非常複雜的方法,我想任何一個讀到這樣代碼的開發者都會對自己的人生充滿了積極而樂觀的判斷,那就是“活著比一切都好”。

對於這樣的代碼,我們應該儘可能的降低代碼的圈複雜度,讓程序滿足基本可讀的需求。

06註釋

程序員:改完這9段屎一樣的代碼,還挺香

我曾經參加過一個使用objectc編寫的應用的,其中有一段代碼是這樣的,這個flag大概是魔法值,作者未經考證直接就在代碼中使用了。然後一直流傳下來,成為一段佳(gui)話(hua)。

還有這樣的註釋。傻傻分不清楚。

程序員:改完這9段屎一樣的代碼,還挺香

還有這樣的。

程序員:改完這9段屎一樣的代碼,還挺香

Robert大叔如是說:

什麼也比不上放置良好的註釋來得有用。什麼也比不會亂七八糟的註釋更有本事搞亂一個模塊。什麼也不會比陳舊、提供錯誤信息的註釋更有破壞性。

當然很多中國程序員自稱其變量命名是自注釋的,例如大概是這樣的。萬能的 Is 命名法,只要是判斷狀態皆可用。

ps:每個程序員能夠成功的生存下來都不容易,他一定有異於常人的本事。

程序員:改完這9段屎一樣的代碼,還挺香

07霰彈式修改

CRUD開發者或許經常會看到這樣的代碼,例如,如果我要對某一個對象的狀態( Status)進行更改,可能會這麼做:

程序員:改完這9段屎一樣的代碼,還挺香

這種霰彈式代碼中,一處代碼規則的變化,可能會需要對許多處代碼進行同步修改,使得我們的代碼異常的難以維護。

08異常

有時候可能會遇到這樣的代碼,在方法中定義一些文本的狀態碼,然後調用方法時,再去判斷這個狀態碼的內容,當返回錯誤碼時,要求調用者立即處理錯誤。

程序員:改完這9段屎一樣的代碼,還挺香

不如直接拋出異常,讓異常處理機制進行處理吧。

程序員:改完這9段屎一樣的代碼,還挺香

09邊界

9.1 模塊間的邊界

即便是簡單的CRUD應用系統,優秀的開發者也能更好的處理應用程序模塊間的邊界。某種意義上講,應用程序內部的邊界看起來或許沒有明確的界限之分,但是稍不留心就可能導致應用程序間關係過於紊亂,讓其他開發者捉摸不透。

例如,假設有一段代碼是這樣的,在用戶操作類中,加入了一個獲取應用數據的方法,確實會讓人很費解吧。

程序員:改完這9段屎一樣的代碼,還挺香

9.2應用間的邊界

相對而言,或許應用間的邊界似乎能相對清晰的分析出來?並非如此。

在當今時代,我們很少開發完全與其他應用系統沒有任何關聯的獨立軟件,這意味著我們或許無時無刻都得與其他第三方應用進行接口行為或數據的交換。這讓我們必須確保採取措施讓外來代碼乾淨利落地整合進自己的代碼中。

程序員:改完這9段屎一樣的代碼,還挺香

在代碼整潔之道書中,Robert大叔推薦應該第三方接口進行隔離,通過Map那樣包裝或者使用適配器模式將我們的接口轉換成第三方提供的接口。讓代碼更好地與我們溝通,在邊界兩邊推動內部一致的用法,當第三方代碼有改動時修改點也會更少。

10總結

寫代碼是開發者的基礎技能,無論你是.NET 開發者,或者C/C++ 開發者,你都在努力用代碼實現自己的夢想。如同韓磊老師在譯作代碼整理之道封面上總結全書,寫下的那句詩

“細節之中自有天地,整潔成就卓越代碼”。

卓越代碼從來不僅僅只是功能完善、代碼齊全,做好細節,每個細節就是一方小天地。優雅的代碼,不僅僅只是開發者的個人能力的體現,更是開發者的立足之本。努力改善壞習慣,提高代碼質量,時刻消除異味,時刻提高自己,更是個人技能的全面發展的必然要求。

我是一名從事了10年開發的老程序員,最近我花了一些時間整理關於C語言、C++,自己有做的材料的整合,一個完整的學習C語言、C++的路線,學習材料和工具。C/C++、編程愛好者的聚集地 企鵝申請

!歡迎初學和進階中的小夥伴。希望你也能憑自己的努力,成為下一個優秀的程序員。工作需要、感興趣、為了入行、轉行需要學習C/C++的夥伴可以跟我一起學習!”

關注我,帶你遨遊代碼世界!

程序員:改完這9段屎一樣的代碼,還挺香

最後分享一張C/C++學習路線圖給愛學習的小夥伴們

程序員:改完這9段屎一樣的代碼,還挺香




分享到:


相關文章: