3. 重构代码示例讲解(不对外):
代码规范对比:
编码规范扫描对比:
idea analyze-inspect code对比:
-
神秘命名Mysterious Name
- 深思熟虑如何给函数、模块、变量和类命名,使它们能清晰地表明自己的功能和用法
-
重复代码 Duplicated Code
- 一旦有重复代码的存在,阅读这些重复的代码时就必须加倍仔细,留意其间细微的差异。如果要修改重复代码,必须找出所有的副本来修改。
- idea工具可以很智能的帮你分析出来
- General--Duplicated code fragement
-
过长函数 Long Function
- Ali-check也可帮我们进行智能分析
-
过长参数列表 Long Parameter List
旧版举例:
。。。。。。
-
全局数据 Global Data
- 全局数据的问题在于,从代码库的任何一个角落都可以修改它,而且没有任何机制可以探测出到底哪段代码做出了修改。
- 把全局数据用一个函数包装起来,至少可以看到修改它的地方,并开始控制对它的访问。之后可以把它放在一个类或者模块中,只允许模块内的代码使用它,尽量控制作用域。
- 代码中存在大量的未经定义的魔法值,其实大多是可以常量化或者用枚举来表达和重构
旧版举例:
新版改造:
-
可变数据Mutable Data
-
对数据的修改经常导致出乎意料的结果和难以发现的bug。需要采用一些手段来约束对数据的更新,降低风险。
旧版举例:
新版改造:
-
-
发散式变化 Divergent Change
- 一旦需要修改,只需要跳到系统的某一点,只在该处做修改。这是必须要做到的抽象,若因为修改一处代码同时牵连要修改多个函数,就会带来很严重的坏味道。
- 一定不要用复制粘贴来写代码
旧版举例:
新版改造:
-
霰弹式修改 Shotgun Surgery
- 散弹式修改类似于发散式变化,如果遇到某种变化,都必须在许多不同的类内做出许多小修改,那么面临的坏味道就是散弹式修改。
- 面对这样的问题,一个常用的策略就是使用与内联(inline)相关的重构把本不该分散的逻辑拽回一处。完成内联之后,可能会闻到过长函数或者过大的类的味道,不过总可以用于提炼相关的重构手法将其拆解成更合理的小块。
- 解决:改变只在一处发生,不要多处修改。将总是一起变化的东西放到一块儿。
旧版举例:
新版改造:
-
数据泥团 Data Clumps
- a\b\c三个变量,经常同时出现在多个函数的参数列表中。
- 解决:那么考虑把a\b\c封为一个类内,以此类当参数。
旧版举例:
新版改造:
-
基本类型偏执 Primitive Obsession
- 用合适的类型匹配合适的对象描述。
- 表现为:用简单的基本类型描述不同的含义的对象描述。
- 不合适的情况举例,如:0表示数学,1表示英语,2表示语文。那么可以考虑将数学/英语/语文的012用枚举定义,更易理解。
-
重复的switch Repeated Switches
- 表现为:在多处不同的地方,重复使用完全相同的switch语句或ifelse语句。重复的switch的问题在于:每当想增加一个选择分支时,必须找到所有的switch,并逐一更新。
- 这样的问题在于,如果产生变化,需要多处修改。任何switch语句都应该用以多态取代条件表达式消除掉。甚至所有条件逻辑都应该用多态取代。
-
循环语句。Loops
- 使用管道操作,如filter/map等,代替循环语句。
- 好处在于使得我们更快地看清被处理的元素以及处理它们的动作。
-
冗赘的元素 Lazy Element
- 指的是,多层不必要的包装。
- 如:方法a中包的是b,b包的是c,c包的是d。但是bc只是基于某种考虑的纯粹包装,而从未有其他变化,这时可以让a直接包d,bc就去掉吧。
-
夸夸其谈通用性 Speculative Generality
- 存在一些方法或参数是用于在未来某一天会实现的,但暂时还未实现就写了进去,这会加深对系统的理解和维护。应该搬移掉。
- 指的是,过度设计,假象的灵活机制。从未被用到的。
旧版:
不使用的代码逻辑尽可能的对它本身及其依赖进行清理
-
临时字段
- 有些类:其内部某个字段仅为某种特定情况而设,违背了通常认为对象在所有时候都需要它的所有字段的思维。
- 使用提炼类和搬移函数把所有和这些字段相关的代码都放到单独的类中统一管理。
旧版:
-
过长的消息链
- 用户向一个对象请求另一个对象,然后再向后者请求另一个对象,然后接着请求另一个对象…这就是消息链。容易造成的问题很明显,客户端代码将与查找过程中的导航结构紧密耦合。如果对象间的关系发生变化,客户端就必须做出修改。
- 使用隐藏委托关系。先观察消息链最终得到的对象是用来干什么的,看能否提炼函数把使用该对象的代码提炼到一个独立的函数中,在运用搬移函数把这个函数推入消息链。
-
中间人。Middle Man
- 对象的基本特征之一就是封装-对外部世界隐藏其内部细节。封装往往伴随着委托。
- 过度委托,举例:a使用b的b1 b2 b3三个函数,但是b1 b2 b3这三个函数都是c的c1 c2 c3。那么就把b这个中间人去掉,直接a使用c1 c2 c3
-
内幕交易
- 模块之间的数据交换会增加模块间的耦合。在实际情况里,一定的数据交换是不可避免的,但必须尽量减少这种情况,把交换放到明面上。
- 如果两个模块有共同的兴趣,可以尝试再新建一个模块,把这些公用的数据放在一个管理良好的地方;或者用隐藏委托关系,把另一个模块变成两者的中介。
在分层架构中详解。
-
过大的类 Large Class
- 让类业务功能单一,避免一个类干很多事情。
-
被拒绝的遗赠 Refused Bequest
- 指的是,子类只想继承超类的部分字段和函数,其他的拒绝使用。这意味着继承体系设计错误。超类中的字段函数应该是子类的必备数据。
- 继承体系设计时,该是真是一个体系。而不应因为多个并行的类有某些行为相像的函数,而抽取超类。比如猪和牛都有四条腿和一个尾巴,但不应该抽取一个只有四条腿和一个尾巴的超类,猪牛不是一个体系的。
旧版:
新版:
-
注释 Comments
- 一段又长又臭的代码,无法自解释,只能靠注释解释其含义。这不是注释原本的意义。
- 注释可以用来记述将来的打算之处,标记并无十足把握的区域,或是写下“为什么做某某某事”,这类信息可以帮助将来的修改者。
4. 重构与设计模式:
软件设计模式的概念
软件设计模式(Software Design Pattern),又称设计模式,是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。它描述了在软件设计过程中的一些不断重复发生的问题,以及该问题的解决方案。也就是说,它是解决特定问题的一系列套路,是前辈们的代码设计经验的总结,具有一定的普遍性,可以反复使用。其目的是为了提高代码的可重用性、代码的可读性和代码的可靠性。
设计模式的意义
设计模式的本质是面向对象设计原则的实际运用,是对类的封装性、继承性和多态性以及类的关联关系和组合关系的充分理解。正确使用设计模式具有以下优点。
- 可以提高程序员的思维能力、编程能力和设计能力。
- 使程序设计更加标准化、代码编制更加工程化,使软件开发效率大大提高,从而缩短软件的开发周期。
- 使设计的代码可重用性高、可读性强、可靠性高、灵活性好、可维护性强。
设计模式的学习过程
第一阶段:手中无剑,心中无剑
初步掌握一门面向对象编程语言,了解了类、对象、接口等概念。听别人说到“设计模式”感觉特别高大上,自己不太懂,也用不太起来。
第二阶段:手中有剑,心中无剑
四人帮经典23式开始一招一招比划。但此时手中虽有了重剑,却不太能舞起来。主要原因是,刚开始学习设计模式的人,开发经验一般相对较少,此时我们还缺乏实际经历的应用场景去做参考,看着书中的例子很难感同身受,比如我们看到“封装XX变化性”之类的描述很难引起共鸣。而且很有可能模式的描述和场景压根就不太明白。
第三阶段:手中有剑,心中有剑
随着自己的学习和开发经历的丰富,你已经对设计模式有一定理解了。在阅读开源代码,或者身边高手的代码时,你经常可以发现可以对应于某模式,因为类命名会出现设计模式中的术语,Factory, Builder,Strategy等。你逐渐明白了某个模式用在某个地方能起到什么作用,你经常会有一种"Wow"的赶脚。这时你在编程过程中,能够照猫画虎的用设计模式了。
第四阶段:手中无剑,心中有剑
你已经有设计和架构的概念了,这时你已经开始面对一个几万行甚至几十万行的项目,有一天你发现代码已经很难快速响应需求变化了,又没办法推倒重来,所以来重构吧,重构的过程中,一个个设计模式把代码维护中的一个个痛点逐个击破,你对代码的“smell”有了更深刻的理解,你开始对copy-paste代码深恶痛绝,你发现原来重构和设计模式竟然是孪生兄弟。
你开始明白一个长期维护和演化的项目,设计如此之重要。你不自觉地就会主动去翻《重构》、《浮现式设计》、《领域驱动设计》、Bob大叔的《敏捷软件开发》、《企业应用架构模式》等等,虽然你之前可能看过,但此时你看他们的心境和底蕴已经不一样了,你会发现这帮家伙确实很会总结。
最终,你回本溯源,你还是拿起了当初那本《设计模式》,你终于深深地体会到了里面闪烁的智慧,“面向接口编程”,“组合优先继承”,多么朴素的文字,或许很多人都能说出来,但是你确定你现在的理解更加深刻了。
你会发现,此时你手中已经无剑了,你不会想着什么模式列表了,不会想着某个模式重要了。进行一个复杂设计时,不自觉地就从软件开发的终极目标“高内聚低耦合”出发,“面向接口编程”,“组合优先继承”,“创建与使用分离”等认知成为体液反应,结合需求场景,每定义下一个类都有“开放封闭”等各种面向对象设计原则护体,随着项目演进,你能敏锐地嗅到各种代码smell,知道如何考虑现有资源去减少该死的技术债,随时进行着重构。