《重构》学习概述
Posted RikkaTheWorld
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《重构》学习概述相关的知识,希望对你有一定的参考价值。
系列文章目录
1. 《重构》学习(1)拆分 statement 函数
2. 《重构》学习(2)拆分逻辑与多态使用
3. 《重构》学习(3)概述
@[TOC]
2. 重构的原则
2.1 什么是重构
重构既可以是名词,也可以是动词
- 作为名词
它表示对软件内部结构进行调整,目的是在不改变软件可观察行为的前提下,提高其理解性,降低其修改的成本 - 作为动词
它表示使用一系列手法,用于改变代码结构,例如前面学过的:提炼函数手法
在第一章的学习中,我们对一个 账单计算的函数进行了重构(n), 重构的时间大约是一两个小时,但是我们却运用了至少五种的重构方式(v)。
在我们平时工作中,经常会看到有人借着“重构”的名义,对某些功能进行大刀阔斧的改造(比如我自己)
而重构的关键则是在于 以微小的步骤且软件可正常运行的步骤,一步步达成大规模的调整,因此在重构的任意时刻停下来,都不会影响软件的集成、测试和发布。
如果有人在“重构”过程中,会让该功能出现一两天甚至以上的时间是不可用的,那可以确定的是他所做的事情,不是重构。
一般称“结构调整”(restructuring)
为对程序结构进行任意程度上的调整,而重构是其子集。
可观察行为:代表重构前和重构后的,这些代码做的事情大致是一样的,但并非完全一样,因为重构可能会改变调用栈。
如果存在一些显性的Bug,在重构后依然会存在。 (如果想去除Bug,那么先要保证在先Fix Bug后,再进行重构,或者调换顺序)
如果存在一些隐性的、潜在的、无人发现的Bug,可以在重构时候将其修复掉。
重构和性能优化的区别是: 两者都会在不影响程序正常运行的情况下,修改其代码,
- 重构在调整结构后,程序可能会运行的更快,也可能运行的更慢,但是代码相比之前更容易理解,且更容易修改
- 性能优化更关注程序运行的更快,但是可能会产生难理解、难维护的代码
2.2 两个帽子概念
书中提出,我们将自己的开发分成两个部分:
- 添加新功能部分
这段时间,我们只管新增功能,新增单元测试,保证程序能符合预期 - 重构部分
这段时间,我们会调整现有程序结构,并且通过原有的测试、不添加任何测试,在极为有必要的情况下才需要添加测试
我们平时的开发中在两种模式中来回切换,但我们要做到无论在那种模式下,都要清楚自己的所在模式下所需要达成的目标。
2.3 重构的意义
重构并不是给程序包治百病的灵药,但是它绝对算得上是控制程序的良药。
2.3.1 改进软件的设计
如果没有重构,程序的架构会随着版本的迭代愈发腐败变质。
因为往往迭代/需求是短期添加,循环往复,当我们为了短期的目的而去修改代码时,经常没有完全理解架构的整体设计,于是代码逐渐失去了自己的结构。
这样就会导致带啊越来越难阅读,越难看出代码所代表的设计意图,就越难保护其设计,于是设计就腐败的越快。经常性的重构有助于维持代码该有的形态。
完全一样的代码,设计欠佳的程序往往需要更多的代码,这是因为代码在不同的地方使用完全相同的语句做同样的事,因此改进设计的一个重要方向就是消除重复代码。 减少代码量并不会使得系统运行更快,因为这对程序的资源占用几乎没有任何明显的影响。 然而代码量减少将使未来可能的程序修改动作容易的多。 消除重复代码,就可以确定所有事物和行为在代码中只表述一次,这正是优秀设计的根本。
2.3.2 使软件更容易理解
这个是前一章就在一直强调的,重构最重要的点:易于修改和理解。
优秀的结构可以让后来的人以极少的时间理解自己写出来的代码,从而减少修改的时间。
2.3.3 帮助找到Bug
这个就有点玄学了。 书中说重构后,就可以深入理解代码的所作所为,并立即把新的理解反映在代码中,在验证一些假设的同时,可以帮助找到Bug。
2.3.4 提高编程速度
这里有两张图片:
软件内部质量优秀的程序,在需要添加新功能时,容易找到在哪里修改、如何修改。良好的模块划分使我们只需要理解代码库的一小部分,就可以做出修改了,这样引入Bug的概率也会小很多。
2.4 什么时候重构
书中推荐了一个原则:
第一次写这段代码的时候只管去写, 第二次写类似的代码的时候会产生反感,但还是在可以接受的范围,第三次再去做类似的事情,就得重构。 毕竟事不过三
2.4.1 预备性的重构:让添加新功能更加容易
重构的最佳时机就是在添加新功能之前,我们可以在写需求之前,看看现有的代码库。
做不做重构,就要看我们会不会有预兆的产生重复代码,如果我们把相同的代码复制一遍,后续修改时就要在两处地方修改,这不是一个好主意。
打个比方,我们去一个地方,肯定不是直接开车走直线过去的,而是查看地图,走大路,这样会更快,因为我们不用撞墙或者穿越森林。
修 Bug 也是同理,在寻找问题根因时,可能会发现如果把3段一模一样且都会导致错误的代码合并到移除,问题修复起来会容易的多。
2.4.2 帮助理解的重构:让代码易懂
我们最先需要理解代码在做什么,然后才能着手修改。 这段代码可能是我写的或者他人写的。 一旦在阅读时产生“这段代码到底再干什么”的时候,就可以自问:“这段代码能不能重构一下,让其一目了然”。
可以抓住一些机会:
- 修改函数名、变量名
- 长函数拆成几个小函数
- …
2.4.3 捡垃圾式重构
当我们已经知道一个函数在做什么了,但是觉得这个函数做的并不够好,例如逻辑太过迂回、两个函数几乎完全相同且可以用一个函数代替。
比如我们在做需求时,我们会发现一些垃圾代码,如果这段代码很容易重构,那么请毫不犹豫的重构它,如果重构需要花费一些精力,那么请记录它,等到当下的任务完成时回来重构它。
得益于重构的原则,即使我们清理一块代码花费好长一段时间,我们也不用担心它中途会影响到程序的运行。
2.4.4 有计划的重构和见机行事的重构
上面的重构都是见机行事的重构。
肮脏的代码必须重构,但是漂亮的代码也需要很多重构。
最常见的误解就是,重构是人们弥补过去的错误或者轻量肮脏的代码。 这是不完全正确的,因为过去的代码不一定是肮脏错误的,甚至写的挺漂亮,只是这次添加的功能时,代码看起来不合理。
所以每次修改时,首先令修改容易,再去进行这次容易的修改
有计划的重构,指的是专门花一定的时间,对代码进行重构,使得代码库更容易理解和被修改。
2.4.5 长期重构
对于一些可能要花上几个星期的大型重构,例如要替换一个正在使用的库,或者将整块代码抽到一个组件中并共享给另一支团队使用,再或者要处理一大堆混乱的依赖关系的情况。
即使如此,也不建议整个使用的团队专门进行重构,而是在未来几周的时间里逐步解决这个问题。例如当你在开发时,已经靠近了这个“重构区”的代码了,那么就可以朝改进的方向推动一点。 这样做的好处在于不会破坏程序,每次小的改动后,整个系统都能照常工作。
例如,如果想替换掉一个正在使用的库,可以先引入一层新的抽象,使其兼容新旧两个库的接口,一旦调用方完全改为使用这层抽象,替换下面的库就会容易的多。
2.4.6 复审代码时重构
这点还是挺常见于我们的日常开发的,就是让代码经验丰富的同事来code review代码, 提出建议,如果可以立即重构,那么就重构它。
2.4.7 何时不重构
- 如果看见一团凌乱的代码,但是不需要修改它,那么就不需要重构它
- 如果丑陋的代码能被隐藏在一个api下,就可以容忍它继续保持丑陋
- 如果重写比重构容易,就别重构了。因为重构有些成本,很多时候我们都不了解这个成本
2.5 重构、架构和YAGNI
重构对架构最大的影响在于,通过重构,我们能得到一个设计良好的代码库,使其能够优雅地应对不断变化的需求。“在编码之前先完成架构”这种做法最大的问题在于,它假设了软件的需求可以预先充分理解。但经验显示,这个假设很多时候甚至可以说大多数时候是不切实际的。只有真正使用了软件、看到了软件对工作的影响,人们才会想明白自己到底需要什么,这样的例子不胜枚举。
比如在新增一个功能函数时,我们会可能会思考“如何让该函数更加灵活”,然后会预测未来很多会发生的场景,比如它可能会传入什么参数进来,所以我们可能会给函数加好多个入参。但是很多时候,我们的预测是有问题的,与其思考让函数更加灵活,不如让函数只根据当前的需求环境来传参,在此基础上把代码结构写好。 如果一种灵活性机制不会增加复杂度(比如添加一些小函数),可以引入它,但如果一种灵活性会增加软件复杂度(例如给android增加 DataBinding、ViewBinding),那就要证明是否值得被引入。
上述的这种设计被称为: 增量设计
或者 YAGNI(you aren’t going to need it)
, 这种工作方式必须有重构作为基础才可靠。
使用这种程序设计思路并不是完全不用考虑架构设计,而是要找到一个平衡点, 平衡 是否需要引入灵活的机制进行重构。
2.6 小结
该章节全是文字,主要对重构进行概述,有一些比较常识性的知识我这边就省略掉了,过得比较快速,下面是一些总结的图:
以上是关于《重构》学习概述的主要内容,如果未能解决你的问题,请参考以下文章