找出那些代码里的坏味道吧——《重构》笔记(一)

image

写在前面

重构起源于smalltalk,发扬于java和C#,它们都有成熟的重构工具。有一种说法是,《重构》和设计模式是java行业的圣经。我个人觉得,重构就像修缮忒休斯之船一样,只是我们是将船上的木板全部替换成了钢板。一个程序员如果看自己一年前写的代码而没有重构的念头,那么这个程序员可能这一年没有什么进步,当然也可能这块代码已经不需要重构了,但我想这种概率挺低的。

因为各种原因,没有人能在框架设计一开始就能套用设计模式写好,所以我们的代码需要不断的重构。Gof的设计模式就是重构的目标。但是重构也是必须有理论准备的,必须系统化的进行,否则可能引入不可察觉的错误,风险更大。

本文记录的重点只在于指出代码里的坏味道,如果在你的代码中“闻到”了这些坏味道就说明这块的代码可能需要重构了,至于怎么重构,书中有一套系统的方法,请期待后续的文章。

最后,希望重构能变成像空气和水一样普通的技术。

代码的坏味道有如厨房的油污,开始时不会觉得有多大的影响,但时间长了就会累积成“恶心”又难以“清除”的污渍。我们需要保持每天的清扫,而不是定期的“大扫除”。上面的“味道”就是一点一点的“油星”溅在“厨房”里,看到它们就顺手擦掉吧!

一、重构原则

1.重构的定义

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

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

  • 关于重构需要强调的两点:

    • 重构的目的是使软件更容易被理解和修改。
    • 重构不会改变软件可观察的行为——重构之后软件功能一如以往。

2.重构的目标

  • 进行重构之后,我们希望我们的程序能达到以下几点目标:
    • 容易阅读
    • 所有逻辑都只在唯一地点指定
    • 新的改动不会危及现有行为
    • 尽可能简单表达条件逻辑

3.何时不该重构

  • 现有代码根本不能正常运行时,应该重写,而不应重构。
  • 如果项目已近最后期限,你也应该避免重构。

二、代码的坏味道

  • Duplicate Code(重复代码)
    • 如果你在一个以上的地点看到相同的程序结构,那么可以肯定,设法将它们合二为一,程序会变得更好。
  • Long Method(过长函数)
    • 拥有短函数的对象会活的比较好,比较长。
    • 我们遵循这样一条原则:每当感觉需要以注释来说明点什么的时候,我们就把需要说明的东西写进一个独立函数中,并以其用途(而非实现手法)命名。
    • 条件表达式和循环常常也是提炼的信号。
  • Large Class(过大的类)
    • 如果想利用单个类做太多事情,其内往往就会出现太多实例变量。
    • 和“太多实例变量”一样,类内如果有太多代码,也是代码重复、混乱并最终走向死亡的源头。最简单的解决方案是把多余的东西消弭于类内部。
    • 这里有个技巧:先确定客户端如何使用它们(指“太多的实例变量”),然后运用Extract Interface为每一种使用方式提炼出一个接口。这或许可以帮助你看清楚如何分解这个类。
  • Long Parameter List(过长参数列)
    • 如果你手上没有所需的东西,总可以叫另一个对象给你。因此,有了对象,你就不必把函数需要的所有东西都以参数传递给它了,只需传给它足够的、让函数能从中获得自己需要的东西就行了。
  • Divergent Change(发散式变化)
    • 指的是,如果一个类中引入一个新的变化,需要修改多个函数,则需要考虑将这个类一分为二。
    • 针对某一外界变化的所有相应修改,都只应该发生在单一类中,而这个新类内的所有内容都应该反应此变化。
  • Shotgun Surgery(霰弹式修改)
    • Divergent Change是指“一个类受多种变化的影响”,Shotgun Surgery则是指“一种变化引发多个类相应的修改”。这两种情况下你都会希望整理代码,使“外界变化”与“需要修改的类”趋于一一对应。
  • Feature Envy(依恋情结)
    • 函数对某个类的兴趣高过对自己所处类的兴趣。
    • 判断哪个类拥有最多被此函数使用的数据,然后就把这个函数和那些数据摆在一起。
    • 最根本的原则是:将总是一起变化的东西放在一块儿。数据和引用这些数据的行为总是一起变化的,但也有例外。如果例外出现,我们就搬移那些行为,保持变化值在一地发生。
  • Data Clumps(数据泥团)
    • 你常常可以在很多地方看到相同的三四项数据:两个类中相同的字段、许多函数签名中相同的参数。这些总是绑在一起出现的数据真应该拥有属于它们自己的对象。
    • 一个好的评判办法是:删除众多数据中的一项。这么做,其他数据有没有因而失去意义?如果它们不再有意义,这就是个明确信号:你应该为它们产生一个新对象。
  • Primitive Obsession(基本类型偏执)
    • 对象的一个极大的价值在于:它们模糊(甚至打破)了横亘于基本类型和体积较大的类之间的界限。你可以轻松编写一些与语言内置(基本)类型无异的小型类。
    • 对象技术的新手通常不愿意在小任务上运用小对象——像是结合数值和币种的money类、由一个起始值和一个结束值组成的range类、电话号码或邮政编码等的特殊字符串。你可以运用Replace Data Value With Object将原来单独存在的数据值替换为对象,从而走出传统的洞窟,进入炙手可热的对象世界。
  • Switch Statements(switch惊悚现身)
    • 面向对象程序的一个最明显特征就是:少用switch(或case)语句。
    • 大多数时候,一看到switch语句,你就应该考虑以多态来替换它。
    • 如果你只是在单一函数中有些选择事例,且并不想改动它们,那么多态就有点杀鸡用牛刀了。
  • Parallel Inheritance Hierarchies(平行继承体系)
    • 每当你为某个类增加一个子类,必须也为另一个类相应增加一个子类。
    • 消除这种重复性的一般策略是:让一个继承体系的实例引用另一个继承体系的实例。
  • Lazy Class(冗赘类)
    • 你所创建的每一个类,都得有人去理解它,维护它,这些工作都是要花钱的,如果一个类的所得不值其身价,它就应该消失。
  • Speculative Generality(夸夸其谈未来性)
    • 当有人说“嗷,我想我们总有一天需要做这事”,并因而企图以各种各样的钩子和特殊情况来处理一些非必要的事情,这种坏味道就出现了。
    • 如果所有装置都会被用到,那就值得那么做;如果用不到,就不值得。用不上的装置只会挡你的路,所以,把它搬开吧。
  • Temporary Field(令人迷惑的暂时字段)
    • 有时你会看到这的对象:其内某个实例变量仅为某个特定情况而设。
    • 请使用Extract Class给这个可怜的孤儿创造一个家,然后把所有和这个变量相关的代码都放进这个新家。
  • Message Chains(过度耦合的消息链)
    • 如果你看到用户向一个对象请求另一个对象,然后再向后者请求另一个对象,然后再请求另一个对象……这就是消息链。
    • 先观察消息链最终得到的对象是用来干什么的,看看能否以Extract Method把使用该对象的代码提炼到一个独立函数中,再运用Move Method把这个函数推入消息链。如果这条链上的某个对象有多位客户打算航行此航线的剩余部分,就加一个函数来做这件事。
  • Middle Man(中间人)
    • 人们可能过度运用委托。你也许会看到某个类接口有一半的函数都委托给其他类,这样就是过度运用。这时应该使用Remove Middle Man,直接和真正负责的对象打交道。
  • Inappropriate Intimacy(狎昵关系)
    • 有时你会看到两个类过于亲密,花费太多时间去探究彼此的private成分。
    • 就像古代恋人一样,过分狎昵的类必须拆散。你可以采用Move Method和Move Field帮它们划清界线,从而减少狎昵行径。
    • 继承往往造成过度亲密,因为子类对超类的了解总是超过后者的主观愿望。
  • Alternative Classes With Different Interfaces(异曲同工的类)
    • 如果两个函数做同一件事,却有着不同的签名。请运用Rename Method根据它们的用途重新命名。但这往往不够,请反复运用Move Method将某些行为移入类,直到两者的协议一致为止。
  • Incomplete Library Class(不完美的类库)
    • 麻烦的是库往往构造得不够好,而且往往不可能让我们修改其中的类使它完成我们希望完成的工作。
    • 如果你只想修改类库的一两个函数,可以运用Introduce Foreign Method;如果想要添加一大堆额外行为,就得运用Introduce Local Extension。
  • Data Class(纯稚的数据类)
    • 所谓Data Class是指:它们拥有一些字段,以及用于访问(读写)这些字段的函数,除此之外一无长物。
    • Data Class就行小孩子,作为一个起点很好,但若要让它们像成熟的对象那样参与整个系统的工作,它们就必须承担一定责任。
  • Refused Bequest(被拒绝的遗赠)
    • 如果子类复用了超类的行为(实现),却又不愿意支持超类的接口,Refused Bequest的坏味道就会变得浓烈。拒绝继承超类的实现,这一点我们不介意;但如果拒绝继承超类的接口,我们不以为然。不过即使你不愿意继承接口,也不要胡乱修改继承体系,应该运用Replace Inheritance With Delegation来达到目的。
  • Comments(过多的注释)
    • 常常会有这样的情况:你看到一段代码有着长长的注释,然后发现,这些注释之所以存在乃是因为代码很糟糕。
    • 如果你不知道该做什么,这才是注释的良好运用时机。

三、两句重要的话

  • 重构的基本技巧——小步前进,频繁测试。
  • 模式是你希望到达的目标,重构则是到达之路。

本系列文章

找出那些代码里的坏味道吧——《重构》笔记(一)
重构,第一个案例(C++版)——最初的程序
重构,第一个案例(C++版)——分解并重组Statement()
重构,第一个案例(C++版)——运用多态取代与价格相关的条件逻辑

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

推荐阅读更多精彩内容