大家好,你们的简单猿来了。
今天我们聊一下《重构:改善既有代码的设计》这本书。以下简称为 “重构”。
1、什么是重构?
按本书中的说法,重构这个概念被分成了动词和名词的方面被分别阐述:
- 重构(名词):对软件内部结构的一种调整,目的是在不改变软件可观察行为的前提下,提高其可理解性,降低其修改成本。
- 重构(动词):使用一系列重构手法,在不改变软件可观察行为的前提下,调整其结构。
重构一词被用作任何形式的代码整理,重构的关键在于运用大量微小且保持软件行为的步骤,一步一步达成大规模的修改。
我们每一次的重构包含诸个小步骤,即便我们重构没有完成,也可以在任何时刻停下来。
如果代码在重构过程中有一两天时间不可用,基本上可以确定,所做的事不是重构。
2、重构与性能优化的区别
相同点:
- 两者都需要修改代码,并且两者都不会改变程序的整体功能。
不同点:
- 重构是为了让代码 “更易理解,更易修改,耦合度更低,维护成本更低”。这可能使程序运行得更快,也可能使程序运行的更慢。
- 性能优化则只关心程序是否运行的更快。对于最终得到的代码是否容易理解和维护就不知道了。
3、书籍介绍
我们来看一下百度百科的介绍:
本书清晰揭示了重构的过程,解释了重构的原理和最佳实践方式,并给出了何时以及何地应该开始挖掘代码以求改善。书中给出了70多个可行的重构,每个重构都介绍了一种经过验证的代码变换手法的动机和技术。本书提出的重构准则将帮助你一次一小步地修改你的代码,从而减少了开发过程中的风险。
我想需要强调一下,不管工作需要还是别人推荐,我们都要好好读一下这本书。
这本书不仅适合软件开发人员、项目管理人员等阅读,也可作为高等院校计算机及相关专业师生的参考读物。
4、为什么要重构?
- 有利于软件更好的设计
当我们如果只为短期目的而修改代码时,经常并没有完全理解架构的整体设计。于是代码逐渐失去了自己的结构。程序员便难以通过阅读代码来理解设计,就越难以保护其设计。
举一下我们项目中的现象:
如果要修改一个需求,往往需要修改多个地方重复的代码。这常常是因为代码在不同的地方使用完全相同的语句做同样的事情。更多的时候是直接CV,导致代码重复度高。
消除重复代码,我就可以确定所有事物和行为在代码中只表述一次,这正是优秀设计的根本。
我们的程序设计最终沟通的对象是计算机:编写代码告诉计算机做什么,响应结果是按照我们的指令执行的。我们编程的核心就在于“准确说出我想要的”。
但平时更多程序设计沟通的对象是 人(其他阅读代码者),还有可能几个月后还是自己,那么更加清晰的表达我们想要的,更加的顾名思义,更加简单,这件事情必不可少。
我们可以联想一下软件设计中的 KISS 原则,看一下百度百科的解释:
KISS 原则是用户体验的高层境界,简单地理解这句话,就是要把一个产品做得连白痴都会用,因而也被称为“懒人原则”。换句话说来,“简单就是美”。
英语:Keep it Simple, Stupid
我们可以再深入类比一下,平时我们写代码或者写业务逻辑的时候,可以想象如何更好的让别人理解,比如自己的家人,同事等等。
- 有利于提前找到 bug
对代码的理解,可以帮助找到系统中存在的一些隐藏的 bug。我们自己很清楚程序结构以及代码执行顺序,考虑的情况也会更全面,我们的测试用例会更完善,方便我们验证,也同步方便测试人员进行测试。
这样代码的逻辑性更严谨代码,代码更健壮。同时我们自己在别人眼里更靠谱了。
很多技术大牛或者其他行业的大佬他们都有一个特征:“平时工作拥有一些特别好的习惯”
- 有利于提高编程速度
大家可能会想重构可能会花大量的时间改善设计、提高阅读性、修改bug,不是在降低开发速度吗?
和大家说一下我们项目中的一下小故事:
一个电商团队一开始进展很快,但如果运营或产品想要添加一个新功能,这需要的时间就要长得多,并且测试的周期会更长。
因为需要花越来越多的时间去考虑如何把新功能添加到现有的业务逻辑代码内,并且测试期间会不断蹦出来的新bug修复起来也越来越慢。业务逻辑代码牵一发而动全身,耦合度极高,需要花费更多的精力才能弄明白整个系统是如何工作的。这份负担不断拖慢新增功能的速度,到最后他们恨不得从头开始重写整个系统。
另外一个社交团队针对添加新功能的速度要快很多。因为他们能利用已有的功能,基于已有的功能快速添加新功能。
两种团队的区别就在于软件的内部质量 (比如命名规范,模块之间耦合度低,函数注释很明确)当需要添加新功能时,内部质量良好的软件可以让我们更容易找到在哪里修改、如何修改。
现在我们可以改善已有代码的设计,前期我们先做一个初步设计,后期不断改善它。提前把设计方案考虑的非常完善基本不太可能,因为后期数据量、新功能都会快速迭代,后期重构必不可少。
5、什么时候重构?
- 我们生活中用的很多的事不过三原则:
第一次编写一块代码33行。第二次把这33行代码复制到其他地方。第三次再复制的话,我们一定要想着重构,比如把33行代码封装一个函数,3个地方直接调用,一个显而易见的优点。修改代码只要修改这个函数就可以,不需要多个地方修改,避免遗漏数。
6、重构的几个方向
- 添加新功能之前
重构的最佳时机就在添加新功能之前。在我们动手添加新功能之前,要看看现有的业务逻辑。
一个现象:如果我们对现有代码结构做一点微调(比如扩展一个参数),我们的工作会容易得多。可能有的函数已经提供了需要的大部分功能,只需要修改几个条件。如果不做重构,我们可能会把整个函数复制过来,修改这几个值。带来一个后果就是后期需要修改就必须同时修改多处并且要保证不能遗漏。
修复bug时的情况也是一样。在寻找问题根因时,我们会发现:把某些更新数据作与查询操作分开,会更容易避免错误逻辑交叉。
- 便于代码更易读,更易懂
我们在改代码都会遇到一些情况。可能这段代码是我写的,也可能是别人写的。
修改的前提是我们需要先看懂代码在做什么,然后才能着手修改。
我们可能看见了一段结构糟糕的条件判断,也可能希望复用一个函数,但花费了几分钟才弄懂它到底在做什么。因为命名太难以理解 $a ,$b....... 命名没有实质性规律,词不达意。
重构给我们带来的帮助——常常是立竿见影。
比如我们可以先在一些小细节上使用重构来帮助理解。给一两个变量改名,让它们更清楚地表达意图,便于其他人员理解、或者可以将长达几百行的函数拆分成几个小函数,更好控制,代码耦合度更低、函数入参过多可以扩展一个空数组来保证函数健壮性........
当我们的代码更加条例清晰,我们就会发现隐藏的问题。
例如柜子衣服很乱,如果按照一个规则(外套、裤子、短袖、长袖、袜子等)进行重构,我们可能永远都不知道柜子还有很多空间,里面放还着其他的东西。因为我们不够聪明,无法在脑海中推演所有这些变化。
有人说过,这些初步的重构就像扫去窗上的尘埃,使我们看到窗外的风景。在研读代码时,重构会引领我们获得更高层面的理解,如果只是阅读代码很难有此领悟。有些人以为这些重构只是毫无意义地把玩代码,他们没有意识到,缺少了这些细微的整理,我们就无法看到隐藏在一片混乱背后的机遇。
- 重构显而易见的垃圾代码
例如不需要循环、两个函数实现功能几乎完全相同,通过入参来区分.......
但一定要找到一个平衡点:如果我们发现的垃圾代码很容易重构,可以直接重构它。
如果需要花费一些精力与时间,我们可以记录到笔记上,完成任务再集中来重构。
如果每次经过这段代码时都把它变好一点点,积少成多,垃圾总会被处理干净。
- 自然流程不需要刻意重构
其实我们并不需要安排特定的时间来重构,而是修改到或者修复问题时顺便重构。
当作一个自然流程。因为项目中不会专门安排时间重构,绝大多数重构都在我们在做其他事的过程中自然发生。
重构不是为来我们弥补过去的错误或者清理肮脏的代码。
当然遇上了肮脏的代码,我们必须重构。
但漂亮的代码也需要很多重构:参数个数需要做到什么程度、10行代码可不可以9行解决、之前功能合理的重构,可能今天又添加新功能时可能就不再合理。整洁的代码重构起来会更容易!
很多人认为版本迭代是一个累加的过程:比如要添加新功能,我们是不是要重新封装一个函数,但优秀的程序员知道,添加新功能最快的方法往往是先修改现有的代码,使新功能容易被加入。越是在已有代码中,这样的改变就越显重要。
有时,团队做了日常的重构,还是会有部分问题逐渐累积长大,最终需要定期花费时间来解决。大部分重构应该是不起眼的、顺带解决的。
- coding review 时重构
Coding Review(代码审核)进行审核代码时,我们会可以找到代码的原作者,可以及时沟通,并相互探讨重构的益处。因为与原作者一起并肩作战,相互学习并节约沟通成本,到达重构最高效果。并强化我们自己在编程的过程中持续不断地进行代码复审。
- 长期重构,拥有代码“洁癖”
大多数重构短时间解决。自己首先要严格要求自己,保持代码洁癖的习惯,之后可以让团队达成共识,形成习惯,逐步解决。
7、什么是坏代码?
我简单列一下书中提到的
- 重复的代码
重复代码就是不同的地方有着相同的程序结构。重复代码很难维护的,如果你要修改其中一段的代码逻辑,就需要修改多次,很可能出现遗漏的情况。
- 长函数
长函数是指一个方法中代码几百行甚至更多,可读性变差,不便于理解。
- 过大类
一个类做了大多事情,可读性变差,性能也会下降。
- 过长的参数
方法中的参数数量过多的话,可读性很差。
- 数据泥团
所有的字段都放在一个类中。
- 冗余类
项目中已经有日期工具类,有些小伙伴在开发中,需要用到日期转换功能。
- 过多的注释
- switch语句
- 神奇命名
方法函数、变量、类名、全部靠自己主观意识瞎起名字。
其实大部分的情况我们我们都有一定的共识!
8、重构的方法
书中介绍了大量的方法,下面结合自己的一些理解简单概括一下:
- 结构化代码
目的:有利于意图和实现分开。结构化的代码更加便于我们阅读和理解
例如:提炼函数
- 更清楚的表达用意
目的:用一个良好命名的来解释对应代码条意义,使语义更加清晰。
例如:根据业务场景命名
- 合理的封装
目的:类太大而不容易理解
例如:拆分提炼类
- 简化条件表达式
目的:化繁为简
例如:分解我们的代码,把一段 「复杂的条件逻辑」 分解成多个独立的函数,这样就能更加清楚地表达自己的意图。
- 自测
目的:尽量减少bug
例如:准备测试数据,编写测试用例,不仅有助于我们检测 bug,更加清楚代码逻辑,模块的关联关系。
9、总结
感谢您的耐心阅读,以上就是整个学习的笔记了。希望对大家有帮助,也很推荐大家去看看这本书。
重构不是一个一蹴而就的事,需要长期的实践和经验才能够完成得很好。我们重构强调的是 使代码变得更好,拥有代码洁癖。