编程方法论:重构

ddlee · April 8, 2017

本文内容主要整理自lynda.com课程Programming Foudations: Refactoring CodeMartin Fowler重构。全部例子来源于refactoring.com

内容大纲: structure

Introduction

intro-method

定义

重构是在不影响软件功能的情况下,重新组织代码,使之更清晰、更容易理解的过程。

  • 前提:功能不变
  • 行为:改写代码
  • 目的:提高可理解性

大白话讲,重构就是改写,造福以后需要理解这段代码的人们。

重构不是什么

给一件事物下定义,有时候从反方面更好讲些。比如你难给正义下一个定义,但很容易举出什么是非正义的例子。

  • 重构不是Debug,代码已经运行良好
  • 重构不是优化
  • 重构不是添加新功能

也就是说,重构对使用代码的人没有任何好处,对使用者来讲,代码是黑箱。重构是准备给要打开黑箱的人,而那个人常常是你自己。

玄学:Code Smells

玄学二字是我自己加的。Martin Fowler当然没有这样说。我只是表达一下对无法精确描述的定义的敬意。

我的理解是Code Smells是best practice和code style的总和,直接和根本来源是自己的代码经验。所谓语感、文笔、血淋淋的人生道理。

准备工作:自动化测试

重构当然不是breaking the code,写好测试,保证代码仍能正常运行。

重构范例:方法层面

首先是一句良言:哪里加了注视,哪里可能就需要重构。

这一点的潜在信念是,好的代码是self-explained的,通过合理的命名、清晰的组织,代码应该像皇帝的新装那样一目了然。

可以用于重构的工具

常见的IDE会有重构的功能,如重命名变量。另外,一个严厉的Linter加上像我这样的强迫癌患者会将风格问题扼杀在摇篮之中。

几个例子

举例均以Code smell和重构建议两部分构成,较抽象(wo kan bu dong)的给出代码。

Extract Method

Code smell: 太长的方法,带注释的代码块

重构: 提取,新建,用评论命名

Remove temps

Code smell: 冗余的临时变量(本地)

重构:

  • Replace with Query: 把表达式提取为方法(规模较大)
  • Inline temps: 直接用表达式代替这个变量(规模较小)
Add temps

Code smell: 同样的变量有多重含义

重构:

  • Split temporary variable: 同一个临时变量在上下文赋予了不同含义(复用),拆
  • Remove assignment to parameters: 对参数默认值的设定,在函数内新建变量,初始化这个新变量

Remove assignment to parameters的例子:

//Before
int discount (int inputVal, int quantity, int yearToDate) {
  if (inputVal > 50) inputVal -= 2;

//After Refactoring
int discount (int inputVal, int quantity, int yearToDate) {
  int result = inputVal;
  if (inputVal > 50) result -= 2;

这一点基于的信念是,参数只能代表被传进来的变量,不应该在本地再赋予别的含义。

重构范例:类与方法

class and method

Move Method

Code smell: feature envy(依恋情结)

用中文来说,是指某个方法操作/使用(依恋)某一个类多过自己所处的类,我们用“出轨”这个词来表示这种现象。但这是违反婚姻法的,因而,我们的重构手段就是,把这个方法移动到它依恋的类中,圆满一段木石良缘。*

重构: 圆满木石良缘。

Extract Class

Code smell: 规模太大的类

重构: 把部分移出,自立门户

Inline Class

Code smell: 冗余的类

重构: 像我这中请天假组内运转几乎不受影响的人,应该清除掉(这是瞎话)

Condition Focused(条件语句相关)

Code smell: 写完判断条件自己都看不懂/看着难受

重构:

  • Decompose conditional: 分解
  • Consolidate conditional expression: 多项条件指向同一段后续操作,提取这些条件为方法
  • Consolidate duplicate conditional fragments: 不同条件的后续操作中含有共同的部分,将共有部分提取出来(不管哪个条件总要执行)
  • Replace condition with polymorphism: 针对有判断分支的方法,替换成多态方法
  • *Replace type code with subclass**: 针对有判断分支的类,替换为子类

Consolidate conditional expression的例子:

//Before
double disabilityAmount() {
  if (_seniority < 2) return 0;
  if (_monthsDisabled > 12) return 0;
  if (_isPartTime) return 0;
  // compute the disability amount
//After Refactoring
double disabilityAmount() {
  if (isNotEligableForDisability()) return 0;
  // compute the disability amount

重构范例:数据相关

class and method

Move field

code smell: inverse feature envy(自造)

某一个类使用某一数据比该数据所属的类还多。

重构:送给你了还不行吗!?

Data Clumps(数据团)

code smell: 某些数据总是抱团出现

重构:

  • Preserve whole object: 在一个方法中反复提取某个类的一些属性,将整个对象传入
  • Introducing parameter object: 把这些参数合并为一个类,把新建的类传入

Similifying

重构:

  • Renaming: 顾名思义
  • Add or remove parameters: 顾名思义
  • Replace parameter with explicit Method: 根据不同参数值新建专属的方法
  • Parameterize Method: 与上者相反,把不同方法合并,传入参数
  • Separate queries form modifiers: 将找到数据和更该数据两个操作拆成两个方法

Replace parameter with explicit Method的例子:

//Before
void setValue (String name, int value) {
  if (name.equals("height")) {
    _height = value;
    return;
  }
  if (name.equals("width")) {
    _width = value;
    return;
  }
  Assert.shouldNeverReachHere();
}
//After Refactoring
void setHeight(int arg) {
  _height = arg;
}
void setWidth (int arg) {
  _width = arg;
}

Pulling and pushing(升级与降级)

  • Pull up method and pull up field
  • Push down method and push down field

解决方法、数据归属不合理的问题。

高阶重构(大坑,大坑)

Convert procedural design to objects

化函数式变成为面向对象,祝好运。

拾遗

写代码和改代码是一个不断被自己坑和被别人坑的旅程。且行且珍惜。

Cheers, have a good one.

@ddlee