一. 重构的原则
最重要:
1.1 测试
重构最最最重要的是测试,重构的本意是让代码更加的优雅和让人理解,如果引发了更多的bug,那么将得不偿失。
1.2 何为重构
使用一系列重构手法,在不改变软件可观察行为的前提下,调整其结构。
重构必须是小步骤的,并且不能影响系统的功能。有人说他们的代码重构导致几天时间不能使用,那么他们在做的事情不是重构。 重构和优化是不一样的: 1. 重构是为了让代码更容易理解,更易于修改,这可能是代码运行的更慢。 2. 在性能优化时,只会关系怎么让程序运行的更快,可能会使代码更难以理解和维护。
1.3 两顶帽子
Kent Beck 提出了两顶帽子的比喻。即:添加新功能和重构。 1. 添加新功能的时候,不应该修改既有的代码,而是只管添加新功能。 2. 重构时就不应该添加新的东西,只管修改代码结构。 开发过程中需要时刻记住自己头上戴的时什么帽子,避免在重构的时候添加新功能,导致无法测试。
1.4 为何重构
- 改进代码的设计
- 使软件更容易被人理解。 傻瓜都能写出计算机可以理解的代码,唯有能写出让人类容易理解的代码,才是优秀的程序员。
- 帮助找bug
- 提高编程速度 所谓磨刀不误砍柴工,代码容易理解了,新增功能总是更快的。
1.5 何时重构
事不过三,三则重构。
1.6 何时不应该重构
- 重写比重构还容易,就别重构了,直接重写吧。
- 代码隐藏在一个api下面,那么搞不懂的话,就让他继续丑陋吧。
1.7 重构与性能
重构过程的的性能损耗问题,大部分可以忽略不急,如果真的引入了性能问题,那么可以先完成重构,在解决性能问题。 换个思路说,如果重构引发了性能问题,那么重构之后的代码应该更有利于性能调优,毕竟更好理解。 关于性能:实际上只会出现在一小部分代码上,如果你一视同仁的优化所有的代码,在开发的时候因为性能而放弃的代码的可读性,那么90%的优化工作都是白费劲的。 所有在开发的过程中,不对性能做太多的关注,最后进入性能调优的阶段,根据工具(接口监控)来找到需要优化的地方进行调优。不需要做过多的臆测。
1.8 自动化工具
这种就太多了,比如IDEA的提取函数,自动生成变量。最基础的比如:查找并替换。
二. 代码的坏味道
命名
这个太重要了,千万不要使用有歧义的命名,就怕命名是created,功能是delete。这遭大罪。
2.1 重复代码
一旦发现了重复代码或者重复变量,那么提取一下肯定是没错的。
2.2 过长的函数,过长的参数列表
提取。封装。
2.3 全局函数,可变数据。
在js存在很多全局函数,相互污染的问题。 全局的函数,变量千万不要修改,你不知道哪个地方调用了。你修改别人背锅。
2.4 发散式变化
你发现你想要修改一个函数,却必须修改很多不想关的函数。 比如:想要添加一个产品类型,你需要修改产品的增,删,改,查,甚至于排序的函数。
问题原因
这种发散式变化是由于编程的结构不合理,或者"CV"太多了导致的,类的职责过多。
解决办法
提炼类,拆分类的行为。
2.5 霰弹式修改
一旦有业务变化,需要修改多处。很容易造成修改上的遗漏。
解决办法
搬移函数,搬移字段。将相同的业务代码放进同一个类。
依恋情结
一个类多次调用另一个类的方法来获取结果。
public class OrderService{
public List<Object> findAll(){};
public Object findFirstObject(){};
public void addObject(){};
}
public class CartService{
public void addObject(){
...
List<Objcet> objects = orderService..findAll();
Object object = orderService.findFirstObject(objects);
orderService.addObject(object);
....
}
}
像上诉代码,实际上就是为了执行addObject 方法而多次调用了OrderService的方法。这就是一种依恋情结。
问题
- 使得代码的职责不再单一。
- 如果OrderService的任何一个方法被修改,也会影响到CartService的方法。
解决办法
原则:将总是一起变化的东西放在一块。
将多次调用的代码使用提炼函数提炼成一个新的函数,并且命名来表达这几行代码的意思。 交给调用方调用。
2.6 数据泥团
多个类/方法参数中都有相同的属性,而且这些属性的业务意义也是相同的。
问题
- 重复数据。
- 涉及到属性的调整,容易遗漏。
- 降低代码的阅读效率。
- 随着代码的增多容易导致长函数,大类。
解决
提炼类 将关联的属性放在一个新的业务类里面。
2.7 基本类型偏执
对于具有意义的业务概念,不愿意进行建模,而是使用基本类型来表示。 听着有些玄乎,我也懵了下。实际上就是比如坐标:x,y,z 不新建一个类,而是使用基础数据类型进行操作。
public void addPoint(int x,int y,int z){}
public void deletePoint(int x,int y,int z){};
问题
- 暴露了较多的细节。
- 业务内聚太差,可读性差。
- 修改麻烦,万一要加个a....
修改
- 对象取代基础类型。
- 子类取代类型。
- 多态取代表达式
- 提炼类、
- 引入参数对象。
public Class point{
int x;
int y;
int z;
}
public void addPoint(Point point){}
public void deletePoint(Point point){};
是不是看起来舒服了很多。
2.8 重复的switch
在不同的地方使用了相同的switch 逻辑。
问题
影响可维护性,每当需要增加一个选择分支时,必须找到所有的switch 分支,并且逐一修改。
修改
消除重复的switch
- 多态加工厂
- 多态加不同的实现类
2.9 循环语句
针对于集合或者数组进行简单的分组,过滤等,采用传统的for循环进行遍历。(这个笔者感觉还好吧) 但是基本功能可以使用stream 更加的简洁。
2.10 冗赘的元素,夸夸其谈通用性
过于追求简洁,追求其通用性了。 一行代码提取一个函数。 在开发初期就各种设计模式全用上,为了炫技各种继承封装。属实没必要。
2.11 临时字段
临时字段必须在函数里面,千万别写在类里。如果两个函数都需要这个临时字段,那么就两个函数都加上。 写在类里面,万一修改了只后被别人使用。。。。