本文内容主要整理自lynda.com课程Programming Foudations: Refactoring Code和Martin Fowler的重构。全部例子来源于refactoring.com。
内容大纲:
Introduction
定义
重构是在不影响软件功能的情况下,重新组织代码,使之更清晰、更容易理解的过程。
- 前提:功能不变
- 行为:改写代码
- 目的:提高可理解性
大白话讲,重构就是改写,造福以后需要理解这段代码的人们。
重构不是什么
给一件事物下定义,有时候从反方面更好讲些。比如你难给正义下一个定义,但很容易举出什么是非正义的例子。
- 重构不是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;
这一点基于的信念是,参数只能代表被传进来的变量,不应该在本地再赋予别的含义。
重构范例:类与方法
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
重构范例:数据相关
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.