系统重构过程以及具体方法
一、 如何识别代码问题(识别技术债)
识别问题是重构的第一步。你需要像医生一样,通过“望闻问切”来诊断代码。
1. 静态代码分析工具(自动化诊断)
这是最快、最客观的方式。
SonarQube:行业标准。它能识别:
代码坏味道:过长的函数/类、过深的嵌套、重复代码、过多的参数等。
漏洞和安全热点:潜在的安全漏洞,如SQL注入、硬编码密码。
覆盖率:测试覆盖率报告。
Checkstyle/PMD:主要用于Java,检查代码风格和特定问题。
ESLint/Prettier:用于JavaScript/TypeScript,强制代码风格和发现常见错误。
SpotBugs:用于Java,查找具体的bug模式,如空指针解引用。
如何使用:将这类工具集成到CI/CD流水线中,每次代码提交都会自动分析,并将问题报告到仪表盘。把质量门槛作为流水线通过的强制条件。
2. 架构与依赖分析工具
Structure 101、Lattix:可视化代码结构和依赖关系,识别循环依赖、过紧的耦合、模块边界侵蚀。
JDepend / DSM:度量包之间的依赖关系。
3. 运行时分析工具(动态诊断)
APM工具:如SkyWalking, Pinpoint, New Relic。它们能帮你发现:
性能瓶颈:哪个方法/服务调用最耗时?
热点代码:哪些方法被调用最频繁?这些就是需要重点优化的地方。
错误率:哪个模块的出错率最高?出错多的地方往往代码不稳定。
4. 人工代码审查与“代码坏味道”清单
工具不能发现所有问题,最终需要人工判断。以下是常见的“代码坏味道”:
可读性差:
命名不清晰:变量名
a
,data
,函数名processData
。过长的函数/类:一个函数几百行,一个类几千行。
过深的嵌套:
if
里面套for
,里面再套if
,层次太深。魔法数字/字符串:代码中直接出现的、意义不明的数字或字符串。
结构性问题:
重复代码:这是最经典的坏味道,违反了DRY原则。
过长的参数列:函数参数超过3-5个,难以理解和使用。
数据泥团:总是同时出现的多个数据项,应该被封装成一个对象。
霰弹式修改:修改一个功能,需要同时改动多个分散的类。
发散式变化:一个类因为不同的原因,在不同的方向上被频繁修改。
面向对象问题:
依恋情结:一个方法对另一个类的数据比对自己所在类的数据更感兴趣。
switch语句:长长的
switch
或if/else
链,一有新的类型就需要修改。平行继承体系:每给一个类加一个子类,必须也为另一个类加一个子类。
二、 具体的修改方法(清理技术债)
识别问题后,就是动用“手术刀”的时候了。以下方法从易到难。
1. 基础重构手法(针对函数和类内部)
重命名:使名称更清晰,揭示意图。
提取函数/方法:将一段代码放入一个独立的函数中,并以它的用途命名。这是对付“过长函数”的首选武器。
内联函数/方法:当一个函数本体比函数名更清晰时,使用它。
提取变量:将复杂表达式的结果或一部分放入一个临时变量,用变量名解释其用途。
引入参数对象:将过长的参数列封装成一个对象。
分解条件表达式:将复杂的条件判断提取成函数。
2. 中级重构手法(处理类与类之间的关系)
搬移函数/字段:将一个函数/字段搬移到更合适的类中。常用于解决“依恋情结”。
提取类:当一个类过于庞大,承担了过多责任时,将一部分相关字段和函数提取到一个新类中。
内联类:与“提取类”相反。如果一个类不再承担足够责任,就把它塞进另一个类里。
以委托取代继承:当子类并不想继承超类的所有接口时,使用委托来替代继承关系,降低耦合度。
3. 引入设计模式(解决特定架构问题)
设计模式是重构的目标,而不是起点。当你发现代码有某种坏味道时,可以用相应的模式来重构。
遇到的代码问题(坏味道) | 可能适用的设计模式 | 重构目标和效果 |
---|---|---|
创建对象逻辑复杂/多变 | 工厂方法、抽象工厂、建造者 | 封装创建逻辑,使代码更灵活,符合开闭原则。 |
大量if/else 或switch ,根据类型执行不同行为 | 策略模式 | 将算法族封装起来,使它们可以互相替换,消除条件判断。 |
一个对象状态改变,需要通知其他多个对象 | 观察者模式 | 定义一对多的依赖关系,实现松耦合的通知机制。 |
需要为对象动态添加功能 | 装饰器模式 | 比继承更灵活地扩展功能,避免子类爆炸。 |
接口过于庞大,客户端只需要其中一部分 | 接口隔离原则 | 将大接口拆分成更小、更具体的接口。 |
类初始化成本高,且需要频繁创建销毁 | 单例模式、享元模式、对象池 | 控制实例数目,节省系统资源。(注意:单例需谨慎,易导致测试困难) |
子系统调用复杂 | 门面模式 | 为子系统提供一个统一的高层接口,使之更易于使用。 |
代码强依赖外部服务(如数据库),难以测试 | 依赖注入、适配器模式、仓库模式 | 解耦核心逻辑与外部依赖,通过Mock进行单元测试,提升可测试性和安全性。 |
4. 提升系统安全性的具体重构方法
引入安全边界:
对输入进行严格的校验和清理,防止XSS、SQL注入、命令注入。
使用参数化查询或ORM来替代字符串拼接SQL。
最小权限原则:
检查代码中服务、用户的权限,确保它们只拥有执行任务所必需的最小权限。
敏感信息处理:
移除硬编码的密码、API密钥,将其移至安全的配置中心或环境变量。
确保日志中不记录敏感信息(如密码、信用卡号、身份证号)。
依赖库安全升级:
使用
OWASP Dependency-Check
等工具扫描项目依赖,将存在已知漏洞的第三方库升级到安全版本。
实践流程:一个完整的重构案例
问题:一个订单处理函数长达200行,包含计算折扣、计算税费、更新库存、发送通知等多种逻辑,且嵌套深,难以测试。
识别:通过SonarQube报告和代码审查,发现“过长函数”和“重复代码”坏味道。
准备:为这个庞大的函数编写一个集成测试,覆盖几个主要业务场景,作为“安全网”。
重构:
小步前进:
第一步:使用“提取函数”,将计算折扣的逻辑抽成一个
calculateDiscount
函数。第二步:将计算税费的逻辑抽成
calculateTax
函数。第三步:将更新库存的逻辑抽成
updateInventory
函数,并发现它与另一个服务中的代码重复,消除重复。第四步:将发送通知的逻辑抽成
sendNotification
函数。
引入模式:
发现计算折扣有多种策略(新人折扣、会员折扣、满减折扣),大量的
if/else
。此时引入策略模式,将每种折扣封装成一个策略类。发现发送通知也有多种方式(短信、邮件、App推送),同样引入策略模式或工厂模式。
验证:每次提取后,都运行之前写好的集成测试和单元测试,确保行为未变。
结果:原始的200行函数,现在可能只剩下20行,像一个“工作流程管理器”,清晰可读。每个小函数都易于单独测试,并且由于引入了策略模式,增加新的折扣或通知方式也变得非常容易,系统可扩展性显著提升。
总结:识别问题是前提,自动化工具是你的侦察兵。具体修改是手段,从简单的重命名、提取函数,到复杂的引入设计模式,步步为营。最终,测试是你的护身符,确保每一次修改都是安全可靠的。