复盘接手 Flutter 生态中顶级热门的 dio 库的几个月,我们做了什么?

8,088 阅读21分钟

写在 100+ PR 合并之际,结构性复盘,总结经验和错误。

2023 年 2 月 13 日,经过数月的流程操作, Flutter 生态中顶级热门的 HTTP 库 dio 从 flutterchina 组织转移到了 CFUG 组织, 并且发布了全新的 5.0 版本,开始了由新团队维护的进程。

笔者本人作为这次事件的主要推动者之一, 想借着我们第二个里程碑——维护 dio 六个月——即将达成之际, 对事件的整体过往进行结构性的复盘,总结在开源过程中的经验和错误, 也让大家能够了解到这么一个风光的 package 在过去的一段时间中经历了什么样的波折。 我也想从个人角度分享给大家我的得失以及感想。

术语指引

Dio

github.com/cfug/dio

dio 是一个强大的 HTTP 网络请求库,支持全局配置、拦截器、FormData、请求取消、文件上传/下载、超时、自定义适配器、转换器等。

flutterchina

github.com/flutterchin…

国内知名开源组织,在 @wendux 的主导下开源了众多实用 package, 包括但不限于 diocookie_jarazlistview, 以及众多 Flutter 新手都会阅读的 《Flutter 实战》 及其第二版书稿。

CFUG

github.com/cfug

Flutter 中文用户组,主要维护 flutter.cndart.cn 本地化站点, flutter-io.cn 镜像站、各种 Flutter 活动时的本地化站(例如 Pinball, Photobooth, I/O Flip)、 发布本地化的季度问卷调查、处理 Flutter 国内相关的使用问题、组织 Flutter GDE 参加活动等。

事情起因

作为一个开源项目,如果它由一个未盈利并且兴趣使然的开发者或者团队维护, 那么很有可能在一段时间维护后就没有人回应问题或者发布更新了。 尽管 dio 是整个生态中数一数二的 package,但遗憾的是它仍然没有逃过 缺乏维护的魔爪

它是社区中网络请求方面的绝对第一梯队的 package, 由于为用户封装了很多开箱即用的特性,使得它非常受欢迎。 说两个数字进行简单的对比:

  • 在 pub 上 dio 的点赞数为 5856http 则是 6593
  • 在 pub 上依赖 dio 的 package 总共有 1328 个http 则是 3676 个
diohttp
dio 评分http 评分
dio 依赖http 依赖

从 dio 诞生至写文章时,它经历过多次维护空档,并且由于缺乏维护也遭遇了两次社区分叉 (fork):

  1. 从 2021 年 6 月到 11 月,经历了 4 个月的空档期,期间社区提出了 该项目是否仍在维护 (#1234) 的疑问,并且有人 对其进行了分叉,创建了 dio_http package。但在 10 月份原作者进行了维护,并且在 #1234 的回复结尾表明他将添加新的维护者来维护项目,所以在 11 月,分叉的 package 进行了归档,分叉进程结束。
  2. 从 2021 年 11 月(即刚回归维护后)到 2022 年 3 月,经历了 4 个月的空档期,尽管期间上一次添加的维护者仍然在维护项目,但是由于没有发布权限,用户并没有办法直接通过新版本接收到修复和优化。
  3. 从 2022 年 3 月到 2023 年 2 月,经历了 11 个月的空档期,期间我们介入进行了分叉,将在后文详细介绍。

用命运多舛来形容它也不为过。 这样的弃更也给它带来了不稳定的体验和较差的对外口碑, 常常能见到有人在 issue 里苦苦等待 BUG 修复。

在 dio 的过往时间里,我也是贡献者之一,修过一些 BUG, 也曾经将 dio 扩展到字节出品的 flutter_ume 上。 这些年经过我手的 package 说多不多说少不少, 看到一个这么主流的 package 给大家造成了不稳定的体验和较差的对外口碑,我一直想做点什么但没有成型的思路。 期间我也曾经多次尝试与原作者建立联系,未曾收到过回应。

你问我为什么想要掺一脚去维护一个快凉了的项目,我的答案可能是: 因为我们深度参与社区,因为我想给开发生态带来好的影响, 因为我是 Flutter GDE 而这个身份可以带领大家向好的方向前进, 因为将它维护好可以给社区对国内的开源项目留下更好的印象。 这样的答案可能难以让人信服,我也没有什么特别要解释的,“清者自清”?😅 而 dio 这样体量的 package 本身也值得被社区好好维护。

构思计划

基于以上事实,我们最终在 2022 年 10 月 15 日进行了一场内部讨论,细致聊了大家的想法以及可以立下的里程碑目标,计划大致如下:

  1. 进行硬分叉而不是软分叉,即不在 repo 上关联 fork 信息;
  2. 保留原作者开源协议信息,新增 CFUG 开源协议信息
  3. 保留所有提交记录,基于最新代码提交进行开发;
  4. 分析原仓库有效的 PR,与贡献者进行讨论和审查,尽量使其合并到分叉仓库;
  5. 发布硬分叉的公开意向,让社区知情,并且通过合并的 PR 关联意向,以及在社交媒体发布信息,扩大知情范围,期间所有决策以公开透明原则进行公布;
  6. 联系原项目维护者,将其经验带到硬分叉继续参与项目的维护,保证维护环节的正循环;
  7. 达到相对稳定状态后,如果原仓库仍未进行有效维护,发布硬分叉的稳定版,正式开始维护分叉道路。

第 2 点对于开源项目而言极其重要,一个项目从开始到结尾所有人的贡献都应当受到尊重, 而我们在原有的基础上进行开发,也不会贬低原作者为我们带来的好作品。

后续的发展证明了第 3 点的选择非常重要, 它是我们将分叉合并回原仓库的基础保障,否则所有改动都无法简单进行合并。

计划拟定后的几天内,我们内部其实仍然对是否实施它有不同的意见, 但经过大家的互相解释和鼓励后,最后决定由我带头推进, 于 2022 年 10 月 18 日正式启动了计划,硬分叉 diodio3(后更名为 diox)。

2022.10.18 CFUG

为什么是这样的计划?

对上面的计划的一些大家可能在意的问题进行解答:

Q: 不稳定的体验和较差的对外口碑有什么依据?

A: 有,而且不少。这里我并不会展示具体的内容,因为这些信息可以直接通过搜索查询到。 在多个空档期中,dio 曾经发生过数次严重 BUG 导致某个功能或某个平台完全无法使用, 但都没有及时修复或更新,导致在项目仓库中出现众多相关 issue 及跟帖。

Q: 为什么硬分叉?软分叉有什么问题?

A: 硬分叉不会对比现仓库与原仓库的差距, 可以有自己的 issue 和 PR 且不包含原仓库的 issue 和 PR, 有利于我们将项目调整为一个可以维护的状态, 而不是从一个非常复杂的状态中开始对 dio 的维护。 硬分叉和软分叉都会造成用户的迁移,不可避免。

Q: 为什么不能想办法更新原仓库?

A: 如上文所说,在这些事件期间,多人多次尝试联系原作者但均未收到回应。 而其维护的项目也有类似的情况,其中一些甚至影响到 Flutter 团队处理问题的流程。 最终我们只能选择进行分叉。

Q: 如何保证硬分叉的可持续性?维护者是否还能胜任维护工作?

A: 在拟定计划时,这两个问题背后的疑问和风险还非常高。 我们并没有在计划作出时就有一个结果,但随着后来的意向以及事件的发展,它们都逐渐有了答案。

计划实施

带着讨论出来的计划,我开始了整个分叉流程:

  1. 2022.10.18,创建了 分叉 diox,同时开始邀请维护者参与共建 (@kuhnroyal, @ueman);
  2. 2022.10.20-2023.2.10,不断合并来自原仓库的 PR,修复并调整代码,引进新功能;
  3. 2022.12.18,发布了 分叉的意向,详细解释了我们的身份、事件的背景、我们的计划等;
  4. 2022.12.18,发布了分叉的 第一个预览版本,同日公开了 分叉仓库,并且介绍了如何从原有 package 进行迁移的流程;
  5. 2022.12.18,社交平台公布消息,让用户参与相关的测试;
  6. 2023.2.10,发布 第一个稳定版本,并且在社交平台公布消息。

2022.12.18 仓库公开

至此,我们依旧没有收到原项目作者的任何反馈,这样的情况把 diox 这个硬分叉推进到了稳定版这般境地。 但是如果从计划实施的角度来看,我们完成的非常不错, 原本的维护者也积极地参与到了硬分叉的维护中,一些下游项目的作者也开始发布适配 diox 的 package。 可以说从这天起,社区开始向分叉项目转向。

事情的转机

然而就在 2 月 10 日 diox 发布稳定版的这一天,dio 世界发生了巨大的波动。

在没有任何沟通的情况下,原作者在 dio 仓库提交了一份声明:

2023.2.10 dio放弃维护

这份发在周末的声明直接导致了社区的舆情发酵,大家都在惊讶及惋惜,不少人也借此机会唱衰开源项目的维护。

2023.2.12 广泛讨论

事情发展到这一步,对于整个社区的生态打击是无比大的。 让整个社区转向是非常长久且消耗开发资源的一件事, 对于普通开发者而言则会在整个过程中体验到及其割裂的集成流程。

但是,在经历了两天的周末的关注后,我们终于联系上了原作者,或者说原作者终于联系上了我们。 经过快速的意见交换,以及各种细节的对接后,原作者最终认同由我们对 dio 进行维护的计划, 并且当天开始进行整个仓库和已发布的 package 的所有权转移。 讨论后敲定的操作细节如下:

  1. 原作者加入 @cfug/devs 团队,继续保持对项目的全部权限;
  2. 项目所有权从 flutterchina 移交到 CFUG(包括 pub),由开发团队共同持有全部权限;
  3. diox 由于基于 dio 的提交记录开发,将在所有权转移完成后 合并回 dio,并且 公开归档
  4. 所有 diox 发布的 package 全部 discontinued 并指引用户继续使用 dio 相关 package;
  5. 原作者及新作者发布 共同声明,解释此次接管及合并,尽可能地消除对用户造成的影响。

至此,整件事情来了个 180° 大调头, diox 完美的完成了它的使命,功成身退成为了 dio 的一份子。 而社区分裂也被及时制止,用户的生态健壮性得到了保障。

2023.2.13 个人公告2023.2.13 联合公告

后续发展

在事情出现转机并且我们与原作者操作过后,diox 完全合并回了 dio, 也由此 发布了全线 package 的新版本。 从这一天起,我们正式接手了这个项目,开始按照我们的计划进行实际操作。

2023.2.13 进度同步2023.2.13 代码合并

项目现状

经过 6 个月的稳定维护,dio 已经从 5.0.0 版本来到了 5.3.2 版本, 期间共解决了 141 个 issue,合并了 101 个 PR。

自分叉以来的所有提交

自接管以来的issue自接管以来合并的PR

在 2023 年 3 月 22 日,我们通报了安全问题 CVE-2021-31402 的解决情况,消除了新版本 dio 被报告有安全风险的问题。

CVE-2021-31402

目前整个项目已进入稳定维护阶段,核心维护者均能及时对应处理问题, 并 通过公开渠道发布新版本

社区经验

经过了从想法到稳定维护的这一段时间,我通过预先制定好的计划、与成员们的想法碰撞、 稳定的开源能力输出、积极与其他开发者接触、关注并引导社区舆论、公开透明的决策原则, 以及足够的权限下放,比较完美地将这个项目盘活, 并且让项目再次成为了生态中极受欢迎且稳定的 package。

我从这件事情中总结出来的 对于开源项目的经验 主要是以下几点:

  1. 尽管社区并不能总是把事情做好,但是要相信它们的活跃度和目标。 国外的社区在这件事情中充当了舆论发酵场的重要因素,每一个使用过 dio 的人在看到项目即将崩盘之时都表达了自己的感想,从而在某些方面影响了事情的走向。在停止维护的公告发出后,仍然有多个国外社区询问是否可以接手项目进行维护,这样的积极性在国内组织中较为少见,值得学习;社区请求转让所有权
  2. 当开源项目有一定规模时,无论是不是兴趣使然,一定要想尽办法让它处于可维护的状态 ,否则在多次空档期发生后,所有的参与者都会丧失维护的动力,导致一个项目快速走向结束,无法适配框架的更新。社区中已经有非常多的例子;Hive寻求维护者
  3. 要想把开源项目维护好,心态要尽量向利他方向靠近。 在 dio 的这件事中,每个旁观者都会有不同的看法。有的人可能认为国内开发者在内斗,有的人可能认为我们新作者可能只是老登,但无论大家如何想,这件事情的结果对于所有人而言都是受益的,这就是我们开展这项计划所追求的最终结果。
  4. 维护一个开源项目其实可以和每天刷短视频一样简单。 你只需要每天花一些时间,回复认真提问的 issue、研究问题发生的原因、及时向用户反馈你当前的进度,就算今天你的研究没有实质性的成果,用户也会对你的进度产生安全感,而安全感也会转化成礼貌且畅快的沟通,让所有的参与人在开源仓库的贡献中都得到正反馈,产生良性循环。某次issue处理
  5. 公开透明很重要。 当你在尝试做一个公开透明的决策时(例如代码结构设计),一旦你能将其表述到位,你就有很大概率能收到来自其他人有用的反馈,你也得以重新评估自己的决策的有效性。

个人得失

毫无疑问,在这件事情中,我是受到最大影响的人, 因为我完整地主导并参与了从头到尾的所有事件, 并且每一步都是我与团队或者我自己作出的决定, 结果无论好坏也主要由我承担。

咱们先说点好听的,讲讲我收获了什么,再来谈谈失去的东西和犯下的错误。

声誉

在维护 dio 之前,我已经长时间游走在 Flutter/Dart 生态支持中, 积极地为仓库作出自己的贡献,帮助联系并协商一些事情。 这些贡献可以在我的 GitHub 贡献历史中可见一斑。 而在今年 (2023) 谷歌开发者全球账号也时不时地帮我宣传, 所以叠加此次事件后,无论是国内外, 很多开发者都对我以及我们 CFUG 形成了比较好的印象和认知。 声明发布当天,原 Flutter 团队的 Tim Sneath 也转发了我们的内容。 2023.2.13 Tim转发

依靠着热点事件和公开处理的方式,我摇身一变, 在突出贡献者的基础上叠加了网红属性,让更多人了解到并认可我的工作。

涨粉

这种热点事件,在哪儿发,就在哪儿涨粉,具体是什么平台就不细说了。

统筹规划执行力

除了上文提到的计划以外,为了完成我们定下的所有里程碑和终极目标,我在中途还做了这些事:

  • 找到先前的维护者,详细地向他们解释了事情的原委,沟通我们的目标机我们能做出的承诺,鼓励大家一起为社区作出贡献;
  • 设计我们的意向书,深思熟虑如何把故事讲好;
  • 设置项目板,关联我们正在处理的修复 PR,对外公开;
  • 在社区中呼吁大家尝试变更,并且在协助大家调整的同时收集反馈。

由于细节非常多,这些事情无疑提升了我在开源项目以及叙事上的统筹规划和执行的能力。

责任

责任可以是一种收获,也可以是一种失去。当你肩负起了维护 package 的责任,你就需要直接负责它的一切,无论好事坏事,所有人都会找你。表扬的也是你,骂的也是你。

某次issue@人

暴躁老哥在线骂人

时间

每个人每天的时间是固定的,做了更多的活,参与的越多,占用的时间也会越多。

在开始维护 dio 之前,我的每日工作流是:

  • 起床 -> 打开电脑 -> 上班 -> 闲暇时看通知 -> 有问题对应问题 -> 想到新功能实现一下 -> 关机 -> 睡觉

现在加上了 dio,大概每天会关注 5 个仓库的动态,工作流就变成:

  • 起床 -> 打开手机刷通知 -> 回复评论 -> 审查 PR -> 上班 -> 收到通知对应 -> 想到新功能实现一下 -> 循环第3步 -> 循环第4步 -> 关机 -> 循环第2步 -> 循环第3步 -> 睡觉

起床就是刷通知,睡前还是刷通知,这次真正的把家搬到了 GitHub 里。

做的不好和做错的事情

破坏了默认的 content-type

在这次大版本的跨越和维护时,我从社区中收集了一些关于隐式指定 content-type 的意见, 从这部分人中得出的结论是:新的大版本不应该再隐式指定 content-type 了, 而是应该让用户自己确定,保证跟随用户指定的值走。

根据反馈,我们最终在 5.0.0 的正式版中加入了这项修改,并且提供了完整的迁移指南。 但是发布后,我们仍然收到了 大量的反馈, 请求无法正常发送。 上面的社区收集好就好在征求了大家的意见, 坏就坏在没有考虑到像 dio 这样被大规模应用的 package 让开发者把所有代码都迁移完成有多困难。 也有人私下找我对线了这个问题,站在不同的角度,每个人都有自己看法, 而很多人升级时也没有看迁移指南, dio 本身对于不正确的 content-type 也没有充足的提示。 最终的结果是这次的改动确实过于大,导致了很多应用方没有办法很好地处理变更, 所以我创建了新的 ImplyContentTypeInterceptor 来恢复部分默认类型的处理。

破坏了对 src/dio_error.dart 的直接引用

PR 1803 中我们废弃了 DioError,将其迁移为 DioException。 这项更改同时也将 src/dio_error.dart 更改为 src/dio_exception.dart。 正常来说,在依赖一个 package 时,不推荐直接引用其 src/ 目录下的文件。 怪就怪在有一个 package stream_chat_flutter 直接引用了这个文件, 所以对方的作者提交了 PR, 同时保留两个文件使得旧的用法仍然有效。 但是我以与最佳实践冲突,应该避免直接引用 src/ 为由拒绝了这项提交。 第二天我就看到另一位维护者在吐槽这件事:

针对最佳实践的吐槽

我们两边都有各自在理的地方,但我更加认同他的说法,接受一个这样的 PR 有多难呢? 下一次出现这样的情况是否可以 LGTM,这次的经验会带给我不一样的评判标准。

感想

这里不是经验,只是输出一些 纯粹且主观的个人感想

做开源,怎么做合适?

玩了 5 年开源,现在混到了常驻 排行榜 前 20, 发现抱着功利心做开源绝对不行,分享的心态做大以后也不行。 唯一可行的只有开放的心态,学会接受别人的意见。 开源世界也是鱼龙混杂,很多能来 GitHub 给你开 issue 的反而是新手用户。 面对新手,有经验的人常常会陷入“知识的诅咒”,我也曾经有过这样的一段时间, 即无法让自己从不懂这件事的角度去思考,理解新手的问题。 而能给你开 PR 的人都有自己的观点, 某种特定场景下也许真的可以帮助你的 package 变得更好。 大家能否认同你除了一部分是基于事实外,另一部分都是基于大家的常识。 我也曾经在一个 Flutter issue 里总计被数十人点了 👎, 但是并不影响我与团队合并正确的 PR 来修复这个问题。

总计数十人点踩,仅有几位 team member 点了赞

所以 多做,多讨论,多换位思考。

怎么维护好一个项目?

笼统的答案:用心

更精准的答案:用心留意谁(个人、组织、基金会均可)对项目的帮助最大, 认真考虑项目的出路,实际地考虑如果项目黄了会有什么后果。

当你一开始只是因为一个分享而做的项目,但是后面却疏于维护的时候, 问问自己:懒?没有能力?花太多时间?效益差?

这些对我来说都还不足以成为一个理由:懒不能让另一个协作者来操作? 没有能力不能招募平常有突出贡献的人来帮助? 花太多时间不能把项目托管给更开放且热心活跃的社区? 效益差不会思考商业化的途径?

说白了就算没法维护了,归档会不会,转交会不会? 非要到大家都准备崩溃了,才挪一下屁股, 这样的操作对社区而言百害而无一利。

负责任也并不是每个人都有的特质, 如果你觉得你没有,请考虑招募第二个协作者, 或者直截了当地告诉大家:项目可能无法及时维护。 把话说明白没有那么难,敞开来说话才能把事情办好。

致谢

最后,当然是列一个感谢列表:

  • 感谢 @wendux 作为创作者开发了这么一个有无数开发者喜爱和使用的 package;
  • 感谢前期支持我们这项工作,并且积极为我们的分叉版本提交反馈的社区成员们;
  • 感谢国外的两位协作者 (@kuhnroyal 和 @ueman) 在这数个月中持续地与我一起维护 dio;
  • 感谢 @CaiJingLong 在我们的维护 dio 过程中提供了所有的 CI/CD 能力的支持;

特别感谢 CFUG 的各位,从我提出想法到今天,给了无偿且充足的信心和动力, 让我们能够成功地完成了我们的计划,拯救了 dio 这样的热门 package。 很开心能有你们一起同行。

CFUG 2023/08/06

让我们继续在 Flutter 的开源世界相见!

注:文中所有 @ 开头的 id 均为 GitHub 用户名。