《重构:改善既有代码的设计》读后感

2021/11/28 总结 共 3535 字,约 11 分钟

前言

花了几天囫囵看了一下这本书,好书!在这里总结一下学到的东西,之后肯定还要回头再翻翻看的。

简介

本书清晰揭示了重构的过程,解释了重构的原理和最佳实践方式,并给出了何时以及何地应该开始挖掘代码以求改善。书中给出了70多个可行的重构,每个重构都介绍了一种经过验证的代码变换手法的动机和技术。本书提出的重构准则将帮助你一次一小步地修改你的代码,从而减少了开发过程中的风险。

本书适合软件开发人员、项目管理人员等阅读,也可作为高等院校计算机及相关专业师生的参考读物。

感想

本来没想看的,因为书名和我对重构的一些固执想法,我以为:重构是一种程序完成之后,对程序进行整理,改造的过程。尤其是这个介绍,看起来就是机械的教给你一些方法,给你一些合适使用的代码范例与场景,然后告诉你这时候可以用某一种重构的方法。这些方法也不外乎抽出公共部分,加点继承,用用设计模式之类的,比起机械的方法,实际的经验更加重要。不如说这样的机械方法根本记不住,难道我在重构的时候特意把这本书翻开放到一边查阅吗?

我对于改造屎山实在是没有兴趣,有心无力,在我看来,与其学习如何整理坏代码,不如学习如何学习好代码,学习设计,如何在开始编程时就为后来打下良好的可拓展的基础而不是挽回他人的过错。我改造不了是我的问题吗,是上一个写代码的人的问题吧,与其增进这方面收益并不是很大的技能不如增进写代码方向上的技能。

这书还是很有意义的,我的想法有许多问题。我对重构理解错误导致了我后面所有推论都有问题,这是典型的大前提错误了。

在本书,重构大概是和设计并列的,传统意义上的设计是项目尚未开始时的设计,而重构是项目搭建过程中的设计。这个论点很重要,也是为什么这本书这么多人推荐,重构在它在他眼里,是对设计的切实补充。设计的能力太过依赖经验,没人知道未来会项目会搭成什么样,也没人知道未来会有什么样的新需求,很多设计,可能是多余,同时,又有可能不够,这怎么能权衡呢?在本书中,作者明确的给出了一个答案,不要做多余设计,只要设计成容易重构的方式即可。真是令人豁然开朗。感觉上隐隐约约和极限编程那边有些关系了,这些取向。

其次重要的一点,作者强烈的明确单元测试的重要性,将重构分为多次小的阶段,小的方法和多次再编译测试。这让大的,需要全盘理解程序的重构不再那么痛苦。经常的场景就是,你需要重构,你整理了很久代码,你出错,你不知道为什么错,你找不到你为什么错或者你花费太多时间找错,你放弃。所以当我想重构的时候,我倾向于全盘理解之后再开始重构,这个过程的折磨程度懂得都懂。这对重构积极性来说实在是很大的打击,而单元测试和多次间断的重构,让你更容易定位错误,也让这个过程对于理解的要求不再那么高。当然,你需要在开始工作前做一些无用功:写尽量多的单元测试。

还有一些编程习惯的取向,对于程序可读性的理解,也给了我一点启发,即使我可能不会采用这些方法。我对于重构的想法都是能让程序消耗更少,更清晰易懂, 这两者是一致的。但在这本书这里,有些时候会牺牲性能来换取更加清晰的可读性,当然,这些性能也确实是微不足道的。比如方法6.4 Replace Temp with Query(以查询取代临时变量),把一个临时变量换成一个函数,调用它来获取返回值。我之前根本不会往这个方向思考,因为这看起来完全是画蛇添足的行为,多了个私有方法,消耗性能,真是有点蹊跷了。在这种思路的延伸下,最后的结果倒是蛮熟悉亲切的一个结论,代码应该像自然语言一样,易于理解。每一种方法的名字都可以很好的概括自己的功能,每一句注释只解释一行代码,如果他解释了多行代码,那么这些代码就应该被抽出来形成一个函数,并把注释变成方法名。这些想法,都开拓了我的思路。

当然,这本书还是出现了经典的二律背反,两种互逆的重构方法,而你需要”看情况“。它还介绍了许多需要重构的标志,很多代码的“坏味道”,蛮有意思的,应该有时间就回来翻翻。

摘录

重构是项目搭建过程中的设计:两顶帽子

两顶帽子 上述第二点引出了Kent Beck的“两顶帽子”比喻。使用重构技术开发软件时,你把自己的时间分配给两 种截然不同的行为:添加新功能,以及重构。添加新功能时,你不应该修改既有代码,只管添加新功能。 通过测试(并让测试正常运行),你可以衡量自己的工作进度。重构时你就不能再添加功能,只管改进程 序结构。此时你不应该添加任何测试(除非发现有先前遗漏的东西),只在绝对必要(用以处理接口变 化)时才修改测试。 软件开发过程中,你可能会发现自己经常变换帽子。首先你会尝试添加新功能,然后会意识到:如果 把程序结构改一下,功能的添加会容易得多。于是你换一顶帽子,做一会儿重构工作。程序结构调整好 后,你又换上原先的帽子,继续添加新功能。新功能正常工作后,你又发现自己的编码造成程序难以理 解,于是又换上重构帽子……整个过程或许只花十分钟,但无论何时你都应该清楚自己戴的是哪一顶帽 子。

设计一个灵活的方案->设计一个易于重构的方案。

这种转变导致一个重要结果:软件设计向简化前进了一大步。过去未曾运用重构时,我总是力求得到灵活的解决方案。任何一个需求都让我提心吊胆地猜疑:在系统的有生之年,这个需求会导致怎样的变化?由于变更设计的代价非常高昂,所以我希望建造一个足够灵活、足够牢靠的解决方案,希望它能承受我所能预见的所有需求变化。问题在于:要建造一个灵活的解决方案,所需的成本难以估算。灵活的解决方案比简单的解决方案复杂许多,所以最终得到的软件通常也会更难维护——虽然它在我预先设想的方向上的确是更加灵活。就算幸运地走在预先设想的方向上,你也必须理解如何修改设计。如果变化只出现在一两个地方,那不算大问题。然而变化其实可能出现在系统各处。如果在所有可能的变化出现地点都建立起灵活性,整个系统的复杂度和维护难度都会大大提高。当然,如果最后发现所有这些灵活性都毫无必要,这才是最大的失败。你知道,这其中肯定有些灵活性的确派不上用场,但你却无法预测到底是哪些派不上用场。为了获得自己想要的灵活性,你不得不加入比实际需要更多的灵活性。

积极测试

实际上,撰写测试代码的最有用时机是在开始编程之前。当你需要添加特性的时候,先写相应测试代码。听起来离经叛道,其实不然。编写测试代码其实就是在问自己:添加这个功能需要做些什么。编写测试代码还能使你把注意力集中于接口而非实现(这永远是件好事)。预先写好的测试代码也为你的工作安上一个明确的结束标志:一旦测试代码正常运行,工作就可以结束了。 关于性能

关于性能,一件很有趣的事情是:如果你对大多数程序进行分析,就会发现它把大半时间都耗费在一 小半代码身上。如果你一视同仁地优化所有代码,90%的优化工作都是白费劲的,因为被你优化的代码大 多很少被执行。你花时间做优化是为了让程序运行更快,但如果因为缺乏对程序的清楚认识而花费时间, 那些时间就都被浪费掉了。 第三种性能提升法就是利用上述的90%统计数据。采用这种方法时,你编写构造良好的程序,不对性 能投以特别的关注,直至进入性能优化阶段——那通常是在开发后期。一旦进入该阶段,你再按照某个特 定程序来调整程序性能。

因为性能改善一旦被分散到程序各角落,每次改善都只不过是从对程序行为的一个狭隘视角出发而已。

分解函数

最终的效果是:你应该更积极地分解函数。我们遵循这样一条原则:每当感觉需要以注释来说明点什么的时候,我们就把需要说明的东西写进一个独立函数中,并以其用途(而非实现手法)命名。我们可以对一组甚至短短一行代码做这件事。哪怕替换后的函数调用动作比函数自身还长,只要函数名称能够解释其用途,我们也该毫不犹豫地那么做。关键不在于函数的长度,而在于函数“做什么”和“如何做”之间的语义距离。

重构帮助理解,而不是重构需要完全的理解,甚至还能找bug

对代码的理解,可以帮助我找到bug。我承认我不太擅长调试。有些人只要盯着一大段代码就可以找出 里面的bug,我可不行。但我发现,如果对代码进行重构,我就可以深入理解代码的作为,并恰到好处地把 新的理解反馈回去。搞清楚程序结构的同时,我也清楚了自己所做的一些假设,于是想不把bug揪出来都 难。

重构=还债的比喻

另外,如果项目已近最后期限,你也应该避免重构。在此时机,从重构过程赢得的生产力只有在最后期限过后才能体现出来,而那个时候已经为时晚矣。Ward Cunningham对此有一个很好的看法。他把未完成 的重构工作形容为“债务”。很多公司都需要借债来使自己更有效地运转。但是借债就得付利息,过于复杂 的代码所造成的维护和扩展的额外成本就是利息。你可以承受一定程度的利息,但如果利息太高你就会被 压垮。把债务管理好是很重要的,你应该随时通过重构来偿还一部分债务。

文档信息

Search

    Table of Contents