1. 软件开发常见坏毛病
-
重复代码(Duplicated Code),Don’trepeat yourself(DRY)。关于DRY原则,我们在平时开发过程中必须要严格遵守。
-
过长函数 (Long Method)
-
过大的类 (Large Class)
-
过长参数列表 (Long Parameter List)
-
冗余类(Lazy Class)
-
冗余函数(Lazy Function)
-
无用函数参数(Unused Function Parameter)
-
函数圈复杂度超过 10(TheComplexity is over 10)
-
依恋情结(Feature Envy)
-
滥用Switch(SwitchAbuse)
-
过度扩展设计(Over-extend design)
-
不可读或者可读性差的变量名和函数名 (unread variable or function name)
-
异曲同工类(Alternative Classes with Different Interfaces)
-
过度耦合的消息链(Message Chains)
-
令人迷惑的临时字段(Temporary Field)
-
过多注释 (Too Many Comments)
2. 代码重构解决坏毛病
重构不只可以改善既有的设计结构,还可以帮助我们理解原来很难理解的流程。比如一个复杂的条件表达式,我们可能需要很久才能看明白这个表达式的作用,还可能看了好久终于看明白了,过了没多长时间又忘了,现在还要从头看,如果我们把这个表达式运用Extract Method 抽象出来,并起一个易于理解的名字,如果函数名字起得好,下次当我们再看到这段代码时,不用看逻辑我们就知道这个函数是做什么的。
如果对这个函数内所有难于理解的地方我们做了适当的重构,把每个细小的逻辑抽象成一个小函数并起一个容易理解的名字,当我们看代码时就有可能像看注释一样,不用再像以前一样通过看代码的实现来猜测这段代码到底是做什么的。
此外重构可以使我们增加对代码和业务逻辑功能的理解,从而帮助我们找到 Bug;重构可以帮助我们提高编程速度,即重构改善了程序结构设计,并且因为重构的可扩展性使添加新功能变得更快更容易。
2. 代码重构遵守的原则
类的单一职责
体现一个类只做一件事,良好的软件设计中系统是由一组大量的短小的类组成,以及需要他们之间功能协作完成,而不是几个上帝类。如果类的职责超过一个,这些职责之间就会产生耦合。改变一个职责,可能会影响和妨碍类为其他人服务的功能。这种类型的耦合将会导致脆弱的设计,在修改的时候可能会引入不少未知的问题。
开闭原则
一个软件实体如类,模块和函数应该对扩展开放,而对修改关闭。具体来说就是你应该通过扩展来实现变化,而不是通过修改原有的代码来实现变化。该原则是面相对象设计最基本的原则。
里氏替换原则
子类可以扩展父类的功能,但不能改变父类原有的功能。简单来说,所有使用基类代码的地方,如果换成子类对象的时候还能够正常运行,则满足这个原则,否则就是继承关系有问题,应该废除两者的继承关系,这个原则可以用来判断我们的对象继承关系是否合理。通常在设计的时候,我们都会优先采用组合而不是继承,因为继承虽然减少了代码,提高了代码的重用性,但是父类跟子类会有很强的耦合性,破坏了封装。
接口隔离原则
不能强迫用户去依赖那些他们不使用的接口。简单来说就是客户端需要什么接口,就提供给它什么样的接口,其它多余的接口就不要提供,不要让接口变得臃肿,否则当对象一个没有使用的方法被改变了,这个对象也将会受到影响。接口的设计应该遵循最小接口原则,其实这也是高内聚的一种表现,换句话说,使用多个功能单一、高内聚的接口总比使用一个庞大的接口要好。
依赖倒置(DIP)
高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。其实这就是我们经常说的“针对接口编程”,这里的接口就是抽象,我们应该依赖接口,而不是依赖具体的实现来编程。DIP 描述组件之间高层组件不应该依赖于底层组件。依赖倒置是指实现和接口倒置,采用自顶向下的方式关注所需的底层组件接口,而不是其实现。DI 模式很好例子的就是应用 IOC(控制反转)框架,构造方式分为分构造注入,函数注入,属性注入 。
3. 代码重构的时机
(1)当添加新功能时如果不是特别容易,可以通过重构使添加特性和新功能变得更容易。在添加新功能的时候,我们就先清理这个功能所需要的代码。花一点时间,用滴水穿石的方法逐渐清理代码,随着时间的推移,我们的代码就会越来越干净,开发速度也会越来越快。
(2)修改Bug的时候去重构,比如你在查找定位Bug的过程中,发现以前自己的代码或者别人的代码因为设计缺陷比如可扩展性、健壮性比较差造成的,那么此时就是一个比较好的重构时机。
(3)CodeReview时去重构,很多公司研发团队都会有定期的CodeReview,这种活动的好处多多,比如有助于在开发团队中传播知识进行技术分享,有助于让较有经验的开发者把知识传递给欠缺经验的人,并帮助更多的人对软件的其他业务模块更加熟悉从而实现跨模块的迭代开发。
4. 代码重构的技巧
(1)函数重构
-
重命名函数(Rename Function)
变量和函数名的可读性要强,从名字就可以知道这个变量和函数去做什么事情,所以好的可读性强的函数名称很重要,特别是有助于理解比较复杂的业务逻辑。 -
移除参数(Remove Parameters)
当函数不再需要某个参数时,要果断移除,不要为了某个未知需求预留参数,过多的参数会给使用者带来参数困扰。 -
分离查询和修改函数(Separate Query and Modify Function)
如果某个函数既返回对象值,又修改对象状态。这时候应该建立两个不同的函数,其中一个负责查询,另一个负责修改。 -
令函数携带参数
如果若干函数做了类似的工作,只是少数几个值不同导致行为略有不同,合并这些函数,以参数来表达不同的值。 -
以明确函数取代参数
有一个函数其中的逻辑完全取决于参数值而采取不同行为,针对该参数的每一个可能值建立一个单独的函数。 -
以函数取代参数
对象调用某个函数,并将所得结果作为参数传递给另外一个函数,而那个函数本身也能够调用前一个函数,直接让那个函数调用就行,可以直接去除那个参数,从而减少参数个数。 -
保持对象完整性
如果你需要从某个对象取若干值,作为函数的多个参数传进去,特别是需要传入较多参数,这种情况建议直接将这个对象直接传入作为函数参数。 -
引入参数对象
某些参数总是同时出现,新建一个对象取代这些参数,不但可以减少参数个数,而且也许还有一些参数可以迁移到新建的参数类中,增加类的参数扩展性。 -
隐藏函数
如果有一个函数从来没有被其他类有用到,那么需要隐藏这个函数,减小作用域。 -
以工厂函数取代构造函数
如果你希望创建对象时候不仅仅做简单的构建动作,最显而易见的动机就是派生子类时根据类型码创建不同的子类,或者控制类的实例个数。
(2)条件表达式重构
-
分解条件表达式
如果有一个复杂的条件语句,if / else语句的段落逻辑提取成一个函数。 -
合并条件表达式
一系列条件测试,都得到相同的测试结果,可以将这些测试表达式合并成一个,并将合并后的表达式提炼成一个独立函数 -
合并重复的条件片段
在条件表达式的每个分支上有着相同的一段代码,把这段代码迁移到表达式之外。 -
移除控制标记
不必遵循单一出口的原则,不用通过控制标记来决定是否退出循环或者跳过函数剩下的操作,直接break 或者 return。 -
以卫语句替代嵌套条件表达式
条件表达式通常有两种表现形式,一:所有分支都属于正常行为;二:只有一种是正常行为,其他都是不常见的情况。对于一的情况,应该使用if/else 条件表达式;对于二这种情况,如果某个条件不常见,应该单独检查条件并在该条件为真时立即从函数返回,这样的单独检查常常被称为卫语句。 -
以多态取代条件表达式
如果有个条件表达式根据对象类型的不同选择而选择不同的行为,将条件表达式的每个分支放进一个子类内的覆写函数中,将原始函数声明为抽象函数。 -
引入 Null 对象
当执行一些操作时,需要再三检查某对象是否为 NULL,可以专门新建一个 NULL 对象,让相应函数执行原来检查条件为 NULL时要执行的动作,除 NULL 对象外,对特殊情况还可以有 Special 对象,这类对象一般是Singleton. -
以 HashTable取代条件表达式
通过 HashTable的Key-Value 键值对优化条件表达式,条件表达式的判断条件作为 key 值,value 值存储条件表达式的返回值