早在2012年,B 站 Android APP 便已上线。当时开发者不过一人,而如今,业务线众多、隶属不同团队的Android 端开发人员数以百计。从单兵作战到百花争鸣,代码库的组织管理也随之经过数次的改革、演进。

单仓库

2014年底,Android 端的常驻开发人员一只手也数的过来。业务发展迅速,为追求效率,方便管理,所有代码都在一个仓库中,甚至包括第三方的、开源的代码(个别用 git submodule 管理)。Clone下来导入 Eclipse 就可以开干。

到大约15年中旬,开始使用 Android Studio,得益于 Gradle 的项目管理理念,分出了多个 library module。外部依赖使用 maven。也是这一期间开始搭建了内网 maven 服务。

这期间代码库组织结构是:单仓库 + 个别 git submodule

这种组织方式好处显而易见:

  1. 项目结构简单:随时随地 clone 下来导入 IDE 即可以开始开发,代码所有人可见,没有额外的限制。
  2. 方便快速迭代:改动可以快速入库,适合小团队,review方便,改动透明

但是,约莫到16年中,业务发展,新团队纷纷成立,招聘要求降低人员迅速膨胀。这种小而美的代码库已经不适用了,主要有以下缺点:

  1. 代码结构混乱,模块之间依赖关系混沌:倒不如说是技术债,前期的疲于业务迭代,以及没有及时的规划出好的代码层级架构,如今人员纷杂水平不一,之前追求的“没有限制”反而诱发了恶果
  2. 编译时间变长:业务增速发展,代码量爆炸式增长,单机编译越来越慢,开发幸福感跌到谷地

多仓库

一个字:“拆”。

单仓库里,所有能切出去的、划走的,甭管横的来按业务分、竖的走按层级分,都拆出去,最好仓库中只留一个光杆app模块,最终只剩下拆无可拆的模块,所有依赖的模块都预先编译好上传到maven。期间冠名曰:组件化

约到17年中,这些拆出来的模块,一些独立为仓库,一些按所属层级聚拢到一起合为一个仓库。业务模块为了快速调试,一般会写一个demo app 模块用作测试。

此时代码库组织结构是:多仓库

好处是:

  1. 隔离性十足:按业务划分的模块所属仓库,倚托于gitlab权限管理,“外人”决计无法修改自己的代码。
  2. 依赖关系清晰:自上而下,由外而内。更容易做到高内聚低藕合。
  3. 编译速度快:大部分依赖都预编译后上传到maven,比起之前从源码编译的速度不知道快到哪里去了

但是☹…

随着业务线发展、人员的进一步扩张,不到一年时间,多仓库的缺点也快速暴露了出来:

  1. 维护复杂:尤其是模块之间有依赖关系时,修改依赖版本号是个重要、重复且容易出错的操作。且不便于review不便于快速暴露修改导致的兼容性问题,更进而影响其它模块进度。历史包袱重,比如由于离职等原因导致责任人丢失,非常难梳理出所有模块的修改历史,多个团队协同开发的时候难上加难
  2. 代码重复:由于代码分散仓库存放,天然的隔离更容易产生互相拷贝的问题。另外也容易产生相同功能的重复开发,如由不同的人作一个类似的模块,但彼此不知。
  3. 过分隔离:由于团队的不同,协作工作流程长且所涉人员多,沟通成本非常之高,开会从早到晚,一线开发人员一般只转注于自己的模块,当一个螺丝钉,无法从项目整体视角看问题,进而又导致项目结构趋于混乱、依赖关系复杂化。

大仓

“大仓”—— 一个来自B站 Go 团队率先实践过切实可行的方案映入眼帘。

大仓,来自英文 Monotonic Repository (简称monorepo)。望文生义,即一个仓库,包罗万象海纳百川。源自西方大厂们(Google/Facebook/…)一致叫好的实践良久的代码管理方案。

说到底其实就是一开始的“单仓库”的进化版。与纯粹的“单仓库”不同的地方是,有众多的工具链来维持大仓的日常开发,而非人力,是工程师文化中“不重复劳动”的极致化体现。

关于大仓,这里不费篇幅去过多解释了,请自行 Google

目前(2018-10-18),因为站在巨人的肩膀上,先行者已经准备好了一部分的工具链(gitlab-ci, saga等),B站 Android 团队已基本完成大仓的组建。

依然遵循之前规划的架构以及组件化的思想,将目录结构约定为形如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<root dir>
├── app
│ ├── main
│ ├── live
│ ├── bangumi
│ ├── ...

├── common
│ ├── ...

├── framework
│ ├── ...

└── entrance

依赖关系约定,app -> common -> framework

好处自不必多说,先说其局限性:

  1. 庞大而臃肿: clone项目和导入所有代码对开发机器是个挑战(为此我已经更换新款MBP)。但可以通过编译配置以及git sparse-checkout解决
  2. git-flow混乱: 因为之前不同团队不一样的git-flow,先天的认知不一,另外个别人对git操作理解不深,会导致出现一些人为因素上的合丢代码

对于Android 大仓如何组成,如何解决上述问题,择期另撰文详述,敬请期待

总结

所谓“分久必合,合久必分”,单仓库也好,多仓库也罢,都是为了解决业务上的问题而生。

从来没有放之四海皆准的方案,千万不要脱离业务谈架构,选择适合自己团队的方案才是好方案。