当提起代码重构时,我们究竟要做什么?

本文是在读《重构:改善既有代码的设计》一书的第二章时趁热写下的,结合了自己的思考与理解。

如有错漏,还望不吝批评指正。

对于重构一词,很多人对它还停留在模糊的概念上,会把这些场景认为是重构:

“这代码实在是太丑了,我有更好看的写法,我想改掉它” “性能不好,我得改造改造这里”

甚至是 “这技术栈有问题吧,我重新实现一下这个项目吧”

这未免有点太摸不到底了,仿佛重构是个筐,啥都往里装。

众所周知“未知是人类的恐惧之源”,摸不清重构是什么,于是害怕影响项目进度或质量就会让人“谈重构色变”。那我们究竟能从重构中获得什么,重构会不会造成严重的影响,有没有什么原则

这需要探究重构的本质。让我们从重构的目的出发,一探它的本质。

重构的目的

开~门见山,其实重构的目的听起来很简单,那就是:让代码更容易理解,更容易修改

看到这里,你可能会想“就这?”,别急别急,其实这个目标没那么简单。

学过软件工程学或者多多少少有了解的同学都知道,衡量软件质量有很多指标:可靠性健壮性安全性效率等等。但假如你接手了一段别人的代码你怎么能保证你的后续开发能满足这些要求呢,甚至不说满足这些要求,就是简简单单把后续功能做好你就得先能读懂前人的代码,如此才能安心在前人的代码上修修改改。

然而事与愿违,随着软件迭代,最初的看似“完美”的架构会慢慢腐败,越来越难以阅读源代码。开发尚且变得困难,更别提改进了。如此循环,代码结构流失得愈来愈严重,积累成一坨谁也不愿意碰的烂摊子。

“代码结构的流失有累积效应。”

“当人们只为短期目的而修改代码时,他们经常没有完全理解架构的整体设计,于是代码逐渐失去了自己的结构。程序员越来越难通过阅读源码来理解原来的设计。”

—— 《重构:改善既有代码的设计》2.3 为何重构

重构这件事想解决的就是上述问题,把难懂的代码变得容易理解,使得后续开发的人(极有可能这个人是自己)很容易在原来的基础上修改

你担心性能会有所下降、担心不够最“优雅美丽”?最重要的,你担心花在重构上的时间降低了开发速度。

也许听起来有点反直觉,但重构实际上是在提高开发速度。按照我们的印象,源代码可读性好、容易修改这不是提高软件质量的么,和开发速度有什么关系呢?

「不积跬步,无以至千里;不积小流,无以成江海。」

一般来说,软件很少会做了就扔,后续还要有大量的维护、新功能要做。如果是一味图快,虽然最开始是很快,但慢慢就变成了在摇摇欲坠的积木堆上搭木块,不久就搭不下去了,甚至想拆了重来;如果一直保证软件内部质量良好,很容易就能找到修改的地方,代码清晰使得引入 bug 可能性也变小,一切都在向着好的地方发展。

除此之外,提高性能也只是一种软件需求,只要好改动,你在需要的时候轻易就能做到。

image.png

所以,你大概猜到了,重构是纯粹从经济利益角度出发的,而不是一味追求整洁光亮的代码。

不要掉入“整洁的代码”“良好的工程实践”之类道德理由的陷阱

选择重构的唯一理由就是能让我们更快,开发功能变快、修复 bug 变快、交接代码变快

软件开发如同行军

无论你的公司是选用哪种开发模式,在持续的软件开发中,我们永远在路上。于是,我们的好伙伴——重构,也永远在路上

Kent Beck 提出了“两顶帽子”的比喻,即软件开发时间会分配给两种行为:添加新功能重构。搞不懂变换帽子的比喻没关系,我们用左腿右腿来描述它。在软件开发时,添加新功能先迈出了左腿,但之后发现修改一部分设计会好添加很多,花上十分钟进行重构迈出右腿,再继续添加功能…如此往复,我们的左右腿迈开了,唰唰唰快速而稳健地向前走。

在这个过程中我们左腿要迈,右腿也要迈。当然,不是说只迈一条腿就走不动路,完全可以拖着走嘛,只是那样不够稳健,就像一个瘸了腿的人。

士兵不是体育运动员

既然是行军,长途跋涉必然要考虑到体力问题,我不愿意像体育运动员一样一路狂奔。没错,我推崇的重构是一种小步快跑的行为,是我们开发过程中持续的、不起眼但无比重要的迈步。

当然有时团队会专门花击个星期甚至几个月的时间进行重构,以弥补他们之前对重构的忽视。这种急行军的行为确实会发生,但不可能一直发生,大部分的重构应该是不起眼的、伴随开发同步去做。这些重构的工作往往是立竿见影,给几个变量重命名、调整函数的功能——这些看似微小的改变就能在开发前让我们看清之前的设计,改造之前的设计,更好地敲代码。

“如果不做前面的重构,我可能永远都看不见这些设计问题,因为我不够聪明,无法在脑海中推演所有这些变化。Ralph Johnson 说,这些初步的重构就像扫去窗上的尘埃,使我们得以看到窗外的风景。”

—— 《重构: 改善既有代码的设计》2.4 何时重构

令行禁止

军队,令行禁止,行军,自然是打哪就往哪里跑,从战略上不会进行非必要的绕路。

在代码里,这表现为:如果有一块丑陋的代码能被隐藏在一个完全不需要改造/接触的API之下,就继续容忍它的丑陋。记住我们的目的,为了经济利益,只有需要理解其工作原理时,对其重构才有价值

因此,漂亮的代码也需要很多重构。有时过度的设计会干扰现在的功能,或者当时很漂亮的设计并不容易修改代码,这些漂亮的石头需要从道路上搬开。可以参考过去的设计,但不必留恋,我们一直在路上。

雷厉风行

哪怕你头脑里预想多少次和真实上手都是不一样的,不要臆测。臆测会让你学到一些东西,但十有八九你是错的。

另外,不要害怕自己没办法一次改好,重构可以是一个渐进的过程,每一次的重构都会让你更明白原来的设计,更清楚自己未来如何去做。还记得那句“不积跬步,无以至千里”么,是时候去做了,加油!

嘿,你们的队伍有指导员么?

一个良好的队伍必须有人指导才能方向坚定,一份重构的代码要如何才能保证与原来的软件行为不偏离呢?答案是也需要被指导——被测试指导。

为了做到快速发现错误,必须有一套完备的测试套件。可靠的测试中,发现错误就能及时定位,随时回滚。

“自测试的代码不仅使重构成为可能,而且使添加新功能更加安全,因为我可以很快发现并干掉新近引入的 bug。”

—— 《重构: 改善既有代码的设计》2.5 重构的挑战

如果没有测试的能力,重构的风险就会变大,因此我们必须重视测试

对于过去遗留的代码,可能并没有测试,这时添加测试就变成了工作量巨大的工作 —— 最初设计系统时压根没有考虑测试的事。因此强烈建议在一开始或者尽早在项目中加入测试能力,越拖下去以后就越难添加。

那对于已“负债累累”的项目该怎么办呢,有一本书《修改代码的艺术》,其中有一个大章节用来指导在过去的代码中添加测试。这本书是《重构: 改善既有代码的设计》推荐的,笔者还没来得及看,大致看看了序言和目录,非常详细的描述了什么代码需要测试、怎样插入测试,应该值得一看。

写在最后

重构不是包治百病的灵丹,也绝对不是所谓的“银弹”。它更像是一把钳子,在搭建新管道时发现旧管道的问题就修修补补,把控住我们的代码。一艘船被造出来是因为开始的良好设计,在海上航行不沉是因为日常的保养。

人自省以明理,代码自省以强健。

与君共勉。