重构,指对软件代码做任何更动,以增加可读性或者简化结构,而不影响输出结果。可是我们要如何才能不影响输出结果呢???答案是:测试。测试的意义在于对输出结果进行测试,用于保障现有代码的功能是正常的。一旦我们修改了代码,导致测试失败了,那我们就知道哪里改错了。
因此重构依赖于单元测试和可测试的代码(即短小、可 mock 的代码)。在重构之前,对应的代码拥有测试是信心的保证。可由于种种情况,我们的代码中不存在测试,我们就放弃代码重构吗?
所以,这篇文章讲述地是,如何在不包含测试的情况下,来对代码进行重构——IDE 重构。需要注意的是,IDE 重构在大部分的情况下是不会影响输出结果的,但是在极少数的情况下可能会影响结果——比如 IDE 出 bug 了。hiahiahiahia~
范式:IDE 重构
IDE 重构,即借助于 IDE 来对代码进行重构,其通常是由快捷键来触发。它将重构的主要工作交给 IDE,而不是由开发人员痛苦的修改代码来完成。多数时候,我们只需要按下快速键,再输入一些必要的名称、参数,重构就完成了。因此,它是每天我们的开发中,不可获缺的一部分。可以随时随地进行,而不需要预留一个专用的重构时间。
自然而然发生
那些,我们视为腐烂的代码,并不是在第一天就如此。在最初设计的时候,它们往往有着好的代码结构,良好的设计规范。只是随着时间的推移,业务不断地增加,代码量便不断地增加,缺乏有效的代码改善机制,便会使得代码腐烂。但是,我们也不能提前对代码进行重构,那样的话就是过度设计。
重构,有时在编写代码的过程中,自然而然发生的。而 IDE 重构则是,编写代码的过程中,看到可以重构的地方,按下IDE 的快速键,迅速地完成战斗。
因此,就这种角度来看,IDE 重构是自然而然发生的,它应该作为日常开发的一部分,而不是额外的花费时间进行重构。在这一点上,也普通的代码重构是不同的。普通的代码重构,是我们看到之前的代码写得不符合规范、设计,便进一步地想去改善质量。往往,我们也会将这处重构,放置在完成功能之后进行。
值得注意的是IDE 重构所能做的内容有限,远不及常规重构的范围来得广泛。
如下是微前端 Mooa 框架中,最初某个函数的代码的一部分:
customEvent('mooa.routing.change', {
url: eventArguments.url,
app: activeApp['appConfig']
})
而随着业务的演进,代码便进一步便得复杂:
if (activeApp.mode === 'iframe') {
...
if (iframeEl && iframeEl.contentWindow) {
...
contentWindow.dispatchEvent(customEvent(MOOA_EVENT.ROUTING_CHANGE, eventArgs))
}
} else {
customEvent(MOOA_EVENT.ROUTING_CHANGE, eventArgs)
}
一旦,我们添加了一个新的条件,我们的代码会进一步复杂化。而在那之前,对于 if 语句而言,我的做法是什么也不做,因为无法预料它的演变。一来,这样的 if 语句没有多少演进的方式,提供重构成多态则进一步复杂代码。二来,当遇到新的需求变更时,原先的重构可能等于白做,甚至于会影响我们下一步的编程。
不过,在上述的过程中,我做了两件事:
提取了公有的变量
提取了公有的参数
而这些都可以通过 IDE 的提取变量的快捷键来完成。
从容不迫应对
由于 IDE 重构,更多的是依赖于工具,而不是依赖于编程经验。它可以为年轻(编程经验少)的程序员,带来一定的信心,使得他/她们开始敢去重构代码。你可能也会担心,它带好一个不好的习惯,即在不编写测试的时候,进行代码重构。所以,它看上去像是一把双刃剑。但是,在我的想法来看,只要能开始去改变,那就是一个好的开始。每个老年(资深)程序员,都是从一个坑过去的,要不就不是资深的程序员。
换句话来说,当你遇到一个棘手的问题,而有一个资深的程序员,快速地帮你解决了问题,说明他/她也走过这个坑。比如,你应对一个 git 冲突的时候,可能会束手无策,哪怕你对 SVN 再熟悉,你也要面对这种变化。这样的问题解决多了,也就有经验了。这种方式和 IDE 重构一样,都只是使用工具而已。
即使都是使用工具,就只是工具的熟悉程度与否。练习,多加练习,然后掌握 IDE 重构,迈出了第一步。再向前,你就开始走向真正的重构了。
最小犯错成本
虽然说是使用 IDE 进行重构,但是在复杂的情况下,它可能还是会出错。因此,进行任何的重构之前,我们都需要打造一个安全的环境——至少,将要重构的代码应该在本地进行提交,又或者提交到服务端。它相当于我们在完成了业务功能之后,进行一系列小的修改。
即它意味着,我们可以在一个安全的时间里提交代码,又意味着哪怕是重构失败,我们也可以回退。诸如 Git 这种支持本地提交的代码版本管理工具,大概是我见过最好的减少犯错成本的工具。不论,我对本地的代码库做怎样的修改,都可以从远程获取原来的代码。
事实上,不止于还需要提供一个安全的团队环境(政治环境),即让程序员能花时间去改善代码。但是,这种重构需要存在时间的限制。
如果你花关天、一天、两天的时间,要对之前一个月的代码重构,那么并不存在问题——业务在不断叠加,每隔一段时间都会出现代码、架构上的问题。这个时候的抢救,能及时把设计正确的方式。再往后推移,可能会影响整个系统的架构,成功地实现了 “千里之堤,溃于蚁穴”。
如果一处代码的重构,需要花费四五天的时间,那么就需要正视这个问题。它不是一个小问题,可能存在多个问题。对它的重构和改善,往往需要和团队里的资深程序员一起讨论。
对于重构来说,新手程序员更加需要适当地鼓励,辅以合适的技术培训,或者进行手把手的结对编程。
缺点:有限的代码质量改善
让我们再强迫一遍:
IDE 重构是一种有限的代码质量改善
IDE 重构是一种有限的代码质量改善
IDE 重构是一种有限的代码质量改善
它只是一种快速的重构方式,对于复杂的代码结构来说,往往是无能无力的。
function openFile(willLoadFile: string, isTempFile: boolean = false) {
let imageRegex = /\.(jpe?g|png|gif|bmp|ico)$/i;
let htmlRegex = /\.(html)$/i;
let wordRegex = /\.(doc?x)$/i;
if (imageRegex.test(willLoadFile)) {
return mainWindow.previewFile(willLoadFile);
} else if (htmlRegex.test(willLoadFile)) {
return openHtmlPage(BrowserWindow, willLoadFile);
} else if (wordRegex.test(willLoadFile)) {
return shell.openItem(willLoadFile);
}
}
不过,要对于这一种类似的 if
语句来说,换成 switch
语句并没有改善多少可读性。
这个时候,我们可能会考虑换成多态,这样便需要依赖于手动进行重构——需要依赖于测试。但是多态,又可能进一步复杂化代码——行数和类变得更多了。
工具
既然,本文的标题是 IDE 重构,但是它应该是叫工具重构。主要是笔者,懒,懒得配置编辑器,所以就习惯使用了 IDE。要怪就怪我司,给程序员配了 IDE。
IDE
大量的 IDE 支持重构功能,打开你最常使用的工具,你也会发现:咦,我的 IDE 也有这样的功能。如 Jetbrians 家的 Intellij IDEA 对于静态类型语言,如 Java 的支持就更好了:
如上图所示,其拥有大量地可用的重构功能。反正都是交了信仰费的人,不掌握好工具,对不起自己的辛辛苦苦的码字。
Editor + 重构插件
现在的,普通的编辑器,也拥有这样的重构功能,如 Visual Studio Code 便自带了一些。哪怕是诸如 Vim 这种原始的 IDE,加上对应的重构插件,也可以完成相应功能。如在 Visual Studio Code 中提供了一些对应的重构功能。其中比较常用的有:
提取变量
提取方法
重命名
诸如 JavaScript 这一类的动态语言,则会应了那句话:“动态类型一时爽,代码重构火葬场”。因为各式的智能提示,都依赖于对语言的静态类型检查,但是动态类型往往很难实现这一点。使用 TypeScript 对于可维护的代码是一个可好的选择。
示例
相关的内容,可以看我之前录的视频:https://v.youku.com/vshow/idXMTI2NjQ0NDAxMg==.html