重构

169 阅读8分钟

1.什么是重构

1.1 概念

重构,是在不改变软件整体功能的情况下对软件内部结构的改善,改善的目的是为了提高内部代码的可读性,可维护性和可扩展性(三个特性)。

1.2 重构的分类

重构按照重构的规模可以分为大型重构小型重构

(1) 大型重构

  • 重构对象:对整个项目系统,项目内部模块,模块内部或项目公共类与类之间的关系等重构
  • 具体方法:垂直拆分(前后层次分明)、水平拆分(模块化)、各个模块之间解耦、公共组件、抽象组件、业务组件、UI组件
  • 方法论:编程范式、设计原则、设计模式
  • 影响:影响范围大、难度大、频率小

(2) 小型重构

  • 重构对象:对类、函数、变量等代码级别的重构、
  • 具体方法:规范命名(见名知意)、规范注释、函数拆分、提取重复代码、eslint等
  • 方法论:统一代码风格、制定规范、语义化编程、eslint
  • 时机:添加新功能 / codeReview / 修补错误时。
  • 影响:重构次数频繁,难度小,风险较小

2.为什么要重构

既然我们决定去重构别人的代码,我们怎么确保后面我们自己的代码不被重构。
(1) 可读性(方便其他开发上手)
(2) 可维护性(更快的去定位错误)
(3) 可扩展性(后期功能的迭代)

3.何时去重构

重构不是 一件应该特别拨出时间做的事情,重构应该随时随地进行。不应该为重构而重构,之所以重构,是因为我们想做别的什么事,而重构可以帮助我们把那些事做好。

  1. 添加功能时重构。
  2. 修补错误时重构。
  3. 复审代码时重构。

4.怎么去重构

4.1 自顶而下的方案:重构模式

自顶而下,最上层:MVC还是DDD模式
MVC模式的特点
DDD模式的特点

4.2 重构需要具备的软技能

为了保证自己的代码后期不会被重构,因此要保证代码的软技能要具备(因此重构的时候要对开发人员的代码严谨性有要求,不建议前端小白在不学习软技能情况下直接堆代码,有人把关)

4.2.1 识别坏代码?

(知道不利于后期维护的代码是什么样子)
这些代码代码并不是因为它是错的代码,但它一定是有问题的代码(至少他在功能逻辑上是通的)

4.2.2 代码质量:

4.2.3 编程范式

1.面向过程编程

2.面向对象编程

面向对象编程的特点:封装、继承、多态。其中多态就是一个事物,多种态度。对应到面向对象中就是类中的一个方法,可以有多种实现方式。主要表现在当子类继承父类的时候,对于父类的一些方法可以进行函数重载(函数参数不同)/ 函数重写。那么就会出现父类和子类拥有相同的方法,但是表现逻辑形式或者输出结果是完全不同的。这就是面向对象编程在继承特点的基础上又出现了多态的特点。

3.函数式编程

4.2.4 设计原则(solid)

1.single单一原则、
2.open开放封闭、
3.lsp里式替换(Liskov Substitution principle)
里氏替换原则中要求,任何父类对象出现的地方,理论上都应该可以替换为子类,替换后保证原有的逻辑性和正确性。因此,里式替换这一原则主要是用来指导和规范我们在定义继承关系的子类时候应该遵循的原则。因为我们在通过继承定义子类的时候,往往会出现对父类函数的重载或者重写,这就是多态的特点。 但是,我们对父类方法进行重载或者重写的时候需要注意遵守一个约定:
1.重载/重写函数不能改变父类函数原有的逻辑和功能(例如:父类函数中使用了低效的排序功能,那么子类可以通过多态重写替换为高效的排序逻辑)
2.输入/输出/异常的处理(比如父类在输入为空的时候不做处理,子类在输入为空的时候抛出异常;父类输出的时候把整个数组返回,子类输出的时候截取部分返回等)。
3.父类函数中特殊注释说明 总结:多态的特点让子类可以修改父类的方法,里氏替换原则又是用来规范,子类“修改”应该遵循的原则。那么,遵循这一原则有什么好处呢?遵循里式替换可以降低我们代码中的继承带来的复杂性。 哪些代码违背了里氏替换原则:

  1. 子类违背父类声明要实现的功能 父类中提供的 sortOrdersByAmount() 订单排序函数,是按照金额从小到大来给订单排序的,而子类重写这个 sortOrdersByAmount() 订单排序函数之后,是按照创建日期来给订单排序的。那子类的设计就违背里式替换原则。

  2. 子类违背父类对输入、输出、异常的约定 在父类中,某个函数约定:运行出错的时候返回 null;获取数据为空的时候返回空集合(empty collection)。而子类重载函数之后,实现变了,运行出错返回异常(exception),获取不到数据返回 null。那子类的设计就违背里式替换原则。 在父类中,某个函数约定,输入数据可以是任意整数,但子类实现的时候,只允许输入数据是正整数,负数就抛出,也就是说,子类对输入的数据的校验比父类更加严格,那子类的设计就违背了里式替换原则。 在父类中,某个函数约定,只会抛出 ArgumentNullException 异常,那子类的设计实现中只允许抛出 ArgumentNullException 异常,任何其他异常的抛出,都会导致子类违背里式替换原则。

  3. 子类违背父类注释中所罗列的任何特殊说明 父类中定义的 withdraw() 提现函数的注释是这么写的:“用户的提现金额不得超过账户余额……”,而子类重写 withdraw() 函数之后,针对 VIP 账号实现了透支提现的功能,也就是提现金额可以大于账户余额,那这个子类的设计也是不符合里式替换原则的。

4.interface接口隔离(ISP)

接口隔离原则出现背景:ISP 最初是由 Robert C. Martin 在为 Xerox 提供咨询时使用和制定的。Xerox 创建了一个新的打印机系统,可以执行各种任务,如装订和传真。该系统的软件是从头开始创建的。随着软件的增长,修改变得越来越困难,以至于即使是最小的更改也需要一个小时的重新部署周期,这使得开发几乎不可能
设计问题是几乎所有任务都使用单个 Job 类。每当需要执行打印作业或装订作业时,都会调用 Job 类。这导致了一个“胖”类,其中包含多种特定于各种不同客户端的方法。由于这种设计,装订作业会知道打印作业的所有方法,即使它们没有用处。
Martin 建议的解决方案利用了今天所谓的接口隔离原则。应用于 Xerox 软件,使用依赖倒置原则在 Job 类与其客户端之间添加了一个接口层。不是有一个大的 Job 类,而是创建了 Staple Job 接口或 Print Job 接口,它们分别由 Staple 或 Print 类使用,调用 Job 类的方法。因此,为每种作业类型创建了一个接口,这些接口都是由 Job 类实现的。

客户端不应该依赖他不需要的接口,“客户端”,可以理解为接口的调用者或者使用者
类之间的依赖关系应该建立在最小接口之上 接口隔离的目的是系统解开耦合,从而容易重构,更改和部署 juejin.cn/post/699332…
5.dependency依赖反转原
6.迪拉米特

4.2.5 设计模式

分类:

  • 1.结构型模式(Structural Patterns): 通过识别系统中组件间的简单关系来简化系统的设计。
  • 2.创建型模式(Creational Patterns): 处理对象的创建,根据实际情况使用合适的方式创建对象。常规的对象创建方式可能会导致设计上的问题,或增加设计的复杂度。创建型模式通过以某种方式控制对象的创建来解决问题。
  • 3.行为型模式(Behavioral Patterns): 用于识别对象之间常见的交互模式并加以实现,如此,增加了这些交互的灵活性。

常见的设计模式:

工厂模式,单例模式,适配器模式,装饰者模式,职责链模式、代理模式、建造者模式