概述

“分解方法”的思想和前面讲到的“提取方法”、“提取方法对象”基本一致。
它是将较大个体的方法不断的拆分,让每个“方法”做单一的事情,从而提高每个方法的可读性和可维护性。
分解方法可以看做是“提取方法”的递归版本,它是对方法反复提炼的一种重构策略。

分解方法

下图表示了这个重构策略,第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获取DataTableDataTable 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获取BudgetItemsIList<BudgetItem> budgetItems = new List<BudgetItem>();for (var i = 1; i < table.Rows.Count; i++){// 获取DataRowDataRow 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获取DataTableDataTable 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获取BudgetItemsIList<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获取DataTableDataTable 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

转载于:https://www.cnblogs.com/keepfool/p/5453536.html

小酌重构系列[4]——分解方法相关推荐

  1. 小酌重构系列[19]——分解大括号

    概述 if else, for, while等是程序中最常用的语句,这些语句有一个共同点--它们的逻辑都封装在一对"{}"包围的代码块中.在实现复杂的业务逻辑时,会较多地用到这些语 ...

  2. 小酌重构系列[3]——方法、字段的提升和降低

    本文要介绍的是4种重构策略,它们分别是提升方法.降低方法.提升字段和降低字段. 由于这4种重构策略具有一定的相通性,所以我将它们放到一篇来讲解. 定义 以下是这4种策略的定义 提升方法:当子类的方法描 ...

  3. 路漫漫其修远兮,吾将上下而求索——小酌重构系列[0]开篇有益

    相信博客园的读者大多都是千万"码农"中的一员,每个人都写过很多代码,但并不是每一个人都能写出高质量的代码. rome is not built in one day !--完成高质 ...

  4. 小酌重构系列[8]——提取接口

    前言 世间唯一"不变"的是"变化"本身,这句话同样适用于软件设计和开发. 在软件系统中,模块(类.方法)应该依赖于抽象,而不应该依赖于实现. 当需求发生&quo ...

  5. 小酌重构系列[18]——重命名

    概述 代码是从命名开始的,我们给类.方法.变量和参数命名,我们也给解决方案.工程.目录命名.在编码时,除了应该遵守编程语言本身的命名规范外,我们应该提供好的命名.好的命名意味着良好的可读性,读你代码的 ...

  6. 小酌重构系列[10]——分离职责

    概述 "分离职责"是经常使用的一个重构策略,当一个类担任的职责太多时,应按职责将它拆分成多个类,每个类分别承担"单一"的职责,也就是让每个类专心地做" ...

  7. 小酌重构系列[17]——提取工厂类

    概述 在程序中创建对象,并设置对象的属性,是我们长干的事儿.当创建对象需要大量的重复代码时,代码看起来就不那么优雅了.从类的职责角度出发,业务类既要实现一定的逻辑,还要负责对象的创建,业务类干的事儿也 ...

  8. 小酌重构系列[16]——引入契约式设计

    概述 试想这样一个场景,你提供了一些API给客户端调用,客户端传入了一些参数,然后根据这些参数执行了API逻辑,最终返回一个结果给客户端. 在这个场景中,有两个隐患,它们分别是: 客户端调用API时, ...

  9. 小酌重构系列[20]——用条件判断代替异常

    小酌重构系列[20]--用条件判断代替异常 参考文章: (1)小酌重构系列[20]--用条件判断代替异常 (2)https://www.cnblogs.com/keepfool/p/5513946.h ...

最新文章

  1. 5天玩转C#并行和多线程编程 —— 第五天 多线程编程大总结
  2. c mysql 连接实例_c连接mysql数据库实例
  3. Objective-C 中Socket常用转换机制(NSData,NSString,int,Uint8,Uint16,Uint32,byte[])
  4. Angularjs 通过asp.net web api认证登录
  5. Android Volley 源码解析(三),图片加载的实现
  6. 编译原理完整学习笔记(八):目标代码生成
  7. PHP判断浏览器类型及版本
  8. windows下面NTP服务器配置,局域网
  9. abc计算机机房建设标准,ABC级数据中心机房建设要求
  10. Spark算子:RDD行动Action操作–aggregate、fold、lookup;reduce/fold/aggregate区别
  11. canoe模拟发有checksum报文_CANoe 入门 Step by step系列(三)简单例子的剖析
  12. ad16 怎么设置单独元件间距_AD软件中怎么添加不同元素之间的间距规则?
  13. 零基础学python_03_字符串(拼接+换行+制表符)
  14. 深信服 VDS设备烤机
  15. 11届蓝桥杯青少年组C++全国赛高级组
  16. ubuntu 软件指南
  17. 【算法】只有五行的Floyd最短路算法
  18. 分享15款为jQuery Mobile定制的插件
  19. <Linux开发>linux开发工具- 之-TFTP
  20. Go语言的学习【2】基础语法

热门文章

  1. css样式文件的引入方式
  2. BugkuCTF 游戏过关
  3. 对话 H3C CloudCell 云业务单元一体机-张栋(CloudCell)
  4. 计算机考研复试之数据结构
  5. 设计模式(结构型)之享元模式(Flyweight Pattern)
  6. 通过指定的URL获取返回图片的BASE64编码
  7. 破解梵蒂冈秘密档案,这个AI认识中世纪手写拉丁文
  8. 神雕侠侣手游服务器维修到多久,神雕侠侣2手游9月17日停服维护公告_神雕侠侣2手游9月17日更新了什么_玩游戏网...
  9. 拜占庭将军问题 原文翻译
  10. LangChain vs Semantic Kernel