你在工作中是否也遇到这样的问题
- 接手老项目,发现老项目代码惨不忍睹,无从下手
- 添加新功能,发现代码难以拓展,心里骂着mmp,谁写的代码,翻看记录发现时自己写的。
然而此时,为什么有些同事代码却刷刷敲的飞快?
嘘!我这有本秘籍《重构,改善既有代码的设计》,看完这本书,赶英超美不是梦~
也许很多同学对重构有很多误解,在读这本书之前,先用两个简单的例子快速的理解为什么重构有用。
把长函数提取成短函数
为什么要提取成短函数
1.长函数难以理解
敲代码的时候,难免会遇到很长的函数。长函数是让代码变得难以理解的最常见的问题之一。特别是那些没有任何注释,且命名又不规范的长函数里,你修改一点代码的都让你胆颤心惊。
2.长函数难以测试
长函数不仅难以理解,而且难以测试。测试长函数需要很多先决条件,而有的时候,仅仅需要测试其中的某一段代码块。
3.长函数带来重复代码
长函数的另一个罪恶是,它必然带来重复的代码,因为长函数里的代码块无法复用。当你需要用到长函数里的代码块,但是你又不想去动到长函数,可能最妥协的办法就是复制这一块代码。
重复的代码几乎是最可怕的事情,一句话就是:复制一时爽,修改火葬场。如果修改了复制的代码,你是无法确定到底全部修改完毕了没有。最后可能导致这个修改过的代码不生效,bug重复出现等问题!
4.短函数帮助你找出BUG
直接测试短函数,可以快速的定位问题。
如何提取方法
在代码编辑器的帮助下,把长函数里面的代码块提取出来却是非常简单的事情。
在Idea下,只要选中需要被提取大代码块,按下Ctrl+Alt+M
如果有四两拨千斤的编码技巧的话,那么提取方法绝对是其中之一。简单的使用提取方法的重构手段,就能够让代码容易理解、修改,让你编码的速度飞快的提升。
使用多态替代switch
switch的罪恶
1.switch导致代码耦合
switch是导致代码耦合的典型原因。在部分语言里,如python,都取消了这个关键字。
大部分情况下switch都会和type一起使用。
//一些前置的共同使用的代码
switch(type){
case type1:
//type1相关的代码
break;
case type2:
//type2相关的代码
break;
}
问题的关键在于,大部分情况下,type1和type2的代码是不相关的。但是仅因为type1需要去修改前置的共同使用的代码时,你不得不考虑这个修改会不会导致type2发生改变。如果你的type有很多,这种代码的耦合性导致修改代码时要考虑的情况程指数级上升。
2.switch导致重复代码
另一个使用switch的问题在于,一旦你使用了一个switch,你在其他的方法里不得不使用另一个switch,最后导致switch泛滥。当需要添加一个新的type时,你就知道这是一件多么恐怖的事情。举一个获取工资的例子:
//获取平常的工资
public double getSalary(String type){
switch(type){
case "普通员工":
//计算普通员工资
break;
case "管理层":
//计算管理层工资
break;
}
}
//获取节假日加班工资
public double getFestivalSalary(String type){
switch(type){
case "普通员工":
//计算普通员节假日工资
break;
case "管理层":
//计算管理层节假日工资
break;
}
}
使用多态解耦
那么既然不建议使用switch,有什么更好的方法吗?
是的,使用多态,我第一次看到这种重构方法,令我心头一惊,居然还有这种操作!为什么不早点告诉我!
第一步:构建一个接口或者抽象类
public interface Staff{
double getSalary();
double getFestivalSalary();
}
第二步:实现接口
public class GeneralStaff implements Staff{
public double getSalary(){
//计算并返回普通员工的平日工资
}
public double getFestivalSalary(){
//计算并返回普通员工的节假日工资
}
}
public class Management implements Staff{
public double getSalary(){
//计算并返回管理层的平日工资
}
public double getFestivalSalary(){
//计算并返回管理层的节假日工资
}
}
第三步:通过多态调用
public class SalaryInfo {
private Staff _staff;
public void set_staff(Staff _staff) {
this._staff = _staff;
}
SalaryInfo(String type){
switch (type) {
case "普通员工":
set_staff(new GeneralStaff());
break;
case "管理层":
set_staff(new Management());
break;
}
}
//假设需要获取节假日工资+平日工资
public double getTotalSalary(){
return _staff.getSalary()+_staff.getFestivalSalary();
}
}
你也许会发现,重构后的代码还是有switch。但是这跟之前的switch不一样了,这是唯一的一个switch。
甚至跟进一步,这里的switch可以修改为反射,但是为了不让代码更复杂,我们在这里不进行进一步的优化。
另外的两个好处则非常明显。
- 修改其中一个type的代码不会影响其他type。
- 如果新增一个新的type将会非常简单,只需要一个实现接口的类并在唯一的switch增加相关代码就可以了。
通过这个重构,可以把混成一团的代码解耦开来。在这样的代码里新加功能,修改代码,难道不会比在混成一团的代码了快上10倍吗?
通过上面的两个例子,你应该了解了重构并不是什么类似架构师的高手的操作,他非常简单而且非常有效。甚至,你不需要征求任何人的同意去重构代码。只要重构代码能够让你工作更快且不会引起其他严重的问题,你就应该去重构。
《重构,改善既有代码的设计》
再回到这本书《重构,改善既有代码的设计》。抽取函数和用多态替代switch只是重构的两个技巧。是否还有其他类似的四两拨千斤的技巧呢?
是的,都在这本书里了。但是这类技术书籍都是列表式的,无法通过像故事书那样通过概述让你了解大概。但是依然可以分为几个部分来进行简易的理解。
为什么要重构
除了例子里展示的帮助你快速的修改代码和添加代码外,另一个重要的原因是:重构和预先设计是互补的。我们知道,在编码之前都会进行整体设计,详细设计等。但是我们也清楚,需求朝令夕改。而且新增的需求可能会完全超出原先设计的预想,所以完全按照最初的设计去编码,肯定是无法满足的,而重构则是补上预先设计无法满足的漏洞。通过根据最新的需求和代码情况进行代码的重构,保持代码的易维护易修改。如果没有引入重构,可以想象代码会像野草一样增长,最后到达无法理解的地步。而重构就像剪刀,对代码进行微小的修剪。
何时进行重构
另一个令人困扰的问题是,何时进行重构。毕竟,攻城狮们都很忙,几乎不会有时间专门去重构。而且重构一些可能之后不会使用到的代码,反而没有什么价值。那什么情况下的重构下才有价值。
- 当你修改BUG时
当你修改代码,如果代码不容易理解,那么很可能修改的代码治标不治本,或者隐藏的问题无法发现。而且,难以理解的代码,测试也将变得非常困难。修改这样的代码,如果没有重构,那修改是否成功大部分情况下就只能拜八阿哥了。 - 当你新增功能时
当你新增功能,你发现很难进行拓展。比如大量的switch,那么使用多态进行重构,让代码容易被拓展。这不仅这一次让你快速的拓展功能,虽然重构会耗费一点时间,但是减少的调试bug的时间绝对会弥补回来的。 - 何时不应该进行重构
并不是所有的时刻都适合做代码重构,比如deadline已经逼近,这个适合还是暂时不重构了。
哪些代码需要被重构
重构遇到的第一个困难是:如果重构没有目标,那么你可能会无休止的重构下去,导致最后改动非常大。所以我们需要清楚的知道,哪些代码是确实需要被重构的,哪些代码可能不需要重构。
在书里面列出了22种“代码的坏味道”。这看起来很多,幸运的是,我们并不需要考试,需要把这22种情况都记忆下来。
但是我们可以看看其中比较经典的“坏味道”。
- 重复代码
重复的代码是最常见的问题,我们无法完全消除重复代码,但是应该尽量的避免代码重复。 - 过长的函数
这个我们已经在例子里阐述过了。 - 过大的类
如果不对类加以整理重构,那么随着代码的增加,类变得巨大几乎是不可避免的事情。过大的类负责太多的事务,往往会导致大量的代码耦合。遇到过大的类应该警惕,通重构的手段,诸如上文提到的使用多态等方法分离类的功能。 - switch
当遇到switch时,应当警惕,switch往往是配合type一起使用,而type随着时间不可避免的会增多,导致类变得复杂和耦合。 - 散弹式修改
如果当你修改一个功能点,发现需要在各个地方都进行修改。而且你还不能保证所有的点都被修改了。这时候应该考虑把相同的代码整合到一块,并通过调用来引用功能。
如何阅读《重构,改善既有代码的设计》
尽管我们已经尽量在本文中详细的介绍该书中的很多细节。但是技术类的书籍大部分列表式的。本书的展示形式就是提供一个列表。每个列表就是一个重构的技法。包括
- 重构技法的名称
- 重构的动机
- 重构的步骤
- 重构的例子
只要快速的过一遍所有重构技法,你不需要记住所有的操作。当你遇到类似的问题时,再回来查看相关的重构的步骤。也许效率会高一些。
总结
综上,这本书真的可以让你快上10倍。如果想要获得这本书的话,关注公众号,回复重构获取下载链接!
这里放公众号二维码。