《重构:改善既有代码的设计》-学习笔记一(+实战解析)

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 《重构:改善既有代码的设计》-学习笔记一(+实战解析)

我不是个伟大的程序员;我只是个有着一些优秀习惯的好程序员而己


本人比较直接,不说虚的,直接上干货。


目录


   Duplicated Code(重复的代码)


   Long Method(过长函数)


    Long Parameter List(过长参数列)


   Large Class(过大类)


提前总结就是四招:


   一、重复的代码提炼成函数


   二、把过长的函数变小


   三、参数列太长或变化太频繁,参数对象化


   四、大招:类的代码行数太多,要考虑提炼子类。



第一招 重复的代码提炼成函数

   第一种情况是:同一个class内的两个函数含有相同表达式(expression)。image.pngimage.pngimage.pngimage.pngimage.png优化的思路1

1、在各个subclass 中分解目标函数,把有差异的部分变成入参,封装成一个模板函数。


2、把模板函数放到父类中。


3、子类根据需要输入不同的入参,得到需要的结果。


*********************************************************************************


如果是相似的表达式,差异的地方不好抽共性,则用模板函数设计模式来处理。


这里用到了JAVA的两个特性,继承和覆写(overrides)。


优化的思路2

1、在各个subclass 中分解目标函数,使分解后的各个函数要不完全相同,要不完全不同。


2、父类有一个主函数包含完全相同的函数和完全不同的函数:相同的函数,抽到父类中,不相同的函数在父类中定义一个函数。


3、子类继承父类,然后覆写完全不同的函数,再调用主函数可得到期望的结果。


第二招 把过长的函数变小

百分之九十九的场合里,要把函数变小,只需使用Extract Method(第一招)。找到函数中适合集在一起的部分,将它们提炼出来形成一个新函数。


如果函数内有大量的参数和临时变量,它们会对你的函数提炼形成阻碍。这时就要用Replace Temp with Query来消除这些临时变量


Replace Temp with Query(以查询取代临时变量)


   优化思路

1、找出只被赋值一次的临时变量。


2、将该临时变量声明为final


3、编译:这可确保该临时变量的确只被赋值一次。


4、将临时变量等号右侧部分提炼到一个独立函数中;


5、首先将函数声明为private。日后你可能会发现有更多class需要使用 它,彼时你可再放松对它的保护。


6、编译,测试:确保提炼出来的函数无任何连带影响(副作用),结果不变;


7、把临时变量全替换成独立出来的函数;


以上,over!


例子:未优化代码

image.pngimage.pngimage.png通过以上的优化,一个大函数,已经变成了多个小函数,重点是代码的可读性提高了,顺带的代码量变少。


第三招 参数对象化

当你看到一个函数的入参有四,五个,甚至更多时,且好几个函数都使用这组入参,这时就要用参数对象化来优化代码。这些函数可能隶属同一个class,也可能隶属不同的classes 。这样一组参数就是所谓的Date Clump (数据泥团)」。这时用一个对象封装这些参数,再用对象取代它们。


优化思路

1、入参有四,五个,甚至更多时,就要着手优化;


2、用一个新的class封装入参,并把这些参数设置为private严格保护起来,写这些参数的get方法和set方法。


3、原函数的入参变成这个新的class对象,函数里的参数用class对象对应的属性替换。


4、编译测试;


5、将原先的参数全部去除之后,观察有无适当函数可以运用Move Method 搬移到参数对象之中。


例子:未优化的代码

@Autowired
  private AddressService addressService;
  public List inquireAddressListAccount( Integer pageNum ,Integer pageSize,String addressName,String mobile,String zipCode,String consignee){
    return addressService.inquireAddressList(pageNum,pageSize,addressName,mobile,zipCode,consignee);
  }

优化

@Autowired
  private AddressService addressService;
  public List inquireAddressListAccount( Integer pageNum ,Integer pageSize,InquireAddressListInput output){
    return addressService.inquireAddressList(pageNum,pageSize,output);
  }
  public class InquireAddressListInput(){
    private String addressName;
    private String mobile;
    private String zipCode;
    private String consignee;
  public String getConsignee() {
    return consignee;
  }
  public void setConsignee(String consignee) {
    this.consignee = consignee;
  }
  public String getMobile() {
    return mobile;
  }
  public void setMobile(String mobile) {
    this.mobile = mobile;
  }
  public String getZipCode() {
    return zipCode;
  }
  public void setZipCode(String zipCode) {
    this.zipCode = zipCode;
  }
  public String getAddressName() {
    return addressName;
  }
  public void setAddressName(String addressName) {
    this.addressName = addressName;
  }
  }

第四招 大招-提炼类和提炼子类

如果想利用单一class做太多事情,其内往往就会出现太多instance变量。一旦如此,Duplicated Code也就接踵而至了。


Extract Class 是Extract Subclass 之外的另一种选择,两者之间的抉择其实就是委托(delegation)和继承(inheritance)之间的抉择。


情况一:某个class做了应该由两个classes做的事。(Extract Class)


优化思路1

1、明确每个class所负的责任,该做什么事情;


2、建立一个新class,用以表现从旧class中分离出来的责任;


3、建立「从旧class访问新class」的连接关系;


4、每次搬移后,编译、测试。


5、决定是否让新的class曝光。


例子:未优化的代码image.pngimage.pngimage.png情况二:class 中的某些特性(features)只被某些(而非全部)实体(instances)用到。Extract Subclass(提炼子类)



优化思路2

1、为source class 定义一个新的subclass


2、为这个新的subclass 提供构造函数。


   简单的作法是:让subclass 构造函数接受与superclass 构造函数相同的参数,并通过super 调用superclass 构造函数;


3、找出调用superclass 构造函数的所有地点。如果它们需要的是新建的subclass , 令它们改而调用新构造函数。


   如果subclass 构造函数需要的参数和superclass 构造函数的参数不同,可以使用Rename Method 修改其参数列。如果subclass 构造函数不需要superclass 构造函数的某些参数,可以使用Rename Method 将它们去除。


   如果不再需要直接实体化(具现化,instantiated)superclass ,就将它声明为抽象类。


4、逐一使用Push Down Method 和 Push Down Field 将source class 的特性移到subclass 去。


5、每次下移之后,编译并测试。


例子:未优化代码


--用来决定当地修车厂的工作报价:

class JobItem ...
   public JobItem (int unitPrice, int quantity, boolean isLabor, Employee employee) {
       _unitPrice = unitPrice;
       _quantity = quantity;
       _isLabor = isLabor;
       _employee = employee;
   }
   public int getTotalPrice() {
       return getUnitPrice() * _quantity;
   }
   public int getUnitPrice(){
       return (_isLabor) ?
            _employee.getRate():
            _unitPrice;
   }
   public int getQuantity(){
       return _quantity;
   }
   public Employee getEmployee() {
       return _employee;
   }
   private int _unitPrice;
   private int _quantity;
   private Employee _employee;
   private boolean _isLabor;
 class Employee...
   public Employee (int rate) {
       _rate = rate;
   }
   public int getRate() {
       return _rate;
   }
   private int _rate;

image.pngimage.png

 class LaborItem extends JobItem {
  public LaborItem (int unitPrice, int quantity, boolean isLabor, Employee employee) {
       super (unitPrice, quantity, isLabor, employee);
   }
    public LaborItem (int quantity, Employee employee) {
       super (0, quantity, true, employee);
   }
   public Employee getEmployee() {
       return _employee;
   }
 }
//因为_employee 值域也将在稍后被下移到LaborItem ,所以我现在先将它声明为protected。
class JobItem...
  protected Employee _employee;

image.pngimage.pngimage.png使用某项值域的函数全被下移至subclass 后,我就可以使用 Push Down Field 将值域也下移。

最后的结果就是:

public class JobItem {
    protected  JobItem (int unitPrice, int quantity) {
         _unitPrice = unitPrice;
         _quantity = quantity;
     }
     public int getTotalPrice() {
         return getUnitPrice() * _quantity;
     }
     public int getUnitPrice(){
         return _unitPrice;
     }
     public int getQuantity(){
         return _quantity;
     }
     private int _unitPrice;
     private int _quantity;
}
//
public class LaborItem extends JobItem {
  private Employee _employee;
  public LaborItem(int quantity, Employee employee) {
    super(0, quantity);
    _employee = employee;
  }
  public Employee getEmployee() {
    return _employee;
  }
  public int getUnitPrice() {
    return _employee.getRate();
  }
}
//public class Employee {
  public Employee(int rate) {
    _rate = rate;
  }
  public int getRate() {
    return _rate;
  }
  private int _rate;
}

一个class如果拥有太多代码,也适合使用Extract Class和Extract Subclass。

想重构代码,直接把以上四招看情况用上,更多精彩内容,请等待后续更新。



***************************************************************************

作者:小虚竹

欢迎任何形式的转载,但请务必注明出处。

限于本人水平,如果文章和代码有表述不当之处,还请不吝赐教。



我不是个伟大的程序员,我只是个有着一些优秀习惯的好程序员而己


目录
打赏
0
0
0
0
28
分享
相关文章
实现一个带有昼夜背景切换的动态时钟:从代码到功能解析
本文介绍了一个使用Python和Tkinter库实现的动态时钟程序,具有昼夜背景切换、指针颜色随机变化及整点和半点报时功能。通过设置不同的背景颜色和随机变换指针颜色,增强视觉吸引力;利用多线程技术确保音频播放不影响主程序运行。该程序结合了Tkinter、Pygame、Pytz等库,提供了一个美观且实用的时间显示工具。欢迎点赞、关注、转发、收藏!
142 94
JSON数据解析实战:从嵌套结构到结构化表格
在信息爆炸的时代,从杂乱数据中提取精准知识图谱是数据侦探的挑战。本文以Google Scholar为例,解析嵌套JSON数据,提取文献信息并转换为结构化表格,通过Graphviz制作技术关系图谱,揭示文献间的隐秘联系。代码涵盖代理IP、请求头设置、JSON解析及可视化,提供完整实战案例。
JSON数据解析实战:从嵌套结构到结构化表格
|
8天前
|
Java代码结构解析:类、方法、主函数(1分钟解剖室)
### Java代码结构简介 掌握Java代码结构如同拥有程序世界的建筑蓝图,类、方法和主函数构成“黄金三角”。类是独立的容器,承载成员变量和方法;方法实现特定功能,参数控制输入环境;主函数是程序入口。常见错误包括类名与文件名不匹配、忘记static修饰符和花括号未闭合。通过实战案例学习电商系统、游戏角色控制和物联网设备监控,理解类的作用、方法类型和主函数任务,避免典型错误,逐步提升编程能力。 **脑图速记法**:类如太空站,方法即舱段;main是发射台,static不能换;文件名对仗,括号要成双;参数是坐标,void不返航。
29 5
保单AI识别技术及代码示例解析
车险保单包含基础信息、车辆信息、人员信息、保险条款及特别约定等关键内容。AI识别技术通过OCR、文档结构化解析和数据校验,实现对保单信息的精准提取。然而,版式多样性、信息复杂性、图像质量和法律术语解析是主要挑战。Python代码示例展示了如何使用PaddleOCR进行保单信息抽取,并提出了定制化训练、版式分析等优化方向。典型应用场景包括智能录入、快速核保、理赔自动化等。未来将向多模态融合、自适应学习和跨区域兼容性发展。
Python执行Shell命令并获取结果:深入解析与实战
通过以上内容,开发者可以在实际项目中灵活应用Python执行Shell命令,实现各种自动化任务,提高开发和运维效率。
79 20
GraphQL开发工具选型指南:Apipost高效调试与文档生成实战解析
本文深入解析了GraphQL开发工具Apipost在高效调试与文档生成方面的优势,对比同类工具Apifox,突出其可视化界面、实时调试及自动化文档生成等特性。Apipost通过智能代码补全、错误提示等功能简化复杂Query编写,支持一键生成标准化文档,显著提升开发效率和团队协作效果,尤其适合中大型团队应对复杂业务场景。
深度解析1688 API对电商的影响与实战应用
在全球电子商务迅猛发展的背景下,1688作为知名的B2B电商平台,为中小企业提供商品批发、分销、供应链管理等一站式服务,并通过开放的API接口,为开发者和电商企业提供数据资源和功能支持。本文将深入解析1688 API的功能(如商品搜索、详情、订单管理等)、应用场景(如商品展示、搜索优化、交易管理和用户行为分析)、收益分析(如流量增长、销售提升、库存优化和成本降低)及实际案例,帮助电商从业者提升运营效率和商业收益。
249 20
如何在 Java 代码中使用 JSqlParser 解析复杂的 SQL 语句?
大家好,我是 V 哥。JSqlParser 是一个用于解析 SQL 语句的 Java 库,可将 SQL 解析为 Java 对象树,支持多种 SQL 类型(如 `SELECT`、`INSERT` 等)。它适用于 SQL 分析、修改、生成和验证等场景。通过 Maven 或 Gradle 安装后,可以方便地在 Java 代码中使用。
433 11
深入解析BeautifulSoup:从sohu.com视频页面提取关键信息的实战技巧
深入解析BeautifulSoup:从sohu.com视频页面提取关键信息的实战技巧
速卖通AliExpress商品详情API接口深度解析与实战应用
速卖通(AliExpress)作为全球化电商的重要平台,提供了丰富的商品资源和便捷的购物体验。为了提升用户体验和优化商品管理,速卖通开放了API接口,其中商品详情API尤为关键。本文介绍如何获取API密钥、调用商品详情API接口,并处理API响应数据,帮助开发者和商家高效利用这些工具。通过合理规划API调用策略和确保合法合规使用,开发者可以更好地获取商品信息,优化管理和营销策略。

热门文章

最新文章

推荐镜像

更多