返回首页

高负载系统中遗留代码的共存

文章探讨大型项目中移除遗留代码的风险,以及通过功能开关和门面实现共存策略。提供 Spring Boot 示例、流量迁移方案和强制删除案例。该方法最小化回归风险,维持正常运行时间。

不要删除遗留代码:功能开关确保稳定性
Advertisement 728x90

稳定优先:大型项目中遗留代码共存策略

在长期运行、高负载的系统中,删除遗留代码可能引发回归问题和系统不稳定。与其进行大规模重构,不如采用功能开关和门面模式,让新旧代码并行运行。这种做法支持持续开发而无需停机,同时保留经过验证的组件作为故障回退方案。

激进代码删除的风险

即使移除已废弃的代码,也可能因隐藏依赖(如反射、动态加载、RPC调用)导致失败。遗留系统的测试通常无法覆盖边缘场景,而生产数据常会触发罕见执行路径。更严重的是,组织知识流失加剧了风险——离职团队留下关键代码,用于迁移或合规等特殊场景。

在企业环境中,厂商定制模块尤为脆弱,任何变更都可能导致厂商支持失效。

Google AdInline article slot

实践中的“不碰老系统”原则

一个经典案例是NASA的深空一号探测器:1970年代的代码被完整保留,并通过适配器封装。只要现有组件运行稳定,就不应轻易改动。这一成熟策略特别适用于运行超过五年的系统。

共存架构:反向藤蔓模式

不要摧毁旧系统,而是并行构建新系统,逐步将流量转移。两种策略:

| 策略 | 操作 | 适用场景 |

Google AdInline article slot

|----------|---------|----------------|

| 删除 | 在新架构上重写 | 逻辑简单、测试覆盖率100%、无依赖 |

| 共存 | 新代码与旧代码并行,渐进式路由 | 核心功能、测试薄弱、风险高 |

Google AdInline article slot

功能开关保障安全:

  • 开发新实现。
  • 包裹在标志判断中。
  • 在1%流量上运行。
  • 成功则逐步扩大。
  • 出现问题可立即通过标志回滚。

遗留代码在仓库中保留2–3年,用于可靠性分析。

Spring Boot 中的实现

将遗留代码封装在接口中,不修改原始代码。

// 遗留服务(保持不变)
@Service
public class LegacyDiscountService {
    public double calculateDiscount(Order order) {
        // 五年前的逻辑
        return order.getTotal() * 0.1;
    }
}

// 新服务
@Service
public class NewDiscountService {
    public double calculateDiscount(Order order) {
        return applyComplexRules(order);
    }
}

带开关的门面:

@Component
public class DiscountServiceFacade {
    private final LegacyDiscountService legacyService;
    private final NewDiscountService newService;
    
    @Value("${feature.discount.new.enabled:false}")
    private boolean useNewService;
    
    public DiscountServiceFacade(LegacyDiscountService legacyService, 
                                 NewDiscountService newService) {
        this.legacyService = legacyService;
        this.newService = newService;
    }
    
    public double calculateDiscount(Order order) {
        if (useNewService) {
            return newService.calculateDiscount(order);
        }
        return legacyService.calculateDiscount(order);
    }
}

通过配置文件隔离:

@Configuration
public class DiscountConfiguration {
    
    @Bean
    @ConditionalOnProperty(name = "feature.discount.new", havingValue = "false", matchIfMissing = true)
    public DiscountService legacyDiscountService() {
        return new LegacyDiscountService();
    }
    
    @Bean
    @ConditionalOnProperty(name = "feature.discount.new", havingValue = "true")
    public DiscountService newDiscountService() {
        return new NewDiscountService();
    }
}

测试排除遗留代码:

@Test
@DisabledIf("!${feature.discount.new.enabled}")
void testNewDiscountLogic() {
    // 仅测试新逻辑
}

流量切换工作流

API网关根据标志路由:单体架构作为后备,微服务逐步扩容。功能开关服务实时管理流量比例。遗留代码在生产环境保留2–3年。

删除不可避免时的应对

  • 安全:过时库存在CVE漏洞。
  • 合规:违反GDPR等法规。
  • 技术终结:无补丁支持的Java版本。
  • 无人可用:缺乏相关领域专家。

即便如此,也应采用渐进式发布并具备回滚能力。

核心要点

  • 系统稳定性高于代码整洁度:可用性比删掉几行旧代码更重要。
  • 隐藏依赖需监控真实生产调用后再决定是否删除。
  • 功能开关显著降低迁移风险。
  • 技术债若维护成本低于重构,则值得保留。
  • 代码中承载组织记忆:未达百分百信心前切勿删除。

— Editorial Team

Advertisement 728x90

继续阅读