程式碼審查“查”什麼?(5):SOLID原則

cucr發表於2015-12-13
這是程式碼審查“查”什麼系列第5篇。 檢視該系列以前的文章

在今天的文章中,我們將更多地關注程式碼本身的設計,專門檢查程式碼是否遵循了良好的物件導向設計實踐。與所有我們討論的其他方面一樣,並不是所有的團隊將它作為最高準則來優先檢查,但是如果你嘗試遵循SOLID原則,或嘗試將你的程式碼往這個方向上努力,這裡的一些建議可能會對你有所幫助。

什麼是SOLID?

SOLID原則 是物件導向程式設計和程式設計的五個核心原則。這篇文章的目的並不是教會你這些原則是什麼或者深入探討為什麼要遵循它們,而是指出那些程式碼評審中發現的程式碼異味,它們可能是沒有遵循這些原則的結果。

SOLID代表:

單一職責原則(SRP)

不應該有多種情況需要修改某個類的物件。

僅僅通過程式碼評審有時會很難發現上述情況。根據定義,作者是(或應該是)由一個獨立的要求去改變程式碼庫——修復一個bug,新增一個新功能,或一次專門的重構。

你應該看看在一個類中哪些方法在同一時間會改變,以及哪些方法幾乎是不可能受其他方法變化影響而被改變。例如:

上面Upsource工具排展示的diff中,一個新功能被新增到TweetMonitor,能夠基於user介面的某種排序畫出Tweeter的十大排行榜。雖然這似乎是合理的, 因為它使用了onMessage方法收集的資料,但有跡象顯示這違反了SRP。onMessage和 getTweetMessageFromFullTweet方法都是和接收或解析一個Twitter訊息相關,而draw用來組織資料並在UI上顯示。

程式碼審查者應該標記這兩個職責,然後和作者研究出一個分離這些特性的更好方法:也許通過移動Twitter字串解析方法到不同的類,或者建立一個不同的類負責來渲染排行榜。

開放封閉原則(OCP)

軟體實體應該對擴充套件開放,對修改封閉。

作為一個程式碼審查者,如果你看到一系列的if語句檢查物件的特殊型別,可能這就是違反該原則的跡象:

如果你審查上面的程式碼應該很清楚,當一個新的Event型別新增到系統,新建立的Event型別可能要新增另一個else到這個方法中來處理它。

最好使用多型來移除這個if:

像往常一樣,這個問題的解決方案不止一個,但關鍵是把複雜的if/else和instanceof判斷移除。

里氏替換原則(LSP)

函式使用基類的引用,必須能夠在不知不覺的情況下使用派生類的物件。

發現違反這一原則的一個簡單的方法是尋找顯示的型別轉換。如果你不得不把一個物件轉換為某種型別,你就沒有做到不知不覺地使用繼承了基類的子類物件。

檢查以下兩條時可以找到更細微的衝突:

例如,想象我們有一個抽象類Order和一些子類——BookOrder、ElectronicsOrder等等。placeOrder方法可能需要一個Warehouse,可以用這個方法來改變倉庫中的實物產品的庫存水平:

現在想象一下,我們引入電子禮品卡,只需簡單地為錢包新增餘額,但不需要物理盤點。如果實現為GiftCardOrder,placeOrder方法就不必使用warehouse引數:

這可能在邏輯上看起來像繼承,但事實上你可以說使用GiftCardOrder應該期望這個類和其他類有類似的行為,例如,你應該期望這個引數傳遞給所有子類:

但這不會傳遞,作為GiftCardOrders有不同型別的order行為。如果你審查這樣的程式碼,應該質疑這裡繼承的使用,也許order行為可以通過使用組合替代繼承的方式插入。

介面分離原則(ISP)

多個客戶端特定的介面比使用一個通用的介面要好。

可以很容易識別某些程式碼違反這一原則,因為介面上具有很多方法。這一原則與SRP呼應,正如你可能看到一個介面具有許多方法,實際上它是負責了多個功能。

但有時甚至一個介面只有兩個方法也可以拆分成兩個介面:

在這個例子中,有些時候可能不需要decode方法,並且一個編解碼器根據使用要求可能被視為一個編碼器或解碼器,把SimpleCodec介面分割成一個編碼器和解碼器可能會更好。一些類可以選擇兩個都實現,但不必去過載不需要的方法,或者某些類只需要編碼器,同時他們的編碼器例項也實現解碼。

依賴倒置原則(DIP)

依賴抽象。不要依賴於具體實現。

雖然想要在試圖尋找違反該原則的簡單例子時,類似到處使用new關鍵字(而不是使用依賴注入工廠模式)和濫用集合型別(例如宣告ArrayList變數或引數而不是List),作為一個程式碼審查者你在程式碼審查時,應該確保程式碼作者已經使用或建立了正確的抽象。

例如,服務級的程式碼直接連線到一個資料庫讀寫資料:

這段程式碼依賴於大量的具體的實現細節:連線到一個(關係)資料庫的JDBC基於具體資料庫的SQL,需要知道資料庫結構,等等。這段程式碼來自你的系統的某處,但是不應該出現在這裡這裡還有其他方法不需要知道具體的資料庫。更好的方式是,提取出一個DAO或使用Repository模式,將DAO或repository注入到這個服務中。

總結

一些程式碼異味可能意味著可能已經違反了一個或多個SOLID原則:

  • 很長的if/else語句
  • 強制轉換成一個子型別
  • 很多公共方法
  • 實現的方法丟擲UnsupportedOperationException異常

與所有的設計問題一樣,在遵循這些原則和故意迴避這些原則間需要找到一個平衡,這個平衡取決於你的團隊的喜好。但是如果你在程式碼審查中看到複雜的程式碼,會發現應用這些原則將提供一個更簡單、更容易理解的解決方案。

相關文章