複雜度經常是備受爭議的品質特性,因為不同的人對「複雜」的感受是不一樣的。那要如何客觀的評估複雜度?有許多的研究文獻各自由不同的角度定義出計算複雜度的公式。
常用的方式就是計算一段程式碼當中的可能路徑數量,基本的想法是,路徑越多表示程式碼越複雜,我們可以透過計算邏輯分支點的數量來判斷路徑數量。在計算分支時,有所謂的分支覆蓋(Branch Converage),還需要考慮分支的組合效果,也就是執行路徑(execution paths)。看起來就很複雜,也不知道辛苦地計算出來這些數值後,對我們的可維護性是否有幫助。
所以書中就採用的一個簡單又有效的計算方式:循環複雜度(cyclomatic complexity),亦稱McCabe複雜度。計算方式就是將分支點數量加 1。也可參考這篇文章:David Ko的學習之旅-Cyclomatic Complexity。
所以將指導方針說白一點,就是在一個method中,邏輯分支(if
, while
, switch
...)的數量不要超過4個。
動機
高複雜度的程式碼單元(method)通常很難理解,因此很難修改。
如何遵守此指導方針
在C#中,下面陳述式與運算子都算分支點:
- if
- case
- ? , ??
- &&, ||
- while
- for, foreach
- catch
辨識出method中是否有過多的分支點,如果有的話,一個方式是將其拆分成多個method。不過,還是需要注意到不要因為需要拆分method出去,反而影響到邏輯合理性與程式可讀性。還有一個處理的方向,就是想辦法減少McCabe複雜度。例如有一個Switch
述句,可以試著使用物件導向的多型的方式,取代條件判斷。也就是重構方法中的Replace Conditional with Polymorphism
。參考:REFACTORING GURU-Replace Conditional with Polymorphism
不過,這種做法的缺點是:導致更多程式碼散佈在更多類別中。開發者必須在擴展性與簡潔性之間做取捨。
試著以讓程式碼更容易理解、更容易測試為目標,進行降低程式碼複雜度的依據。至於要怎麼做,書中並沒有特定的方法,還需要靠開發者自己的經驗,以及對需求的掌握度。
常見的反對意見
試著反向思考這個問題
高複雜度無可避免
「我們的知識領域非常複雜,所以程式碼的複雜度必定很高」
作者反對這樣的解讀,複雜的知識領域未必需要複雜的技術性時做。事實上,身為開發者,你的責任就是簡化問題,並且撰寫單純的程式碼。
不可否認,領域越複雜,開發人員就必須花費更多精力來建構簡單的技術方案。作者認為,解決及妥善控制複雜業務問題的唯一途徑,就是透過簡單的程式碼。
拆分方法不會降低複雜度
「將1個McCabe 15的方法替換成3個McCabe 5的方法,意味著整體的McCabe還是 15,所以根本沒有甚麼好處」
當然,僅將一個方法拆分為幾個新方法,並不會降低整體McCabe複雜度。但它會變得更容易測試、更容易理解,這是McCabe複雜度無法衡量的。