首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >📖  《代码重构的独孤九剑:如何优雅改造祖传屎山代码》

📖  《代码重构的独孤九剑:如何优雅改造祖传屎山代码》

原创
作者头像
Jimaks
发布2025-04-29 08:41:07
发布2025-04-29 08:41:07
3240
举报
文章被收录于专栏:技术杂烩技术杂烩

—— 阿里云技术团队实战经验沉淀


💡 第一式:以终为始,谋定后动

“屎山”代码的典型症状:

1️⃣ 逻辑迷宫:函数层层嵌套,跳转逻辑如蛛网

2️⃣ 脆弱如纸:改一行崩全局,测试覆盖率不足30%

3️⃣ 文档黑洞:注释与代码南辕北辙,新人入职即劝退

重构前的灵魂三问(附自查清单):

评估维度

高危信号🚨

应对策略💡

业务价值

无活跃用户/已下线功能

直接下刀删除,忌恋战!

技术债务

每天50%时间在修Bug

建立技术债看板量化成本

团队能力

无人知晓核心模块逻辑

先画领域地图再动工

实战心法

  1. 绘制热力图:用代码扫描工具(如SonarQube)生成技术债务热力图
代码语言:java
复制
// 示例:通过自定义规则识别典型问题代码
public class LegacyCodeDetector {
    void detectSmells(Project project) {
        new LongMethodRule().apply(project);  // 方法过长检测
        new CyclomaticComplexityRule().apply(project); // 圈复杂度检测
    }
}
  1. 制定逃生路线:用「绞杀者模式」渐进式重构(流程图见配图)
  2. 建立安全网:至少达成80%单元测试覆盖率后方可动核心逻辑

🔪 第二式:庖丁解牛——领域驱动下的精准手术刀

传统分层架构的困局

当代码库膨胀至百万行级别时,常见的Controller-Service-DAO分层模式会演变为:

  • 逻辑缝合怪:单个Service类动辄5000+行,混杂支付、风控、通知等不同领域逻辑
  • 依赖黑洞:循环引用随处可见,改一处依赖链如多米诺骨牌崩塌

DDD重构四步法

步骤

核心动作

输入材料

产出物

1

领域事件风暴

用户旅程图+现有代码

事件风暴矩阵(附案例)

2

划定限界上下文

领域事件流

上下文映射图

3

设计聚合根

业务流程中的核心实体

聚合根定义+不变式规则

4

战术模式落地

领域模型

整洁架构代码框架

典型案例:订单系统重构

代码语言:java
复制
// 重构前:贫血模型的Service类
public class OrderService {
    public void createOrder(OrderDTO dto) {
        // 校验逻辑(200行)
        // 库存计算(300行)
        // 优惠券核销(400行) 
    }
}

// 重构后:领域模型显性化
public class Order {
    private OrderId id;
    private List<OrderItem> items;
    private Coupon coupon;

    public void applyCoupon(Coupon coupon) {
        if (!coupon.isValid(items)) { 
            throw new InvalidCouponException();
        }
        this.coupon = coupon;
    }
    
    @DomainService
    public class OrderCalculator {
        public Money calculateTotal() {
            return items.stream()
                .map(item -> item.getPrice().multiply(item.getQuantity()))
                .reduce(Money.ZERO, Money::add)
                .subtract(coupon.getDiscount());
        }
    }
}

避坑指南

1️⃣ 不要过度设计:初期保持1个限界上下文对应1个代码模块

2️⃣ 警惕数据驱动陷阱:优先用领域事件代替直接数据库关联查询

3️⃣ 防腐层设计:新旧系统并存时用适配器隔离脏数据

代码语言:mermaid
复制
graph LR
    A[旧系统] -->|原始数据| B(防腐层Adapter)
    B -->|领域对象| C[新领域模型]
    C -->|标准化数据| D[基础设施层]

🌀 第三式:乾坤大挪移——模块化重构的太极之道

循环依赖的末日审判

当代码中出现 A→B→C→A 的死亡缠绕时,典型症状包括:

  • 🔥 编译卡顿:修改一个类触发全量重新编译
  • 💣 幽灵BUG:某个模块的改动引发无关功能崩溃
  • 🕳️ 知识诅咒:开发者不敢删代码,只能不断打补丁

模块化重构三阶心法

阶段

目标

核心招式

工具示例

物理隔离

Maven/Gradle模块化拆分

mvn --projects core,api

逻辑解耦

依赖倒置+接口隔离

Spring @Autowired

生态重组

领域包+功能包分层

Java 9 Module System

实战案例:用户系统依赖链破解

代码语言:java
复制
// 重构前:直接调用下游服务(紧耦合)
public class UserService {
    private OrderService orderService; // 直接依赖
    
    public List<Order> getOrders(Long userId) {
        return orderService.queryByUser(userId); // 跨领域调用
    }
}

// 重构后:依赖倒置+事件驱动
public interface UserEventPublisher {
    void publishUserAction(UserEvent event); // 抽象接口
}

@Service
public class OrderEventHandler {
    @EventListener // Spring事件监听
    public void handleUserAction(UserEvent event) {
        // 解耦后的处理逻辑
    }
}

依赖治理四象限

代码语言:mermaid
复制
graph TD
    A[核心领域] --> B(通用工具包)
    A --> C(第三方适配层)
    B --> D[基础设施]
    C -->|防腐处理| E((外部API))

(箭头方向=依赖流向,严禁逆向)

避坑指南

1️⃣ 分层不是越多越好:中小系统建议采用 领域层→应用层→基础设施层 三级结构

2️⃣ 警惕隐性耦合:禁止跨模块直接访问数据库表,统一通过领域服务交互

3️⃣ 依赖检查自动化:在CI流水线中加入架构守护规则

代码语言:xml
复制
<!-- ArchUnit 依赖约束示例 -->
<dependencyCheck>
    <rule>禁止基础设施层反向调用领域层</rule>
    <rule>限界上下文之间只能通过API通信</rule>
</dependencyCheck>

🔥 第四式:九阳神功——单元测试筑基大法

重构中的「达摩克利斯之剑」

当开发者试图改造旧代码时,常陷入两难困境:

  • 🚨 改不动:没有测试保护,每次修改都如履薄冰
  • 💥 改不完:修复一个Bug引发三个新Bug,陷入死亡螺旋
  • 🕸️ 改不净:遗留代码的隐性依赖导致测试用例难以编写

单元测试重构三部曲

阶段

目标

核心策略

工具链支持

筑基

关键路径保护

用测试包裹高危代码

JUnit 5 + JaCoCo

破境

测试驱动重构

红→绿→重构循环

Mockito + Testcontainers

飞升

测试即文档

用例即业务规则说明书

Cucumber + SpringBootTest


🛡️ 实战案例:支付服务重构

重构前(脆弱代码+零测试)

代码语言:java
复制
public class PaymentService {
    public boolean pay(User user, BigDecimal amount) {
        // 200行混合逻辑:风控校验+支付渠道路由+记账
        if (user.getBalance().subtract(amount).doubleValue() < 0) {
            throw new RuntimeException("余额不足"); // 直接抛RuntimeException
        }
        // 直接调用第三方支付API(无Mock)
    }
}

重构后(测试驱动+分层防护)

代码语言:java
复制
class PaymentServiceTest {
    @Mock PaymentGateway gateway;
    @InjectMocks PaymentService service;

    @Test
    @DisplayName("当用户余额充足时,支付成功")
    void should_succeed_when_balance_enough() {
        User user = User.builder().balance(new BigDecimal("100.00")).build();
        service.pay(user, new BigDecimal("50.00"));
        verify(gateway).execute(any()); // 验证支付网关调用
    }

    @Test
    @DisplayName("当余额不足时,抛出BusinessException")
    void should_throw_exception_when_balance_insufficient() {
        User user = User.builder().balance(new BigDecimal("30.00")).build();
        assertThrows(BusinessException.class, 
            () -> service.pay(user, new BigDecimal("50.00")));
    }
}

// 重构后的领域对象
public class Payment {
    public void validateBalance(BigDecimal amount) {
        if (this.balance.compareTo(amount) < 0) {
            throw new BusinessException("INSUFFICIENT_BALANCE"); // 明确业务异常
        }
    }
}

🚧 测试防护网设计指南

测试金字塔实践(附资源配比建议):

代码语言:txt
复制
测试金字塔资源配比
▲
│                 
│        ╭┈┈┈ E2E(10%) ┈┈┈╮
│     ╭┈┈ Integration(20%) ┈╮
│  ╭┈┈┈┈┈ Unit Tests(70%) ┈┈┈╮
└──────────────────────────────▶

测试类型

占比

投入重点

典型工具

单元测试

70%

核心业务逻辑/领域模型

JUnit, Mockito, TestNG

集成测试

20%

模块间交互/关键流程

SpringBootTest, REST Assured

端到端测试

10%

主干用户旅程/支付等关键路径

Selenium, Cypress

四类必须覆盖的黄金场景

  1. 正向路径:阳光下的Happy Path
  2. 边界条件:零值/极值/临界值攻击
  3. 异常流:网络抖动、依赖超时、脏数据
  4. 副作用验证:是否误改数据库或发送消息

💣 避坑指南

1️⃣ 不要追求100%覆盖率:核心业务代码85%+,工具类代码60%即可

2️⃣ 警惕Mock滥用:过度Mock会导致测试失真,对DAO层建议用Testcontainers

3️⃣ 测试用例不是垃圾桶:禁止出现catch(Exception e){/* ignored */}

4️⃣ 测试命名即文档:采用Given-When-Then模式命名用例

代码语言:java
复制
@Test  
@DisplayName("Given用户有未支付订单 When尝试再次下单 Then抛出重复订单异常")  
void should_throw_duplicate_order_exception() { ... }  

重构中的「生死时速」

当面对数万行历史代码时,纯手工重构如同在雷区跳舞:

  • 🕒 效率黑洞:重命名一个字段需人工修改50+文件
  • 🧨 人肉误差:稍有不慎便引发连锁报错
  • 🤖 重复劳动:80%的基础重构动作可通过工具标准化

🛠️ 重构工具箱矩阵

按场景选择你的「倚天剑」

工具类型

典型代表

杀手锏功能

适用场景

IDE神器

IntelliJ IDEA

安全重命名/提取方法/内联

日常局部重构

静态分析

SonarQube

技术债量化+坏味道检测

重构优先级评估

代码规约

Alibaba Java Coding Guide

自动化代码格式化

统一团队风格

智能重构

JetBrains AI Assistant

语义级代码自动优化

复杂逻辑拆分


🔧 实战:批量改造「烂代码」

场景:将旧系统中散落的 new Date() 统一替换为 LocalDateTime

手动重构痛点

  • 需全局搜索+逐行检查时区处理逻辑
  • 容易漏改隐式依赖Date的第三方库

工具化重构流程

代码语言:txt
复制
工具化重构五步流
┌────────────────┐
│ 1. IDE全局搜索   │
│   'new Date()'  │
└───────┬─────────┘
        ↓
┌────────────────┐
│ 2. 创建迁移类    │
│   DateUtils    │
└───────┬─────────┘
        ↓
┌────────────────┐
│ 3. 自动替换     │
│   Alt+Enter    │
└───────┬─────────┘
        ↓
┌────────────────┐
│ 4. 静态分析     │
│   时区风险检测  │
└───────┬─────────┘
        ↓
┌────────────────┐
│ 5. 补充测试     │
│   单元测试覆盖  │
└────────────────┘

代码对比

代码语言:java
复制
// 重构前:脆弱的Date使用
public class Order {
    private Date createTime; // 可序列化问题
    
    public boolean isExpired() {
        return new Date().after(this.createTime); // 隐式系统时区
    }
}

// 重构后:线程安全的时间处理
public class DateUtils {
    public static LocalDateTime now() {
        return LocalDateTime.now(ZoneId.of("Asia/Shanghai"));
    }
}

public class Order {
    private LocalDateTime createTime;
    
    public boolean isExpired() {
        return DateUtils.now().isAfter(createTime);
    }
}

🚀 高阶技巧:IDE重构快捷键速查表

操作

Windows快捷键

Mac快捷键

安全等级⭐

安全删除未使用代码

Alt + Delete

⌘ + Delete

⭐⭐⭐⭐

提取方法

Ctrl + Alt + M

⌘ + ⌥ + M

⭐⭐⭐

内联变量

Ctrl + Alt + N

⌘ + ⌥ + N

⭐⭐⭐⭐

类型迁移

Ctrl + Alt + Shift + T

⌘ + ⌥ + Shift + T

⭐⭐


⚠️ 避坑指南

1️⃣ 不要迷信全自动:工具处理后的代码需人工校验业务语义

2️⃣ 版本控制是生命线:每次重构前提交代码,确保随时可回滚

3️⃣ 保持小步快跑:单次重构不超过50行,结合CI快速验证

4️⃣ 警惕「假安全」:部分工具无法识别反射/动态代理等特殊调用


▌▍▎▏ 你的每个互动都在为技术社区蓄能 ▏▎▍▌

点赞 → 让优质经验被更多人看见

📥 收藏 → 构建你的专属知识库

🔄 转发 → 与技术伙伴共享避坑指南

点赞 ➕ 收藏 ➕ 转发,助力更多小伙伴一起成长!💪

💌 深度连接

点击 「头像」→「+关注」

每周解锁:

🔥 一线架构实录 | 💡 故障排查手册 | 🚀 效能提升秘籍

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 💡 第一式:以终为始,谋定后动
  • 🔪 第二式:庖丁解牛——领域驱动下的精准手术刀
  • 🌀 第三式:乾坤大挪移——模块化重构的太极之道
  • 🔥 第四式:九阳神功——单元测试筑基大法
    • 🛡️ 实战案例:支付服务重构
    • 🚧 测试防护网设计指南
    • 💣 避坑指南
    • 🛠️ 重构工具箱矩阵
    • 🔧 实战:批量改造「烂代码」
    • 🚀 高阶技巧:IDE重构快捷键速查表
    • ⚠️ 避坑指南
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档