背景(我也不知道这个算不算读书笔记,书本知识点整理和个人理解内容可跳至下面正文):
最近过年加找工作一直没想起来整理学习内容, 时间都很零碎。回想一下, 的确一直以来都是为了完成项目去看知识点。 除了刚开始想转行做IT的时候跟着慕课网的两条前后端路线系统地学习了一下, 但是终究囫囵吞枣。
前几天刚好有被问到《重构》这本书的内容。这两天临时抱佛脚看一下。 有了一定量的代码量积累的现在, 带着些许疑惑和朦胧的概念, 刚好到了看这本书的时候。
其实再上一家公司, 很早以前就被推荐这本书了, 不过不知道为什么后来忘了。 现在稍微看了下, 突然发现, 之前被要求我做到的一些代码习惯和公司内部的代码风格很多都能在书中找到。有些写代码的时候遇到的疑惑在书中也有提到(经常一边写新代码一边看之前的代码,尽量优化,奈何水平太菜, 经常迷茫于如何取舍,甚至最开始有段时间为了代码的简短和重复使用用力过头),感谢同事一直以来的帮助和容忍(我写的屎山和比蜗牛快不了多少的代码速度)。
在这个迷迷糊糊但是有点头绪的时候看这本书, 是最好的。
===========================================================================================
正文:
关于面向对象的五大原则:
所以, 在开发过程中, 我们的目标是:复用性、重构性、维护性。这本书很多地方都是以这三个目标为中心, 以求养成良好的代码风格和代码习惯。
----------------------------------------------------------------------------------------------------------
第一章:
这一章主要是用一个案例简单引出了一个重构的大概形象。
读这一章的时候我反思对比了一下以前开发过程中经历。如果有时间, 可以尝试下自己如何重构再对比下作者的做法
第一点我注意到的是, 重构需要以微小的步伐进行修改程序, 可以多次修改, 但是切忌不能一次性把一大段修改掉, 很容易出错。刚开始的时候我会犯这种错误, 心太急, 直接把整段代码拷贝出来, 有时候会把中间有些引入的参数或者被修改的参数(特别是Java传对象时引用传递,原对象会被不小心改掉)遗漏引发不必要的bug。所以在每一步的修改后都阶段性进行测试都是必要的。
所在在看到第一章的内容时如觉醍醐灌顶:在提取代码前, 首先我们必须 找出函数内局部变量和参数,未被修改的作为传入值, 被修改的作为返回值。 但是要注意的是被修改的参数尽量要少。
有一点之前我没想到过的是:只涉及某个类的方法, 写到该类下, 使用return返回该方法。 这个一般在第一遍写的时候就决定了代码放在哪个类, 但是在后期多次修改后的确会出现这个方法可能和其它类关系密切点的情况, 但是我不会去注意到需要移动他(也有一部分怕出错吧, 毕竟要是换了地方会引起其它地方拿不到, 越是到后期越是难以动这些代码, 涉及的东西太多, 修改非常容易出现遗漏, 工作量也很大)。
然后第一章的示例中还提到了变量的问题,感谢老大, 这方面之前没有问题,我们的代码中:
1. 引入参数以a开头,方法内变量名以类型首字母小写开头, 但是成员变量不加类型首字母, 自动生成get/set的方法时会出问题。
2. 类名采用名词,方法名采用动词加名词的格式,且不能以is/get/set开头,这些会和部分框架之类的冲突, 尽量避免。 类似增删改查固定功能的方法名会以特定单词和格式,所有代码统一。
3. 工具类的构造方法时静态的, 以防被实例化, 下面的方法采用静态方法。不同功能的工具类都归类整理,比如文件上传下载, world、excel文档处理放在一个工具类中,各类格式的转换(包括金额,英文时间处理)是一个工具类,文件和数据加解密是一个工具类, 这点符合书中说到的一个类只做一件事。
4. 一些不便的参数等使用常量存储在单独的class中, 主要分两个class, 一个放配置方面的, 比如Ip地址等, 一个是放项目中用法到的, 比如不同交易方式对应的状态码。 统一采用大写, 使用下划线分割, 相同功能的多个参数开头部分有相同单词, 放在一起。
--------------------------------------------------------------------
第二章
重构含义:目的是不改变软件可观察行为的前提下,提高其可理解性,降低修改成本。
作者重复强调的是:重构之后软件功能一如既往, 其他程序员或用户是不会发现有些东西已经改变了。
开发时需要遵守的准则:
为何重构:
何时重构:
何时不该重构:
重构的目标:
情况描述 |
目标 |
难以阅读的程序,难以修改 |
容易阅读 |
逻辑重复的程序,难以修改 |
所有逻辑都只在唯一地点指定 |
添加新行为时需要修改已有代码的程序,难以修改 |
新的改动不会危及现有行为 |
带复杂条件逻辑的程序,难以修改 |
尽可能简单表达条件逻辑 |
重构中的间接层:
间接层的作用(这里的几点达到的优化其实可以对应下一章的坏代码归纳):
允许逻辑共享(避免重复代码)
分开解释意图和实现(方法越短小,越容易起好名字揭示意图,单一职责)
隔离变化(软件需要根据需求的变化不断修改,隔离缩小修改的范围)
封装条件逻辑(多态消息)
正如作者所言, 间接层的使用需要注意层次多少处于恰当的范围。个人觉得之前有段时间有点过分追求避免重复代码而做不必要甚至负面的工作, 写完时候回发现不恰当的地方, 又改回去, 很浪费时间, 这大概就是我速度过慢的一部分原因。
-----------------------------------------------------------------------------------
第三章 代码的坏味道
这一章总结归纳了典型的需要重构的情况:
这一点, 在之前的工作中, 比如前后端传输查询条件(一般我们的软件查询条件会很多)会采用类名是一个特定单词结尾的类的对象来存放。在后端方法中我可能会构造采用HashMap来存放, 如果有现成类可以用会放到一个对象中, 3个以下的就直接传多个参数。
这点回想下, 我的逻辑基本放在Service中了, SSM结构中Controller只做接受参数, 调用Service然后向前端返回, Mapper中只有sql语句, 很少涉及到逻辑, 有也只是一些简单的if或者日期之类格式转换。总共结构基本只有简单的这三部。
这点虽然看着简单易懂且容易入门, 但是在特定情况下可能迷惑性而且需要一定的技巧,在做的时候需要有条理, 详见第一章。
这让我想到另一个, 有很多个简单if(根据条件返回一个值)连在一起的时候, 我会用HashMap, key为条件对应的状态值, value为结果。
以上内容很多, 文中只有总结, 没有案例, 这个需要在不断加大代码阅读量和思考以及动手实践中不断去体会。
-------------------------------------------------------------------------
第四章 建立测试体系
// 之后再看, 之前测试只是简单地每个小功能模块完成后测试, 再拼装后整个流程测试。
----------------------------------------------------------------------------
接下来的章节都是在详细说明各类重构方法
第五章 重构列表
参数按照列表形式传递,检测重构每一个节点。
这一章, 作者先介绍了重构的记录格式:名称-->概要-->动机-->做法-->范例
工作中用Eclipse, 在开发过程中比较方便, 这一点可以得到控制,但是书中提到编译器可能存在的缺点, 总结如下:
1. 被删除的部分在继承体系中声明不止一次,那么编译器会被迷惑。
这点不是很理解。 我想的可能有点问题:以前在子类重写父类方法的时候没有加@override注解, 父类删除方法后, 子类没删除,eclipse是不会提示的。当然那时候让我意识到@override注解还是需要写的。
2. 编译器查找会拖慢速度, 这点没有明显感觉到, 可能我还没到境界, 或者时代不同, 工具不同。
3. 编译器无法找到通过反射机制而得到的引用点。 实际项目没遇到过orz
作者推荐的是结合菜单选项或者文本查找方式。
----------------------------------------------------------------------
第六章 重新组织函数
函数不应过长, 传递的参数尽可能少而完整。 不要把传入的参数修改后返回。
1. Extract Method(提炼函数)
把过长的函数中一部分提取出来, 写成一个单独的方法, 这个比较容易理解。
但是需要注意的是:长度不是问题, 关键在于函数名称和函数本体之间的语义距离。个人理解是需要让每个方法所做的事都属于它名称所表示的内容。
在提取过程中, 仅在提取方法中使用的临时变量和原方法中需要用到的局部变量的定义位置需要注意, 前者, 需要从原方法迁移进来; 后者需要作为传入参数,根据在此之后, 改参数是够被其它地方使用决定是否再作为返回值。
具体做法见原文
2. Inline Method(内联函数)
动机:
1) 对于内部代码和函数名同样清晰易读的函数, 可以把它去掉, 直接使用其中的代码。
2) 假如手中有一群组织不合理的函数, 可以先把他们内联到一个大型函数中, 然后再从中提炼出组织合理的小函数。
3) 面对过多的间接层, 可以使用内联手法, 去除多余间接层。
需要注意: 找出所有这个函数的被调用点, 对于具有多态,递归调用, 多返回点, 内联至另一个对象而该对象并无提供访问点, 则尽量不要使用该重构手法。
3. Inline Temp(内联临时变量)
动机:某个临时变量被赋予某个函数调用的返回值, 且妨碍其它重构则可以将它内联化。
书中提到了一个小窍门:如果这个变量未被声明为final, 则将它声明为final, ide如果报错, 从而判断该值在后面是否被改变过, 如果有过改变, 就不太适合将其内联化。
书中还提到, 在修改完所有引用点后再删除该变量的声明和赋值语句, 在实际开发过程中, 有时候我会由于之前设计不合理会在之后造成需要内联化得变量的产生, 但是我都是直接先删除, 然后按照eclipse的错误提示去第一遍查找引用点的, 所以在想是不是应该按作者这么来, 然后在删除之后再通过eclipse是否有错误提示来判断是否还有疏漏。
有时候也有疑惑, 这方面讲, 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
移除对参数的赋值
感觉特别是写一些公共方法的时候, 如果把别人入参改了, 会一不小心给人一个surprise, 而且让代码可读性变差。 以前自己写的时候感觉改变入参很奇怪, 后来做了几个项目后, emmm. 现在也是习惯对一些值传递的进来参数, 如果要修改, 都会先给个临时变量, 传对象进来的时候就需要特别注意点
这里作者提到了, Java传递对象的时候指向的是该对象的引用, 所以把对象作为参数传递的时候, 需要特别注意
题外话, 这个让我想到了“面试题整理”之类的内容出镜率很高的题:Java的包装类, 特别是String和Integer,常量池让他们在不同情况下表现不一样。 但是我在日常中, 还是把他们直接当对象来用, 不怎么考虑常量池的情况, 以免出错。 可能是我的代码量不大, 做的项目都比较简单。
8. Replace Method With Method Object