技术债识别与偿还策略¶
30 秒版(开场)¶
技术债是 为短期速度牺牲的长期维护成本。资深回答要会 分类(故意/无意)、量化影响(变更 lead time、故障率)、偿还策略(绞杀者、增量重构、预算 20%);Lead 面看能否在业务压力下 谈妥优先级。
3 分钟版(精讲深度)¶
- 是什么:设计/实现上的妥协,导致后续修改更慢、更易出错;包括代码、架构、测试、文档、依赖陈旧。
- 为什么:5 年+ 不是「零技术债」,而是 可管理的债;提问者想听你在 交付与质量 间的权衡案例。
- 怎么做:建立 Tech Debt Register(条目、影响、成本估算、建议偿还窗口);每迭代预留 10~20% 容量;大债用 Strangler Fig 渐进替换。
10 分钟版(原理 + 图示)¶
技术债四分法(好用)
| 类型 | 例子 | 偿还方式 |
|---|---|---|
| 代码债 | 巨型 handler、无测试、全局变量 | 小步重构 + 测试护栏 |
| 架构债 | 单体耦合、无边界上下文 | 模块提取、接口防腐层 |
| 基础设施债 | Go 1.16、无 CI、vendor 失控 | 升级窗口、自动化 |
| 流程债 | 无 CR 规范、无 ADR | 规范 + 模板 |
quadrantChart
title 技术债优先级矩阵
x-axis 低偿还成本 --> 高偿还成本
y-axis 低业务影响 --> 高业务影响
quadrant-1 计划偿还
quadrant-2 立即处理
quadrant-3 可忽略
quadrant-4 监控
Go 项目常见债
main包几千行、无internal/边界- handler 直接调 DB,无 service 层
- 包装错误时用
%v丢失 error chain,导致errors.Is/As无法继续匹配;应按边界使用%w(Go error 本身并不自动携带 stack) - 无
-raceCI、无 benchmark 基线 vendor/与go.mod长期不同步
生产场景¶
- 症状:同样需求估期从 3 天变 2 周;线上小改频繁回归;新人 3 个月仍不敢动核心模块
- 量化:变更失败率、部署频率、P95 构建时间、测试覆盖率趋势
- 业务对话:用「不还可预期的 X 月大故障 / Y% 人力浪费」换排期
排查与工具¶
- 静态分析:
staticcheck、golangci-lint技术债报告 - 依赖:
go list -m -u all、govulncheck、Dependabot/Renovate - 代码复杂度:cyclomatic、包依赖图(
go mod graph,或专门的静态分析工具) - 文档:ADR(Architecture Decision Record)记录「为何欠债」
架构取舍¶
| 策略 | 何时用 |
|---|---|
| Boy Scout Rule | 日常 touch 文件顺手改一点 |
| 专项 Sprint | 大版本前集中还「阻塞型债」 |
| Strangler | 老模块旁路新实现,流量渐进切换 |
| 重写 | 仅当域模型清晰且业务可停 feature |
何时不急着还:低流量遗留模块、6 个月内要下线、偿还成本 > 重写收益。
深挖问答¶
- 业务说没空还债? → 选 1~2 条 高影响低成本(如加 CI race、拆最痛模块测试);绑定下个故障成本案例。
- 如何向非技术老板解释? → 类比「信用卡利息」:现在不还,以后每个需求多付 30% 时间税。
- 重构引入 regression? → characterization test、金丝雀、feature flag 双跑对比。
- 怎么防止新债? → CR checklist、架构评审门槛、Definition of Done 含测试与文档。
反模式与事故¶
- Big Bang 重写 两年无产出 → 业务失去信任
- 只抱怨不还 → Lead 信誉下降
- 隐藏债 不报风险 → 估期持续失真
- 为「干净」过度抽象 → 新的一种债
代码示例¶
绞杀者模式:老接口委托新实现
// legacy 包逐步下线,新逻辑在 internal/order
func CreateOrder(ctx context.Context, req *Request) (*Order, error) {
return order.NewService(repo).Create(ctx, req)
}
延伸阅读¶
- Martin Fowler — Technical Debt
- Go 官方模块布局指南
golang-standards/project-layout是社区示例仓库,不是 Go 官方标准,不能把目录结构背成唯一规范