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 本章知识体系总览
下面这张图,把本章的核心逻辑串起来了。你可以把它当作复盘时的检查清单。
复盘这件事,说白了就是“回头看,往前走”。技术债务要定期清理,架构要跟着业务演进,团队协作要靠制度和工具来保障。这三件事做好了,项目的健康度就不会差。
嗯,这一章的内容就到这里。希望这些经验能帮你在下一个项目里少踩几个坑。
公众号:蓝海资料掘金营,微信deep3321