這篇算是 一堂如何提高代碼質量的培訓課 的讀后感。作者網名fangang. 這系列文章涵蓋很多內容,包括設計模式,DDD,代碼審核等等。這些話題大于“代碼質量”。更像是“提高軟件質量的培訓課”。培訓課本身就是高質量的,贊!網上有不少轉載,比如http://www.tech-q.cn/thread-3831-1-1.html。贊同作者的很多觀點,也收益不少。總結一下:
[fangang]我們評價高質量代碼有三要素:可讀性、可維護性、可變更性。
[vcycyv]同意這三個方面。我總這樣想,軟件的核心是可維護性。可維護性主要就體現在可讀性和擴展性。
[fangang](可讀性)不要編寫大段的代碼
[vcycyv]Martin Fowler管"從大段代碼抽取獨立的方法"叫做"extract method",這是常見而重要的重構手段。在他的refactor書里從技術層面做了詳細的闡述。分解的過程中,比較容易形成技術障礙的local variable. 引一段書里的話,專門討論這個話題的:
? Copy the extracted code from the source method into the new target method.
? Scan the extracted code for references to any variables that are local in scope to the
source method. These are local variables and parameters to the method.
? See whether any temporary variables are used only within this extracted code. If so,
declare them in the target method as temporary variables.
? Look to see whether any of these local-scope variables are modified by the extracted
code. If one variable is modified, see whether you can treat the extracted code as a query
and assign the result to the variable concerned. If this is awkward, or if there is more than
one such variable, you can't extract the method as it stands. You may need to use Split
Temporary Variable and try again. You can eliminate temporary variables with
Replace Temp with Query (see the discussion in the examples).
? Pass into the target method as parameters local-scope variables that are read from the
extracted code.
? Compile when you have dealt with all the locally-scoped variables.
? Replace the extracted code in the source method with a call to the target method.
? Compile and test.
前兩年看到一個討論的很火的話題,說任意復雜的產品,都能break into一系列的小方法,每個方法行數不超過5行。當然沒必要這么狠,但如果你真這么狠,我倒覺得沒啥明顯的壞處。
"而一些更有經驗的程序員會采用另外一種從上往下的編寫方式。當他們在編寫程序的時候,每個被分出去的程序,可以暫時只寫一個空程序而不去具體實現功能。當主程序完成以后,再一個個實現它的所有子程序。采用這樣的編寫方式,可以使復雜程序有更好的規劃,避免只見樹木不見森林的弊病。"這也是一個好的實踐。
提到這種自上而下的編程方法,順便推薦 《斯坦福大學開放課程:編程方法》。這是一系列視頻教程。google一下有很多地方可以下載或在線觀看,比如優酷http://v.youku.com/v_show/id_XMjE4NzQ0NDI0.html 在視頻教程里教授也演示了自上而下的編程方法。 這是入門級的教程,本來想通過它練練聽力,后來發現這課程根本就是喜劇片,看的過程一直狂笑,教授特逗。
跟可讀性相關的一個話題就是注釋。近年來一個廣泛被接受的做法是,盡量通過 避免大段代碼 以及良好的變量命名達到可讀性。注釋在這個過程中被淡化了。我基本同意這種做法,程序結構清晰,自解釋的變量名都是重要的實踐。變量名盡量不要寫縮寫,如果寫縮寫的話,一定要有縮寫字典有據可查。看spring的代碼,可以發現很多類名或方法名在30個字符以上。大段注釋往往暗示程序結構的不合理。不鼓勵寫過多注釋的結果就是,剩下的注釋都是重要不可或缺的,注釋是程序的一部分,需要和程序保持一致,也需要被維護。另一個基本而重要的原則是,要把public package protected private方法按照順序寫。更重要的、可訪問性更高的方法放前面,盡管eclipse里可以方便地在視圖過濾非public方法。
[fangang](可維護性)但是,事實卻不一樣,對機關級次計算的代碼遍布整個項目,甚至有些還寫入到了那些復雜的SQL語句中。
[vcycyv] Domain Driven Disign強調,domain的邏輯要圈在一定邊界中。往上不能爬到service, 甚至UI層。往下不能滲透到repository以下,比如sql語句里。 Domain Driven Design的書在這里下載。更多電子書可以看之前寫的博客 分享十二本經典電子書
[fangang]將專用代碼提升為通用代碼
[vcycyv] 這個過程大家都會遇到。什么代碼算通用代碼并不容易界定。過多的通用代碼淹沒了真正有通用意義的方法,不足的通用代碼容易造成代碼重復。這個過程有這么幾個方面需要注意。
一,避免重復造輪子。 要對JDK常用的API以及經典第三方的軟件包有一定了解。舉兩個例子:古典的singleton在JDK1.5之后可以用單一元素的enum表示,而且不用考慮serializable的readresolve問題; apache commons里的io, lang等包提供了很多常用的API。我在我們的產品里,用到io的FileNameUtils和ExceptionUtils的一些方法。包括取文件后綴名,獲取exception的root cause以及excepiton stack的字符串。 要盡量重用可靠的第三方代碼, 原因是你自己寫的代碼多半不如Sun和apache的人好。更重要的是,你自己寫的每一行code都要自己維護。代碼行數本身就是代碼維護量的一個因素。最近一兩年瘋狂地喜歡刪除代碼,估計再發展下去我就要被刪除了。
二, 還是domain為先。當你猶豫一段比較“通用”的代碼應該放在module里還是拿出來的時候,盡量還是保留在module里吧。因為如果你猶豫來,猶豫去之后,決定它是global的通用代碼,下一個人非常可能在module里找不到,就認為沒有這種code,自己又寫一遍。太激進的寫一堆通用代碼,一旦組織不好,就沒人愛去從通用代碼里尋寶了。關于這一點,martin大叔有近似的闡述, 在Patterns of Enterprise Application Architecture里
A common concern with domain logic is bloated domain objects. As you build a screen to manipulate orders you'll notice that some of the order behavior is only needed only for it. If you put these responsibilities on the order, the risk is that the Order class will become too big because it's full of responsibilities that are only used in a single use case. This concern leads people to consider whether some responsibility is general, in which case it should sit in the order class, or specific, in which case it should sit in some usage-specific class, which might be a Transaction Script (110) or perhaps the presentation itself.
The problem with separating usage-specific behavior is that it can lead to duplication. Behavior that's separated from the order is harder to find, so people tend to not see it and duplicate it instead. Duplication can quickly lead to more complexity and inconsistency, but I've found that bloating occurs much less frequently than predicted. If it does occur, it's relatively easy to see and not difficult to fix. My advice is not to separate usage-specific behavior. Put it all in the object that's the natural fit. Fix the bloating when, and if, it becomes a problem.
三、通用代碼一定要有整體設計。如果調用通用代碼的那些invoker不完全在你控制的范圍,比如你寫的程序有其他項目組在調用,那么你修改API的簽名一定會被罵死的。所以通用代碼要思前想后design清楚了才release.結構清楚的通用代碼也比較方便以后查找。好的程序總是分層的,通用代碼要盡量往底層放。
[fangang]一個快速提高軟件質量的捷徑就是利用設計模式。
[vcycyv]設計模式除了是一種工具以外,學習、思考設計模式能擴展coding的思維方式。自己也總結了一篇博客 串講23種設計模式 。
[fangang]拿一個員工工資系統來說吧。當人力資源在發放一個月工資的時候,以及離職的員工肯定不能再發放工資了。在系統設計的期初,開發人員商量好,在員工信息中設定一個“離職標志”字段。編寫工資發放的開發人員通過查詢,將“離職標志”為false的員工查詢出來,并為他們計算和發放工資。但是,隨著這個系統的不斷使用,編寫員工管理的開發人員發現,“離職標志”字段已經不能滿足客戶的需求,因而將“離職標志”字段廢棄,并增加了一個“離職時間”字段來管理離職的員工。然而,編寫工資發放的開發人員并不知道這樣的變更,依然使用著“離職標志”字段。顯然,這樣的結果就是,軟件系統開始對離職員工發放工資了。仔細分析這個問題的原因,我們不難發現,確認員工是否離職,并不是“發放工資”軟件類應當完成的工作,而應當是“員工管理”軟件類應當完成的。如果將“獲取非離職員工”的任務交給“員工管理”軟件類,而“發放工資”軟件類僅僅只是去調用,那么離職功能由“離職標志”字段改為了“離職時間”字段,其實就與“發放工資”軟件類毫無關系。而作為“員工管理”的開發人員,一旦發生這樣的變更,他當然知道去修改自己相應的“獲取非離職員工”函數,這樣就不會發生以上問題。
[vcycyv]哈哈,我喜歡這個例子。DDD的核心思想就是這個。當然DDD的理論還有一些具體的實踐。Eric Evan在他的DDD書里講了一個cargo的例子,當時我還找了一下代碼,沒找到,從fangang的博客評論里居然發現哪里能找到代碼了,http://dddsample.sourceforge.net/, 還沒來得及具體看。以后有機會深入了解一下。
[fangang](代碼審查) 被審查后的代碼如果還出現缺陷,審查者應當負有責任
[vcycyv] 這個系列的最后一篇文章主題是代碼審查。作者所說的最佳實踐很有道理,但不是在任何環境下可操作性都強的。我不知道最佳實踐是什么,但我覺得有一些問題是值得注意的。
一、代碼審查的范圍要小,最好是one-one的,跟績效沒有關系,范圍小可以保證審查不會太傷“面子”,否則會影響團結氛圍。
二、審查者在指出錯誤的同時,一定要說如何修正問題。我非常非常反感那種善于用大道理指出別人錯誤,自己卻把錯誤犯得更夸張的那種人。如果你自己沒有更好的解決方案,就別說那是問題。
三、代碼審查制度的推行以良好的團隊氛圍為前提。大家都是為了產品著想,這樣代碼審查才不至于扯皮。
四、關于審查者的責任。我同意fangang說的"被審查后的代碼如果還出現缺陷,審查者應當負有責任", 但是誰來追究責任,如何界定責任,怎么追究責任呢?呵呵,這個恐怕不好操作。代碼審查顯然不能杜絕defect, 但是明顯的設計,編碼錯誤是應該看到的,但怎么又算"明顯"呢。軟件本身不像數學那樣嚴格,容易界定問題。紀律在軟件行業作用有限,團隊氛圍比紀律重要。但怎么營造好的團隊氛圍又是一個太寬泛的問題。