最初听说 Monorepo,是群里的同学问我是否了解 lerna,我还真没听说过,于是去学习了一下。简单来说,就是把好多个软件放在一个大型仓库里一起管理。
0. Monorepo 简介
为方便灵活使用,我们一般会把软件包拆散,每个小包只负责某个特定功能,通过组合完成复杂功能。这个时候我们有两个选择:
- 每个包放在独立仓库,独立管理、独立发布,然后通过 NPM 等包管理工具安装互为依赖。
- 所有包放在一起,统一管理,直接内部引用。即 Monorepo。
我之前在 OpenResty 一直使用前者,而很多开源项目都选择后者,比如 vue-cli。它的仓库里包含了 vue-cli 主体和大量插件,还有测试套件等。
这样做有几个显而易见的好处:
- 这些软件包通常高度耦合,彼此之间功能关联紧密。A 软件 X 版本依赖 B 软件的 Y 版本,B 软件的 Y 版本又依赖 C 软件的 Z 版本。统一管理切换环境更轻松。
- 统一管理依赖,既能减少磁盘占用,也可以保证开发环境统一。
- 没有编译、没有黑盒,所有源代码对项目成员公开,遇到问题分析调试都容易很多。
我觉得在 Google 这样的技术流公司这么搞没问题;在崇尚奉献和高参与质量的开源项目里这么搞也很好,但是对很多技术水平一般,自我要求很低的软件公司这么搞就是乱来了。
1. 问题
以我曾经短暂工作过的XX办公(为保护当事人隐私我隐去了“金山”二字)为例,使用 monorepo 带来了几个未曾预料的问题:
1.1 版本管理混乱
所有产品代码都放在一个仓库里,数个不同的团队同时进行开发,每天会产生大量的 commit。负责合并分支的人既不懂业务也不懂版本管理,基本就是点一下合并按钮,于是大量未经 rebase squash 的 commit 被用 merged
方式合并入主干。整个提交历史混乱不堪毫无价值。
同时由于采取 merge 方式,仓库里存在大量分支,git 无法判断版本之间的先后关系,也无法自动解决冲突,于是几乎每两三天就遇到冲突无法合并,需要开发人员解决冲突。
1.2 代码质量参差不齐,且互相影响
monorepo 好的一方面是代码对大家公开,所有人都可以学习其他人的写法,互相帮助解决问题;坏的一方面是坏的代码影响的也是整个项目仓库。
有些同学用 VS Code,这本身不是问题,但 ta 们不研究配置、不研究插件,于是代码中很多低级错误——甚至因为低级错误太多,反而不会报错。比如有个事件侦听函数,参数传进来是 e
,但是函数体里用的是 event
。结果竟然没有报错,也能通过测试(人肉)。我找到开发人员让他改,他发现竟然有一个全局 event
变量,不知道是谁在哪里带进去的……
【2022-09-20 更新】这里还真触及到我的知识盲区。这是早期 JS 的兼容性设计,即事件触发时 window
上也会有个全局变量 event
,主要方便大家乱来。没想到救了 XX 办公。
1.3 技术栈升级困难
架构升级我就不谈了,这东西见仁见智,也不是能经常干的事情。这里说的主要是工具链。
如,eslint、错字检查、安全性检查。好比说,我发现部分代码存在风格问题,希望引入 eslint 检查,并且把 eslint 加入到集成环境里。按说这个想法并不复杂,这些工具都不影响整体架构,只需要外挂到 pre-commit 之类的钩子上即可。但此时必须征得整个开发团队几百号人的同意,那就不是一句话能解决的。
于是打开 WebStorm,海量的臭气(smell)扑面而来,难以直视。
1.4 代码互相耦合
原本 monorepo 只是方便大家阅读代码、维护代码。但是难免会有人滥用,直接引用、导入别人的代码,或者把自己的代码写入别人的目录里。
比如我想优化构建过程,希望能拆成 ES6 和 ES5 两个版本(因为厂里明确要求支持 ES5 的只有文档、表格、幻灯片)。然后我发现,虽然名义上大家各自维护自己的目录,但实际上跨目录引用比比皆是。简单按照目录区分基本不可能。
2. 总结
当然,我数落这些问题,不是想说 monorepo 不好。分散项目独立管理也会有别的问题,比如技术栈不统一、集成困难、人员变更后难以交接继承等。每种技术都会有适合的场景,各位技术决策者也应该从自己的团队实际出发,选择最适合自己的技术选型。
现在技术领域各种声音分外吵杂,很多公司会把技术方案作为一种宣传方式,名为推广技术实践,实则宣传自己的团队和品牌。如果不加区分盲目追随,就很容易掉进坑里。
如果你有使用 monorepo 的先进经验和最佳实践,欢迎分享给我。有其它想法和问题,也欢迎随时交流。
欢迎吐槽,共同进步