为什么我们决定重构 Uber 司机端

1,640 阅读12分钟

Why We Decided to Rewrite Uber's Driver App
Driver App Rewrite: Why - feature image

本文是 Uber 的客户端工程师团队是如何开发最新版本司机端的系列文章中的第一篇,代号 Carbon ,是我们拼车业务的一个核心组件。除了其他新功能之外,司机端 APP 还为超过 300万 司机提供收入,引导他们挣钱。2017年我们结合司机的反馈开始对司机端进行重新设计,并在2018年9月份启动了该项目。

2017年初,我们决定对司机端进行重构。这就是 StackOverflow 的 CEO Joel Spolsky 曾经 说过的“任何一家软件公司都可能犯下的一个巨大的错误”的决定。

重构风险极高,需要集中大量资源,花费大量时间才能给用户带来收益。这次重要的重构,除了设计师、PM、数据分析师、运营、法务和市场之外,有几百名工程师参与。实际上,我们花费了一年半的时间才完成并推广到全球。

我们的情况是一个所有部门的工程师都面临的一个极端问题。如果你是一个正在准备编写或者准备重构某些代码或者一个功能的工程师,你或许会问:“我们有多少开发的时间?”,如果你在一个大的部门下面的一个小团队里面,你或许会问:“对一个没有开发的一个功能,是否值得做这么大的改变?”,一个出色的的工程师和一个出色的团队在准备接受重构的挑战的之前将会认真的研究一下这些广泛的问题。

因此,虽然重构过程中会涉及一些重要的技术决策(后续文章会有涉及),但需要综合技术考虑和广泛的业务问题来决定。虽然这些问题很难回答,但针对上述问题还是需要一个好的答案来向你的团队或者部门证明重构是合理的。

最后,这些决策不是凭空而来的。我们之所以决定重构 APP,并不是基于理论架构思维(“我们的代码可能会更好,如果我们……”),而是需要长达三个月的集中研究数百页的文档和广泛的跨组织的支持。在下面的章节中,我们将探讨重构 Uber 司机端的决定和我们在这个过程中发现了什么。

奠定基础

重构的需要并不总是自然而然地来自于对新体系结构的简单认识。重构的代价是巨大的,虽然工程组织经常想要重构代码,但是工程师的时间还需要做新需求,而不是一遍又一遍的重构同一个功能。对于司机端来说,有以下三个趋势有助于推动重构的决定:

技术债务

首先,司机端本身存在技术债务。这些债务是优步快速增长的结果,也是产品需求变化的结果(下一节讨论)。不仅如此, 技术债务还来自于修复以前的技术债务:应用本身陷入了多次持续迁移,使得功能看起来越来越复杂。

值得指出的是司机端存在的技术债务不只是理论上的。由于持续的中断和维护成本导致开发人员生产力下降,我们看到了真正的业务影响。2016 年底,我们不得不暂停 APP 的开发,以修复多个功能退化。在我们解决这些问题之前,开发和启动新功能都变得非常困难。

任何中断对于我们司机端来说都是一个巨大的问题,因为用户依赖它谋生。我们认为,任何事都不能让可用时长低于 99.99% ,然而我们经常发布在应用核心流程中出现严重问题的版本。

产品挑战

我们面临一个最大的问题就是司机端之前的版本无法很好地适用新的业务场景。早期只是简单的按照优选轿车去设计和迭代的司机端,但是现在我们的业务已经发展到还包含拼车、特价车和服务于市场线下现金交易体验等等。

我们发现除了打车业务,司机还需要其他的功能来管理他们的资产和私人业务。例如,收入和透明的评分对司机的体验至关重要,而在优步司机端的早期版本中这方面投资不足。我们需要提供类似这样功能的可扩展性,来提升产品体验。

Two screenshots of the previous driver app

图一:司机端之前的版本中,底部的功能标签超出了之前的预期(左)。地图超负荷的承载了许多业务大头针和线路图(右)也超出了之前的预期。

我们在 2015年 和 2016年 采取了一些初步的步骤来减少这些痛点,发布了一个更新版本。不幸的是,我们为不同的团队设计了一些 UI ,而不是围绕司机的需求和工作流程设计。如果你在这段时间仔细看了我们的 UI ,你就会发现主页下面有四个标签:收入、评分、设置和首页。每个功能标签都变得越来越臃肿,并且收入和评分标签经常为了需求改变意图,而违背了设计初衷。

我们从 APP 的迭代中得到的教训,以及向着我们长期的产品规划,实际上已经促使我们彻底重新思考司机端应该如何寻找我们的司机合作伙伴。即使重构不是必然的,也需要重新设计。

工程对齐

我们的研发团队预先在新的方向做了一些投入。特别是,伴随着 2016年 乘客端的重构 我们引进了一个新的移动端架构,我们叫做 RIBsVIPER 架构的一个演进),来帮助我们处理日益增长的规模。它能够解决在司机端的大多数问题:框架的可扩展性、强大的应用架构和有说服力的内存管理模块。我们在 2017年 开源了 RIBs 架构。

随着 RIBs 架构确实改进了我们的乘客端,同时它也代表了我们移动端组织一个新的方向。未来我们的核心平台将会主要投入在 RIBs 架构上。相比使用标准化的 RIBs 架构而言,每个应用都使用自己的架构将会花费更多的资金。

决策过程

拿到新的 UI 设计和一个新的架构,我们本质上有三个不同的做法:不使用 RIBs 架构重新设计司机端、让现有的司机端去适配 RIBs 架构和基于 RIBs 架构完全对司机端重构。

不使用 RIBs 架构

第一个方法就是我们不使用 RIBs 架构重新设计。原因是我们考虑到迁移 RIBs 架构是资源密集型。虽然 RIBs 引入了大量新的代码库,但也是构建应用的一种新方式:将业务逻辑和显示逻辑解耦。RIBs 架构很有说服力,但浸入性非常强。

首先,我们考虑现有的应用是否能够处理我们正在考虑的主要的产品变更。我们发现,由于一部分逻辑在视图控制器里,导致许多业务逻辑和视图展示层是强耦合的。这也就意味着 UI 层面的重新设计必然会牵扯到许多业务逻辑的修改。

其次,正如前面所说,现有的司机端架构一些问题需要解决。这些问题部分正好与 app 的逻辑有关,有一些地方(特别是 Android )都用一个模式开发是移动开发者的一个通病:不同版本的 MVC,都存在臃肿的视图控制器问题,我们大部分的核心代码都写在一个几千行的控制器文件里。因此,我们不愿意把现有架构变得更糟,变得越来越复杂和难以维护。

最后,虽然旧版本司机端的架构曾经是完美的,但是长远战略考虑适配 RIBs 架构能够避免在优步不同 app 的架构分歧。一种强有力的架构,能够给我们的平台带来双倍的收益,在一个团队(比如乘客端)写的代码还能在另一个团队(比如司机端)复用。

如果适配 RIBs,我们该怎么做呢?

适配

许多团队喜欢小心翼翼地迁移,这样能够允许他们在系统的架构变更的时候继续开发新功能。在大多数情况下这种方法是完美有效的,在这里我们讨论下优步这样做出现的一些问题。

首先,我们分析了过去几年在优步实施的十个主要的适配,发现它们的失败率很高。正如他们所说,我们将要开始适配一个基础库,但是彻底失败了。新功能是基于新的库开发的,一些旧的功能也适配了,但是基础库里面仍然有一些遗留代码在运行。

在进一步调查研究之后,我们发现司机端的许多技术债务的根本原因是这样的适配引起的。例如,我们有竞争条件因为我们的应用程序的发布订阅模式在安卓上是一分为二的。我们核心的应用架构最初是利用安卓的fragments,后来部分适配了内部框架。这种不完整的适配导致适配层和一般开发者的困惑。这些不完整的架构推进将会最后导致运行中断直接影响到我们的用户。

其次,我们经常发现在适配时造成大量的不稳定。我们有许多次宕机都是由于打算改善底层应用框架,例如网络协议。从技术上讲,这些不会对我们的用户造成直接的影响,但是最终会影响 app 的核心功能。

最后,在我们的经验中,甚至连继续开发功能的承诺都没有实现。如果一个团队依赖一个正在进行的适配,则经常会被阻塞直到适配工作完成。它还导致:回滚适配通常意味着我们还必须回滚大量的功能。

因此,当我们对是否进行完整的产品重新设计和采用 RIBs 架构评估后,不完整的适配或无止境的适配层极大的增加了应用程序的不稳定性的风险太高了。

重构

在某种程度上,我们作出这个决定是通过否定(其他选项,不使用 RIBs 架构和适配,是不合理的),但是重构有决定的好处,它提高了我们做最后决定的信心。

第一,重构能够提高我们对重新设计 app 的生产力,而不需要受先理解以前是怎么工作的。也就意味着它的设计可以更加通用。

第二,选择重构也意味着我们的架构将会更加清晰,因为它将从一开始就会有一个令人信服的策略方向。如果我们选择适配,我们可能会被出于方便重用或者便利的遗留代码所卡住。

第三,对 app 的重构会促使我们走到画板面前更加完整的思考我们想要的产品方向。结果,app 中的某些主要框架将被重构。

对于一个工程师来说,重构是一个挑战自我的机会,我们迫不及待的要开始了。

结论

值得强调的是重构司机端的决定并不是基于“如果我们能重做就会更好”这样的想法。事实上,一些工程师肯能会惊讶的听到,即使在重构之后,我们发布的 app 不仅使用了新的功能和新的架构,甚至还有一点新的技术债务。

也就是说,你不可能把事情处理的非常完美。阅读了这篇文章的工程师应该对“适配不合理,重构才能写出完美的代码”这个结论质疑。相反,重要的是要意识到重构应该是在非常具体的组织,业务和技术需求下决定的。

如果我们没有在数月之前产出一个新的手机架构,或许不会重构。如果我们没有一个产品团队来调研的话,或许不会重构。如果优步之前的适配工作非常成功的话,我们或许不会重构。当然,促使我们重构的原因不是说重构一定是非常好的,甚至不是一个好主意。

反而,司机端的重构来自于想要为我们的用户创建一个更可靠和更强的产品体验,同时,在这个版本也增强了我们团队的能力。在做这个决定的过程中或许没有创建一个最佳抽象层那么兴奋,但它是成功的、完全的改进了手机应用。