1 Introduction

Refactoring, https://refactoring.com/ by Martin Fowler, Kent Beck

  • High Cohesion 高内聚
  • Low Coupling 低耦合
  • 代码的本质是给人阅读
  • 一个修改需要修改10个类20个方法
    • 内聚度不够高,一个功能被拆分到了太多的类
    • 耦合度很高,一个功能的执行,涉及到太多的类
    • 维护即变更,难以理解,容易出错 => 代码质量
  • 从生理角度看,太多的关联关系,修改时需要更多的大脑思考活动,意味着更高的能量消耗,更低的效率
  • 10个类,1对1对1… 最多9个关系,多对多对多…最多10! = 3628800

2 Duplicated Code 重复代码

2.1 现象

  • 相似的代码
  • 相似的代码结构
  • 似曾眼熟即为相似 ```typescript // number.ts function sortNumberDesc(numbers: number[]) {} function sortNumberAsc(numbers: number[]) {}

// string.ts function sortStringDesc(strings: string[]) {} function sortStringAsc(strings: string[]) {}

// date.ts function sortDateDesc(dates: string[]) {} function sortDateAsc(dates: string[]) {}

  1. <a name="H7Pid"></a>
  2. ## 2.2 解法
  3. - Extract Method 抽取公共方法
  4. - 仅抽取相同的业务逻辑,不要用if-else来处理不同部分
  5. - 不同部分保留在原处,数据作为公共方法的参数传入
  6. - Pull Up Field 提升字段
  7. ![image.png](https://cdn.nlark.com/yuque/0/2021/png/162086/1625748320735-ae6ccaf3-4ec5-48b0-ad2b-090137d93132.png#align=left&display=inline&height=156&id=Iz8vW&margin=%5Bobject%20Object%5D&name=image.png&originHeight=156&originWidth=500&size=12534&status=done&style=none&width=500)
  8. - Form Template Method 形成**模板方法**:
  9. - 使用继承来消除重复代码
  10. - 把相似的业务逻辑抽取到父类
  11. - 把不同的业务逻辑变成抽象方法,留给子类实现
  12. ```typescript
  13. class Site {
  14. }
  15. class ResidentialSite extends Site {
  16. getSellAmount() {
  17. const base = this.area * this.rate * 0.5; // 基本售价
  18. const tax = base * Site.TAX_RATE * 0.5; // 税金
  19. const commission = base * ResidentialSite.COMMISSION_RATE; // 交易佣金
  20. return base + tax + commission;
  21. }
  22. }
  23. class CommerceSite extends Site {
  24. getSellAmount() {
  25. const base = this.area * this.rate;
  26. const tax = base * Site.TAX_RATE;
  27. return base + tax;
  28. }
  29. }
  1. class Site {
  2. getSellAmount(): number {
  3. // 基本售价 + 税金 + 交易佣金
  4. return this.getBaseAmount() + this.getTaxAmount() + this.getCommissionAmount();
  5. }
  6. abstract getBaseAmount(): number;
  7. abstract getTaxAmount(): number;
  8. abstract getCommissionAmount(): number;
  9. }
  10. class ResidentialSite extends Site {
  11. getBaseAmount(): number {
  12. return this.area * this.rate * 0.5;
  13. }
  14. getTaxAmount(): number {
  15. return this.getBaseAmount() * Site.TAX_RATE * 0.5;
  16. }
  17. getCommissionAmount(): number {
  18. return this.getBaseAmount() * ResidentialSite.COMMISSION_RATE;
  19. }
  20. }
  21. class CommerceSite extends Site {
  22. getBaseAmount(): number {
  23. return this.area * this.rate;
  24. }
  25. getTaxAmount(): number {
  26. return this.getBaseAmount() * Site.TAX_RATE;
  27. }
  28. getCommissionAmount(): number {
  29. return 0;
  30. }
  31. }

Refer: https://gitlab.shoplazza.site/shoplaza/frontend/checkout/sunfish/blob/master/src/pageAction.ts#L43

  • Substitute Algorithm 替换算法
  • Extract Class 抽取类

    • 把相同代码抽取到一个class中去 ```typescript class Persion { officeAreaCode: number; officeNumber: number; homeAreaCode: number; homeNumber: number

    getOfficeTelephoneNumer(): string; getHomeTelephoneNumber(): string; } typescript class TelephoneNumber { areaCode: number; number: number; getTelephoneNumber(); }

class Person { officeTelephoneNumber: TelephoneNumber; homeTelephoneNumber: TelephoneNumber; getOfficeTelephoneNumer(): string; getHomeTelephoneNumber(): string; }

  1. - Extract Function 抽取函数
  2. ```typescript
  3. // number.ts
  4. function sortNumberDesc(numbers: number[]) {}
  5. function sortNumberAsc(numbers: number[]) {}
  6. // string.ts
  7. function sortStringDesc(strings: string[]) {}
  8. function sortStringAsc(strings: string[]) {}
  9. // date.ts
  10. function sortDateDesc(dates: string[]) {}
  11. function sortDateAsc(dates: string[]) {}
  1. function sort(items: T[], comparator: (a: T, b: T) => 0 | 1 | -1): T[]{}

3 Long Method 过长方法

3.1 现象

  • 函数超过100行代码

    3.2 解法

  • Extract Method 抽取多个方法

    • Replace Temp with Query: 如果有大量中间变量,使用方法调用替换中间变量
      1. const basePrice = quantity * itemPrice;
      2. if (basePrice > 1000)
      3. return basePrice * 0.95;
      4. else
      5. return basePrice * 0.98;
      ```typescript if (basePrice() > 1000) return basePrice() 0.95; else return basePrice() 0.98;

function basePrice() { return quantity * itemPrice; }

  1. - 如果需要传递大量参数到各个方法
  2. - Introduce Parameter Object 使用参数对象
  3. ```typescript
  4. function fn(a, b, c, d, e, f){}
  1. function fn({ a, b, c, d, e, f}){}
  1. - Preserve Whole Object 保留整个对象
  1. const low = daysTempRange().getLow();
  2. const high = daysTempRange().getHigh();
  3. const timezone = daysTempRange().getTimezon();
  4. withinPlan = plan.withinRange(low, high, timezone);
  1. withinPlan = plan.withinRange(daysTempRange());
  • Decompose Conditional 拆解条件语句
    1. // 计算取暖费
    2. if (date.before (SUMMER_START) || date.after(SUMMER_END)) {
    3. charge = quantity * _winterRate + _winterServiceCharge;
    4. } else {
    5. charge = quantity * _summerRate;
    6. }
    1. if (notSummer(date)) {
    2. charge = winterCharge(quantity);
    3. } else {
    4. charge = summerCharge (quantity);
    5. }

4 Large Class 体积庞大的类

  • 通常违反SRP原则,承担了太多各种各样的功能职责
  • Extract Class 抽取类:优先考虑,组合优于继承
  • Extract Subclass 抽取子类

`


5 Long Parameter List 过长的参数列表

  • Introduce Parameter Object 使用参数对象
  • Preserve Whole Object 保留整个对象
  • Replace Parameter with Method 使用方法替换参数

6 Divergent Change 扩散的变更

  • 为一个类添加功能时,需要同时修改该类的多个方法
  • 通常需要把一个类的职责拆分到多个类中

7 Shotgun Surgery 霰弹式手术

  • 做出某种变更时(如支持另一种数据库,支持新的文件格式等),需要对大量的类做出大量的细微变更
  • Move Method & Move Field:把分布在各处的变更集中到一个类中,如果没有这样的类,可以新创建一个
  • Inline Class(和Extract Class相反):把一个类的所有功能转移到另外一个类中,然后删除它

8 Feature Envy 特性羡慕

  • A类的一个方法似乎应该放在B类中
  • 该方法大量调用了B类的getter方法获取数据进行计算
  • Move Method:转移该方法到B类中
  • Extract Method:有时候只需要抽取一部分代码作为一个新方法转移到B类中

9 Data Clumps 数据扎堆

  • 一些数据项经常一起出现,有时一起出现在类中,有时一起出现在方法签名中
  • Extract Class:抽取一个公共类
  • Introduce Parameter Object & Preserve Whole Object:处理方法签名
  • 可能需要进一步拆分更多的公共类
  • 如果移除某些字段,其他字段组成的类业务语义完整,那么意味着需要进一步拆分

10 Primitive Obsession 原始类型困扰

  • Primitive Type vs Reference Type
  • 新手通常不愿意创建简单的类
  • Replace Data Value with Object:有明确的业务语义时,即使一个字段也可以创建类

image.png

  • Replace Type Code with Class

image.png

  • Replace Type Code with Subclasses:如果需要通过type code执行条件语句,利用多态来移除条件语句

image.png

  • Replace Type Code with State/Strategy:组合优于继承

image.png


11 Switch Statements Switch语句

  • OOP代码中,一般很少出现Switch语句,因为添加新分支时,需要修改多处
  • Replace Conditional with Polymorphism: 通常使用多态来替换Switch语句(包括相似的if-else语句) ```typescript class HTMLElement { validate() { switch(this.tagName) {

    1. case 'DIV':
    2. // ...
    3. case 'BUTTON':
    4. // ...
    5. ...

    } }

    render() { switch(this.tagName) {

    1. case 'DIV':
    2. // ...
    3. case 'BUTTON':
    4. // ...
    5. ...

    } } }

const elements = createElement(htmlCode); elements.forEach(e => e.render());

  1. ```typescript
  2. abstract class HTMLElement {
  3. abstract validate();
  4. abstract render();
  5. }
  6. class HTMLDIVElement extends HTMLElement {
  7. readonly tagName: string = 'DIV';
  8. validate() {
  9. // ...
  10. }
  11. render() {
  12. // ...
  13. }
  14. }
  15. class HTMLButtonElement extends HTMLElement {
  16. readonly tagName: string = 'BUTTON';
  17. validate() {
  18. // ...
  19. }
  20. render() {
  21. // ...
  22. }
  23. }
  24. const elements = createElement(htmlCode);
  25. elements.forEach(e => e.render());
  • Replace Parameter with Explicit Methods:如果条件语句影响较小,抽取多个子类是过度设计
    1. function setValue (name: string, value: number): void {
    2. if (name.equals("height"))
    3. height = value;
    4. if (name.equals("width"))
    5. width = value;
    6. Assert.shouldNeverReachHere();
    7. }

12 Parallel Inheritance Hierarchies 并列的继承层级

  • 创建一个子类A,就必须创建另外一个子类B
  • Move Method & Move Field:把两个并列的层级合并起来

13 Lazy Class

  • 过度设计,只有一个子类的父类

image.png


14 Speculative Generality

  • 过度设计:提前设计实现好父类子类,抽象类等

15 Temporary Field 临时字段

  • 使用对象的实例字段来存储一些中间变量
  • 对象的实例字段反映实例的状态

16 Message Chains 消息链

16.1 现象

  1. const state = objA.getObjB().getObjC().getObjD().getObjE().getState();
  • 一旦类A、B、C、D、E之间的关系发生变化,代码就需要修改

    16.2 解法

  • Hide Delegate

image.png

  1. const state = objA.getState();
  2. class A {
  3. getState() {
  4. return objB.getState();
  5. }
  6. }
  7. class B {
  8. getState() {
  9. return objC.getState();
  10. }
  11. }
  12. // ....
  • Extract Method: 获得State的代码逻辑可以抽出一个公共方法,直接调用

17 Middle Man 中间人

  • 对象的主要特性之一是封装,封装常常意味着委托
  • 如果一个类一半以上的方法实现都委托给了其他类,通常它只是一个中间
  • 把调用委托对象方法的代码转移到中间人方法的调用者中去
  • 注意Facade模式:又叫作门面模式,是一种通过为多个复杂的子系统提供一个一致的接口,而使这些子系统更加容易被访问的模式。该模式对外有一个统一接口
  • Remove Middle Man

image.png

  1. class MiddleMan {
  2. doA() {
  3. return a.do();
  4. }
  5. doB() {
  6. return b.doB();
  7. }
  8. doC() {
  9. return a.doC();
  10. }
  11. }
  12. // client.js
  13. middleMan.doA();
  14. middleMan.doB();
  15. middleman.doC();
  1. class MiddleMan {
  2. doA() {
  3. return a.do();
  4. }
  5. doB() {
  6. return b.doB();
  7. }
  8. doC() {
  9. return c.doC();
  10. }
  11. }
  12. // client.js
  13. a.do();
  14. b.do();
  15. c.do();

18 Inappropriate Intimacy 不正当亲密关系

  • 一个类过于关注另一个类的私有代码,类和类之间的协议和契约,应该由且仅由共有接口和协议指定:Hack-into
  • Move method & Move field & Extract Class:可能存在职责分配不合理
  • Change Bidirectional Association to Unidirectional

image.png

  1. class Order {
  2. private customer: Customer;
  3. Customer getCustomer() {
  4. return this.customer;
  5. }
  6. setCustomer (Customer arg): void {
  7. if (this.customer != null) this.customer.friendOrders().remove(this);
  8. this.customer = arg;
  9. if (this.customer != null) this.customer.friendOrders().add(this);
  10. }
  11. getDiscountedPrice(): number {
  12. return getGrossPrice() * (1 - this.customer.getDiscount());
  13. }
  14. }
  15. class Customer {
  16. private orders: Set = new Set();
  17. addOrder(Order arg): void {
  18. arg.setCustomer(this);
  19. this.orders.add(arg);
  20. }
  21. friendOrders(): Set {
  22. /** should only be used by Order */
  23. return this.orders;
  24. }
  25. getPriceFor(Order order) {
  26. Assert.isTrue(this.orders.contains(order));
  27. return order.getDiscountedPrice();
  28. }
  29. }
  1. class Order {
  2. getDiscountedPrice(customer: Customer): number {
  3. return getGrossPrice() * (1 - customer.getDiscount());
  4. }
  5. }
  6. class Customer {
  7. private orders: Set = new Set();
  8. addOrder(Order arg): void {
  9. this.orders.add(arg);
  10. }
  11. getPriceFor(Order order) {
  12. Assert.isTrue(this.orders.contains(order));
  13. return order.getDiscountedPrice(this);
  14. }
  15. }

19 Alternative Classes with Different Interfaces


20 Incomplete Library Class


21 Data Class 数据类

  • 贫血模型:领域对象只有状态以及getter/setter方法
  • 充血模型:将大多数业务逻辑和持久化放在领域对象中
  • Encapsulate Field => Encapsulate Collection => Remove Setting Method => Extract Method & Move Method => Hide Method
  • Data classes像小孩子一样慢慢长大

22 Refused Bequest 拒绝遗产

  • 子类逐渐开始拒绝接纳父类的方法、数据乃至接口
  • Push Down Method & Push Down Field:把父类中用不到的字段和方法放到一个兄弟类中,已有的子类去继承这个兄弟类
  • Replace Inheritance with Delegation

image.png


23 Comment

  • 使用Comment来解释
  • Extract Method & Rename Method:需要特别解释的代码
  • Introduce Assertion:检查和申请参数和系统状态

Tip: When you feel the need to write a comment, first try to refactor the code so that any comment becomes superfluous.

  • 写注释的最佳时机
    • 不知道该做什么、怎么做等不确定的内容
    • 为什么代码要这么写
    • TODO list