30、项目复盘:技术债务管理,架构演进案例,团队协作经验总结

项目做完了,代码也上线了。这时候最该干什么?

我个人习惯,不是急着开香槟,而是拉上团队做一次彻底的复盘。说白了,就是把这一路踩过的坑、欠下的债、做对的事,统统翻出来晒一晒。你想想看,一个大型C++项目跑了一年多,代码量从几万行膨胀到几十万行,中间经历了多少次架构调整、人员变动、需求变更?如果不复盘,下一轮迭代只会重蹈覆辙。

30.1 技术债务管理:欠债容易还债难

技术债务这个词,大家都不陌生。但真正把它当回事的团队,其实不多。

我在项目中遇到过最典型的场景:为了赶一个紧急版本,团队决定“先上线再说”。于是代码里塞满了临时补丁、硬编码、重复逻辑。等版本上线了,大家也都忘了这茬。三个月后,新需求来了,发现改不动了——因为那坨临时代码已经和核心逻辑长在一起了。

技术债务的四种常见类型:

  • 设计债务:架构设计不合理,模块耦合严重,扩展困难
  • 代码债务:命名混乱、函数过长、重复代码、缺乏注释
  • 测试债务:单元测试覆盖率低,集成测试缺失,回归测试靠人工
  • 文档债务:接口文档过期,架构图没有更新,部署手册缺失

怎么管理?我建议用“债务清单”的方式。每个迭代结束后,花半天时间梳理当前的技术债务项,按严重程度和修复成本打分。然后排入下一个迭代的 backlog 里。

债务项 严重程度(1-5) 修复成本(人天) 优先级 状态
日志模块硬编码路径 3 0.5 已修复
网络层与业务层耦合 5 8 待排期
配置文件格式不统一 2 1 已修复
单元测试覆盖率不足30% 4 15 进行中

我的经验:每次修复技术债务时,顺手把相关的代码重构一下。比如你改一个日志模块,顺带把周围的异常处理也统一了。这叫“路过式重构”,效率很高。

30.2 架构演进案例:从单体到微服务,再回到模块化

这个案例很有意思。我们当时做一个实时数据处理平台,初期为了快速验证业务,用了单体架构。所有逻辑都在一个进程里:数据接收、解析、计算、存储、告警,全搅在一起。

一开始跑得挺欢。但数据量上来后,问题就暴露了:

  • 某个模块的 bug 会导致整个进程崩溃
  • 不同模块对资源的需求不同,没法独立扩缩容
  • 每次发布都要全量部署,风险大、时间长

于是我们决定拆微服务。嗯,当时微服务正火,我们也跟了一波风。拆了半年,服务数量从1个变成了12个。结果呢?

新问题来了:服务间通信开销巨大,序列化/反序列化占了30%的CPU。而且调试一个跨服务的 bug,得同时看四五个服务的日志,头都大了。

最后我们怎么做的?

回归到“模块化单体”架构。把核心逻辑拆成动态库(.so),每个模块独立编译、独立测试,但部署时还是放在同一个进程里。模块间通过接口类通信,不直接依赖。这样既保留了模块化的好处,又避免了微服务的通信开销。

// 模块化单体的接口定义示例
class IDataProcessor {
public:
    virtual ~IDataProcessor() = default;
    virtual bool Initialize(const Config& config) = 0;
    virtual ProcessResult Process(const DataPacket& packet) = 0;
    virtual void Shutdown() = 0;
};

// 每个模块实现这个接口,通过工厂模式创建
class ParserModule : public IDataProcessor { /* ... */ };
class CalculatorModule : public IDataProcessor { /* ... */ };
class StorageModule : public IDataProcessor { /* ... */ };

注意:架构演进没有银弹。不要为了“潮流”而盲目拆服务。我见过太多团队,微服务拆了之后,运维成本翻了三倍,业务交付速度反而慢了。先想清楚你的痛点是什么,再选架构。

这个案例给我的启发是:架构演进要跟着业务走,不要跟着概念走。你想想看,一个日活只有几千的系统,搞什么微服务?单体加个负载均衡就够用了。

30.3 团队协作经验总结:代码规范、Code Review、知识传递

技术问题好解决,人的问题才头疼。我做了这么多年,发现团队协作的坑,比技术债务还难填。

30.3.1 代码规范:别让风格成为吵架的理由

我曾经在一个项目里,两个资深工程师因为“大括号换不换行”吵了半小时。你说这有意义吗?

所以我的建议是:项目启动第一天,就把代码规范定下来。用 clang-format 自动格式化,用 clang-tidy 做静态检查。把这些工具集成到 CI 里,不符合规范直接不让合入。

# .clang-format 配置片段
BasedOnStyle: Google
IndentWidth: 4
ColumnLimit: 100
AllowShortFunctionsOnASingleLine: None
BreakBeforeBraces: Attach

别在这上面花太多时间讨论。选一个主流的风格(Google、LLVM、Mozilla都行),然后严格执行。习惯两周就养成了。

30.3.2 Code Review:不是走过场

很多团队的 Code Review 就是“LGTM”三个字母。这跟没 review 有什么区别?

我要求团队做 review 时,必须关注以下几点:

  • 逻辑正确性:这段代码真的能解决它要解决的问题吗?
  • 边界情况:空指针、越界、并发访问,这些处理了吗?
  • 可维护性:三个月后,别人能看懂这段代码吗?
  • 性能影响:这个改动会不会拖慢系统?有没有不必要的拷贝?

一个小技巧:review 时把代码拉到本地跑一遍。光看 diff 是看不出运行时问题的。我习惯在 review 前先跑一下单元测试,再跑一下性能基准测试。如果性能下降了,直接打回。

30.3.3 知识传递:别让核心逻辑只存在一个人的脑子里

这是最容易被忽视的。项目里总有一两个“大神”,什么都知道。但万一他请假了、离职了,整个项目就瘫痪了。

怎么解决?

  • 轮岗制度:每个模块至少两个人熟悉。定期轮换维护职责。
  • 技术分享:每周一次,轮流讲自己负责的模块。讲不清楚的,说明他自己也没完全搞懂。
  • 文档即代码:架构文档、接口文档、部署文档,都放在代码仓库里,和代码一起 review、一起更新。

我记得有一次,负责核心算法的同事突然要休产假。还好我们之前做了轮岗,另一个同事能顶上。虽然慢了点,但至少没断档。从那以后,我就更坚定地推行“知识不独占”原则了。

30.4 本章知识体系总览

下面这张图,把本章的核心逻辑串起来了。你可以把它当作复盘时的检查清单。

项目复盘核心三要素 技术债务管理 • 设计债务:架构耦合 • 代码债务:重复/混乱 • 测试债务:覆盖率低 • 文档债务:过期缺失 架构演进案例 • 单体架构:快速验证 • 微服务:过度拆分 • 模块化单体:平衡方案 • 演进原则:跟着业务走 团队协作经验 • 代码规范:工具自动化 • Code Review:关注要点 • 知识传递:轮岗分享 • 文档即代码:同步更新 复盘不是找谁背锅,而是找到系统性的改进点 让下一次迭代比这一次更好 持续改进 数据驱动 团队共识

复盘这件事,说白了就是“回头看,往前走”。技术债务要定期清理,架构要跟着业务演进,团队协作要靠制度和工具来保障。这三件事做好了,项目的健康度就不会差。

嗯,这一章的内容就到这里。希望这些经验能帮你在下一个项目里少踩几个坑。


公众号:蓝海资料掘金营,微信deep3321