在我心目中,好代碼必須要符合以下四條標準
- 正確
- 易懂
- 易改
- 高效
而糟糕的代碼,只有一個衡量標準,那就是你在閱讀或修改代碼時罵的臟話的程度與次數(shù)
1. 正確
這是最基本的要求,代碼當然是滿足需求,運行起來正確無誤,這一點并不那么容易做到,尤其是運行環(huán)境比較復(fù)雜,各種異常情況較多的時候。
好代碼要考慮周到,各種邏輯流程和意外情況的處理要面面俱到, 單元和模塊測試要覆蓋異常邏輯和邊界
對于服務(wù)質(zhì)量 SLA 要考慮周全, 簡單說起來就是滿足用戶的七大基本需求
- 功能性
- 穩(wěn)定性
- 可靠性
- 性能
- 可維護性
- 可移植性
- 靈活性
我們的代碼既要防錯,也要容錯,足夠的健壯,不易出錯,不怕出錯,網(wǎng)絡(luò)崩了,電源斷了,磁盤滿了等異常情況,都要有相應(yīng)的應(yīng)對措施。
2. 易懂
- 好代碼必須是看起來很舒服,很干凈,容易理解
象一篇好文章,不羅嗦,容易懂,有頭有尾。
各個層次,模塊及函數(shù)分工明確,各司其職, 望文知義,代碼無需注釋即可自我描述。
接口即契約,要足夠簡單,易懂易用, 窄接口好過寬接口。
其實只要符合代碼規(guī)范,命名簡單易懂,代碼就沒那么丑。
有空翻翻“重構(gòu)”那本書中的臭味介紹, 可以提高品味。
有一些基本軟件開發(fā)的普適原則,能遵守盡量遵守。
KISS: Keep It Simple and Straight
保持簡單和直接, 適當隱藏復(fù)雜性
或者
KISS: Keep It Simple and Stupid 保持簡單, 象傻瓜一樣, 不要讓別人多加思考
軟件接口或 API 的設(shè)計要讓人一看就明白, 一看就知道作者的意圖和想法, 如何使用它, 有什么結(jié)果和可能的異常及副作用. 看過很多代碼, 踩過許多坑, 大多是作者或者我自己使用了出乎意料的實現(xiàn), 不做好必要的抽象和封裝, 復(fù)雜的判斷和算法到處都是
DRY: Don't Repeat Yourself
別重復(fù)你自己, 如有重復(fù)代碼, 請抽象或重用,切勿將重復(fù)的代碼散落在多處,否則修改代碼時就知道有多痛苦了
SRP: Single Responsibility Principle
單一職責原則: 就一類而言, 應(yīng)該僅有一個引起它變化的原因,就一個函數(shù)來說,它應(yīng)該只做好一件事,貪多嚼不爛。
也有一個別稱 DOTADIW - Do One Thing and Do It Well 就做一件事并做好它
OCP: Open Close Principle
"software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification
一個軟件實體如類、模塊和函數(shù)應(yīng)當對擴展開放,對修改關(guān)閉。即軟件實體應(yīng)盡量在不修改原有代碼的情況下進行擴展。
我們可以在原有的類中添加字段或方法進行擴展,或者給函數(shù)添加新的參數(shù)(C++可賦予一個默認值, Java 干脆重載一個新函數(shù)),以及擴展出一個新的子類。
而對于已經(jīng)暴露出去的API 以及接口, 類及公有函數(shù),盡量不要修改它們,因為你需要說服你的客戶(其他使用這塊代碼的模塊)做相應(yīng)的修改,而你往往并不清楚有多少客戶在使用它。
LSP: Liskov Substitution Principle
LSP替換基類原則: 子類型應(yīng)該可以替換掉它們的基類型,或者說父類型擁有子類型的全部共性,子類型擁有父類型的全部特征。
ISP: Interface Segregation Principle
ISP接口隔離原則: 不應(yīng)該強迫客戶依賴于它們不用的方法, 接口屬于客戶, 不屬于它所在的類層次結(jié)構(gòu),接口是生產(chǎn)者與消費者之間的契約,應(yīng)該精煉簡約,生產(chǎn)者只通過接口提供服務(wù),而消費者也只通過接口來調(diào)用服務(wù)。
DIP: Dependency Inversion Principle
DIP依賴倒置原則: 抽象不應(yīng)該依賴于細節(jié), 細節(jié)應(yīng)該依賴于抽象。
我們要依賴于高層接口,而不是依賴于底層實現(xiàn),接口是不會輕易改變的,而實現(xiàn)可以修改和替換
時下常用的插件機制,依賴注入方法都遵循這一原則
-- 上述 5 個原則常被統(tǒng)稱為 SOLID 原則
3. 易改
好代碼要易于測試和修改, 適當?shù)某橄?,封裝和模塊化有利于后期的修改和重構(gòu)。
封裝好復(fù)雜性,區(qū)分開經(jīng)常變化與基本不變的代碼, 適當抽取易變參數(shù)作為配置。
為未來的變化設(shè)計有限的靈活性,無需多改就能很容易的擴展新功能。
還是書里那句話,高內(nèi)聚,低耦合。將依賴倒置,做好抽象,封裝和模塊化,代碼就不難改。
例如最常用的 MVC 模式,為什么我們要分成模型,視圖和控制器三塊,原因之一就在于分開易變的與不易變的,分開不會在一起變化的部分,減小每次修改的范圍。
如果某個方面的功能需要修改,最好是改個配置, 其次是擴展個類或函數(shù), 或者傳個不同參數(shù),最差的就是改多個地方,要做多個判斷, 還要做霰彈式的修改。
好代碼要易于觀測和調(diào)試,也就是說日志和度量數(shù)據(jù)要完備,可以分級,分模塊進行日志和度量數(shù)據(jù)的調(diào)整與分析,自帶度量 API, 調(diào)試 API 及控制臺為佳
好代碼還要與時俱進,自我蛻變,寫代碼時要將不變的與易變的分開,技術(shù)再怎么變,人性不會變,掙錢的業(yè)務(wù)變化也不會太大。一般來說,要封裝好業(yè)務(wù)邏輯,核心業(yè)務(wù)不會大變,即使推到重寫也要理解和參照老系統(tǒng)的業(yè)務(wù)流程。
人會變老,代碼也會,新業(yè)務(wù),新技術(shù),新架構(gòu),新框架層出不窮,要大膽試驗,小心引入,逐步演進,不必抱殘守缺,也不要盲目沖動,把握住變與不變的平衡,經(jīng)常變的地方應(yīng)該盡量少,且盡量方便修改。
引述一下,Python 之禪,雖然說的是Python, 其實適用于多數(shù)編程語言
| 英文 | 中文 |
|---|---|
| Beautiful is better than ugly. | 美比丑好 |
| Explicit is better than implicit. | 明顯比隱晦好 |
| Simple is better than complex. | 簡單比復(fù)雜好 |
| Complex is better than complicated. | 復(fù)雜比難懂好 |
| Flat is better than nested. | 扁平比嵌套好 |
| Sparse is better than dense. | 稀疏比稠密好 |
| Readability counts. | 可讀性很重要 |
| Special cases aren't special enough to break the rules. | 特例也不要打破這個原則 |
| Although practicality beats purity. | 盡管實踐會破壞純潔性 |
| Errors should never pass silently. | 錯誤還是不能讓其悄然滑過 |
| Unless explicitly silenced. | 除非你明確聲明不用理會它 |
| In the face of ambiguity, refuse the temptation to guess. | 別讓人來猜測不確定的可能性 |
| There should be one-- and preferably only one --obvious way to do it. | 應(yīng)該有一個且只有一個比較好的明顯的方法來做事 |
| Although that way may not be obvious at first unless you're Dutch. | 盡管那個方法可能并非一開始就顯而易見 |
| Now is better than never. | 現(xiàn)在就做比永遠不做好 |
| Although never is often better than right now. | 盡管永遠不做經(jīng)常比馬上就動手做好 |
| If the implementation is hard to explain, it's a bad idea. | 如果實現(xiàn)很難解釋清楚, 那它不是一個好主意 |
| If the implementation is easy to explain, it may be a good idea. | 如果實現(xiàn)很容易說清楚, 那它是個好主意 |
| Namespaces are one honking great idea – let's do more of those! | 命名空間是個絕妙點子, 讓我們那樣做得更多 |
4. 高效
好的代碼應(yīng)該充分利用可用的資源,性能達標,沒有無謂的浪費和等待。
使用合適的算法和數(shù)據(jù)結(jié)構(gòu),對 CPU, 內(nèi)存,網(wǎng)絡(luò)等資源的使用有節(jié)制。
在關(guān)鍵算法,關(guān)鍵路徑上要做算法復(fù)雜度分析,應(yīng)用大 O 分析法以及Amdahl加速定律等進行定性和定量分析,再結(jié)合性能度量數(shù)據(jù)進行調(diào)優(yōu)。
我們不但要給業(yè)務(wù)數(shù)據(jù)制定 KPI, 也要給性能數(shù)據(jù)制定 KPI, 例如通常用的到 UPS - Usage, Performance 和 Saturation, 通過觀測,度量和分析這些 KPI 再做有的放矢的調(diào)優(yōu)。
壓力測試(stress testing)和性能分析(performance profiling)是必不可少的方法。
一般來說,高效率要在設(shè)計,實現(xiàn)和測試多方面都要做有針對性的考慮,通過度量驅(qū)動,想清楚代碼上線怎么生成和收集度量數(shù)據(jù),如何通過度量進行調(diào)優(yōu)。
參考
- Agile Software Development, Principles, Patterns, and Practices by Robert C. Martin
- Clean code by Robert C. Martin
- Refactoring: Improving the Design of Existing Code Martin Fowler