程式設計是一門創造性的工作,是一門藝術。我們每天與程式碼打交道,為什麼普通碼農辛苦一年只拿十萬,而高階架構師年薪百萬。最主要的就是我們敲出來的程式碼有差別,差別在意大部分碼農敲出來壞的程式碼,而高階架構師能敲出優雅的好的程式碼。
我們每天都會敲程式碼,但當被問道什麼是好的優雅的程式碼時,大家可能會先愣一下,然後給出的回答要麼比較空泛,要麼比較散,沒辦法簡單明了地概括出來。顯然,這個問題並沒有唯一的標準答案,誰都可以談論自己的理解。要成為合格的架構師最基本的要求是能寫好的優雅的程式碼,所以必須要知道什麼是優雅程式碼。這篇文章我來分享一下阿里系高階架構師對於好的優雅程式碼的理解。
一句話概括
衡量程式碼品質的唯一有效標準:WTF/min —— Robert C. Martin
Martin(Bob大叔)曾在《程式碼整潔之道》一書中說:當你的程式碼在做 Code Review 時,審查者要是憤怒地吼道:“What the fuck, is this shit?”、“Dude, What the fuck!”等言辭激烈的詞語,那說明你寫的程式碼是 Bad Code,如果審查者只是漫不經心的吐出幾個:“What the fuck?”,那說明你寫的是 Good Code。
衡量程式碼品質的唯一標準就是每分鐘罵出“WTF”的頻率。
我敢打賭每個人都遇到過這樣的情況:過幾周或者幾個月之後,再看到自己寫的程式碼,感覺一團糟,不禁懷疑人生。
我們自己寫的程式碼,一段時間後自己看尚且如此,更別提拿給別人看了。
一、好的優雅的程式碼
我們如何來形容好的優雅的程式碼?好的優雅的程式碼一定具備以下特徵:
精簡程式碼,可讀性高邏輯清晰高內聚,低耦合OOP三大特徵(封裝、繼承、多型)1、精簡程式碼,可讀性高
任何一個傻瓜都能寫出計算機可以理解的程式碼。唯有寫出人類容易理解的程式碼,才是優秀的程式設計師。—— Martin Fowler
assert((!(bucket = findBucket(key))) || !bucket.isOccupied());
上面這行程式碼雖然比較短,但是難以閱讀。為了更好地閱讀,我們做如下修改:
1. bucket = findBucket(key);if(bucket != null){2. assert(!bucket.isOccupied());}
減少程式碼行數是一個好目標,但是讓閱讀程式碼的事件最小化是個更好的目標。
但是這些詞沒有任何指導意義,我準備從最基本的概念入手。
所以,談到好程式碼,首先跳入自己腦子裡的一個詞就是:精簡。
好的程式碼一定是精簡的,給閱讀的人一種輕鬆愉快感覺。
2、邏輯清晰
對程式碼的邏輯層次要有感覺。
比如大體上,一個程式會分三個層次:介面層,邏輯層,資料層。簡化後一般也有兩個層次:介面和邏輯層。
邏輯層是去掉外表的,內在的,實質的東西。一般來說,就是表現為對資料的一組操作。
而介面層,是關注程式應該如何和使用者溝通的。比如可視的視窗,圖表,控制元件等。它是內部邏輯的呈現,也是使用者和內部邏輯溝通的橋樑。
區分這兩個層次的好處,一個是這兩個層次所注重的核心內容有所不同,用到的技巧或者指導方法有所差別。第二點是,可以將問題解構和區域性化,減輕開發難度。第三點,有助分開來修改內容,比如介面層挪動一下,改變一下形式,並不需要修改邏輯層的;而邏輯層改進一下演算法,也不會影響介面層的程式碼。
對程式碼的邏輯層次有感覺,以上的要求只是很基本的,編寫程式碼要時時刻刻對當前程式碼所代表的邏輯層次要有“感覺”,要能意識到這段程式碼和上一段程式碼是否在某種標準下,處在同一個層次。比較經典的範例如:網際網路的7層協議,還有作業系統的層次分部等。編寫程式碼要善於歸納這些層次,才能建構一個優美的結構。
3、高內聚低耦合
高內聚低耦合幾乎是每個程式設計師員都會掛在嘴邊的,但這個詞太過於寬泛,太過於正確,所以聰明的程式設計人員們提出了若干面向物件設計原則來衡量程式碼的優劣:
開閉原則 OCP (The Open-Close Principle)單一職責原則 SRP (Single Responsibility Principle)依賴倒置原則 DIP (Dependence Inversion Principle)最少知識原則 LKP (Least Knowledge Principle)) / 迪米特法則 (Law Of Demeter)里氏替換原則 LSP (Liskov Substitution Principle)介面隔離原則 ISP (Interface Segregation Principle)組合/聚合複用原則 CARP (Composite/Aggregate Reuse Principle)這些原則想必大家都很熟悉了,是我們編寫程式碼時的指導方針,按照這些原則開發的程式碼具有高內聚低耦合的特性。換句話說,我們可以用這些原則來衡量程式碼的優劣。
但這些原則並不是死板的教條,我們也經常會因為其他的權衡(例如可讀性、複雜度等)違背或者放棄一些原則。比如子類擁有特性的方法時,我們很可能打破里氏替換原則。再比如,單一職責原則跟介面隔離原則有時候是衝突的,我們通常會捨棄介面隔離原則,保持單一職責。只要打破原則的理由足夠充分,也並不見得是壞的程式碼。
4、OOP三大特徵
4.1封裝
儘可能隱藏一個模組的實現細節(屬性名稱,屬性是否可變,演算法,資料結構,資料型別)
訪問控制只是為了防止程式設計師的無意誤用,不打算,也無法防止程式設計師的故意破壞
4.2繼承
繼承使用不當會破壞封裝,造成資訊洩露
先考慮組合,在考慮繼承
繼承是 behaves-like-a, is-substitutable-for 的關係,不是 is-a 或 is-a-kind-of 的關係
4.3多型
相同的實現程式碼適用不同的場合不同的實現程式碼適用相同的場合二、如何判斷不是好的程式碼
討論了好程式碼的必要條件,我們再來看看好程式碼的否定條件:什麼不是好的程式碼。Kent Beck 使用味道來形容重構的時機,我認為當代碼有壞味道的時候,也代表了其並不是好的程式碼。
程式碼的壞味道
► 重複
重複可能是軟體中一切邪惡的根源。—— Robert C.Martin
Martin Fowler 也認為壞味道中首當其衝的就是重複程式碼。
很多時候,當我們消除了重複程式碼之後,發現程式碼就已經比原來整潔多了。
► 函式過長、類過大、引數過長
過長的函式解釋能力、共享能力、選擇能力都較差,也不易維護。
過大的類代表了類做了很多事情,也常常有過多的重複程式碼。
引數過長,不易理解,呼叫時也容易出錯。
► 發散式變化、霰彈式修改、依戀情結
如果一個類不是單一職責的,則不同的變化可能都需要修改這個類,說明存在發散式變化,應考慮將不同的變化分離開。
如果某個變化需要修改多個類的方法,則說明存在霰彈式修改,應考慮將這些需要修改的方法放入同一個類。
如果函式對於某個類的興趣高於了自己所處的類,說明存在依戀情結,應考慮將函式轉移到他應有的類中。
► 資料泥團
有時候會發現三四個相同的欄位,在多個類和函式中均出現,這時候說明有必要給這一組欄位建立一個類,將其封裝起來。
► 過多的 if...else 或者使用 switch
過多的 if...else 或者 switch ,都應該考慮用多型來替換掉。甚至有些人認為除個別情況外,程式碼中就不應該存在 if...else 。
三、總結
本文首先一句話概括了我認為的好的優雅程式碼的必要條件:精簡,邏輯清晰,高內聚,低耦合,接著具體分析了壞程式碼的特點,什麼樣的程式碼不是好的程式碼。僅是本人的一些見解,希望對各位以後的程式設計有些許的幫助。
對於如何保持程式碼整潔,離不開設計模式和程式碼重構,多閱讀開源社群的程式碼,比如最近微信開源的MMKV就可以讀來學習,像世界同行大佬學習交流如何優雅的寫程式碼,也可以讀一些經典的書籍如《程式碼整潔之道》、《重構改善既有程式碼的設計》、《重構改善既有程式碼的設計》等等。