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

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 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。

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



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

作者:小虚竹

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

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



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


目录
相关文章
|
15天前
|
自然语言处理 编译器 Linux
|
20天前
|
存储 安全 Java
系统安全架构的深度解析与实践:Java代码实现
【11月更文挑战第1天】系统安全架构是保护信息系统免受各种威胁和攻击的关键。作为系统架构师,设计一套完善的系统安全架构不仅需要对各种安全威胁有深入理解,还需要熟练掌握各种安全技术和工具。
57 10
|
19天前
|
Prometheus 监控 Cloud Native
实战经验:成功的DevOps实施案例解析
实战经验:成功的DevOps实施案例解析
35 6
|
17天前
|
UED
<大厂实战经验> Flutter&鸿蒙next 中使用 initState 和 mounted 处理异步请求的详细解析
在 Flutter 开发中,处理异步请求是常见需求。本文详细介绍了如何在 `initState` 中触发异步请求,并使用 `mounted` 属性确保在适当时机更新 UI。通过示例代码,展示了如何安全地进行异步操作和处理异常,避免在组件卸载后更新 UI 的问题。希望本文能帮助你更好地理解和应用 Flutter 中的异步处理。
61 3
|
17天前
|
JavaScript API 开发工具
<大厂实战场景> ~ Flutter&鸿蒙next 解析后端返回的 HTML 数据详解
本文介绍了如何在 Flutter 中解析后端返回的 HTML 数据。首先解释了 HTML 解析的概念,然后详细介绍了使用 `http` 和 `html` 库的步骤,包括添加依赖、获取 HTML 数据、解析 HTML 内容和在 Flutter UI 中显示解析结果。通过具体的代码示例,展示了如何从 URL 获取 HTML 并提取特定信息,如链接列表。希望本文能帮助你在 Flutter 应用中更好地处理 HTML 数据。
99 1
|
19天前
|
前端开发 JavaScript 开发者
揭秘前端高手的秘密武器:深度解析递归组件与动态组件的奥妙,让你代码效率翻倍!
【10月更文挑战第23天】在Web开发中,组件化已成为主流。本文深入探讨了递归组件与动态组件的概念、应用及实现方式。递归组件通过在组件内部调用自身,适用于处理层级结构数据,如菜单和树形控件。动态组件则根据数据变化动态切换组件显示,适用于不同业务逻辑下的组件展示。通过示例,展示了这两种组件的实现方法及其在实际开发中的应用价值。
27 1
|
10天前
|
前端开发 中间件 PHP
PHP框架深度解析:Laravel的魔力与实战应用####
【10月更文挑战第31天】 本文作为一篇技术深度好文,旨在揭开PHP领域璀璨明星——Laravel框架的神秘面纱。不同于常规摘要的概括性介绍,本文将直接以一段引人入胜的技术剖析开场,随后通过具体代码示例和实战案例,逐步引导读者领略Laravel在简化开发流程、提升代码质量及促进团队协作方面的卓越能力。无论你是PHP初学者渴望深入了解现代开发范式,还是经验丰富的开发者寻求优化项目架构的灵感,本文都将为你提供宝贵的见解与实践指导。 ####
|
13天前
|
前端开发 JavaScript
JavaScript新纪元:ES6+特性深度解析与实战应用
【10月更文挑战第29天】本文深入解析ES6+的核心特性,包括箭头函数、模板字符串、解构赋值、Promise、模块化和类等,结合实战应用,展示如何利用这些新特性编写更加高效和优雅的代码。
31 0
|
3天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
15 2
|
1月前
|
缓存 Java 程序员
Map - LinkedHashSet&Map源码解析
Map - LinkedHashSet&Map源码解析
66 0

推荐镜像

更多