本月初的时候朋友和我说《重构》出第 2 版了,我兴冲冲地下单,花了一个礼拜时间一口气把它读完后,才有了这篇书评。掩卷沉思,我无比赞同豆瓣网友“天心一”的评论:前端
这本书虽然很流行,可是应该看它而没有看的人,仍是太多太多了。程序员
做为一个开发者,2012年初识本书的时候,我在写 Java;2019年本书再版,我在写 JavaScript。真是应了那句老话儿:“凡是能够用 JavaScript 来写的应用,最终都会用 JavaScript 来写。”面试
JavaScript 特别适合重构,由于它很容易写的没法维护。 编程
固然这只是个玩笑,实际上做者也解释过:重构背后的理念和架构适用于任何编程语言,选择 JavaScript 只是由于它应用的比较普遍。不管使用哪一种编程语言均可以写出优秀的或者糟糕的代码,一样也均可以以本书的思路和技巧进行重构。设计模式
使用 JavaScript 展现代码范例,并不意味这本书中介绍的技巧只适用于JavaScript。架构
对比新旧两版,做者“重构”了这本书:前几章有所扩展,后几章结构调整较大,移除了原来的 12-14 章。总的来讲,重构后的第 2 版更接地气、更适应时代:再也不有“大型重构”,更多地聚焦操做的细节。编程语言
“Fowler 先生不只没有拔高,反而把功夫作得更扎实了。”——摘自译者序函数
虽然本书的副标题是“改善既有代码的设计”,但通读全书以后,我以为这本书对于设计新系统时如何避免“坏味道”也是颇有指导意义的。单元测试
提重构就不能不提敏捷开发,马丁·福勒自己就是敏捷开发的发起者之一。敏捷做为“当红炸子鸡”,与重构有着不少类似的地方。测试
一是,这二者都容易成为“挂羊头,卖狗肉”中的“羊头”,不少状况下,所谓的重构就是抽出时间来重写现有的几乎没法维护的代码,就如同不少“敏捷”只作到了“不拒绝需求变动”而没有真正作到响应变化;二是,它们实现起来都是必定难度且它们的实践过程能够是交叉的——它们都着眼于具体细节而不是空架子,都欢迎变化,都强调小步快走、持续改进;三是,敏捷开发很重要的两个环节就是设计与重构,二者相辅相成,彼此互补,在实践的过程当中保持较强的适应力。
能够说,我在重构过程当中遇到的问题大多都能在本书中找到答案。
咱们看看做者对重构的定义:
**重构(名词):**对软件内部结构的一种调整,目的是在不改变软件可观察行为的前提下,提升其可理解性,下降其修改为本。
**重构(动词):**使用一系列重构手法,在不改变软件可观察行为的前提下,调整其结构。
为什么重构、如何重构、重构的原则与手法,均可以在这本书中找到。从第 5 章起做者提供了多达 300 页的重构名录、60 余项重构的具体技巧(老版本是 70 多项,新版本移除了大规模项目的重构)。我以为这一份很是详尽的重构手法清单更接近于字典,适合粗读以后在用到的时候再具体查阅。
至于何时可以用到这份名录,做者在第 3 章也有介绍:当代码有了“坏味道”就能够着手进行重构了。所谓“坏味道”,我认为并不是是一程不变的准则,而是须要根据团队、项目、采用的技术栈等各方面综合得出的一种没法定量描述的经验。因此,做者用了“味道”这样一种体验来代指须要重构的地方。在做者列出的每种“坏味道”中,都给出了对应的重构手法。虽然做者罗列的 20 多种“坏味道”覆盖面很广,可是你和你的团队仍然能够总结出本身的经验来指导重构。实际上,与第 1 版相比,第 2 版中的“坏味道”增长了“神秘命名”“全局数据”“循环语句”,删除了“不完美的库类”。
我认为本书最重要也最容易被忽略的章节就是第 4 章——构筑测试体系。在第 4 章中,做者经过一个生产计划的示例一步一步的构建了一个完整的单元测试体系。显然,掌握单元测试是有必定成本的,这就致使有些开发者(尤为是前端领域)彻底不注重单元测试。他们认为测试是QA的职责,本身只须要保证冒烟测试经过便可。然而反直觉的是,良好的单元测试不可是重构的先决条件和好帮手,并且能帮咱们整理设计的思路,从而更好的写出优秀的代码。由于在写单元测试的时候,咱们会假设本身是一个“代码破坏者”,思考如何破坏代码的运行、寻找那些可能出错的边界条件。单元测试的编写和运行能够在写完代码后进行,也能够在写代码以前动手。先写单元测试再写代码的技巧叫做测试驱动开发(TDD),也是敏捷开发的基石之一。关于TDD的技艺,做者的好友 Kent Beck 专门写了一本书,即《测试驱动开发》。
做者在第 1 章的示例中提到:“小步快走,代码永远处于可工做状态。”并且做者特地强调:“每当我要进行重构的时候,第一个步骤永远相同:我得确保即将修改的代码拥有一组可靠的测试。”
对于单元测试,我有一点小小的心得能够与你们分享:**尽可能编写纯函数。**纯函数是没有反作用的函数,给出一样的参数值,纯函数老是返回一样的结果,它不依赖于参数之外的值。显然,纯函数更便于单元测试。
固然单元测试也不是万能的,它不可能检出全部的bug,并且单元测试集的覆盖率也是一个见仁见智的指标,具体须要写多少单元测试,覆盖多少代码,都是须要咱们在开发中结合实际状况本身权衡的。不管如何,单元测试一直是一中很是重要却经常被忽视的技能。
另外,我在开发实践中坚持一个“432”的原则,供你们参考:
有些朋友对“重构”是不支持甚至是深恶痛绝的。
他们以为重构是“给飞行中的飞机修引擎”,有可能出现不少问题却带不来多少拿得出手的成绩;重构老是会在“不经意间”破坏原有功能,带来的麻烦不少,投入与收益彻底不成比例,也不多会是面试的重点,花精力在这上面实在是费力不讨好。
在创业公司里基本不会有重构的呼声,缘由无须赘言;而在一些大企业里,leader们也不是都喜欢重构,由于花时间重构意味着占用了开发新功能的时间,在代码还能跑起来甚至看起来跑得还不错的时候去重构无疑是多此一举;与重构带来的风险相比,重构带来的好处就不是那么有说服力了。
代码的变更意味着须要进行回归测试,而敏捷当道的时代,每一个迭代中QA的关注重点都在新功能上,可以分配给回归测试的精力颇有限,而在测试经过后的重构极有可能致使这次变动对QA不透明,无形中增长了上线的风险。
我认为以上几种反对重构的场景都是不恰当的重构致使的。
你们只是愈来愈接纳“重构”这个词,由于这个词听起来很好,有一种积极应对变化的感受,但真正在作的仍是跟之前同样,毫无规矩的修改。
在实践中,重构的要求是很高的:它须要有足够详尽的单元测试,须要有持续集成的环境,须要随时随地在“小步伐地永远让代码处于可工做状态”下去进行改善。正是由于许多项目的“重构”是在并不知足以上条件也没有通过成本估算、策略规划的状况下进行的,天然很容易致使失败。
实际上,还有一部分开发者虽然认识到了重构是提高代码质量的有效手段,是诸如“在当下努力工做,以避免往后有更多的活儿”此类观念的具现。然而在某种程度上说,这在当前996.icu大环境下是不适用的。关于这一点就只能见仁见智、本身衡量了。
没有银弹
最后,我想说一句: 没有银弹。
重构和设计模式同样,是对于最佳实践的提炼,是一系列技巧的集合,它不是打通任督二脉的灵丹妙药。若是你是一个有追求但却历来没有系统地了解太重构的程序员(固然我不相信世界上会有这种程序员),那你会发现,你在平常工做中不经意间已经用过了这本书中提到的各类重构手法。
重构是注重实践的技艺,仅仅了解其理念而忽视实践则有如抟沙做饭,白费心思;而企图把它当作“万金油”来解决全部问题也只会陷入不恰当重构的陷阱,最终得不偿失。只有在合适的场景下恰当的实践,才会实现其应有的价值。