《重构:改善既有代码的设计》读书笔记(持续更新中)

更新时间:2023-05-09 08:23:49 阅读: 评论:0

《重构:改善既有代码的设计》读书笔记(持续更新中)
背景(我也不知道这个算不算读书笔记,书本知识点整理和个⼈理解内容可跳⾄下⾯正⽂):
最近过年加找⼯作⼀直没想起来整理学习内容, 时间都很零碎。回想⼀下, 的确⼀直以来都是为了完成项⽬去看知识点。 除了刚开始想转⾏做IT的时候跟着慕课⽹的两条前后端路线系统地学习了⼀下, 但是终究囫囵吞枣。
前⼏天刚好有被问到《重构》这本书的内容。这两天临时抱佛脚看⼀下。 有了⼀定量的代码量积累的现在, 带着些许疑惑和朦胧的概念, 刚好到了看这本书的时候。
其实再上⼀家公司, 很早以前就被推荐这本书了, 不过不知道为什么后来忘了。 现在稍微看了下, 突然发现, 之前被要求我做到的⼀些代码习惯和公司内部的代码风格很多都能在书中找到。有些写代码的时候遇到的疑惑在书中也有提到(经常⼀边写新代码⼀边看之前的代码,尽量优化,奈何⽔平太菜, 经常迷茫于如何取舍,甚⾄最开始有段时间为了代码的简短和重复使⽤⽤⼒过头),感谢同事⼀直以来的帮助和容忍(我写的屎⼭和⽐蜗⽜快不了多少的代码速度)。
在这个迷迷糊糊但是有点头绪的时候看这本书, 是最好的。
===========================================================================================
正⽂:
关于⾯向对象的五⼤原则:
1. 单⼀职责: ⼀个类只负责⼀个职责。 ⽬的在于解耦增强内聚。
2. 开闭原则:对于扩展是开放的,对于修改是关闭的。可利⽤多态、抽象类等实现扩展,超类不应该做改变。
3. ⾥⽒替换原则:⼦类必能代替⽗类。
4. 依赖倒转原则:⾼层次的模块不应该依赖于低层次的模块,他们都应该依赖于抽象;抽象不应该依赖于具体实现,具体实现应该依赖于抽
5. 接⼝隔离原则:⼀个类对另⼀个类的依赖应该建⽴在最⼩的接⼝上,使⽤多个专门的接⼝⽐使⽤单⼀的总接⼝要好
6. 迪⽶特原则(最⼩知识原则):⼀个对象应当对其他对象有尽可能少的了解。
所以, 在开发过程中, 我们的⽬标是:复⽤性、重构性、维护性。这本书很多地⽅都是以这三个⽬标为中⼼, 以求养成良好的代码风格和代码习惯。
----------------------------------------------------------------------------------------------------------
第⼀章:
这⼀章主要是⽤⼀个案例简单引出了⼀个重构的⼤概形象。
读这⼀章的时候我反思对⽐了⼀下以前开发过程中经历。如果有时间, 可以尝试下⾃⼰如何重构再对⽐下作者的做法
第⼀点我注意到的是, 重构需要以微⼩的步伐进⾏修改程序, 可以多次修改, 但是切忌不能⼀次性把⼀⼤段修改掉, 很容易出错。刚开始的时候我会犯这种错误, ⼼太急, 直接把整段代码拷贝出来, 有时候会把中间有些引⼊的参数或者被修改的参数(特别是Java传对象时引⽤传递,原对象会被不⼩⼼改掉)遗漏引发不必要的bug。所以在每⼀步的修改后都阶段性进⾏测试都是必要的。
所在在看到第⼀章的内容时如觉醍醐灌顶:在提取代码前, ⾸先我们必须 找出函数内局部变量和参数,未被修改的作为传⼊值, 被修改的作为返回值。 但是要注意的是被修改的参数尽量要少。
有⼀点之前我没想到过的是:只涉及某个类的⽅法, 写到该类下, 使⽤return返回该⽅法。 这个⼀般在第⼀遍写的时候就决定了代码放在哪个类, 但是在后期多次修改后的确会出现这个⽅法可能和其它类关系密切点的情况, 但是我不会去注意到需要移动他(也有⼀部分怕出错吧,毕竟要是换了地⽅会引起其它地⽅拿不到, 越是到后期越是难以动这些代码, 涉及的东西太多, 修改⾮常容易出现遗漏, ⼯作量也很⼤)。
然后第⼀章的⽰例中还提到了变量的问题,感谢⽼⼤, 这⽅⾯之前没有问题,我们的代码中:
1. 引⼊参数以a开头,⽅法内变量名以类型⾸字母⼩写开头, 但是成员变量不加类型⾸字母, ⾃动⽣成get/t的⽅法时会出问题。
2. 类名采⽤名词,⽅法名采⽤动词加名词的格式,且不能以is/get/t开头,这些会和部分框架之类的冲突, 尽量避免。 类似增删改查固定功能的⽅法名会以特定单词和格式,所有代码统⼀。
3. ⼯具类的构造⽅法时静态的, 以防被实例化, 下⾯的⽅法采⽤静态⽅法。不同功能的⼯具类都归类整理,⽐如⽂件上传下载, world、excel⽂档处理放在⼀个⼯具类中,各类格式的转换(包括⾦额,英⽂时间处理)是⼀个⼯具类,⽂件和数据加解密是⼀个⼯具类, 这点符合书中说到的⼀个类只做⼀件事。
4. ⼀些不便的参数等使⽤常量存储在单独的class中, 主要分两个class, ⼀个放配置⽅⾯的, ⽐如Ip地址等, ⼀个是放项⽬中⽤法到的, ⽐如不同交易⽅式对应的状态码。 统⼀采⽤⼤写, 使⽤下划线分割, 相同功能的多个参数开头部分有相同单词, 放在⼀起。
--------------------------------------------------------------------
第⼆章
重构含义:⽬的是不改变软件可观察⾏为的前提下,提⾼其可理解性,降低修改成本。
作者重复强调的是:重构之后软件功能⼀如既往, 其他程序员或⽤户是不会发现有些东西已经改变了。
开发时需要遵守的准则:
添加新功能时,不应该修改既有代码,只管添加新功能并通过测试。
重构时不再添加新功能,只管改进程序结构,并通过已有测试。
为何重构:
重构改进软件设计(Design)
重构使软件更容易理解(Maintain)
重构帮助找到BUG(Debug)
重构提⾼编程速度(Efficiency)
何时重构:
三次法则:事不过三,三则重构
添加功能时重构(New Feature)
修补错误时重构(Bug Fix)
复审代码时重构(Code Review)
何时不该重构:
既有代码太混乱,且不能正常⼯作,需要重写⽽不是重构。
项⽬接近最后期限时,应该避免重构。
重构的⽬标:
情况描述⽬标
难以阅读的程序,难以修改容易阅读
逻辑重复的程序,难以修改所有逻辑都只在唯⼀地点指定
添加新⾏为时需要修改已有代码的程序,难以修改新的改动不会危及现有⾏为
带复杂条件逻辑的程序,难以修改尽可能简单表达条件逻辑
重构中的间接层:
间接层的作⽤(这⾥的⼏点达到的优化其实可以对应下⼀章的坏代码归纳):
允许逻辑共享(避免重复代码)
分开解释意图和实现(⽅法越短⼩,越容易起好名字揭⽰意图,单⼀职责)
隔离变化(软件需要根据需求的变化不断修改,隔离缩⼩修改的范围)
封装条件逻辑(多态消息)
正如作者所⾔, 间接层的使⽤需要注意层次多少处于恰当的范围。个⼈觉得之前有段时间有点过分追求避免重复代码⽽做不必要甚⾄负⾯的⼯作, 写完时候回发现不恰当的地⽅, ⼜改回去, 很浪费时间, 这⼤概就是我速度过慢的⼀部分原因。
-----------------------------------------------------------------------------------
第三章 代码的坏味道
这⼀章总结归纳了典型的需要重构的情况:
1. Duplicated Code(重复代码):抽取⽅法到⽗类或者第三⽅类
2. Long Method(过长函数):分解提炼函数, 以⽤途命名函数名
3. Large Class(过⼤的类):遵循单⼀职责原则(SRP)。
4. Long Parameter List(过长参数列表):常见的是将相关的参数组织成⼀个对象来替换掉这些参数。
这⼀点, 在之前的⼯作中, ⽐如前后端传输查询条件(⼀般我们的软件查询条件会很多)会采⽤类名是⼀个特定单词结尾的类的对象来存放。在后端⽅法中我可能会构造采⽤HashMap来存放, 如果有现成类可以⽤会放到⼀个对象中, 3个以下的就直接传多个参数。
1. Divergent Change(发散式变化):因为不同的原因,在不同的⽅向上,修改同⼀个类。应该分解成更⼩的类,每个类只因⼀种原因⽽
修改。
这点回想下, 我的逻辑基本放在Service中了, SSM结构中Controller只做接受参数, 调⽤Service然后向前端返回, Mapper中只有sql语句, 很少涉及到逻辑, 有也只是⼀些简单的if或者⽇期之类格式转换。总共结构基本只有简单的这三部。
1. Shotgun Surgery(霰弹式修改):每遇到变化,需要修改多个类,容易遗漏。把需要修改的部分放到⼀个类⾥。
2. Feature Envy(依恋情结):函数⼤量地使⽤了另外类的数据。这种情况下最好将此函数移动到那个类中
3. Data Clumps(数据泥团):两个类中相同的字段、函数签名中相同的参数等,都适合提取成⼀个单独的数据类。
这点虽然看着简单易懂且容易⼊门, 但是在特定情况下可能迷惑性⽽且需要⼀定的技巧,在做的时候需要有条理, 详见第⼀章。
1. Primitive Obssion(基本类型偏执):不要动不动就来个对象, 如果有⼀组经常被放在⼀起的数据, 可以让他们组成⼀个extract
class ;如果在参数列中看到基本型数据, 尝试intoduce Parameter Object;如果发现⾃⼰正从数组中挑选数据, 可使⽤运⽤Replace Array with Object。
2. Switch Statements(switch惊悚现⾝):少⽤switch,考虑⽤多态替换它。
这让我想到另⼀个, 有很多个简单if(根据条件返回⼀个值)连在⼀起的时候, 我会⽤HashMap, key为条件对应的状态值, value为结果。
1. Parallel Inheritance(平⾏继承体系):如果发现每当为某个类增加⼀个⼦类,必须也为另外⼀个类相应增加⼀个⼦类, 这时需要考虑
消除这种重复性。⽅法是让⼀个继承体系的实例引⽤另⼀个继承体系的实例。
2. PLazy Class(冗赘类):如果⼀个类不值得存在,那么它就应该消失。
3. Speculative Generality(夸夸其谈未来性):如果你的抽象类、委托、⽅法的参数没有实际的作⽤,那么就应当被移除掉。
4. Temporary Field(令⼈迷惑的暂时字段):类中某个字段只为某些特殊情况⽽设置。把这些变量提炼到⼀个独⽴的类中。提炼后的新对
象是个函数对象
5. Message Chains(过度耦合的消息链):常常是因为数据结构的层次很深,需要层层调⽤getter获取内层数据。可以考虑这个字段是否
应该移到较外层的类,或者把调⽤链封装在较外层类的⽅法。
6. Middle Man(中间⼈):如果⼀个类的很多功能都通过委托给其他类来完成,那么就不如去掉这些中间⼈直接和真正负责的对象打交
道。
7. Inappropriate Intimacy(两个类过度亲密):
8. Alternative Class(异曲同⼯的类):根据⽤途重新命名,将某些⾏为移⼊类,或者使⽤超类类。
9. Incomplete Lib Class(不完美的类库):修改类库的⼀两个函数 - 引⼊外部函数(Introduce Foreign Method);添加⼀⼤堆额外⾏
为 - 添加本地扩展(Introduce Local Extension)
10. Data Class(纯稚的数据类):数据类不应该把全部字段单纯的通过getter/tter暴露出来(我们在多层结构系统开发时经常这么
做),⽽应该暴露抽象接⼝,封装内部结构。
11. Refud Bequest(被拒绝的遗赠):a)⼦类继承⽗类的所有函数和数据,⼦类只挑选⼏样来使⽤:为⼦类新建⼀个兄弟类,再运⽤下移
⽅法(Push Down Method)和下移字段(Push Down Field)把⽤不到的函数下推个兄弟类。b)⼦类只复⽤了⽗类的⾏为,却不想⽀持⽗类的接⼝:运⽤委托替代继承(Replace Inheritance with Delegation)来达到⽬的。
12. Comments(过多的注释):适当注释, 不要太多, 代码中很明显的就不要注释了。但也不要⼀点都没有。
以上内容很多, ⽂中只有总结, 没有案例, 这个需要在不断加⼤代码阅读量和思考以及动⼿实践中不断去体会。
-------------------------------------------------------------------------
第四章 建⽴测试体系
// 之后再看, 之前测试只是简单地每个⼩功能模块完成后测试, 再拼装后整个流程测试。
----------------------------------------------------------------------------
接下来的章节都是在详细说明各类重构⽅法
第五章 重构列表
参数按照列表形式传递,检测重构每⼀个节点。
这⼀章, 作者先介绍了重构的记录格式:名称-->概要-->动机-->做法-->范例
⼯作中⽤Eclip, 在开发过程中⽐较⽅便, 这⼀点可以得到控制,但是书中提到编译器可能存在的缺点, 总结如下:
1. 被删除的部分在继承体系中声明不⽌⼀次,那么编译器会被迷惑。
这点不是很理解。 我想的可能有点问题:以前在⼦类重写⽗类⽅法的时候没有加@override注解, ⽗类删除⽅法后, ⼦类没删除,eclip 是不会提⽰的。当然那时候让我意识到@override注解还是需要写的。
2. 编译器查找会拖慢速度, 这点没有明显感觉到, 可能我还没到境界, 或者时代不同, ⼯具不同。
3. 编译器⽆法找到通过反射机制⽽得到的引⽤点。 实际项⽬没遇到过orz
作者推荐的是结合菜单选项或者⽂本查找⽅式。
----------------------------------------------------------------------
第六章 重新组织函数
函数不应过长, 传递的参数尽可能少⽽完整。 不要把传⼊的参数修改后返回。
1. Extract Method(提炼函数)
把过长的函数中⼀部分提取出来, 写成⼀个单独的⽅法, 这个⽐较容易理解。
但是需要注意的是:长度不是问题, 关键在于函数名称和函数本体之间的语义距离。个⼈理解是需要让每个⽅法所做的事都属于它名称所表⽰的内容。
在提取过程中, 仅在提取⽅法中使⽤的临时变量和原⽅法中需要⽤到的局部变量的定义位置需要注意, 前者, 需要从原⽅法迁移进来; 后者需要作为传⼊参数,根据在此之后, 改参数是够被其它地⽅使⽤决定是否再作为返回值。
具体做法见原⽂
2. Inline Method(内联函数)
动机:
1) 对于内部代码和函数名同样清晰易读的函数, 可以把它去掉, 直接使⽤其中的代码。
2) 假如⼿中有⼀群组织不合理的函数, 可以先把他们内联到⼀个⼤型函数中, 然后再从中提炼出组织合理的⼩函数。
3) ⾯对过多的间接层, 可以使⽤内联⼿法, 去除多余间接层。
需要注意: 找出所有这个函数的被调⽤点, 对于具有多态,递归调⽤, 多返回点, 内联⾄另⼀个对象⽽该对象并⽆提供访问点, 则尽量不要使⽤该重构⼿法。
3. Inline Temp(内联临时变量)
动机:某个临时变量被赋予某个函数调⽤的返回值, 且妨碍其它重构则可以将它内联化。
书中提到了⼀个⼩窍门:如果这个变量未被声明为final, 则将它声明为final, ide如果报错, 从⽽判断该值在后⾯是否被改变过, 如果有过改变, 就不太适合将其内联化。
书中还提到, 在修改完所有引⽤点后再删除该变量的声明和赋值语句, 在实际开发过程中, 有时候我会由于之前设计不合理会在之后造成需要内联化得变量的产⽣, 但是我都是直接先删除, 然后按照eclip的错误提⽰去第⼀遍查找引⽤点的, 所以在想是不是应该按作者这么来,然后在删除之后再通过eclip是否有错误提⽰来判断是否还有疏漏。
有时候也有疑惑, 这⽅⾯讲, IDE是让我们过于依赖它了还是简化了开发过程
4. Replace Temp with Query
以查询取代临时变量
个⼈感觉就是当某个临时变量的获取在多出会被⽤到时,且该临时变量只被赋值⼀次, 且这个赋值过程不受其它条件影响,  可以把获取这个临时变量的过程单独提炼出来作为⼀个⽅法。
这样的好处在于⼀⽅⾯可以提⾼代码的复⽤率, 在以后需要修改时只要修改⼀个地⽅;且临时变量的减少可以使当前函数的优化变得简单。
步骤: 因为需要缺点该临时变量只被赋值⼀次, 所以可以先给这个变量设置为final, 如果未报错, 即可进⾏下⾯步骤。
5. Introduce Explaining Variable
将较长或者复杂的条件⼦句提炼出来, 赋值给⼀个临时命名清晰的变量。
这⼀条前段时间在阿⾥巴巴Java开发⼿册上刚好看到过。
本书作者相对这个优化⽅法,更加倾向于上⼀条的 单独提炼成⼀个private函数, 如果这个private函数被多次调⽤, 则改为project或者public。但是假如, 提炼到外部函数时会传递很多参数的时候, 则使⽤这⼀条。
6. Splite Temporary  Variable
分解临时变量
对于被多次赋值, 且赋值后含义不同的临时变量, 应该按照其含义拆分成多个不同的临时变量。
看到这点, 好像有点明⽩了⾃⼰为什么每次写代码总有种⼿忙脚乱的感觉23333
修改过程: 在每个临时变量赋值处对临时变量进⾏重命名, 并修改下次赋值之前的引⽤点, 在这⾥我们依旧可以使⽤final和编译器来判断下个赋值点在哪⾥。
7. Remove Assignments to Parameters
移除对参数的赋值
感觉特别是写⼀些公共⽅法的时候, 如果把别⼈⼊参改了, 会⼀不⼩⼼给⼈⼀个surpri, ⽽且让代码可读性变差。 以前⾃⼰写的时候感觉改变⼊参很奇怪, 后来做了⼏个项⽬后, emmm. 现在也是习惯对⼀些值传递的进来参数, 如果要修改, 都会先给个临时变量, 传对象进来的时候就需要特别注意点
这⾥作者提到了, Java传递对象的时候指向的是该对象的引⽤, 所以把对象作为参数传递的时候, 需要特别注意.
有时候会有传⼀个对象进去, 然后修改对象中的变量来操作. 这种在实际中也是常有的, 需要区别对待. 但是直接使变量指向性的对象还是不要做的好.
题外话, 这个让我想到了“⾯试题整理”之类的内容出镜率很⾼的题:Java的包装类, 特别是String和Integer,常量池让他们在不同情况下表现不⼀样。 但是我在⽇常中, 还是把他们直接当对象来⽤, 不怎么考虑常量池的情况, 以免出错。 可能是我的代码量不⼤, 做的项⽬都⽐较简单。
8. Replace Method With Method Object
以函数对象取代函数.
当我们的某个函数⽐较巨⼤的时候, 我们会把这个函数按更加细致的功能进⾏拆解, 但是假如在拆解过程中我们发现这些⼩函数之间会有⼤量共⽤的变量, 这时候把这些⼩函数继续放在原先的类中就会看上去⽐较混乱(要么会有⼤量⼊参, 要么有⼤量浪费的填装Map, 要么把这些共⽤变量混在原类的成员变量中把⽔搅混). 这时候就可以考虑将这个⼤函数放到⼀个单独的类⾥⾯, 然后再拆解.

本文发布于:2023-05-09 08:23:49,感谢您对本站的认可!

本文链接:https://www.wtabcd.cn/fanwen/fan/89/873580.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:代码   需要   函数   修改   重构   变量
相关文章
留言与评论(共有 0 条评论)
   
验证码:
推荐文章
排行榜
Copyright ©2019-2022 Comsenz Inc.Powered by © 专利检索| 网站地图