## ofo 移动端的过去与未来 文/李冈谕 ofo 是一家极高速成长的公司,在短短一年间,日活是呈现几个量级地在成长,而这也直接导致面临着一些严峻的问题。 - 人才的成长速度跟不上公司发展速度:好的开发人才难寻。当公司发展太快,有越来越多工作需求时,提升开发效率远远比提升人员数量来得重要。就如《人月神话》一书中提到的,盲目增加人员数量只会让开发成本更高、项目更不可控。 - 国际本土化(Glocalization):共享单车并不像 Uber、Airbnb 这类的共享经济,利用现有的市民资源来共享。ofo 自己投放单车到城市中,意味着当改变都市景观时,自然就会引起各城市和政府的提早介入与管制,这也导致 ofo 会提早遇到需要因应各政府的需求。因此,如何降低业务逻辑的复杂度就变得尤为重要。 - 需要持续优化来找到更好的模式:ofo 找到了 Product/Market fit,但现在还需要不断地去进行成长黑客(Growth Hacking)以及优化运营,来让收益和盈余最大化。这些都需要数据的支持,也就意味着客户端的 A/B Testing 的搜集效率非常重要。 由此,在面临着高速成长的情况下,ofo 移动客户端要如何有效控制技术债(Technical Debt)、提高开发效率,避免在将来变得难以开发维护,是最为重要的课题。 ### React Native 解决了什么问题 ofo 自2017年6月开始便在国际版中推动 React Native(以下简称为 RN)技术,希望通过 RN 诸多利好特性解决许多即将面临的问题: - 快速迭代:RN 不需要编译、可热更新,代码变更可以立刻在设备上展示,缩短了开发确认的时间;RN 是声明式视图(Declarative View),这让复杂状态下的视图层(View Layers)容易撰写代码、单元测试和维护;RN 使用 JS 脚本语言,在 ES6 之后,语法变得简洁,开发效率也更高。 - 跨平台:由于业务逻辑和视图排版均由 JS 撰写,这些代码几乎都可以复用,只有少部分会需要依照平台特异性开发,这能大幅减少开发人员的需求和沟通成本;另外,在 iOS 和 Android 人数不均的情况下,使用 RN 也可以确保能相互支援;我们的 RN 开发者基本上都是原生开发者培训而来。还有就是共用代码还能确保各端逻辑一致,降低 iOS/Android 各平台实现不一致的情况。 - 热更新:RN 的热更新特性,大幅降低发布上线的周期。RN 的快速开发、上线及热更新,能够更有效地帮助进行 A/B Testing,让我们快速从市场中学习。这也是精益创业(Lean Startup)的核心之一。 - 易于单元测试:RN 和 Redux 的函数式编程概念,让代码更容易拆分、复用和单元测试。这也让自动化测试和持续集成更容易推动,对于长期开发来说至关重要。 ### RN 在 ofo 的应用现状 目前 ofo 共有6个客户端 App 在开发,其中,ofo 国内版采用的是原生开发,部分页面使用 RN 的混合开发方式进行。而国际版客户端则是将切换至纯 RN 开发,预计十月上线。另外,ofo 也有开发内部使用的 App 给供应链和一线运营人员使用,这些部分也是使用 RN 开发的。 而我们的 RN 开发者,是由 iOS/Android 工程师培训两周而来,未来还将让大多数的移动端开发者都会写 RN。这与 Airbnb 采用 RN 的理念相同 —— “One paradigm, one ecosystem”。 ofo 的客户端有许多必要的原生代码,包括蓝牙、定位、地图等。我们正在开发一套更新机制,除了让 JS Bundle 能够动态更新外,也可以匹配原生的版本以及更新原生的功能,达到能高度可热更新的状态。将来还会搭配分包下载,以及 A/B Testing 的下发策略。 当前,我们也正在积极展开自动化测试、持续集成(CI),来降低产品迭代的测试问题,并提升代码质量,实现高迭代效率、快速应对市场的 DevOps 文化。 ### 基于 RN 的 ofo App 开发架构 目前,ofo 的国际版 App 以及一些公司内部使用的 App 都采取了全 RN 架构。亦即一进入 App,便开始载入 RN 的 JS Bundle,并在原生视图上进行渲染。 其中,开发架构是单纯的 RN/Redux 的单向数据流(Unidirection Data Flow)。数据流的部分目前使用 Immutable.js,来确保状态可追踪性。React Native 则使用 Storybook/Storyshots 来写 UI 测试。除了使用 Jest 写测试外,还用了 ESLin t和 Flowtype 来确保代码的质量。 在项目结构上采用的是 monorepo。我们将所有 Mobile App 以及模组的代码都放在单一 Git Repo 进行管理。这让共用模组的开发、测试以及自动化能够容易进行,确保共用代码的变更,可以让每个 App 都能通过自动化测试确保可以被集成,并缩短集成测试的周期。我们写了许多脚本,以及建立开发规范,来让 monorepo 可以顺利进行。 在模组集成方面,使用的是 lerna.js,并尽量模组化,让代码实现高内聚、低耦合,可被其他项目复用。这些包括了蓝牙锁、地图、网路通信层、UI 元件等。由于 RN 提供了 link 的指令,能够将 iOS/Android 原生代码整合入目标 App 中,这也让模块化更容易进行。 总结一下,ofo 移动端架构的特性主要体现在以下两点: - 注重代码品质:引入 ESLint、Flowtype、Jest 以及代码审查来确保代码品质; - 注重集成效率:使用 monorepo、lerna.js、react-native link 来进行模组化,以利持续集成和持续布署。 ### 遇到的问题与解决方案 自 Facebook 公布 React Native 至今已有两年多,目前已经进入稳定期,从过去两周一更新,改成一个月一更新,早期核心架构上的问题大多已解决。最近的改版着重在一些API上的改变、性能优化、开发环境等问题,以及修复这些由新改版引发的新问题。 我们尽量使用最新版本的 RN,在性能遇到问题的部分,就先采取原生桥接的方式,待 RN 新版有效解决之后,再切回 RN 开发。目前主要遇到的性能问题,是在 FlatList 的性能上,这部分我们采用的方案是使用原生视图来替代。 另外一个问题是人才培育,我们发现国内优秀的 RN 开发者并不是很多,即便是好的 React 开发者也不容易招募到。目前我们移动端是让原生开发者进行约两周的培训,然后开始 RN 开发。也就直接导致一开始的开发效率并不是很高,由于是接触新的语言和框架,估计也要2-3个月才能进入较高效率的产出。 此外,我们在8月开始,在许多项目使用了 RN,这也造成 RN 培训成本的升高。目前也在优化 RN 的培训方式以及开发文化,让新入职的开发者,能够更快地进入 RN 开发状况。 ### 总结 虽然 RN 已经开源超过两年,并进入了核心稳定的状况。但国内的 RN 开发风气还不盛行,许多人才和文化还需要培养。ofo 面对着接下来国际化的市场,以及成长需要做的市场测试,代码复杂度和开发周期强度会持续提升,如何降低技术债的问题并维持高效率的开发,是竞争的关键之一。