小酌重構系列[4]——分解方法

來源:互聯網
上載者:User

標籤:

概述

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

分解方法

表示了這個重構策略,第1次提煉和第2次提煉都採用了“提取方法”這個策略。

何時分解方法?

“分解方法”最終可以讓方法的可讀性極大地增強,通常我們可以依據以下幾點來辨別方法是否需要分解:

1. 每個方法應該只做一件事情(對事情的理解程度,決定了事情的粒度)
2. 方法應該盡量短小,方法最好不要超過20行(依不同情況,酌情考慮行數)
3. 方法的縮排層次不宜太多,最好不要超過兩級
4. 方法需要太多的注釋才能理解 樣本

在企業年度預算的情境中,使用者需要按照如下Excel模板填寫科目、部門、各月的預算資料,然後將Excel檔案匯入到“預算系統”。

為了表示使用者填寫的每一行預算資料,開發人員在系統中設計了兩個class:BudgetItem(預算項)和BudgetItemDetail(預算項明細)。
紅色方框標註的表示一個BudgetItem對象,每個藍色方框則對應一個BudgetItemDetail對象。

BudgetItem.cs和BudgetItemDetail.cs
/// <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件事情拆分出來。

隱藏代碼
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;    } }
二次重構

雖然拆分成了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行,我們甚至不需要為這些方法寫注釋了。

小結

在經曆過兩次重構後,我們得到了結構良好的代碼。回顧這個樣本的重構過程,我們可以用下面一副圖來表示。

寫代碼和寫別的東西很像。在寫文章時,你先想什麼就寫什麼,然後再打磨它。初稿也許醜陋無序,你就雕章琢句,直至達到你心目中的樣子。
我們並不能直接寫出結構和可讀性良好的方法,一開始我們的方法寫得複雜且冗長,包含了各種迴圈、判斷、縮排和注釋。
然後我們打磨這些代碼,通過分解方法逐一解決這些問題。

小酌重構系列[4]——分解方法

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.