歡迎加QQ群討論:157672725
前言
最近在團隊推行Code Review,遇到一個頭痛的問題。當向夥伴的代碼提一個comment時,他們不解為什麽需要這樣改。細細想來,是他們不知道何為好代碼,也不知道自己的代碼有哪些 “壞味道” 。因此,分享了幾期Clean Code,團隊受益良多,故成此文。
Clean Code
由於Clean Code篇幅較長,故先安排如下我認為較為重要的幾點:
- 命名
- 函數(方法)
- 註釋
- 對象、數據結構
- 類
命名
命名有許多規則,但總結起來就是 “有意義” 才是硬道理。
名副其實
Int d;//逝去的時間
這句代碼的問題在於d沒有表達好逝去的時間這個概念,故需要註釋。請記住 “名副其實就不需要註釋”
Int elapsedTime;
再來看個例子 誰都很難猜出其意義,看看小優化後的結果 基本看清了意義,這就是命名的重要性。細心的朋友還會發現這段代碼的一些瑕疵 :這裏的4是什麽鬼?習慣性我們管它叫 “魔法數字” 還是覺得有點問題,再優化 對比下最早的代碼,相信你會有感覺了。
避免誤導
生活中的場景也常出現在Code中,看下圖,你的Code是否也出現這樣的尷尬呢?那就Make it clean
是否傻傻分不清了呢? 再來個
accountList
我知道你想說,這有什麽問題。是的,如果你不是做Java開發,不會知道鏈表叫List,所以如果你不是用鏈表存儲account,請不要用其修飾,或許這個時候你使用acountGroup會更好些。 該點需要在具體開發環境下因地制宜
有意義的區分
Product ProductInfo ProductData
可以想象下,當一個項目中同時出現以上三個類的時候,你是如何區分開的,反正我是沒有這個能力。類似的還有
game theGame
name nameString
分享時,夥伴說nameString有什麽問題。我反問說難道你的名字會是Float型的?你懂了吧。
前綴
m_desc
有人提出加m前綴表示該變量為私有變量。 我想說:你的變量很多?需要區分私有的還是公有的?如果你的變量很多,那就要想想是不是沒設計好類,沒有遵循 單一職責原則 ,另外私有和公有變量編譯器會幫忙高亮顯示區分的,不需要自己來區分(若某些編譯器無此特性,怪編譯器去)。
命名慣性
命名需要 註重詞性 類名:名詞 or 名詞短語 方法名: 動詞 or 動詞短語
每個概念對應一個詞
在一個模塊中不要使用兩個相似的概念來表達不同的操作。我在一份代碼中看到過一個類中同時出現以下三個詞打頭的方法
fetch get retrieve
請問那個才是真的獲取值的方法?我實在分不清。
使用領域名稱
使用領域命名能讓夥伴更明白你的程序結構(關於 領域 這個概念,不熟悉的可以看下一本書叫 《領域驅動設計》 ,俗稱DDD) 舉個例子,比如你使用訪問者模式來構建用戶系統,那麽
AccountVisitor
就顯得明確、易懂
抵制縮寫誘惑
縮寫需要註意,適當的縮寫是可以的,但是要保證縮寫後的詞語仍然能表達其本意。舉個有意思的例子
ABCDEFG
這也是個縮寫,但是乍看這個真不知道是什麽的縮寫,直接公布答案吧
小結
命名是永恒的難題,我提幾個建議吧
- 多看開源代碼,積累好的用詞
- 不懂的詞就查下詞典,好過你自己想的
- 做個自己的開源項目,讓別人給你建議
- 做好積累、再積累、還是積累
函數(方法)
函數的第一條規則是要短小,第二條規則還是要短小。
短小
那到底多短合適呢?歷史上出現過幾個標準
- 一屏
- 100行
- 50行
- 20行 有人問我為什麽會差這麽多,我的回答是:以前的屏幕分辨率那麽低,一屏也就20-50行之間吧,所以以前一屏的說法也是合理的。 對於行數,行業沒有一個固定的標準。我所知道的Oracle建議是50行,Bob大叔的建議是20行。
代碼短小,好處自然很多。
- 單元測試覆蓋率高
- 每個函數一目了然,只做一件事
- 有利於函數中的代碼都在同一個抽象層級
只做一件事
函數應該做一件事。做好這件事。只做一件事。那麽如何判斷只做一件事? 請問這個函數做了幾件事?夥伴的答案是
1.判斷是否為測試頁面 2.加入測試數據 3.渲染頁面
你的答案是多少呢?其實答案是只做了一件事,主要是沒有看清 一件事 OR 一件事的多個步驟 ,關於這點,大家要好好體會。
另外一個判斷是否只做一件事的好方法: 是否能再次分離出新函數
同一個抽象層級
關於層級,比較難講明,直接看例子吧 再看一個版本 你會發現看第二個版本的代碼,明顯舒服很多。因為第二的版本的三句代碼都在同一個層級。而第一個版本的代碼中的第一句是設置roundView的某個屬性,但是最後一句卻是在設置bubbleView,層級不同(roundView與bubbleView才是同層級)
使用描述性名稱
如果長一點的名稱可以更加清晰,不要猶豫,用清晰的吧(註意是要有意義的)
calculate calculatePrice
相比起來calculatePrice就好很多。 再來看個例子
addComment addCommentAndReturnCount
你不是說長一點更清晰嗎,那addCommentAndReturnCount很好吧。 關於這點大家要註意,如果你 需要用and、or之類的介詞來修辭函數時,要考慮下你是否違背了單一職責原則
參數個數
0個最好, 1個次之, 2個還行, 3個以上不是太好了。 參數與函數名位於不同的抽象層級,它要求你必須了解目前並不特別重要的細節。 解決辦法有許多,比如某些場景可使用 DTO
嵌套層次、分支過多
嵌套、分支過多會讓代碼變得很難理解,解決的辦法有如下:
- 衛語句
- do-while,引入break
- if-else if-then
- 提取函數
- 以子類取代類型代碼
- 以多態取代條件式
- … 具體可根據項目特點選用
分割指令與查詢
set這個函數很不明確的是到底是設置成功了返回true,還是名字存在返回true,但真正的問題在於,它是個指令但是摻雜了查詢的功能。 將查詢和命令分離後,代碼便清晰很多了。
小結
如何寫出好的函數
- 先寫對的,再寫好的
- 對 =》 單元測試 =》識別壞味道 =》重構
註釋
“別給糟糕的代碼加註釋 — 重新寫吧。” –Brian & P.J. “註釋總是一種失敗” –Bob
用代碼來闡述
感受兩段代碼會發現 代碼即註釋 的美
壞註釋
先來看看什麽是壞的註釋
喃喃自語
這註釋絕對是給自己看的
多余的註釋
解釋跟沒解釋一樣,不如代碼來的簡單明了
誤導性的註釋
你在誤導吧
循規式註釋
這個一定要註意,循環式的註釋完全多余(除了做sdk、開源)
括號後的註釋
如果括號後需要註釋,只表明你這段代碼太長了,需要做的不是加註釋,而是將它變短。
歸屬於署名
Git、SVN知道是你提交的,不用這樣刷存在感
註釋掉代碼
註釋掉的代碼,只會讓修改你代碼的人蒙圈,如果你覺得這段代碼有可能以後會用,也不用擔心,Git、SVN會幫你找回來
信息過多
面向對象講究,暴露操作,隱藏實現,如果你還要註釋這些信息,表示你沒有封裝好。這些信息,可考慮放個鏈接或者其他的簡短提示,太長的註釋,別人懶得讀、也難讀懂
好註釋
看了那麽多壞註釋,來看看什麽是好的註釋
法律信息
提供信息
對意圖的註釋
闡釋
警示
TODO註釋
放大
對象、數據結構
數據抽象
將變量設置為私有(Private),主要是不想讓其他人依賴這些變量。所以,不要隨便給變量添加賦值方法和取值方法(set/get方法),這樣其實是把私有變量公之於眾。 隱藏變量和實現,並不是在變量與外界之間放一個函數層那麽簡單。隱藏關乎抽象。 類並不簡單地用賦值方法和取值方法將其變量推向外間,而是暴露抽象接口,以便用戶 無需了解數據的實現而能操作數據本體。 要以什麽方式呈現對象所包含的數據,需要做嚴肅的思考。隨便加賦值方法和取值方法,是最壞的選擇。
數據、對象的反對稱性
前者是一種過程式代碼,後者是面向對象式代碼。我們會發現假如要添加一個新形狀的話,後者絕對是不錯的選擇,因為以上代碼都不需要修改,只需寫一個新形狀類,這符合“開放–封閉”原則。然而假如添加一個計算周長的功能的話那就杯具了,因為這樣子每個形狀類都得改動。但是假如是用過程式代碼的話只需要添加一個新函數。
過程式代碼(使用數據結構的代碼)便於在不改動既有數據結構的前提下添加新函數。 面向對象代碼便於在不改動既有函數的前提下添加新類。 一切都是對象只是一個傳說
類
組織
- 公共靜態變量
- 私有靜態變量
- 私有實體變量
- 公共函數
- 私有函數 自頂向下原則 這裏為什麽沒有寫公有實體變量是因為,其不建議出現在代碼中。
短小
函數的短小標準是行數,那類是什麽呢?答案是 職責 類需要遵循單一職責原則
內聚
如以上代碼,內聚性高,除了size方法外,其他方法都使用了兩個實例變量。 內聚: 模塊內部各個元素彼此結合的緊密程度(類中方法和變量間的結合程度) 保持內聚會得到許多短小的類 當一個類喪失內聚性時我們應當拆分它
總結
Clean Code能幫助團隊構建代碼質量體系,有助於開發的各個環節(靜態分析、持續集成、Code Review…)。當然,對個人的能力提高也很有好處,建議大家都應該熟悉。等團隊Code Review一段時間後,有其他收獲的話,再給大家分享。 預祝大家國慶節快樂!
- ← Previous Post
Tags: 代碼 Code 命名 Clean 需要 註釋
文章來源: