《重构》学习概述

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 小结

该章节全是文字,主要对重构进行概述,有一些比较常识性的知识我这边就省略掉了,过得比较快速,下面是一些总结的图:

以上是关于《重构》学习概述的主要内容,如果未能解决你的问题,请参考以下文章

《重构》学习常用的重构手法 上

《重构》学习常用的重构手法 下

C#重构学习

10 个你可能还不知道 VS Code 使用技巧

设计模式之美学习-重构

C#重构学习2