50万奖金+官方证书,深圳国际金融科技大赛正式启动,点击报名 了解详情
写点什么

单体代码仓库:Uber 的 Android 代码仓库演化史

  • 2017-05-21
  • 本文字数:3272 字

    阅读完需:约 11 分钟

Uber 技术日开幕式上,软件工程师 Aimee Lucido 呈现了一个有关 Uber Android 代码库历史的演讲。在这篇文章里,她继续展开说明 Uber 为什么要构建一个单体仓库来支持 Uber 的 Android 开发。

今天,你们要开始构建一个全新的 Android 应用,对于你们来说,开头部分总是最困难的。那么要如何开始呢?

如果你们跟我一样,那你们一定也是先在 Android Studio 里创建一个新的项目,然后创建一个主 Activity,配置 Gradle ,或者再创建一个 git 仓库,这样其他人就可以与你一起合作开发这个应用。那么恭喜!你的代码结构就组成了第一个版本的 Uber 搭车应用。

我们使用一个大纸箱表示 Uber 的第一个 Android 代码基库:一个装着代码的大箱子。

我们在 2010 年启动了我们的第一个 Android 打车应用,那时我们还是一个小公司。Uber 的工程团队只有寥寥数人。我们有一个合同工负责 Android 平台的开发,而且(如果你能够想象得到)我们甚至还没有 Android 的司机应用。我们的光杆 Android 工程师基于一个单独的代码库开发了打车应用的第一个版本:一个装着代码的大箱子。

在早期,使用单独的代码仓库来创建一个新的应用有几个好处:

  • 开箱即用的 Android:Eclipse(开发第一个版本打车应用所使用的 IDE)提供的代码结构就是一个单独的代码库。在 2017 年,构建一个单独的代码仓库比在 2010 年要容易得多,因为我们现在有 Android Studio,可以使用更多的 Android 类库。不过,不管你使用的是哪一种 IDE,这些 IDE 都会为你提供一个默认的仓库结构和一些基本工具,比如构建脚本和 git 集成。
  • 加快小型团队的开发:因为所有的依赖都集中在一个地方,对于一个小型的开发团队来说,使用单独的代码仓库可以提升他们的效率,共享代码和重构也会变得更简单。

几年之后,Uber 的 Android 开发有了小规模的扩张。到了 2013 年,我们聘请了第一个全职 Android 工程师,在这一时期,我们的工程团队已经比之前翻了一翻。也就是从那个时候,我们才开始开发第一个司机应用。

开发司机应用让我们有机会对我们的代码基库结构进行改进。打车应用仍然放在单独的代码基库里,不过我们将一些可重用的组件抽取出来,因为我们已经拥有了必要的资源和工具来做这件事情。核心的代码仍然放在它自己的仓库里,不过我们构建了一个类库,包含了两个应用都要用到的公共组件。

到了 2014 年,我们的增长规模要求我们去寻找其他不同的解决方案。Uber 的工程师已经超过一百个。Android 工程团队的工程师也从一个变成了八个。随着工程师数量的增长,我们的代码基库规模也在增长。

我们看着前行的方向,我们意识到,如果我们不做出一些改变,我们将会碰到如下几个问题:

  • 长时间的构建:我们最初的 Eclipse 项目使用 Ant 作为默认的构建工具,而使用 Ant 构建大型的代码库会越来越慢。
  • 功能耦合:共享代码很容易导致过度共享。随着 Android 应用功能不断的增加,我们担心功能会无机地耦合在一起。
  • 破坏 Master 分支:将变更 rebase 到最新的 Master 分支上之后发生构建错误,然后需要花很多时间进行调试,这种事情经常发生。而你会发现,构建错误与你的代码并没有关系,而是之前有人在没有运行测试的情况下将代码 rebase 到 Master 分支上。如果你跟我一样,那么也已经处在一个两难的境地。多个工程师在同一个代码基库上贡献代码,却不使用持续集成工具(见后面的 Submit Queue ),那么就存在产生冲突的风险。这会对 Master 分支造成破坏,而且会浪费时间。

所以,在 2013 年到 2014 年期间,为了解决这些问题,我们做出了一系列改变,我们迁移到了多仓库的代码基库。2013 年,我们使用 IntelliJ 和 Maven 代替了 Eclipse 和 Ant,这样我们就可以从服务器上拉取依赖包,并将我们的包仓库拆分成 20 多个更小的独立仓库。(例如,网络相关的包被移到自己的仓库里,然后应用程序在编译时通过 Maven 将它们拉取到本地。)同时,我们改用 Gradle 构建脚本,迈出了向多仓库侵袭的第一步。

我们使用几个纸箱表示多库代码仓库,Uber 在 2013 年将代码基库转成这种结构,以便满足不断增长的用户需求。

Uber 的多库代码基库由一些小代码基库组成,每个小代码基库代表了一个单独的离散想法,包含了一些类库,打车应用和司机应用在编译时拉取这些类库。每个代码库就像是一小箱子的代码,包含了自己的 IDE 项目文件、git 仓库和构建脚本。多库代码仓库是一种稳固的具有前瞻性的架构,解决了构建时间长、功能特性耦合和 Master 分支遭到破坏的问题。

现在的问题变成:为什么我们不是一开始就使用多库代码仓库?一句话:成本。将功能特性拆分到独立的仓库需要大量的时间,而且需要很多专家来完成。它需要很多领域的知识,比如 Maven、Gradle、VPN 和类库管理。这种知识的投入也只有在公司的规模增长到一定程度的时候才是值得的。

在将近三年的时间里,我们伴随着多库代码仓库结构一起成长。但到了 2016 年,我们的多库代码仓库开始出现瓶颈,我们的开发人员开始面临新的问题:

  • 架构孤岛:因为功能特性的强去耦,开始出现架构孤岛。我们早期构建了一个统一的 lint 系统,但它并不能防止不同的团队使用各种各样的 Activity Fragment ,以及 MVC 架构和我们自己的架构。从某种程度上说,架构孤岛是被允许的,甚至还有好处:工程师可以选择适合他们的架构。但随着规模的增长,我们的团队需要使用越来越多的类库。这意味着开发人员需要经常性地学习新的架构,而且学习曲线非常陡峭。因此,一个类库的架构如果没有被正确实现,将导致与消费者应用或其他功能特性的集成变得很困难,或者需要重度的代码重构。
  • ** 依赖地狱( Dependency Hell ):** 这个方案确实能够减小依赖地狱问题所带来的影响,不过这也取决于你的变更涉及到多少类库,即使是在代码基库上运行这个工具就需要很长的时间。另外,修复问题可能需要好几天的时间:识别问题的依赖情况,修复代码,然后在受影响的代码库上拉出新的版本。
  • 长时间的构建:我们的代码基库规模已经达到 Gradle 能够进行快速构建的上限。一个新应用需要超过 15 分钟的时间来构建,对一些 XML 小文件的调整可能会占用数个小时的开发时间。

那么我们是怎么解决这些问题的?

我们的解决方案:使用单体仓库,一个包含了多个独立项目的代码仓库。一个代码基库包含一个单体仓库,就像我们最初的打车应用那样。不过与打车应用不同的是,这个箱子里包含了多个逻辑组件,它们是相互独立的。现在,我们可以在工具和架构上投入时间和资源,来修正大型单体仓库的缺点:

  • IDE 支持: Android Studio 被作为默认的轻量级的 Android 开发者平台,不过并不是唯一的选择。我们发现,在一个大型的单体仓库里,IntelliJ 更适合我们目前的规模。
  • 长时间的构建: Uber 目前已经从 Gradle 转向 Buck ,Buck 是一个模块化的构建系统。我们的代码已经被拆分成离散的组件,所以很容易与 Buck 集成起来。我们自己开发的 Gradle 插件 OkBuck 将 15 分钟多的构建时间降到了 5 分钟以下,而对于增量构建,只需要不到一分钟的时间。
  • 破坏 Master 分支:我们最近引入了一个叫作 Submit Queue 的系统,用于将变更 rebase 到 Master 分支上,并且在合并之前运行一系列测试。这样可以防止工程师在提交代码时破坏构建过程,保持 Master 分支的整洁。

这看起来花费了不少功夫,事实确实如此。目前还没有开箱即用的单体仓库解决方案,因为很少有公司可以达到需要使用单体仓库的规模。我们现在有了时间、专家和资源来构建工具,防止那些在 2013 年出现过的痛点再次对我们的生产力造成破坏。

Uber 花了数年的时间才达到了目前的开发状态。当我们还是一个由几个工程师组成的团队时,我们没有时间和资源来创建 Submit Queue 或者搭建 Buck。不过,我们早期的洞见促进了架构决策,让我们可以快速地伸缩。现在,我们有了更进一步的发展,我们可以在开发上投入更多,确保未来的服务增长是无缝和高效的。

提升开发者的生产力是块难啃的骨头,但却非常重要。随着每一次小步跑的进步,我们不仅让 Uber 变得更好,也让整个 Android 社区变得更好。

查看英文原文: THE JOURNEY TO ANDROID MONOREPO: THE HISTORY OF UBER ENGINEERING’S ANDROID CODEBASE ORGANIZATION

2017-05-21 19:002263
用户头像

发布了 322 篇内容, 共 157.5 次阅读, 收获喜欢 148 次。

关注

评论

发布
暂无评论
发现更多内容

【C语言】auto 关键字

謓泽

11月月更

webpack高级配置

Geek_02d948

webpack

计算机网络:数据报与虚电路

timerring

计算机网络 11月月更 数据报 虚电路

令人头疼的Javascript隐式&强制转换

hellocoder2029

JavaScript

程序员的浪漫(代码猜诗词)

叶秋学长

程序员 11月月更 诗词 专属浪漫

一文彻底读懂webpack常用配置

Geek_02d948

webpack

从历史讲起,JavaScript 基因里写着函数式编程

掘金安东尼

前端 11月月更

快出数量级的性能是怎样炼成的 审核中

jiangxl

赛迪网|专注财务全流程数字化,元年科技PaaS平台再上新台阶

元年技术洞察

方舟

vue实战中的一些小技巧

yyds2026

Vue

你需要知道的webpack高频面试题

Geek_02d948

webpack

SAP UI5 BarcodeScannerButton 的初始化逻辑 - feature 检测,Cordova API 检测等逻辑

汪子熙

前端开发 Fiori SAP UI5 ui5 11月月更

超级App们有一个共同的技术特点

Onegun

小程序容器 超级app 小程序化

前端懒加载和预加载

hellocoder2029

JavaScript

OKR之剑·实战篇01:我们的OKR制定落地

vivo互联网技术

团队管理 OKR 目标管理

在数据增强、蒸馏剪枝下ERNIE3.0分类模型性能提升

汀丶人工智能

nlp 文本分类 11月月更 ernie

文盘Rust -- 把程序作为守护进程启动

京东科技开发者

rust 后端 进程 守护进程 rust语言

数据标准化红宝书权威发布!一文速读核心内容~~

博文视点Broadview

这是你没见过的MindSpore 2.0.0 for Windows GPU版

华为云开发者联盟

人工智能 华为云 企业号十月 PK 榜

数据中台的终点是DataOps还是DaaS?

雨果

数据中台 DataOps DaaS

敏捷开发模式下如何快速提升产品质量

敏捷开发

敏捷 敏捷开发 软件测试

体验一把 Flowable 三种常见网关

江南一点雨

Java spring springboot flowable JavaEE

理解NodeJS多进程

coder2028

node.js

腾讯WeTest七年路,中国“质”造向未来

极客天地

“企业级零代码黑客马拉松”决赛圆满落幕

明道云

黑客 零代码 无代码 黑客马拉松 黑客松

【LeetCode】被围绕的区域Java题解

Albert

算法 LeetCode 11月月更

小程序如何开通流量主

源字节1号

微信小程序 软件开发 小程序开发

vue实战-深入响应式数据原理

yyds2026

Vue

Vue-组件详解

格斗家不爱在外太空沉思

vue.js 组件化 11月月更

深聊Nodejs模块化

coder2028

node.js

深度阐述Nodejs模块机制

coder2028

node.js

单体代码仓库:Uber的Android代码仓库演化史_语言 & 开发_薛命灯_InfoQ精选文章