代碼重構之法——方法重構分析
Intro#
想要寫出比較優秀的代碼,需要時刻警惕代碼中的壞味道,今天想寫一篇文章介紹一下如何分析你的方法是不是需要考慮重構
一個方法通常有三個部分組成,輸入(Input),輸出(Output),方法體(Method Body),我們就從這三個方面來分析一個方法是否該考慮重構
Input#
方法輸入也就是方法的參數,通常來說一個方法的參數基本可以控制在7個以內(僅作參考,可以自己衡量,SonarQube
默認方法最多七個參數),如果你的方法參數過多的話,可能就需要考慮重構一個方法參數了,通常的做法是封裝一個獨立的 model,參數作為 model 的屬性。
舉一個常見的例子,比如一個新聞列表的API,起初可能很簡單,就只需要一個 lastId
,一個 count
兩個參數,但是隨着業務需求的增加,可能會增加很多別的參數,比如前端提供一個 keyword 進行全文檢索,提供一個 sortBy 進行排序,根據新聞標題匹配,作者名稱匹配,分類匹配,根據發布時間篩選等等,最后可能會導致這個方法的參數有很多
通常我會新增一個 XxxRequest
的 model,然后方法參數替換成這個 model,然后指定 [FromQuery]
就可以了,可以對比一個修改前后的差異,是不是后面的方式更清爽一些呢
Task<IActionResult> List(int lastId, int count, string title, string author, string keyword, int categoryId, string sortBy, DateTime? beginTime, DateTime? endTime)
Task<IActionResult> List([FromQuery]NewsListQueryRequest request)
Output#
Output 就是方法的返回值,盡可能返回具體的類型,盡可能避免使用 Tuple
等類型,方法的返回值應該具有明確的意義
使用具體的 Model 代替 Tuple 返回值,尤其是一些 public 的,要被外部訪問的方法更應該返回具體的類型,雖然 C# 7.2 開始支持了 named tuple,會比之前友好很多,支持給 tuple 指定名稱,但是這只是編譯器級別的,實際還是 Item1,Item2 ...,還是比較推薦使用具體的 model,更加明了
Body#
通常一個方法不要太長,曾經在群里看到群友吐槽一個方法兩千多行,這樣的方法維護起來簡直就是災難,不要讓一個方法太長,保持方體體的簡單,一些通用的邏輯通過 Filter 或結合 AOP 來實現
Sonar 有一個分析方法復雜度的一個方法,官方稱之為 Cognitive Complexity
簡單介紹一下,代碼里的 if
/switch
/for
/foreach
/try
...catch
/while
都會增加方法的復雜度,出現一層嵌套則復雜度再加1, Sonar 默認的一個方法的復雜度不能超過 15
來幾個簡單的示例:
下面這個方法的復雜度是 3,有三個 if(else) 分支
void Method1(int num) {
if(num > 0)
{
}
else if(num <0)
{
}
else
{
}
}
下面這個方法的復雜度是 3,foreach
帶來了 1 的復雜度,if
也是1的復雜度,但是因為 if
是嵌套在 foreach
內部的,一層嵌套會導致復雜度增加1
void Method1(int[] nums) {
foreach(var num in nums)
{
if(num > 0)
{
}
}
}
下面這個方法的復雜度在上面的基礎上增加了兩個 catch
,這使得復雜度會加 2,從而變成 5
void Method1(int[] nums) {
try
{
foreach(var num in nums)
{
if(num > 0)
{
}
}
}
catch(InvalidOperationException e)
{
}
catch(Exception e)
{
}
}
更多示例可以參考官方介紹: https://www.sonarsource.com/docs/CognitiveComplexity.pdf
Reduce Complexity#
前面我們介紹了一些復雜度的分析,如何能夠切實有效的降低方法的復雜度呢:
- 方法參數不宜過多,參數過多考慮重構輸入參數,通常可以新建一個 model 來管理輸入參數
- 方法返回值不宜使用意義不明的返回值,盡量不用
Tuple
作為返回值 - 方法的行數不要太多,利用新語法減少行數,減少
if
判斷,比如使用 null 傳播符代替一系列的if
,list?.FirstOrDefault()?.Name
,a ??="test"
- 多個方法的相同邏輯使用切面邏輯處理,比如每個方法里都有
try...catch
,那我們就可以使用一個復雜 try...catch 的切面邏輯,如果是 mvc/webapi 也可以借助ExceptionFilter
來實現 - 參數校驗使用微軟的 ModelValidator 或者使用 FluentValidation 進行校驗,在代碼里盡量避免使用大量的
if
判斷導致復雜度的增加 - 仔細 review 代碼,有些邏輯是否合並在一起,避免在多個
if
里嵌套相同遍歷邏輯 - and more...(等你來補充
More#
除了自己主動感知方法的復雜度之外,我們也可以借助一些第三方的靜態代碼分析工具來分析我們的代碼,從而獲得一些修改建議進而保證代碼的高質量。
SonarQube 是目前使用較多的工具,可以方便的和 CI 集成,有一個 SonarCloud 網站提供雲服務,可以輕松為你的開源項目集成靜態代碼分析,有興趣可以看看,地址是 https://sonarcloud.io/,之前還用過 codacy
,似乎不太流行,推薦 SonarQube
Reference#
==