开头71次提交的Git仓库里藏着一个程序员对自己反复的自我否定、推翻和重新确认。43天前我动手写第一行代码时脑子里想的是这一次架构一定要想清楚了再写。43天后翻看日志发现自以为想得很清楚的事情在真实演进面前显得既幼稚又合理。这篇文章不打算教你怎么分层、怎么选框架而是想复盘一个项目从开始到交付过程中我在架构层面做的那些决策以及它们在后来的提交记录里是怎么被一步步修正的。服务端老兵都懂一个道理架构不是设计出来的是被提交记录推着长出来的。这话我从前嗤之以鼻现在信了。你要是正在做技术方案或者刚被架构评审折磨完可以看看这篇权当给自己以后的系统设计留一面镜子。1. 从第一行代码开始我定的那条规矩是怎么被自己打破的项目启动那天我在仓库里立了一个初始目录结构三个顶层模块业务领域、基础框架、对外接口。当时觉得分类清晰得不得了业务代码不会碰到框架细节接口层统一出口简直完美。头三天确实很舒服提交记录也规规矩矩每个commit都对应一个模块改动。到了第四天需求变动了原来设计的实体模型少了一个关键字段连带导致对外接口的出入参格式整个要调整。当时的处理方式很直白既然模型要改接口也要改那就直接在原文件上改。表面上代码量不大但提交记录里出现了第一次跨模块的提交——一个 commit 同时动了领域模型和接口定义。这是第一个被我打破的规矩。为什么这值得拿出来说因为大部分团队里的架构约定崩塌都是从这种就改一下不碍事的瞬间开始的。一次跨模块提交不可怕可怕的是它开了先例后面的提交会越来越不守规矩。反思之后我做了一次调整把所有跨模块变更全部拆开提交每个模块独立成commit顺序按照依赖方向排列。这个习惯保住了后期每个模块的单独回溯能力为后面那次大改动留了退路。架构决策从来不是一次性设计完的。它是一连串的取舍在你看见问题和没看见问题之间反复摇摆。你要是指望拿到需求就能画出一张永远不用变的架构图除非业务不再增长否则迟早要为自己的天真买单。项目进行到中期我发现自己最该敬畏的恰恰是那句代码一直在变架构必须为变化留口子。2. 中期重构71次提交背后的取舍信号项目走到第20天功能基本成型提交记录到了第30次左右。我开始接到新的需求整合加上一些自己代码里的坏味道很明显感受到原有拆分方式开始吃力。集中体现是每次改一个功能都要在两个模块之间来回跳测试链路也被拉得特别长。彼时我面临两个选项一是继续在现有结构上打补丁属于快但会越来越乱的路子另一个是停下来做一次结构性重构把接口层和业务层的边界重新划清属于短痛但长期干净的路子。我选了后者也因为这一步后续的提交记录从31次跳到47次全部集中在重构相关的改动上。如果只看提交次数会觉得这段时间进展变慢了代码量也没怎么增加。但正是那十几笔提交把这个系统的寿命极大延长了。重构过程中我做了一个值得拿来说的决策把原来统一的对外接口层按调用方拆成了三个独立入口。每次改动一个调用方的需求时不会牵连其他调用方同时把共享的鉴权逻辑下沉到框架层各入口只保留自己的参数校验和转换。那次重构的代价是构建脚本的配置全部重写了一遍。过程很痛有一笔提交仅注释和文件移动就占了200多行 diff完全没有实质代码变更。但在后期需求陆续进来的阶段这套调整的价值就显现出来了——后续每次提交基本都能精准落在一个模块里review起来比之前顺畅得多。回头看那次看起来产出率下降的阶段恰恰是整个项目最值钱的一段时间。3. 提交信息与代码审查我给自己定的提交规范为何执行不到位关于 Git 提交信息网上争论不少有人喜欢一句英文有人喜欢关联需求单号。项目初期我给自己定的标准是每次提交必须写明白为什么改和影响范围不要只写修复bug更新代码这种废话。实际执行下来前20笔提交我做得不错每一条都写得很清楚。到了赶进度的后半程压力来了连续三天的提交信息全是英文动词短语乍一看都像在跟开源仓库对齐格式实际上根本没有说明问题。这导致第45天我回看提交记录时有几笔改动已经完全想不起来当时为什么这么做了。那段代码现在看着很别扭却不敢轻举妄动因为信息丢了。我的教训是提交规范在压力下最容易先崩而它恰恰是关键救命的线索。如果有半数提交信息不能帮未来的你恢复记忆那你的提交记录就只是留档文件完全失去考古价值。从那之后我强制自己每次提交前多想30秒把动机写进 message 里不带功能描述只写因果。后面这招救了我一次在排查某个并发问题时正是靠一条看似不起眼的提交信息定位到改动引入点。如果你也在带团队或者维护自己的长期项目我建议在仓库的根目录放一个简短的提交说明模板提示每次提交该写什么。哪怕只有一句话这条改动解决了什么问题效果也远好过让提交者在空白框里FreeStyle。4. 依赖决策复盘我曾以为的稳到最后成了阻碍技术选型方面我在项目中途做了一次冒险的调整把底层处理引擎从一个成熟稳定的方案换成了可扩展性更强但踩坑概率更高的新方案。当时给自己的理由是老方案的瓶颈已经肉眼可见未来三个月必然拖后腿不如早换。这个决策在提交记录里可以看到清晰的转折点某一天之前几乎每次提交都是业务逻辑迭代之后连续几笔提交全部围绕引擎适配层展开业务代码非常零碎。这是因为更换底层方案之后原有接口签名发生了变动所有调用方都要跟着更新。这个迁移过程耗时五天中间还出现了两次回滚原因各有不同一回是序列化兼容问题一回是边界条件没考虑全老方案根本没有那层约束新方案额外暴露了一批边界问题。后来我复盘发现当时其实有更平滑的路径用适配器模式把新旧引擎的差异封装起来先让业务侧无感切换再逐步过渡。但当时项目节奏确实紧张加上我对新方案的熟悉程度中等偏上才选了直改的做法。这里我学到的东西是底层依赖迁移时任何一次决定性的重写都会放大隐患轻则让提交记录变乱重则让系统在关键阶段动摇。如果让我再来一次会先做技术验证再决定改动规模。不过话说回来那次迁移后带来的动态扩展能力确实是老方案给不了的。后期新增的两类消息处理逻辑几乎没有改动框架本身光靠配置就接了进去。这笔账算下来短期阵痛换来了中长期的自由度。至于是否值得取决于你的系统到底要在未来承担多少种变化。5. 提交粒度的教训大提交带来的考古灾难在71次提交里有三笔diff行数超过800行还有一笔接近1500行。这三笔全部集中在项目后期功能联调阶段当时脑子里只有一个想法——先把功能跑通具体拆分后面再说。结果就是一旦功能出现问题想在超大提交里找具体改动点堪比大海捞针。有一次排查线上偶现的数据不一致问题定位到某笔大提交里的一个小判断条件因为混杂在上千行改动中review的时候漏掉了。如果当时拆成几笔逻辑独立的提交这个问题在提交阶段就会被发现。说起来这不算架构决策但提交粒度本身就是架构维护的一部分它决定了你的架构在演进过程中能否被有效审查。在这一点上我给自己的规矩是单次提交控制在200行以内逻辑变更严格分离。哪怕物理上是同一个文件不同意图的话就分多次提交。实际执行时虽然偶尔因为赶进度会破例但只要意识到自己在写大提交就会先停下来想想能不能拆。拆提交其实是有技巧的。我常用的方式是先用git diff仔细检查当前工作区的变更内容按功能点排一下序然后使用git add -p分块暂存。这样每次提交只包含一个完整的逻辑单元提交信息也能写得准确。如果你习惯用 IDE 的图形化提交工具同样可以选中部分文件或改动块提交效果类似。关键是别怕麻烦拆分的成本远低于日后排查问题时浪费的时间。6. 如果重来我在架构决策上会坚持的几件事现在站在71次提交的终点往回看有些决策重来一次我会坚持有些则想给自己敲一下头。先说要坚持的一是模块边界始终保持单向依赖不让业务层反向调用框架层二是所有对外接口变动必须通过版本化方式引入不在原接口上直接改签名三是提交信息表达因果而不是流水账。这三条是整个项目后期还能顺下来最重要的基础没它们后面每次演进都会是噩梦。再讲会改的第一点初期做架构方案时就应该把需求可能会变这个前提打得更足不要按当前已知需求把结构定得那么具体留出扩展口。第二点换底层依赖前必须先做技术验证哪怕多花两三天也比上线阶段回滚强。第三点重构类改动不要和业务开发混在一起排期专注度对关键重构的成功率影响巨大。架构决策本身没有绝对的对错只有是否适合当时的上下文。写这篇文章也不是想证明我的方案有多好而是分享这次完整项目周期里那些被提交记录逼着成熟起来的节点。你下一个项目里未必会遇到完全一样的困境但我踩过的这几个坑很有参考意义。最后说个实在的无论你把架构想得多么清澈它最终会体现在每一个提交里。每次 commit 不是简单的代码存档而是一次架构决策的真实表达。保持提交的清晰、规范化其实是一种让架构持续可演进的手段值得每个人都重视起来。