重构 - 12. 处理继承关系

12.1 函数上移(Pull Up Method)

反向重构函数下移(Push Down Method)

避免重复代码是很重要的。

重构前

class Employee
{
}

class Salesman : Employee
{
    public void Rest() { }
}

class Engineer : Employee
{
    public void Rest() { }
}
重构 - 11. 重构 API

11.1 将查询函数和修改函数分离(Separate Query from Modifier)

如果某个函数只提供一个值,没有任何看得到的副作用,那么这个函数可以任意调用,也很容易测试。

重构前

decimal GetTotalOutstandingAndSendBill()
{
    var result = _customer.Invoices.Sum(m => m.Amount);
    SendBill();
    return result;
}
重构 - 6. 第一组重构

第 5 章 是介绍之后几章重构手法的说明,就不单独写一篇博客了。

从第 6 章开始直到最后都是在介绍各种重构手法的。每个重构手法包含 名称速写(Skeetch)动机(motivation)做法(mechanics)范例 (examples) 5 个部分。这里只记录一下 速写(Skeetch) ,速写是用来帮助回忆重构手法的,具体的重构用途和重构的具体步骤这里就不介绍了,还是推荐大家买实体书来看。

重构 - 4. 构筑测试体系

第 4 章主要介绍了测试的价值以及一些测试实践方法。书中是以 JavaScript 为例的,.NET 开发人员可以参考 MSDN 上关于单元测试的文章。

重构 - 10. 简化条件逻辑

10.1 分解条件表达式(Decompose Conditional)

本手法其实只是提炼函数(Extract Function)的一个应用场景。

重构前

if (aDate >= plan.SummerStart && aDate <= plan.SummerEnd)
{
    charge = quantity * plan.SummerRate;
}
else
{
    charge = quantity * plan.RegularRate + plan.RegularServiceCharge;
}
重构 - 9. 重新组织数据

9.1 拆分变量(Split Variable)

曾用名移除对参数的赋值(Remove Assignments to Parameters)
曾用名分解临时变量(Split Temp)

如果变量承担多个职责,它就应该被替换(分解)为多个变量,每个变量只承担一个责任。

重构前

重构 - 8. 搬移特性

8.1 搬移函数(Move Function)

曾用名搬移函数(Move Method)

重构前

class Account
{
    decimal OverdraftCharge { get; }
}
重构 - 7. 封装

7.1 封装记录(Encapsulate Record)

曾用名以数据类取代记录(Replace Record with Data Class)

重构前

(string Name, string Country) organization = ("JiaJia's Blog", "China");
重构 - 3. 代码的坏味道

第 3 章介绍了何时应当重构?作者总结了一些常见的场景,但由于不可能给出一个精确的衡量标准,所以还需依赖开发者的经验来判断。
书中还给出了每种场景应该使用何种重构方法来解决,有兴趣的小伙伴建议买实体书来阅读。

1. 神秘命名(Mysterious Name

整洁代码最重要的一环就是好的名字,所以我们会深思熟虑如何给函数、模块、变量和类命名,使它们能清晰地表明自己的功能和用法。
命名是编程中最困难的两件事之一。
为一个恼人的名字所付出的纠结,常常能推动我们对代码进行精简。

重构 - 2. 重构的原则

第 2 章介绍了重构的一些重大原则,这里仍然只是记录一些重点以作备忘。

  • 重构(名词):对软件内部结构的一种调整,目的是在不改变软件可观察行为的前提下,提高其可理解性,降低其修改成本。

  • 重构(动词):使用一系列重构手法,在不改变软件可观察行为的前提下,调整其结构。

  • 如果有人说他们的代码在重构过程中有一两天时间不可用,基本上可以确定,他们在做的事不是重构。

  • 重构和性能优化的差别:重构是为了让代码“更容易理解,更易于修改”;性能优化只关心让程序运行的更快。

  • 两顶帽子:添加新功能时,不应该修改既有代码,只管添加新功能;重构时不能再添加功能,只管调整代码结构。

  • 代码结构的流失有累积效应。

  • 为何重构

    1. 改进软件的设计
    2. 使软件更容易理解
    3. 帮助找到 bug
    4. 提高编程速度
  • “设计耐久性假说”:通过投入精力改善内部设计,增加了软件的耐久性,从而可以更长时间地保持开发速度。

  • 三次法则:第一次做某件事时只管去做;第二次做类似的事会产生反感,但无论如何还是可以去做;第三次再做类似的事,你就应该重构。事不过三,三则重构。

  • 重构的最佳时机就在添加新功能之前。

  • 并不需要专门安排一段时间来重构,而是在添加新功能或修改 bug 的同时顺便重构。

  • 肮脏的代码必须重构,但漂亮的代码也需要很多重构。

  • 添加新功能最快的方法往往是修改现有的代码,使新功能容易被加入。

  • 重构的唯一目的就是让我们开发更快,用更少的工作量创造更大的价值。

  • 重构应该总是由经济利益驱动。

  • 已发布接口(published interface):接口的使用者(客户端)与声明者彼此独立,声明者无权修改使用的代码。

  • 在隔离的分支上工作的越久,将完成的工作集成(integrate)回主线就会越困难。

  • 持续集成(CI),也叫“基于主干开发”(Trunk-Based Development)。使用 CI 时,每个团队成员每天至少向主线集成一次。代价:必须使用相关的实践以确保主线随时处于健康状态。

  • CI 和重构能良好的配合。

  • 团队必须投入时间和精力在测试上,但收益绝对是划算的。

  • 缺乏测试时的重构

    1. 如果我的开发环境很好的支持自动化重构,就可以信任这些重构,不必运行测试。

      现在工作的主要开发工具是 Visual StudioIntelli IDEA,对重构都有很好的支持,很多简单的重构都可以自动化的完成。

    2. 只使用一组经过验证是安全的重构手法。

  • 一般来说,只有在设计系统时就考虑到了测试,这样的系统才容易添加测试。

    这个说到痛点上了,最近搭建的 .NET Core 项目的时候也没有考虑这方面的需求,很多功能也是基于之前的框架结构,想添加测试总感觉无从下手。

  • 数据库:渐进式数据库设计和数据库重构:借助数据迁移脚本,将数据库结构的修改与代码结合,使大规模的、涉及数据库的修改可以比较容易的展开。

  • 重构起初是作为极限编程(XP)的一部分被人们采用的。

  • 重构的第一块基石是自测试代码。

重构 - 1. 重构,第一个示例

最近在看 马丁·福勒Martin Fowler)写的《重构:改善既有代码的设计》(《Refactoring: Improving the Design of Existing Code》)第二版。

第一版应该是 1999 年出版的,第二版是 2018 年底出版的。示例的语言从 Java 换成了 JavaScript ,但其说明的重构方法适用于所有的开发语言。