Kun

Kun

IT学徒、技术民工、斜杠青年,机器人爱好者、摄影爱好 PS、PR、LR、达芬奇潜在学习者


共 279 篇文章


​ 二方库维护困境

https://juejin.cn/post/7259331446002925629#heading-16

二方库通常指由团队内自己的需求出发,自己开发的一些lib、sdk、framework。既包括业务小团队开发的公共代码,也包括公司级别内开发的公共代码。

在本篇文章中,这是讨论的主要对象

三方库通常是不由自己团队掌控的代码,比如github上的开源代码。

虽然有些公司级别的公共代码也不由自己团队掌握,但是考虑到这种类型的代码库通常有着非常好的维护、方便的oncall渠道,且是一部分人专职的东西(这是重点!),所以我也分类到了二方库里。

针对业务开发同学,在下面的部分讨论中并不需要区分二方库与三方库,此时会以“二/三方库”这种写法加以区分

所谓困境,就意味着这是一个在比较长时间内,多方诉求都无法得到满足与平衡,以至于大家都怨声不断而又无力改变的一个场景。而一眼可以看出,困境中主要的角色是业务开发同学二方库开发同学

二方库开发同学通常会按照roadmap,同时结合各个业务方的功能诉求、性能诉求、bug反馈,不断完善二方库。在这个过程中,二方库开发同学天然会有动力去推动大家升级二方库:

  1. 二方库开发同学也背负着KPI、OKR、ROI之类的压力,希望业务开发同学尽快升级二方库到最新版本,以便在节省XX CPU资源、新功能被XX个团队使用到等指标上体现出自己工作的价值。
  2. 很多对feature与bug的oncall都是基于老版本的,升级到最新的版本就能减少这些oncall。
  3. 最新版本的问题的oncall更容易解决,版本越老,相关oncall越难解决。
  4. 部分break change的改动可以做兼容,但是需要2-3个版本进行渐进式的升级。而如果业务方升级不及时,则这种渐进式的兼容升级就无法实施。
  5. 如果有v0到v1这种大版本升级,如果业务方不及时升级到最新的v1,则二方库开发同学需要同时维护多个大版本。维护压力大。
  6. 渴望自己开发的东西可以上到生产环境中实际运行,实现个人价值。

业务开发

业务开发同学通常会按照排期完成相应feature的开发和bug的修复,而二/三方库的升级并不在工作范围内。面对二/三方库的升级,开发同学通常遇到了这些问题:

  1. 业务同学负责feature开发与bug修复,而花时间研究二/三方库新版本并升级并不一定能提高开发效率,也与绩效无关。因此不愿意投入时间去了解二方库新版本。
  2. 二/三方库的升级成功了也没有肉眼可见的收益,升级出bug了还要自己负责来修复,对绩效有(潜在的)负面影响。
  3. 看不懂二/三方库的release note,有些甚至没有release note,导致不敢升级。
  4. 不了解升级的收益在哪里,不仅是单次升级的收益,也包括随着二/三方库的规划持续升级的收益。如果二/三方库自己的长期规划也没有说清楚,则大家就没有持续升级的动力了。
  5. 担心二/三方库的最新版本不稳定。有些大家都依赖的二/三方库的测试也没有那么完善,单测覆盖率并不高(更别提场景覆盖、分支覆盖了)。
  6. 就是不想依赖最新版本,只想依赖相对稳定的次新版本。希望二/三方库自己可以区分实验版本与稳定版本。

升级真的有收益吗

从业务开发同学的角度去看,升级并不能总是真的带来收益。常见的升级收益有这些:

  1. 二/三方库升级能带来性能提升
  2. 二/三方库新版本解决了已知的bug
  3. 二/三方库提供了新功能,并且新功能恰好可以支撑业务开发

遗憾的是,当我们去看一个二/三方库的release note的时候,我们并不是每次都会得到如上信息。有时候是因为release note没有把事情说清楚,有时候是真的这次升级和自己没啥关系。

那么,如果我们升级没有这些收益,我们就不升级了吗?有些二/三方库确实是这样的。但是那些被项目深刻依赖的二/三方库会是例外。被项目深刻依赖的二/三方库在没有确定性收益的情况下,也值得定期升级。首先解释下什么是“被项目深刻依赖的二/三方库”,通常而言,可以称为framework的东西、是业务逻辑的底层二/三方库都是,比如:

  1. go、Java、Python这样的语言版本,以及随版本发布的核心库
  2. kitex、fasthttp、gin这些web server框架
  3. gorm、gen这些ORM框架,以及其底层驱动,比如mysql-driver
  4. 异步任务、工作流框架
  5. go-valid等用于API校验的库
  6. logger、metrics、promonitor之类的和监控有关的库

其次我想用一个例子来论证被项目深刻依赖的二/三方库在没有确定性收益的情况下,也值得定期升级

代码的功能、性能的优化是没有止境的。业务代码是如此,二/三方库的代码也是如此。有时候我们想实现特定功能,或者做特定优化,就是要依赖新版本的二/三方库(或者依赖二/三方库的生态链工具,而这些生态链工具依赖了新版本的二/三方库)。但是,我们不能等到有这些需求的时候再做二/三方库的升级,因为在此时,服务依赖的版本距离最新的版本已经差太多了,有太多的不兼容改动,升级的风险被累积和放大了。就算是把保障兼容性放在生命信条里的golang,也会因为各种各样的原因作出不兼容的改动:

  1. 因为bugfix和安全问题而导致的不兼容。这种情况下二/三方库的维护同学没有办法做兼容。比如golang因为安全问题要修改os/exec的默认执行,这打破了兼容性:go.dev/blog/path-s…
  2. 随着功能迭代而逐渐不兼容,比如golang要用go install来代替go get进行程序安装,go get命令在1.17安装程序时会warning,go get命令在1.18时就无法用于安装程序了。ioutil的废弃也会经历类似的过程。
  3. 其他潜在的因为功能迭代或者代码重构导致的不兼容。

而如果我们不只是依赖了二/三方库,还fork下来做了一些魔改,那么事情会更加糟糕:我们的修改可能会和最新版本有冲突,而修复冲突又让整个事情麻烦了一重。

在项目立项之初,谁能想到我们未来会根据白名单动态决定某个字段是否需要返回呢?(当我们需要这个功能的时候,距离立项选择grpc已经过去了3年多了)。但是从一个混沌的角度来说,我们未来需要的功能,别人也会需要,而且可能比我们更早需求,并实现了,我们只需要定期升级,就可以享受到别人的成果。而且我相信grpc实现的对我们业务有帮助的功能和优化远不止这些。

因此,如果是一个还在频繁开发的项目,那么外部依赖的二/三方库或是出于功能、或是出于性能,或是出于生态链工具依赖新版本,总是要升级依赖的。其中二方库因为和开发诉求贴合更近,更容易频繁升级。那么迟迟不做依赖升级,等到堆积了足够多的版本才做升级,升级自然变成一个很复杂的事情(长期不升级堆积历史债务)。所以我觉得对于还在频繁开发的项目,不做依赖版本升级是一个没有远见的懒政。

能用就不要动

我想特别讨论一下“能用就不要动”这个观点。

首先,服务的稳定性依赖的是系统性的dev ops,而不是“不改代码”。对于一个还在演进的项目,修改的数量并不能与风险的大小画等号,指望通过不改代码就能提高服务的稳定性,是一个很荒谬的事情。

有些同学会担心升级SDK会导致服务出bug,进而被追责,影响绩效,我觉得大可不必担心。如果一个功能重要,那么必然会有完善的回归测试来覆盖,不用担心改代码就会导致重大故障。(当然如果没有dev ops那就QAQemmmm……= =!)而小bug本身不是重点(谁家系统里没点bug呢)。另外,只要代码有变更,就可能会导致升级挂了,那么凭什么二方库的升级导致的事故,比加新功能导致的事故要罪加一等呢?一般来说,只要不是触发红线导致的事故,都不会追责到个人。

所以我们看到,提高服务稳定性的关键是系统性的dev ops。如果一个具有旺盛生命力的项目会信奉“能用就不要动”而不升级依赖,那么说明这个项目的dev ops一定有比较大的问题,其本身质量也堪忧。不过还是要限定一下范围,如果在版本末期,回归测试都快结束了,那么此时暂时不升级依赖,等下个版本再升级也是合理的。

其次,“看任何原则、理念、方法的时候都不能脱离上下文和适用场景,做教条式的理解”。我们刚刚讨论了什么情况下,不应该“能用就不要动”。那么情况下适用“能用就不要动”呢?

如果是一个已经很少有改动的项目,不升级就不升级,问题不大。这类项目通常不只是一个二方库的版本落后了,甚至编译器、官方库、所需要的运行环境都可能已经落后了;甚至情况可以更遭:这个项目没有自动化的单元测试和系统测试,也没有CI/CD(或者有一个非常混乱,有了还不如没有的CI/CD系统),甚至没有测试用例!对这种项目的任何改动,在现代的软件开发方法论和要求中,都需要比较大的精力去完善和实施测试。此外,因为这个项目不频繁改动了,我们并不能享受到二方库升级带来的开发效率提升。

或者,如果是一个已经长期没有新代码的项目,并且没有人对代码足够熟悉,那么“能用就不要动”这个观点也勉强可以成立。

解决方案

自动升级

公司还开发了自动升级平台,能够提交mr/pr来更新依赖版本,来简化二方库版本更新。不过这个功能也有局限性。

  1. 如果有任何的break change,那么通常不能由工具自动完成更新。
  2. 就算mr/pr都提了,业务开发同学也不一定乐意合入。原因参考上一章节【发布卡点】

没有银弹

好吧,在公司有了这么多解决方案之后,还是有这么多吐槽,说明这可能又是一个没有银弹的事情了。我们来浅浅讨论一下可能可以缓解这个问题的一些解决方案吧

说清楚收益,坦诚清晰

这一条是针对二方库开发同学的。业务同学的最主要诉求其实很简单:二方包可以把自己的版本内容讲清楚。因此二方库同学应该在版本管理上下一些功夫。针对版本管理问题,我发现有一些做起来很简单,且能有比较大作用的事情:

  1. 用中文讲清楚每个MR的改动面

对,中文!亲爱的母语!虽然英文看起来很专业,但是在实践中大家的英文表达能力有限,理解能力也有限,通过mr来声明改动面的作用被大大降低了。

保持兼容,积累信任

另一方面,有时候业务同学不升级二方库的版本是出于信任原因,我常见到这些吐槽:

  1. 上次升级你们的版本,结果服务出了bug。以后再也不升级了。
  2. 上次升级发现了一个逻辑不兼容的改动,你们也没说,结果程序挂了,以后再也不升级了。
  3. 上次升级你们的最新版本,就发现一个P0级别的bug,以后再也不升级了。
  4. ……

针对信任问题,我觉得只有不断提高代码的质量、提高覆盖率、长期坚持保证兼容性、有bug及时认错并周知使用方等等方式来不断获取信任。而在细节上,有这些小方法可以帮助你达成上述目标。

外科手术式的团队

对于开发同学而言,并不是每一个开发同学都有意愿去升级二/三方库版本的,免不了不少同学有惰性去依赖老版本。但是站在一个团队、一个项目的角度而言,优质二/三方库的重要性是不言而喻的。有时候替换一个json序列化库就可以带来巨大的性能优化,有时候一个好的二方库可以省去非常多重复编码的时间,有时候依赖的库的老版本爆出来了安全漏洞必须要升级。因此在一个团队内,势必要有一些同学熟悉二/三方库,能够帮助团队做技术选型和底层框架建设。

对于这种团队需求,我的经验是,借鉴外科手术式的研发团队的思路,在团队内部区分出一小批同学来“维护开发工具”(包括IDE、开发插件、二/三方库等等)。这一小批同学也是开发,只是他们会分一部分精力去“维护开发工具”,所以不太需要制定特殊的绩效规则。但是这部分同学需要有比较强的自驱能力或者编程兴趣,能自主地去了解二/三方库、在手头有非关键的项目去试用二/三方库、不定期做二/三方库新版本的分享和升级建议。

无形的压力,有限的人才

我们如果再回顾一下业务开发与二方库开发同学的诉求,就会发现大家最主要的诉求就是自己的付出要有对应绩效的体现。如果没有对应的绩效体现,那么非自己责任内的事情就不做。那么和绩效有关的事情应该找谁解决呢?找老板。

对于二方库同学而言,让大家升级到最新的版本是有天然驱动力的。老板只需要识别到这部分驱动力,给到对应的人力预留即可。比如去建设版本流水线、每个新功能预留足够的时间去保障兼容性、测试覆盖即可。

而对于业务开发的老板而言,就相对麻烦一些了,由浅到深,需要意识到这些事情:

  1. 意识到大家的工作产出必须得到认可(物质和心理),包括维护二/三方库。
  2. 认可二/三方库升级与维护的价值,认可“磨刀不误砍柴工”的软件开发效能原则。
  3. 找到合适的、有自驱力的同学来维护、分享、推广优质的二/三方库。
  4. 建立宽松的开发氛围,严格的系统的dev ops,提高开发的积极性的同时提高服务稳定性。
如果你觉得我的文章对你有帮助的话,希望可以推荐和交流一下。欢迎關注和 Star 本博客或者关注我的 Github