草庐IT

项目中第三方库并不是必须的

Swift君 2023-03-28 原文

前言

我在Lyft的八年间,很多产品经理以及工程师经常想往我们 app 里添加第三方库。有时候集成一个特定的库(比如 PayPal)是必须的,有时候是避免去开发一些非常复杂的功能,有时候仅仅只是避免重复造轮子。

虽然这些都是合理的考量,但使用第三方库的风险和相关成本往往被忽视或误解。在某些情况下,风险是值得的,但是在决定冒险之前,首先要能够明确的定义风险。为了使风险评估更加的透明和一致,我们制定了一个流程来衡量我们将其集成到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。这使得将来替换(或删除)底层库更加容易,再次减轻了与长期开发相关的一些风险。

有关项目中第三方库并不是必须的的更多相关文章

  1. ruby - 如何在 buildr 项目中使用 Ruby 代码? - 2

    如何在buildr项目中使用Ruby?我在很多不同的项目中使用过Ruby、JRuby、Java和Clojure。我目前正在使用我的标准Ruby开发一个模拟应用程序,我想尝试使用Clojure后端(我确实喜欢功能代码)以及JRubygui和测试套件。我还可以看到在未来的不同项目中使用Scala作为后端。我想我要为我的项目尝试一下buildr(http://buildr.apache.org/),但我注意到buildr似乎没有设置为在项目中使用JRuby代码本身!这看起来有点傻,因为该工具旨在统一通用的JVM语言并且是在ruby中构建的。除了将输出的jar包含在一个独特的、仅限ruby​​

  2. ruby-on-rails - 项目升级后 Pow 不会更改 ruby​​ 版本 - 2

    我在我的Rails项目中使用Pow和powifygem。现在我尝试升级我的ruby​​版本(从1.9.3到2.0.0,我使用RVM)当我切换ruby​​版本、安装所有gem依赖项时,我通过运行railss并访问localhost:3000确保该应用程序正常运行以前,我通过使用pow访问http://my_app.dev来浏览我的应用程序。升级后,由于错误Bundler::RubyVersionMismatch:YourRubyversionis1.9.3,butyourGemfilespecified2.0.0,此url不起作用我尝试过的:重新创建pow应用程序重启pow服务器更新战俘

  3. ruby-on-rails - 新 Rails 项目 : 'bundle install' can't install rails in gemfile - 2

    我已经像这样安装了一个新的Rails项目:$railsnewsite它执行并到达:bundleinstall但是当它似乎尝试安装依赖项时我得到了这个错误Gem::Ext::BuildError:ERROR:Failedtobuildgemnativeextension./System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/bin/rubyextconf.rbcheckingforlibkern/OSAtomic.h...yescreatingMakefilemake"DESTDIR="cleanmake"DESTDIR="

  4. Ruby 从大范围中获取第 n 个项目 - 2

    假设我有这个范围:("aaaaa".."zzzzz")如何在不事先/每次生成整个项目的情况下从范围中获取第N个项目? 最佳答案 一种快速简便的方法:("aaaaa".."zzzzz").first(42).last#==>"aaabp"如果出于某种原因你不得不一遍又一遍地这样做,或者如果你需要避免为前N个元素构建中间数组,你可以这样写:moduleEnumerabledefskip(n)returnto_enum:skip,nunlessblock_given?each_with_indexdo|item,index|yieldit

  5. 【Java 面试合集】HashMap中为什么引入红黑树,而不是AVL树呢 - 2

    HashMap中为什么引入红黑树,而不是AVL树呢1.概述开始学习这个知识点之前我们需要知道,在JDK1.8以及之前,针对HashMap有什么不同。JDK1.7的时候,HashMap的底层实现是数组+链表JDK1.8的时候,HashMap的底层实现是数组+链表+红黑树我们要思考一个问题,为什么要从链表转为红黑树呢。首先先让我们了解下链表有什么不好???2.链表上述的截图其实就是链表的结构,我们来看下链表的增删改查的时间复杂度增:因为链表不是线性结构,所以每次添加的时候,只需要移动一个节点,所以可以理解为复杂度是N(1)删:算法时间复杂度跟增保持一致查:既然是非线性结构,所以查询某一个节点的时候

  6. ruby - 如何在 Ruby 字符串中插入项目符号字符? - 2

    我正在尝试创建一个带有项目符号字符的Ruby1.9.3字符串。str="•"+"helloworld"但是,当我输入它时,我收到有关非ASCII字符的语法错误。我该怎么做? 最佳答案 你可以把Unicode字符放在那里。str="\u2022"+"helloworld" 关于ruby-如何在Ruby字符串中插入项目符号字符?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/1195

  7. ruby - 在 Rails 项目中测试本地版本的 gem - 2

    我的Rails站点使用了一个确实不是很好的gem。每次我需要做一些新的事情时,我最终不得不花费与向实际Rails项目添加代码一样多的时间来为gem添加功能。但我不介意,我将我的Gemfile设置为指向我的gem的GitHub分支(我尝试提交PR,但维护者似乎已经下台)。问题是我真的没有找到一种合理的方法来测试我添加到gem的新东西。在railsc中测试它会特别好,但我能想到的唯一方法是a)更改~/.rvm/gems/.../foo。rb,这看起来不对或者b)升级版本,推送到Github,然后运行​​bundleup,这除了耗时之外显然是一场灾难,因为我不确定我所做的promise是否正

  8. ruby-on-rails - 只有当不是 nil 时才执行映射? - 2

    如果names为nil,则以下中断。我怎样才能让这个map只有在它不是nil时才执行?self.topics=names.split(",").mapdo|n|Topic.where(name:n.strip).first_or_create!end 最佳答案 其他几个选项:选项1(在其上执行map时检查split的结果):names_list=names.try(:split,",")self.topics=names_list.mapdo|n|Topic.where(name:n.strip).first_or_create!e

  9. ruby-on-rails - Rails 格式验证——字母数字,但不是纯数字 - 2

    什么是测试格式验证的最佳方法让我们说一个用户名,使用字母数字的正则表达式,但不是纯数字?我一直在我的模型中使用以下验证validates:username,:format=>{:with=>/^[a-z0-9]+[-a-z0-9]*[a-z0-9]+$/i}数字用户名(例如“342”)通过了验证,这是我不想要的。 最佳答案 您想“向前看”一封信:/\A(?=.*[a-z])[a-z\d]+\Z/i 关于ruby-on-rails-Rails格式验证——字母数字,但不是纯数字,我们在Sta

  10. ruby - 为什么必须明确指定 2 个参数才能 curry :> - 2

    考虑这个,它工作正常::>.to_proc.curry(2)[9][8]#=>true,because9>8然而,即使>是一个二元运算符,如果没有指定的元数,上面的代码将无法工作::>.to_proc.curry[9][8]#=>ArgumentError:wrongnumberofarguments(0for1)为什么两者不等价?注意:我特别想用提供的一个参数创建中间柯里化(Currying)函数,然后然后调用然后用第二个参数调用它。 最佳答案 curry必须知道传入的过程的数量,对吧?:-1来自arity的负值令人困惑,但基本上

随机推荐