《重构-改善既有代码的设计》总结

1 代码的坏味道

  • 重复代码(Duplicated Code)
    同一个类的两个函数含有相同的表达式,两个互为兄弟的子类内含有相同的表达式,两个毫不相关的类中出现重复代码。

  • 过长函数(Long Function)
    把需要用注释说明的内容写进独立的函数中,并以其用途命名。

  • 过大的类(Large Class)
    单个类做太多事情,出现太多实例变量及代码。

  • 过长参数列(Long Parameter List)
    尽量通过对象传递参数。

  • 发散式变化(Divergent Change)
    一个类受多种变化的影响。

  • 霰弹式修改(Shotgun Surgery)
    一种变化引发多个类相应修改,需要将变化提炼出来。

  • 数据泥团(Data Clumps)
    相同的若干项数据出现在不同地方,这些绑在一起出现的数据应提炼到独立的对象。

  • 纯数据类(Data Class)
    类仅拥有字段以及用于访问这些字段的函数,可考虑搬迁访问字段函数周围的代码。当纯数据类不可修改且仅用于传递信息例外。

  • 基本类型偏执(Private Obsession)
    如果有一组总是被放在一起的基本类型数据,可以尝试将这组数据放到一个单独类中变成结构类型数据。

  • 依恋情结(Feature Envy)
    充血模式函数对某个类的兴趣高过自己所处类的兴趣。

  • 被拒绝的遗赠(Refused Bequest)
    子类不想继承超类所有的函数和数据,可考虑提炼继承体系。

  • 异曲同工的类(Alternative Classes with Different Interfaces)
    不同名字的类或函数做相同的事。

  • 令人迷惑的暂时字段(Temporary Field)
    类中某个字段只为某些特殊情况而设置。

  • 冗赘的元素(Lazy Element)
    消除不值得存在的元素。

  • 夸夸其谈的未来性(Speculative Generality)
    预留无用的抽象类,无用的抽象参数。

  • 中间人(Middle Man)
    类中拥有过多的中间层委托。

  • 内幕交易(Insider Trading)
    一个模块过于关注另一个模块的字段。

  • 过多的注释(Comments)
    当需要撰写注释时,先尝试重构,试着让所有注释都变得多余。如果不知道该做什么的,才是注释的良好运用时机。

2 重构手法

重新组织函数与数据,简化条件表达式与函数调用,在对象之间搬移特性,处理概括关系。

2.1 重新组织函数

  • 提炼函数(Extract Method):提炼代码并通过函数名称解释该函数的用途。
  • 内联函数(Inline Method):函数的内容清晰易懂,在函数调用点插入函数本体并移除该函数。
  • 内联变量(Inline Variable):临时变量只被赋值一次,替换临时变量为赋值的表达式本身。
  • 引入解释性变量(Introduce Explaining Variable):将复杂表达式的结果放进一个或多个临时变量,以此变量名称来解释表达式用途。
  • 以查询取代临时变量(Replace Temp with Query):临时变量保存表达式运算结果,可将表达式提炼到新函数中,替换临时变量为对新函数的调用。
  • 分解临时变量(Split Temporary Variable):临时变量不应被多次赋值,可针对每次赋值创建独立的临时变量。
  • 移除对参数的赋值(Remove Assignments Parameters):当对参数进行赋值时,可引入临时变量替代。
  • 以函数对象取代函数(Replace Method with Method Object):将大型函数放进独立对象中,再分解为多个小型函数。
  • 替换算法(Substitute Algorithm):提炼函数算法,使其更加清晰。

2.2 重新组织数据

  • 封装字段(Encapsulate Field):类中只以字段取值或设值函数来访问字段而非直接访问字段,类中public字段声明为private并提供相应的访问函数。
  • 移除设值函数(Remove Setting Method):当字段在创建后不希望被修改,应移除设值函数。
  • 封装集合(Encapsulate Collection):当函数返回一个集合时,让函数返回该集合的只读副本,并在类中提供添加、移除集合元素的函数。
  • 以对象取代数据值(Replace Data Value with Object):当数据项需要与其他数据和行为一起使用才有意义时,将数据项变成对象。
  • 以对象取代数组(Replace Array with Object):当数组中的元素各自代表不同东西,用拥有明确含义的字段对象替换该数组。
  • 将单向关联改为双向关联(Change Unidirectional Association to Bidirectional):两个类都需要使用对方特性,但其间只有一条单向链接时,添加反向指针,并使修改函数能够同时更新两条链接。
  • 将双向关联改为单向关联(Change Bidirectional Association to Unidirectional):两个类之间有双向关联,但其中一个类不再需要另一个类的特性时,去除不必要的关联。
  • 以字面常量取代魔法数(Replace Magic Number with Symbolic Constant)。
  • 以类取代类型码(Replace Type Code with Class):使用类或枚举替换数值类型码。
  • 以子类取代类型码(Replace Type Code with Subclass):对影响类行为的不可变类型码,使用子类取代。
  • 以State/Strategy取代类型码(Replace Type Code with State/Strategy):对影响类行为的不可变类型码,当无法通过继承消除,引入状态对象与继承取代类型码。
  • 以字段取代子类(Replace Subclass with Fields):当子类间的差别是返回常量数据的函数,应将常量上移到超类并消除子类。
  • 将引用对象改为值对象(Change Reference to Value):当多个类引用相同对象,且该对象很小、字段不可变且不易管理,将它变成值对象。

2.3 简化条件表达式

  • 分解条件表达式(Decompose Conditional):将复杂的条件表达式提炼到独立函数中。
  • 合并条件表达式(Consolidate Conditional Expression):多个条件得到相同结果,使用与非逻辑将多个条件进行合并。
  • 合并重复的条件片段(Consolidate Duplicate Conditional Fragments):每个条件分支上有相同的代码,将其搬移到条件表达式之外。
  • 移除控制标记(Remove Control Flag):以break或return取代控制标记。
  • 以卫语句取代嵌套条件表达式(Replace nested Conditional with Guard Clauses):拆分或反转条件,引入适当卫语句(return)以减少条件的嵌套层数。
  • 以多态取代条件表达式(Replace Conditional with Polymorphism):当条件表达式根据对象类型选择不同的行为,将表达式的每个分支放进子类的覆写函数中,再将原始函数声明为抽象函数。
  • 引入断言(Introduce Assertion):代码对某个条件做出假设,以断言明确表达这种假设,注意不可滥用。

2.4 简化函数调用

  • 修改函数声明(Change Function Declaration):修改未能表达函数用途的名称,去除没有被使用的参数。
  • 引入参数对象(Introduce Parameter Object):当多个参数总是很自然地同时出现,以一个对象取代这些参数。
  • 保持对象完整(Preserve Whole Object):当调用者从某个对象中取出若干值作为函数调用的参数,可改为传递整个对象。
  • 以查询取代参数(Replace Parameter with Query):若函数可通过其他途径获得参数值,就不应该通过参数取得该值。
  • 以参数取代查询(Replace Query with Parameter):若函数通过其他途径获得参数值成本较高,可考虑通过参数取得该值。
  • 令函数携带参数(Parameterize Method):若干函数本体包含了不同的值却做了类似的工作,可提取出公共函数,以参数表达不同的值。
  • 隐藏函数(Hide Method):通过修改可见范围,尽可能降低所有函数可见度。
  • 将查询函数和修改函数分离(Separate Query from Modifier):函数既返回对象状态值,又修改对象状态,将查询跟修改拆分出来。
  • 以明确函数取代参数(Replace Parameter with Explicit Methods):函数根据参数值采取不同行为,可根据参数值拆分到独立函数。
  • 以工厂函数取代构造函数(Replace Constructor with Factory Method):存在复杂构建或根据类型来创建不同对象,可将构建函数替换为工厂函数。
  • 封装向下转型(Encapsulate Downcast):函数调用者需要对函数返回对象执行向下转型,将向下转型动作移到函数中。
  • 以异常取代错误码(Replace Error Code with Exception):通过业务异常替代特定返回码,当外部需要提前检查时抛非受控异常,否则函数声明受控异常并由调用者处理。
  • 以测试取代异常(Replace Exception with Test):当调用的函数可能因预检查而抛出异常,应在调用函数之前先做检查。

2.5 在对象之间搬移特性

  • 搬移函数(Move Method):函数与某个类进行密切交流,可将函数搬移到该类中。
  • 搬移字段(Move Field):字段与某个类进行密切交流,可将字段搬移到该类中。
  • 提炼类(Extract Class):类做了多个类做的事,可将相关的字段和函数拆分到新类。
  • 将类内联化(Inline Class):类没有做太多事情,可将类的所有特性搬移到另一个类并删除原类。
  • 隐藏委托关系(Hide Delegate):外部通过类里的委托关系来调用另一个对象,可在类建立外部所需的所有函数,并隐藏委托关系。
  • 移除中间人(Remove Middle Man):类做了过多的简单委托动作,让外部直接调用受托类。
  • 引入外加函数(Introduce Foreign Method):当需要为无法修改的服务类增加函数,可在客户类中建立函数,并以第一参数形式传入服务类实例。
  • 引入本地扩展(Introduce Local Extension):当需要为无法修改的服务类提供额外函数,通过新类扩展或包装服务类,使它包含这些额外函数。

2.6 处理概括关系

  • 字段上移(Pull Up Field):两个子类拥有相同的字段,将该字段移至超类。
  • 函数上移(Pull Up Method):函数在各个子类中产生完全相同的结果,将该函数移至超类。若待上移函数调用了只出现于子类的函数,可在超类中为被调用函数声明抽象函数。
  • 构造函数本体上移(Pull Up Constructor Body):多个子类拥有完全一致的构造函数,可将其上移到超类,并在子类构造函数中调用它。
  • 函数下移(Push Down Method):超类中的某个函数只与部分子类有关,将这个函数移到相关的子类。
  • 字段下移(Push Down Field):超类中的某个字段只被部分子类用到,将这个字段移到相关的子类。
  • 提炼超类(Extract Superclass):多个类有相似的特性,为这些类建立一个超类,将相同特性提炼到超类。
  • 提炼接口(Extract Interface):若干客户使用类接口中的同一子集,或多个类的接口有部分相同,将相同的子集提炼到独立接口。
  • 折叠继承体系(Collapse Hierarchy):超类和子类之间无太大差别时,将它们合为一体。
  • 塑造模板函数(Form TemPlate Method):子类中某些函数以相同顺序执行类似的操作,但各操作的细节上有所不同,可将操作分别提炼到相同签名的独立函数,并将组装顺序的函数上移到超类。
  • 以委托取代继承(Replace Inheritance with Delegation):子类仅使用超类的部分内容或不需要继承而来的数据,调整子类函数令它委托超类,去掉两者之间的继承关系。

参考文献

《重构-改善既有代码的设计》

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容