返回首页

安全模块重构:保留行为 | IT 实践

本文揭示了从单体系统中安全提取模块的策略。它描述了保留行为的原则,使用功能标志和测试防止回归。强调第一阶段最小化更改。

无故障重构:提取模块指南
Advertisement 728x90

无风险重构:如何在保持行为不变的情况下提取模块

将模块从单体应用迁移到独立服务时,主要目标是完全保持原有行为不变,包括已知的 bug 和未文档化的怪癖。在这个阶段试图“清理”代码往往会导致回归问题。我们将展示如何采用最小变更策略避开这些陷阱。

为什么“保守”的方法能让你免于回归问题

生产系统经过多年运行,形成了一种隐含的契约:客户端集成、错误处理、时序延迟,以及对外部 API 的适应。这种契约没有文档记录——它埋藏在日志、提交记录和团队经验中。重构不应该改变行为,即使其中包含 bug。例如,如果旧代码返回空字符串而不是 null,修改这一点就会破坏已经适应的客户端。主要原则:提取改变形式,但保持语义。任何偏离原有行为的举动,都会把重构变成后果不可预测的发布。

一次只做一类变更:先边界,后内部结构

将任务拆分成阶段:

Google AdInline article slot
  • 阶段 A:提取模块,同时保持契约(HTTP、队列、总线)不变,并引入特性标志。
  • 阶段 B:更新技术栈、库或代码风格。
  • 阶段 C:修复 bug。
  • 阶段 D:优化性能。

在单个 PR 中混合这些阶段,必然导致难以阅读的差异、回滚难题,以及关于“这是 bug 还是新功能?”的争论。在第一阶段,用新服务边界包裹旧代码:外部是新边界,内部是旧逻辑。例如,提取路由模块时,保留原有的 route() 函数,但将其包装成 HTTP 端点。这样确保行为完全一致,即使代码看起来“丑陋”。

特性标志:你的紧急杀手开关

在生产环境中并行运行旧代码和新代码,需要一个通过配置或环境变量控制的开关。这能提供:

  • 事件发生时无需重新部署即可立即回滚。
  • 渐进式 rollout 到部分流量(金丝雀发布)。
  • 长期保留旧实现,用于行为对比。

关键是,该开关必须无需重启服务即可生效。否则,夜间事件就会变成紧急电话,而不是简单的配置调整。在业务逻辑层面实现标志,而不是基础设施——避免对编排器的复杂依赖。

Google AdInline article slot

复制行为,不要重写它

安全提取的准则:照搬文件“原样”,仅更改构建所需的部分。真实案例——将路由从 actor 迁移到纯函数:

object UpdateRouting {
  def route(update: Update): Either[RoutingError, RouteTarget] =
    if (update.message.exists(_.chat.isGroupOrSupergroup)) Right(GroupFlow)
    else if (update.callbackQuery.isDefined) Right(CallbackFlow)
    else Right(PrivateFlow)
}

映射模型时,即使是细微细节也要保留。如果旧代码将缺失的 channelId 转为字符串,则同样处理:

def toBusMessage(in: PlatformInMessage): BusIncoming =
  BusIncoming(
    correlationId = newCorrelationId(),
    channelId = in.channelId.getOrElse("")
  )

任何语义变更(比如用 null 替换空字符串)都会破坏与其他服务的契约。目标:比特级一致的行为,即使代码看起来过时。

Google AdInline article slot

提取过程中绝对要避免的事项

特性标志激活期间,坚决避免:

  • 新库。每个库都会引入传递依赖和攻击面。栈更新是单独任务。
  • 代码风格变更。将旧领域代码与新包装器混合是安全性的暂时代价。
  • 修复发现的 bug。记录工单,在稳定后处理。否则,每个事件都会被归咎于重构。
  • “顺手”优化。性能分析和加速是单独周期。

这些限制看似多余,但六个月后的 git blame 中,你会看到清晰历史:“提取模块,连接总线”,而非“重写了所有东西”。

测试:锁定契约,而不是 KPI

纯函数(路由、映射)的单元测试应在提取前捕获行为。提取后绿色的测试保证逻辑未变。对于集成场景(网络、队列、DB),使用 E2E 测试,但要清楚它们的局限:

  • 外部系统的模拟可能延续代码中的错误假设。
  • 真实 API 常有测试未覆盖的细微怪癖(如 /api/ 前缀)。

始终包括在类生产 staging 环境的手动运行。自动化无法取代近真实条件的检查。

代码膨胀不是 bug,而是流程阶段

第一阶段后,代码量往往膨胀:适配器、特性标志、单体重复。这是正常现象——你在为安全回滚和分阶段 rollout 买单。清理和精简在第二阶段进行,此时边界已稳、旧代码已除、测试确认正确。先确保它能工作,再优化。

关键要点

  • 提取目标——转移行为“原样”,而非全面改进。
  • 特性标志必备,用于无部署的安全回滚。
  • 测试锁定契约,在提取开始前,而非之后。
  • 禁止并行变更技术栈、bug 修复和优化。
  • 代码可膨胀——这是策略的一部分,不是错误。

重构遗留代码不是实验场所。流程越保守,凌晨 3 点因生产宕机被叫醒的可能性越小。遵循这些规则,将高风险提取变成可预测操作。

— Editorial Team

Advertisement 728x90

继续阅读