Categories
程式開發

如何提高代碼質量


說起代碼質量,腦子裡會冒出很多詞,命名規範、格式規範、日誌規範、單元測試覆蓋率…
但我覺得,代碼質量總結起來就兩個:好看和好用。
好看是指代碼可讀性好,容易理解、容易維護,別人接手了不罵你;好用則指代碼健壯,不容易出錯,機器跑著不罵你。 即使出錯,也容易定位,容易止損和恢復。

為何需要提高代碼質量?

以下是我認為的幾點:

  • 提升代碼的可維護性,降低新人接手的成本
  • 促進交流,促進知識共享,做好backup
  • 促進風格一致,降低團隊間應用流轉的難度
  • 建設寫好代碼、做好設計的團隊氛圍

但有一點需要說明,我認為寫代碼本身是一個創造過程,能讓人享受其中,如果有太多的條條框框約束,寫代碼就失去了創造的樂趣,所以,這里為代碼質量建設立一個原則:

  • 只提供建議,不強制遵循
  • 鼓勵創造性的編碼
  • 鼓勵藝術性的編碼

如何才能擁有高質量的代碼

有兩種途徑:

  • 第一種途徑:先有好的設計—>然後用優秀的編碼去實現—>再把優秀的編碼風格延續下去
  • 第二種途徑:從糟糕的代碼開始—>不斷去重構,向優秀的設計方案和代碼風格不斷逼近—>再延續下去

代碼質量建設怎麼開始呢?

首先得知道什麼是好的代碼, 這就要有標準,那就是我們常常看到的各種各樣的規範,但我覺得要有幾個簡單的原則,太多了,記不住,有幾條原則簡單的原則,可以時不時拿來判斷,當前做得對不對。

然後就是去實踐規範,這裡需要一些技巧、一些工具,來幫助我們更好地遵循規範。

接著是度量, 看我們對規範實踐的效果,這就是我們常說也常做的Code Review,但Code Review也需要遵循一定的規範,應用一定的技巧。

度量之後是改進, CR結果要及時跟進,這是最重要一環,否則CR就沒有實際意義。

總結不可少,复盤是一種很有用的工具,CR也需要復盤,總結CR流程、過程等方面好的和不好的地方,更新規範和checklist。

接下來我們分別聊一聊各個步驟。

規範: 先知道什麼是好代碼

從上邊高質量代碼的誕生途徑我們可以看出,設計也是很重要的一環,所以我們的規範包括設計規範和編碼規範,結合我們的生產實際,這裡加上安全生產的規範,所以規範有3部分:設計、編碼、安全生產。

設計: 先有優秀的方案

設計推薦多用圖表達,圖比文字有更直觀的傳達能力:

首先是業務流程圖, 它能快速構建起我們對業務的認知,帶著對業務的理解再來看代碼,事半功倍。

然後是用例圖, 清晰地表達出我們系統的職責、邊界、服務對象,結合業務流程圖,能快速構建起我們對系統職責的認知。

接著是架構圖, 從我們日常的設計需求來看,架構圖是需要的。 好的架構圖能快速給人搭建起理解的框架,再來看系統的細節部分,就很好理解。 架構圖推薦C4 規範,它是我目前接觸的表達最清晰的架構圖規範。

接著再用時序圖、狀態圖、ER圖 等把關鍵和復雜部分的設計表達出來。

但日常我們的需求有大有小,方案也不需要都遵循統一的範本,為了設計而設計,就徒增加工作量了。 以按需為第一原則,能把要做啥,怎麼做的表達清楚即可。 這裡按場景推薦各個圖的使用場景:

新建應用/對原有應用進行重大修改/複雜項目

  • 業務流程圖(交代業務背景)
  • C4的系統上下文、容器、組件這3張圖
  • 用例圖:有多個外部參與者
  • 類圖:關鍵模型超過5個
  • 狀態圖:對象狀態超過3個
  • 時序圖:關鍵流程或複雜鏈路的參與對象超過3個
  • ER圖:涉及數據庫變更(包含數據表結構文檔)

一般項目/重大日常

  • 業務流程圖
  • 時序圖(複雜功能、關鍵流程)

日常

  • 按需

編碼: 優秀的方案需要優秀的編碼

編碼最重要的是可讀,控制複雜度,做到自解釋,能讓人像讀自然語言一樣讀自己的代碼,這是最高境界,也是神仙境界。 然後是可維護性和可變更性,能快速、安全地修改代碼是目標。 最後是對優雅實現的要求,卓越的代碼會讓人拍著大腿叫好,這個不稀奇,我們亂糟糟的代碼裡也偶爾會有閃光的片段。

編碼最高原則:

可讀性

  • 控制複雜度
  • 自我證明

可維護性

優雅

✎ 分層規範

合理的代碼分層,能控制各層的複雜度,以分層的思路去設計,也能提高代碼的複用性。 對於分層,我認為熟悉的就是好的,能滿足工作中的大部分情況就好,這裡不談六邊形架構、清晰架構、DODAF等概念,自己駕馭不了,還不能拿出來吹。 我推薦DDD最基礎的4層分層架構,如下:

用户界面/接口层
    ⇩
   应用层
    ⇩
   领域层
    ⇩
 基础设施层

這裡舉個我實際項目中用到的例子:

-- bootstrap
    -- BeanConfig
-- application
    -- pv
        -- ChannelPvApplicationService
    -- sns
-- domain
    -- abtest
        -- AbtestService
    -- address
    -- coupon
        -- entity
            -- Coupon
            -- CouponStatus
            -- CategoryCouponTemplate
    -- category
    -- user
        -- UserRepository
        -- service
            -- OneIdService
            -- UserService
    -- item
        -- ItemRepostory
    -- live
        -- LiveStatus
-- infrastructure
    -- concurrent
        -- ThreadPoolExecutorFactory
        -- MonitorableCallerRunsPolicy
    -- dal
        -- IGraphDal
        -- TuringDal
        -- DefaultUserRepository
    -- dao
        -- MybatisItemDao
    -- util
        -- DateUtil
        -- MoneyUtil
        -- UriUtil
    -- monitor
        -- Event
        -- Timing
        -- TimingAspect
        -- TimingEvent
        -- Monitors
-- view
    -- atomicwidget
        -- BannerWidget
        -- CrazySubsidyWidget
        -- FeedItemsWidget
        -- NavigateBarWidget
        -- LiveWidget
    -- page
        -- HomeScreenPage
        -- CategoryFeedsPage
        -- SearchCardPage
    -- widget
        -- Widget
        -- DispatchableWidget
        -- Debuggable
        -- AbstractWidget
        -- AbstractDispatchableWidget
        -- WidgetDispatcher
        -- WidgetResult
        -- WidgetContextIncompatibleException

上述項目結構中,因為是導購項目,view相當於用戶界面層,application是應用層,domain是領域層,infrastructure是基礎設施層。

再對包的劃分說明一下:

  • 領域對象、值對象、DTO、Service等定義都放在子域的包下,不要有大而全的entity、service、impl等包(這裡的子域是一個內聚的邏輯概念,對應的是領域設計裡的子域,如上例中的item在我們的導購裡就是商品這個子域)
  • 常量定義盡量跟著相關的類走,作為類的靜態字段,不要有大而全的Constant類(Switch相關的除外,但也要按職責盡量拆分開關類)
✎ 代碼規範

代碼規範就推薦阿里經濟體開發規約,很全面,也是阿里同學的基本要求。 代碼規範就推薦「阿里經濟體開發規約」,很全面,也是阿里同學的基本要求,開源版本:阿里巴巴java開發手冊 https://github.com/alibaba/p3c

結合自己的經驗,重點說幾點:

命名

  • 命名不用泛稱(反例:processData)
  • 盡量用完整的單詞描述清楚作用和意圖,不要怕字多
  • 對像後綴領域對像不帶後綴DTO:RPC接口提供的對像以作為VO:跟前端交互的對象PO:跟數據庫直接交互的對象

日誌

  • 所有後台都要有操作日誌、數據變更日誌
  • 日誌要配置異步寫盤
  • 線上僅保留WARN和ERROR級別日誌
  • 所有日誌都要有traceId
  • 異常日誌要有堆棧、入參、能說清楚是什麼錯誤的信息(可以出統一組件)
  • 打印日誌時,禁止直接用JSON工具將對象轉換成String

異常

  • 怎麼拋:盡量使用非受檢異常,提高代碼可讀性
  • 怎麼處理:統一異常用切面處理,或依賴SpringMvc的ControllerAdvice統一處理
  • 異常catch範圍盡量小,分清穩定代碼和非穩定代碼
  • 禁止直接吞掉異常
  • 時刻警惕NPE,多用Optional處理

註釋

  • 註釋只為了說明為什麼這麼做,不用來說明是在做什麼

面向對象

  • 遵循原則:SRP/OCP/LSP/ISP/DIP
  • 盡量只暴露行為,不暴露數據
  • 慎用繼承,優先使用組合方式

其它規範

  • 方法行數保持在一屏之內(30行以內)
  • 代碼提交commit message一定要講清楚做了啥控制每次提交的代碼量(一個功能一提交)
  • 參數盡量用不可變對象(不對入參做修改,保持明確的入參和出參)盡量不用隱式入參(ThreadLocal)
  • 數據結構無隨機讀取時,用LinkedList替代ArrayList
  • 風格做好分層,同層用統一的風格(設計/編碼)

安全生產

安全生產還沒有系統總結過,結合自己做穩定性的工作經驗提幾點,後邊跟負責安全生產的同學多學習學習,再來更新:

防資損

  • 要有資損評估/監控

易恢復

  • 任何新功能上線都要有灰度能力

監控/報警

  • 兜底設計/監控
  • 性能監控
  • 異常監控
  • 低容忍錯誤要報警
  • 關鍵指標要監控(業務/技術)
  • 減少不必要的報警

降級/限流

  • 識別出弱依賴,保證弱依賴可降級
  • 識別出可限流的依賴方,做好監控和限流配置

實踐: 如何去實踐規範+

給一些原則和技巧建議,幫忙落地規範。

設計

  • 圖都不是必須的,只要能講明白是怎麼做的,為什麼這麼做

編碼

  • 使用Aone-Idea,它已經集成了PMD/FindBugs/CheckStyle功能,給開發的同學點個暫,超牛逼。 開源版本:Alibaba Java Coding Guidelines alibaba https://plugins.jetbrains.com/plugin/10046-alibaba-java-coding-guidelines
  • 使用lombok,保持代碼的簡潔性
  • 不斷重構,且遵循以下原則: DRY/YAGNI/Rule Of Three/KISS/POLA每次需求都是重構契機反問自己,能不能在[可读性/易维护性]做得更好
  • 使代碼讀起來像自然語言
  • 功能性代碼和非功能性代碼分離

安全生產

這得跟著公司和部門規範來,學習學習再來補充。

度量: 如何去驗證實踐效果-CodeReview

Review時機

  • 項目提測後第一時間:不要在項目上線的前夜review,來不及改,review結果容易擱置,浪費參與人的青春

Review方式

  • 小模塊:隨時/Aone代碼評審/@backup同學
  • 項目代碼:面對面投屏/Aone代碼評審+ IDE show/項目組+重點關注同學

Review內容

  • 關注代碼的設計是如何落地需求的
  • 總體流程
  • 關鍵設計
  • 重點功能

Review前提

  • 代碼是編譯通過的
  • 開啟Aone-Idea的實時檢測

檢查清單

  • 規範性

可讀性

可維護性(高內聚低耦合、面向對象原則、實現複雜性等)

可變更性(擴展性等)

  • 安全性/健壯性輸入檢查異常處理邊界檢查
  • 性能依賴合理性

改進: 跟踪CodeReview結果的執行

  • 有運行時風險的問題必須上線前完成改動
  • 其它問題盡量上線前完成修改,如未修改則要加todo,指定人和時間來修改
  • 用工具來統計和提示CR結果改進情況

總結優化

  • 定期回顧和總結(週會環節)
  • 更新checklist和代碼規範
  • 發現好的代碼和設計,定期展示,給與獎勵

後話

以上是摘取的各家之言,加上自己的一些思考。 學習是個漸進過程,代碼質量的學習我還在進行中,如果有收穫,會來更新。 如果被人diss,我還覺得有理,我也會更新進來。

寫的這些也是我自己的學習和實踐方向,所以,如果發現我的代碼沒做到這些,吐槽我,然後給我建議,讓我做得更好。

本文轉載自公眾號淘系技術(ID:AlibabaMTT)。

原文鏈接

如何提高代碼質量