1.代码的改写从大范围到小范围大致可以分为四级:系统级别,功能级别,代码级别,机器级别; 2.代码级别以下改动可视为“重构”,功能级别以上级别只能视为“重写”
3.重构是持续的日常过程,而重写不是
辨析了“重写”与“重构”之后,下面专注地讲一下重构
1.1 重构的概念和背景
- EPC
- 破窗理论与懒惰:在没有刻意优化下,代码腐烂是必然的
- 80%在别人的代码上进行修改
1.2 重构的目的:使软件结构更加合理
- 1.2.1 WHAT:
在不改变可观察行为下,修改代码内部结构
- 1.2.2 WHY:
差的设计在后期越来越难以新增功能,好的设计在软件开发的每个阶段新增功能的速度都是差不多的
- 1.2.3 WHEN:
童子军法则,只要修改完比修改后更易懂,大到设计,小到函数,命名
- 1.2.4 HOW :
-1.2.4.1 构建测试安全网
代码语言:txt复制 1. 使用单元测试;分层测试;UI自动化测试
2. 进行可测试性改造:UI分离;Mock;依赖注入
- 1.2.4.2 消除代码坏味道
代码语言:txt复制 **1. - 函数问题(30s能读懂)**
代码语言:txt复制- 过长函数(Long Method):最好不超过20行
- 过长参数列(Long Parameter List):最好不超过5个《代码整洁之道推荐不超过3个》
- 基本类型偏执(Primitive Obsession)
- 重复的Switch(Repeat Switch)
- 循环问题(Loops)
2.单个类问题
代码语言:txt复制 - 过大的类(Large Class)
- 临时字段(Temporary Field)
- 数据泥团(Data Clumps):大量的数据要用结构组织,不要平铺
代码语言:txt复制 - 纯数据类 (Data Class)
- 神秘命名(Mysterious Name)
- 全局变量 (Global Data)
- 可变数据 (Mutable Data)
- 3. 类关系
代码语言:txt复制 - 发散式变化(Divergant Change)
- 散弹式修改(Shotgun Surgery)
- 依恋情节(Feature Envy)
- 夸夸其谈通用性(Speculative Generality)
- 过长的消息链(Message Chain)
- 中间人(Middle Man)
- 内幕交易(Insider Trading)
- 被拒绝的馈赠(Refuse Bequest)如子类不需要父类功能特性, **组合而不是继承**
- 4. 需要删除
代码语言:txt复制 - 注释(Comments): 代码自注释 > 写注释 > 不写注释
- 重复代码(Duplicated Code)
- 冗余元素(Lazy Element)
- 异曲同工的类(Alternative Class with Defenter interface )
- 1.2.5 工具
代码语言:txt复制 - SonarLint 插件 CodeDog
- IDE Refactor Complex Method
2. 函数重构
2.1 工具
- ApprovalTest Coverage = 无脑单测
- CombinationAppovals.verifyAllCombinations
- Android Studio IDE 覆盖率设置 设置Tracing 格式的可以查看单测命中率 - Preference -> Coverage -> Replace active suites with the new one - Edit Configuration -> Junit -> Tracing
2.2何时重构?
- 当你想要写注释时
2.3 大函数改造
- Bloaters - Long Methodundefined维护者未必无法识别Bad Code Smell,重构难,懒惰心理等问题使得coder 躺平。 - 难以维护 - 容易出现bug - 破窗效应
- 优秀函数的原则:
- 函数一般写10行
- 超过20行就考虑重构
- 第一条规则是短小
- 第二条规则还是短小
- 如何处理条件语句
- 函数提取:即按照逻辑拆分子函数。
- 分解表达式
- 以多态处理堆叠的条件表达式(如switch)
代码语言:txt复制 - 状态模式
- 策略模式
代码语言:txt复制- 将条件表达式转换为查找表,使用注解完成映射
2.4 进阶优化
- 组合函数(Composed Method)
- 封装细节
- 保留关键函数路径
- 抽象层次一致
- 最好不要超过10行
- 函数自注释
- 过长参数
- 问题
代码语言:txt复制 - 调用参数不易传递
- 增加理解难度
- 伴随巨大函数,基本类型偏执
代码语言:txt复制- 解决方案
代码语言:txt复制 - 构造参数对象
- 用builder 代替构造器
- 卫语句,即异常case先返回,主要逻辑在后。将嵌套逻辑扁平化
- 管道替代循环,声明式替代命令式
3. 类重构
Program to an interface, not an implementation.
组合优于继承
3.1 重构为啥难?
- 粘滞性
- 个人因素
- 代码阅读能力
- 重构方法的掌握
- 环境因素
- 时间,如需求倒排
3.2 面向对象
- 3.2.1 三大特性
- 抽象
- 继承
- 多态
- 3.2.2 设计原则 使用接口进行解偶
- 单一职责
- 接口设计
代码语言:txt复制 - 依赖倒置原则
- 接口分离原则
- 接口隔离原则
代码语言:txt复制- 一个类要尽可能不依赖外部
代码语言:txt复制 - 高内聚、低耦合
- 开闭原则
- 迪米特法则
代码语言:txt复制- 如何处理父子关系
代码语言:txt复制 - 里氏替换原则
- 合成、聚合原则
- 3.2.3 四个要素
- 3.2.3.1 抽象:提取关键元素
代码语言:txt复制 - 三原则
代码语言:txt复制 - DRY 原则:Don't Repeat Yourself
- YAGNI 原则:极限编程,不需要抽象那些你不需要的东西
- Rule of three: 原则1,2的取舍方法
代码语言:txt复制 - 抽象不足
代码语言:txt复制 - God Project:违反了单一职责原则
代码语言:txt复制 Android 刚开始开发时,使用MVC,V与C混杂在一起,造就了很多God Object。
代码语言:txt复制 - 提取类
代码语言:txt复制 - 抽象过度
代码语言:txt复制 - 如没有变量,只有方法,则抽象过于具体
代码语言:txt复制- 3.2.3.2 封装:隐藏细节
代码语言:txt复制 - 封装细节
代码语言:txt复制 - 1. 成员变量,一般设置为private
- 2. 集合:Collections.unmodifiableList
- 3. 函数:暴露较少接口,慎重写public函数
代码语言:txt复制 - 封装可预期变化
代码语言:txt复制 - 1. 散弹式修改,如每个AIDL调用,新增时非常复杂(使用查找表 注解依赖注入完成自动映射,不必每次新增)
代码语言:txt复制- 3.2.3.3 模块化
代码语言:txt复制 - 实现手法
代码语言:txt复制 - 通过封装得到模块
- 模块之间使用接口交互
代码语言:txt复制 - 常见问题
代码语言:txt复制 - 模块接口异常
代码语言:txt复制 - 臃肿 如Refuse Bequest
- 多变
代码语言:txt复制 - 模块依赖异常
代码语言:txt复制 - 循环依赖
代码语言:txt复制 - 修改一处,相互影响,产生震荡
- 切断循环依赖
- 继承可能也产生循环依赖
代码语言:txt复制 - 中间人依赖
代码语言:txt复制 - 依赖方个数 被依赖方个数越大,(一般)出现问题可能性越高。
代码语言:txt复制- 3.2.3.4 层次结构
代码语言:txt复制 - 常见问题
- 继承关系复杂
- 不恰当的继承(Stack -> vector ,不成立的继承关系)
代码语言:txt复制 - 解决方案
- 确保可替换性
- 组合优于继承
- 依赖顺序正确,最好是层级次序
- 继承结构简洁,如2层
推荐书籍
- 重构
- 设计模式
- 代码整洁之道
- 重构与模式
工程师素养