前言
我在Lyft的八年间,很多产品经理以及工程师经常想往我们 app 里添加第三方库。有时候集成一个特定的库(比如 PayPal)是必须的,有时候是避免去开发一些非常复杂的功能,有时候仅仅只是避免c重复造轮子。
虽然这些都是合理的考量,但使用第三方库的风险和相关成本往往被忽视或误解。在某些情况下,风险是值得的,但是在决定冒险之前,首先要能够明确的定义风险。为了使风险评估更加的透明和一致,我们制定了一个流程来衡量我们将其集成到app有多大的风险。
风险
大多数大型组织,包括我们,都有某种形式的代码审查,作为开发实践的一部分。对这些团队来说,添加一个第三方库就相当于添加了一堆由不属于团队成员开发,未经审查的代码。这破坏了团队一直坚持的代码审查原则,交付了质量未知的代码。这给app的运行方式以及长期开发带来了风险,对于大型团队而言,更是对整体业务带来了风险。
运行时风险
库代码通常来说,对于系统资源,和app拥有相同级别的访问权限,但它们不一定应用团队为管理这些资源而制定的最佳实践。这意味着它们可以在没有限制的情况下访问磁盘,网络,内存,CPU等等,因此,它们可以(过度)将文件写入磁盘,使用未优化的代码占用内存或CPU,导致死锁或主线程延迟,下载(和上传!)大量数据等等。更糟糕的是他们会导致崩溃,甚至崩溃循环
其中许多情况直到 app 已经上架才被发现,在这种情况下,修复它需要创建一个新版本,并通过审核,这通常需要大量时间和成本。这种风险可以通过一个变量控制是否调用来进行一定程度的控制,但是这种方法也并非万无一失(看下文)。
开发风险
引用一个同事的话:“每一行代码都是一种负担”,对不是你自己写的代码而言,这句话更甚。库在适配新技术或API时可能很慢,这阻碍了代码开发,或者太快,导致开发的版本过高。
库在采用新技术或API时可能很慢,阻碍了代码库,或者太快,导致部署目标太高。每当 Apple 和 Google 每年发布一个新 OS 版本时,他们通常要求开发人员根据SDK的变化更新代码,库开发人员也必须这样做。这需要协调一致的努力、优先事项的一致性以及及时完成工作的能力。
随着移动平台的不断变化,以及团队(成员)也不是一成不变,这将会成为一个持续不断的风险。当被集成的库不存在了,而库又需要更新时,会花很多时间来决定谁来做。事实证明一旦一个库存在,就很少也很难被移除,因此我们将其视为长期维护成本。
商业风险
如同我上面所说,现代的操作系统并没有对 app 代码和库代码进行区分,因此除了系统资源之外,它们还可以访问用户信息。作为 app 的开发者,我们负责恰当的使用这部分信息,也需要为任何第三方库负责。
如果用户给了 Lyft app 地理位置授权,任何第三方库也将自动得获得授权。他们可以将那些(地理位置)数据上传到自己服务器,竞对服务器,或者谁知道还有什么地方。当一个库需要我们没有的权限时,那问题就更大了。
同样,一个系统的安全取决于其最薄弱的环节,但如果其中包含未经审核的代码,那么你就不知道它到底有多安全。你精心设计的安全编码实践可能会被一个行为不当的库所破坏。苹果和谷歌实施的任何政策都是如此,例如“你不得对用户追踪”。
减少风险
当对一个库(是否)进行使用评估时,我们首先要问几个问题,以了解对库的需求。
我们内部能做么?
有时候我们只需要简单的粘贴复制真正需要的部分。在更复杂的场景中,库与自定义后端通信,我们对该API进行了逆向,并自己构建了一个迷你SDK(同样,只构建了我们需要的部分)。在90%的情况下,这是首选,但在与非常特定的供应商或需求集成时并不总是可行。
有多少用户从该库中受益?
在一种情况下,我们正在考虑添加一个风险很大的库(根据下面的标准),旨在为一小部分用户提供服务,同时将我们的所有用户都暴露在该库中。 对于我们认为会从中受益的一小部分客户,我们冒了为我们所有用户带来问题的风险。
这个库有什么传递依赖?
我们还需要评估库的所有依赖项的以下标准。
退出标准是什么?
如果集成成功,是否有办法将其转移到内部? 如果不成功,是否有办法删除?
评价标准
如果此时团队仍然希望集成库,我们要求他们根据一组标准对库进行“评分”。下面的列表并不全面,但应该能很好地说明我们希望看到的。
阻断标准
这些标准将阻止我们从技术上或者公司政策上集成此库,在进行下一步之前,我们必须解决:
过高的 deployment target/target SDKs。 我们支持过去4年主流的操作系统(版本),所以第三方库至少也需要支持一样多。
许可证不正确/缺失。 我们将许可文件与应用捆绑在一起,以确保我们可以合法使用代码并将其归属于许可持有人。
没有冲突的传递依赖关系。 一个库不能有一个我们已经包含但版本不同的传递依赖项。
不显示它自己的 UI 。 我们非常小心地使我们的产品看起来尽可能统一,定制用户界面对此不利。
它不使用私有 API 。 我们不愿意冒 app 因使用私有 API 而被拒绝的风险。
主要关注点
闭源。 访问源代码意味着我们可以选择我们想要包含的库的哪些部分,以及如何将该源代码与应用程序的其余部分捆绑在一起。 对于我们来说,一个封闭源代码的二进制发行版更难集成。
编译时有警告。 我们启用了“警告视为错误”,具有编译警告的库是库整体质量(下降)的良好指示。
糟糕的文档。 我们希望有高质量的内联文档,外部”如何使用“文档,以及有意义的更新日志。
二进制体积。 这个库有多大?一些库提供了很多功能,而我们只需要其中的一小部分。尤其是在没有访问源码权限的情况下,这通常是一个全有或全无的情况。
外部的网络流量。 与我们无法控制的上游服务器/端点通信的库可能会在服务器关闭、错误数据被发回等时关闭整个应用程序。这也与我上面提到的隐私问题相同。
技术支持。 当事情不能正常工作时,我们需要能够报告/上报问题,并在合理的时间内解决问题。开源项目通常由志愿者维护,也很难有一个时间线,但至少我们可以自己进行修改。这在闭源项目是不可能的。
无法禁用。 虽然大多数库特别要求我们初始化它,但有些库在实例化时更“主动”,并且在我们不调用它的情况下可以自己执行工作。这意味着当库导致问题时,我们无法通过功能变量或其他机制将其关闭。
我们为所有这些(和其他一些)标准分配了点数,并要求工程师为他们想要集成的库汇总这些点数。虽然默认情况下,低分数并不难被拒绝,但我们通常会要求更多的理由来继续前进。
最后
虽然这个过程看起来非常严格,在许多情况下,潜在风险是假设的,但我们有我在这篇博文中描述的每个场景的实际例子。将评估记录下来并公开,也有助于将相对风险传达给不熟悉移动平台工作方式的人,并证明我们没有随意评估风险。
此外,我不想声称每一个第三方库本质上都是坏的。事实上,我们在Lyft使用了很多:RxSwift
和RxJava
、Bugsnag
的SDK
、Google Maps
、Tensorflow
,以及一些较小的用于非常特定的用例。但所有这些要么都经过了充分审查,要么我们已经决定风险值得收益,同时对这些风险和收益的真正含义有了清晰的认识。
最后,作为一个专业开发人员提示:始终在库的API
之上创建自己的抽象,不要直接调用它们的API
。这使得将来替换(或删除)底层库更加容易,再次减轻了与长期开发相关的一些风险。