小酌重構系列[4]——分解方法
概述
“分解方法”的思想和前面講到的“提取方法”、“提取方法對象”基本一致。
它是將較大個體的方法不斷的拆分,讓每個“方法”做單一的事情,從而提高每個方法的可讀性和可維護性。
分解方法可以看做是“提取方法”的遞歸版本,它是對方法反復提煉的一種重構策略。
分解方法
下圖表示了這個重構策略,第1次提煉和第2次提煉都采用了“提取方法”這個策略。
何時分解方法?
“分解方法”最終可以讓方法的可讀性極大地增強,通常我們可以依據以下幾點來辨別方法是否需要分解:
2. 方法應該盡量短小,方法最好不要超過20行(依不同情況,酌情考慮行數)
3. 方法的縮進層次不宜太多,最好不要超過兩級
4. 方法需要太多的注釋才能理解
示例
場景說明
假設在企業制作年度預算的場景中,用戶需要按照如下Excel模板填寫科目、部門、各月的預算數據,然后將Excel文件導入到“預算系統”。
為了表示用戶填寫的每一行預算數據,開發人員在系統中設計了兩個class:BudgetItem(預算項)和BudgetItemDetail(預算項明細)。
上圖紅色方框標注的表示一個BudgetItem對象,每個藍色方框則對應一個BudgetItemDetail對象。
/// <summary>
/// 預算項
/// </summary>
public class BudgetItem
{
public string Dept { get; set; }
public string Account { get; set; }
public IList<BudgetItemDetail> BudgetItemDetails { get; set; }
}
/// <summary>
/// 預算項明細
/// </summary>
public class BudgetItemDetail
{
public string Month { get; set; }
public decimal Amount { get; set; }
}
重構前
在表示這段邏輯時,我們編寫了一個BudgetItemImport類,用于讀取Excel并返回IList<BudgetItem>集合
public class BudgetItemImport
{
private Regex _monthRegex = new Regex(@"\d{4}\\\d{2}");
public IList<BudgetItem> GetBudgetItems(string path)
{
// 讀取Excel獲取DataTable
DataTable table = ExcelUtil.RenderFromExcel(path);
// 獲取表示月份的列名
IList<string> monthColumns = new List<string>();
for (var i = 0; i < table.Columns.Count; i++)
{
var columnName = table.Columns[i].ColumnName;
if (_monthRegex.IsMatch(columnName))
{
monthColumns.Add(columnName);
}
}
// 遍歷DataRow獲取BudgetItems
IList<BudgetItem> budgetItems = new List<BudgetItem>();
for (var i = 1; i < table.Rows.Count; i++)
{
// 獲取DataRow
DataRow dataRow = table.Rows[i];
// 創建BudgetItem對象,并設置部門和科目信息
BudgetItem budgetItem = new BudgetItem
{
Dept = dataRow[0].ToString(),
Account = dataRow[1].ToString()
};
// 創建BudgetItemDetail集合
IList<BudgetItemDetail> budgetItemDetails = new List<BudgetItemDetail>();
foreach (var column in monthColumns)
{
// 創建BudgetItemDetail對象,并設置預算月份和相應金額
BudgetItemDetail detail = new BudgetItemDetail
{
Month = column,
Amount = Convert.ToDecimal(dataRow[column])
};
budgetItemDetails.Add(detail);
}
budgetItem.BudgetItemDetails = budgetItemDetails;
budgetItems.Add(budgetItem);
}
return budgetItems;
}
}
以上這段代碼,如果沒有這些注釋,GetBudgetItems()方法是比較難以讀懂的。
接下來,我們采用“分解方法”這個策略來對它重構。
第一次重構
我們粗略分析一下,可以得知GetBudgetItems()方法一共做了3件事情,下圖闡述了它的邏輯。

秉承著“一個方法只做一件事情”的原則,我們將這3件事情拆分出來,使其變成3個方法。
public class BudgetItemImport
{
private Regex _monthRegex = new Regex(@"\d{4}\\\d{2}");
public IList<BudgetItem> GetBudgetItems(string path)
{
// 讀取Excel獲取DataTable
DataTable table = ExcelUtil.RenderFromExcel(path);
// 獲取表示月份的列名
IList<string> monthColumns = GetMonthColumns(table.Columns);
// 讀取DataTable獲取BudgetItem集合
return GetBudgetItemsFromDataTable(table, monthColumns);
}
// 獲取表示月份的列名
private IList<string> GetMonthColumns(DataColumnCollection collection)
{
IList<string> monthColumns = new List<string>();
for (var i = 0; i < collection.Count; i++)
{
var columnName = collection[i].ColumnName;
if (_monthRegex.IsMatch(columnName))
{
monthColumns.Add(columnName);
}
}
return monthColumns;
}
// 讀取DataTable獲取BudgetItem集合
private IList<BudgetItem> GetBudgetItemsFromDataTable(DataTable table, IList<string> monthColumns)
{
// 遍歷DataRow獲取BudgetItems
IList<BudgetItem> budgetItems = new List<BudgetItem>();
for (var i = 1; i < table.Rows.Count; i++)
{
DataRow dataRow = table.Rows[i];
// 創建BudgetItem對象,并設置部門和科目信息
BudgetItem budgetItem = new BudgetItem
{
Dept = dataRow[0].ToString(),
Account = dataRow[1].ToString()
};
// 創建BudgetItemDetail集合,并設置每個BudgetItemDetail對象的月份和金額
IList<BudgetItemDetail> budgetItemDetails = monthColumns.Select(column => new BudgetItemDetail
{
Month = column,
Amount = Convert.ToDecimal(dataRow[column])
}).ToList();
budgetItem.BudgetItemDetails = budgetItemDetails;
budgetItems.Add(budgetItem);
}
return budgetItems;
}
}
第二次重構
雖然GetBudgetItems()拆分成了3個方法,但新追加的GetBudgetItemsFromDataTable()方法還是不具備良好的可讀性,這個方法我們仍然需要借助注釋才能讀懂。
我們再具體分析這個方法內部的邏輯,GetBudgetItemsFromDataTable()這個方法也做了3件事情,見下圖:
按照這個更加明細的邏輯流程,我們將這3件事情再拆分出來。
public class BudgetItemImport
{
private Regex _monthRegex = new Regex(@"\d{4}\\\d{2}");
public IList<BudgetItem> GetBudgetItems(string path)
{
// 讀取Excel獲取DataTable
DataTable table = ExcelUtil.RenderFromExcel(path);
// 獲取表示月份的列名
IList<string> monthColumns = GetMonthColumns(table.Columns);
// 讀取DataTable獲取BudgetItem集合
return GetBudgetItemsFromDataTable(table, monthColumns);
}
// 獲取表示月份的列名
private IList<string> GetMonthColumns(DataColumnCollection collection)
{
IList<string> monthColumns = new List<string>();
for (var i = 0; i < collection.Count; i++)
{
var columnName = collection[i].ColumnName;
if (_monthRegex.IsMatch(columnName))
{
monthColumns.Add(columnName);
}
}
return monthColumns;
}
// 讀取DataTable獲取BudgetItem集合
private IList<BudgetItem> GetBudgetItemsFromDataTable(DataTable table, IList<string> monthColumns)
{
IList<BudgetItem> budgetItems = new List<BudgetItem>();
foreach (DataRow dataRow in table.Rows)
{
BudgetItem budgetItem = GetBudgetItemFromDataRow(dataRow, monthColumns);
budgetItems.Add(budgetItem);
}
return budgetItems;
}
// 創建BudgetItem對象,并設置部門和科目信息
private BudgetItem GetBudgetItemFromDataRow(DataRow dataRow, IList<string> monthColumns)
{
BudgetItem budgetItem = new BudgetItem
{
Dept = dataRow[0].ToString(),
Account = dataRow[1].ToString(),
BudgetItemDetails = GetBudgetItemDetailsFromDataRow(dataRow, monthColumns)
};
return budgetItem;
}
// 創建BudgetItemDetail集合,并設置每個BudgetItemDetail對象的月份和金額
private IList<BudgetItemDetail> GetBudgetItemDetailsFromDataRow(DataRow dataRow,
IList<string> monthColumns)
{
return monthColumns.Select(column => new BudgetItemDetail
{
Month = column,
Amount = Convert.ToDecimal(dataRow[column]),
}).ToList();
}
}
經過這次重構后,BudgetItemImport類的可讀性已經很好了。每個方法都只做一件事情,每個方法都很短小,都不超過20行,我們甚至不需要為這些方法寫注釋了。
小結
在經歷過兩次重構后,我們得到了結構良好的代碼。回顧這個示例的重構過程,我們可以用下面一副圖來表示。
寫代碼和寫別的東西很像。在寫文章時,你先想什么就寫什么,然后再打磨它。初稿也許丑陋無序,你就雕章琢句,直至達到你心目中的樣子。
我們并不能直接寫出結構和可讀性良好的方法,一開始我們的方法寫得復雜且冗長,包含了各種循環、判斷、縮進和注釋。
然后我們打磨這些代碼,通過分解方法逐一解決這些問題。
本文鏈接: 文章作者:keepfool 文章出處:http://www.rzrgm.cn/keepfool/ 如果您覺得閱讀本文對您有幫助,請點一右下角的“推薦”按鈕,您的“推薦”將是我最大的寫作動力!歡迎看官們轉載,轉載之后請給出作者和原文連接。

“分解方法”的思想和前面講到的“提取方法”、“提取方法對象”基本一致。
它是將較大個體的方法不斷的拆分,讓每個“方法”做單一的事情,從而提高每個方法的可讀性和可維護性。
分解方法可以看做是“提取方法”的遞歸版本,它是對方法反復提煉的一種重構策略。






浙公網安備 33010602011771號