当前位置: 首页 > 新闻中心 > 要闻速递 > 正文

新闻中心

要闻速递

NEWS

如何成为优秀开源软件管理者

时间:2021-07-28    来源:

  • 分享

本文原作者Jeb Barabanov是开源项目angular-builders, jest-marbles, drummer, kite-surfer, gamer和writer的管理者。以下全文翻译李霄。

01 什么是开源

广义定义—开源是信息可以公开获得,允许复制和修改的概念。

在软件开发领域—开源是一种去中心化和鼓励合作的模式。主要原则包括:并行生产,公众可以免费获得相关产品,如源代码和文本等。

开源模式有什么好处呢?可想而知的一个主因是开放有益于收获合作者。

Together we’re strong. (Zarya, 2016)

以维基百科Wikipedia为例子,它的创始人Larry Sanger最早一个人一年只能产出12篇文章。而如今维基百科拥有3789万+的注册用户,其中13万+活跃用户,这就相当于维基百科有13万+的贡献者在编写着维基百科的文章,维基百科现在一共有6百万的海量词条文章。

就如同维基百科用户提交文章,然后由编辑审核通过一样;开源软件的贡献者提交拉取请求(pull request, PR),然后由开源软件管理者审核通过提交的代码。

(译者:在教育界,开源教科书也开始兴起了,起源于UC Davis的网络开源、免费的网络教科书LibreTexts平台近几年引领了美国教育界的开源风潮,很多美国大学都开始使用这个平台。在这个平台上,教师账户可以自定义自己的教科书,可以对平台上已有的开放教材进行改编应用于自己的课堂,教师分享了这个版本以后,也可以供其它教师直接使用或改编。很多学生都受益于这个项目。)

02 创立开源软件的动机

虽然寻求合作者是选择开源模式的主因,但大部分人并不单单为这一个原因,通常还有其他原因,一些常见的动机和情景总结如下:

你想解决的问题目前市面上没有免费的解决方案(软件)

你遇到了一个问题,目前市面上没有软件可以解决这个问题,或者说相关软件需要付费获取。你把这问题解决了,你对你的工作非常兴奋,你也相信你的工作会给他人带来便利,于是你把你的工作变成一个开源软件。

你想成为一个创始人

你想要成为一个开源软件项目的创始人,这样你就可以在你的简历上添上华丽的一笔。你想要实现自我的成功(毕竟我们都是人)。如果这是驱使你启动一个开源项目的主要原因,那么我可以向你保证,当你读完这篇指南你就会从新考虑了,为了这个原因不值得这么做。

你想比别人更好的解决一个问题

你遇到了一个问题,在市面上有一个开源软件能解决这个问题,但是你认为它不够好或并不完全是你想要的。

如果你完全处于想要做的比当前市面上的开源软件好而启动了一个全新的软件项目,那么这基本上属第#2种原因(为了实现自我成功)。与其开启一个全新的开源软件项目,你不如做一个贡献者,给当前存在的软件提交pull request (PR).如果提交PR不是当前该开源软件版本的一个选项,那么你可以考虑扩展它,或者是重复利用该软件中的相关功能,这样可以让你之后省去很多头疼的事情。

你想通过创立开源软件的方式解决一个问题

你遇到了一个问题,目前市面上没有软件可以解决这个问题,于是你开启了一个开源软件项目,希望这样可以找到解决方案。在我看来,不一定。

你应当先解决这个问题,确保你写的软件有用,然后回到第1个原因。

以上是我经常见到的四个动机,在此指南中,我主要关注第1个原因。原因很简单,如果你开启一个开源项目的初心不是想要分享和贡献出你所做的工作,是走不长远的。会有很长一段时间,你所得到的就是你曾帮助他人。如果这不是你寻求的满足感,那么你就不用浪费时间往下读了。

在这里,还值得一提的还有另外一种情况,有时候一些公司会向社区开放部分源码,比如,谷歌维护的Angular,脸书维护的React,微软维护的VSCode等等。这些公司开放源码的原因可能各有不同,但一定都包含寻求合作者这一条原因。我不用强调这一做法的重要性,但我想指出这种情景和我们所说的其它开源软件都不太相同,因为在这种情境中,相关开源软件的维护者是相应公司的员工拿着相应公司的薪水。如果你属于这种情况,那么这篇指南的大部分内容对你也会有所帮助,但我们的动机就是不同的。

用一句话总结这一部分就是:确认你的目的和你的期望是一致的。

03 如何启动一个开源软件项目

那么,你现在属于第1种情况——你有一个解决特定问题的方法,你期待与大家分享:

我们再强调一次:

这无关你的抱负

你不指望从中获利

你是真心想帮助那些和你遇到同样问题的人

如果你对以上回答都是“是”的话,那么以下是可以帮助你做对这件事的一个清单:

确认开源软件是正确的打开方式。如果你想要分享的方案不足以构成一个软件,那么你通过一个部落格的分享也许就够了。

再三确认市面上并没有相似的方案。如果有的话,也许你的方案可以成为已有开源软件的一个完美PR.

为即将发生的事做好准备

就如我之前提到的,管理一个开源软件项目有很多挑战。其中最突出的挑战,就是这个任务将会花费你大量的时间。你所做的每一件事都要花时间,无论你是写代码,管理议题(issues),更新相关项,和人们交谈,回答问题,等等。

你投入到开源软件的每一分钟都将减少你对家人,业余爱好和健康方面投入的一分钟。你可以做的更好的最好途径就是开始委派任务,当你有足够多的合作者的时候,你就可以“外包”一部分你的责任给那些你信任的人。

代码分离

进入正题,你现在已经写了一个解决某特定问题的软件,你也认为其他人能从中获益。你的软件代码此时还是你的代码库的一部分,如果你不是想开放你代码库的全部内容,那么你首先应该把你要开源的那部分代码从你自己的代码库中分离出来并放到另外一个目录下。

普适化你的代码

你要确保你新目录下的代码是普遍适用的而不是只能解决某一个特定的问题。需要的时候,做一个抽象层。

举一个例子,我开始做angular-builder的起因是为了解决一个非常特定的问题,(这个问题来源于我的另外一个开源软件项目),当时我想要将本机模块的自定义加载器添加到Angular build中。我当时可以写一个native-module-builder来只为解决这一问题。然而,我意识到,我可以投入相对较少的经历把代码写得更通用一些,这样我的代码就可以解决其它类似但不完全相同的问题。custom-webpack builder就是这样诞生的。

让事情简单些

通用性虽好,且追求且谨慎。

“过度优化”和“过于通用”是两个软件工程里众所周知的问题,你应当在这两个极端之间寻求一个平衡点----你的代码应该在解决你的一个特定问题之外还能解决其它的类似问题,但却不是全世界的所有问题。说得形象一点,如果从1到100,只解决你自己的特定问题是1,能解决世界上的全部问题是100,那么你应该从2开始。

尽量多用你自己开发的软件

多在你的代码库里使用你将要分享的这份具有通用性的代码—这会帮助你去除那些你不需要的部分。这也保证你将要开源的这部分代码运行是正常的。

记住,你是你的开源软件的第一个用户。

不要吃官司

如果你要从你所在公司的代码库中分离出一部分代码,一定要咨询你的上峰,有必要时咨询法务部门。确保公司支持你的倡议,也要确保你想要开源的代码不属于你公司的知识产权。这个咨询的过程也会帮你确定哪种开源许可证更适用于你的项目。

发表你的代码

开源软件,顾名思义,就是要开放你的软件源码。

公开源码的途径有不少,但我们这个攻略里默认你用的是GitHub.

首先,在GitHub上创建一个仓库(repository).

克隆这个仓库

把你的开源代码拷贝到这个目录下(暂时还不要删除原先那个源代码的目录)。

提交及推送(Commit & push)--大功告成,现在该软件是个开源软件了。

创建安装包

现在你的软件已经开源了,但还没有用户。现在还没人指导它的存在。并且,你的软件现在是以源代码的形式呈现在网上,用户只有把这些代码拷贝+粘贴到你的代码库才能使用。这并不是很方便,对吧?

为了更合理地分发你的软件,你需要:

把你的源代码创建成一个安装包。

把这个安装包发布在一个公开的安装包注册平台(选择什么安装包注册平台,取决于你所在行业的生态。比如,Java软件通常选择Maven Central Repository,如果是Javascript的话,你应当选择Npm Package Registry)。

这正是你在你的新仓库里加入一个构建链,确定你的软件名字等等这些事情的时候。我在这里就不展开叙述了,因为这取决于你所在的生态,你所用的工具和编程语言。

如果你是一个自己做一切的人,命名软件项目,加入构建链和生成安装包对你来说都是小事一桩的话,那么恭喜你!但如果你是一个比较习惯于只写代码,但从未处理过定义(defining),配置(configure),构件(artifact),等这类事情,那么前方有一个全新的世界等你来学习。如果你是后一类人,那么你得开始学习了。这不会是很速成的事情,但相信我,你会学会的。

在你的软件大红大紫以前,确保它能正常运行。再强调一次,你是你自己开源软件的第一个用户。

总而言之,当你做完这一切— 把安装包发布了,这个时候你才能说你的软件是一个开源软件了。这个时候,你就可以大声的告诉大家,”嗨,这个软件可以解决你的问题,去下载和使用吧!“

如何处理后续的开发

当你在你开始使用你软件的安装包时,工作流程就会有所不同。之前,你的软件代码是你的代码库的一部分,如果有什么修改,你马上就能用上。但现在,你的软件相当于一个外来的安装包,和任何你使用的第三方软件无异。

所以,以下是一些能帮你规避提交问题版本的几点建议:

确保你的新版本被充分的测试了,包括单元测试,和端到端测试。关于测试的重要性,我无需再强调了吧。

在你的本地环境里,打包和安装你的软件,确保每个环节都按期待进行,接下来你就可以发布这个新版本了。

先给想要的用户分发beta版本,而不是一次性就把新版本发布给全世界。举一个例子,在npm package registry平台上,dist tags (分发标签)就是用来做这个事的。

默认的标签是latest。也就是说当你运行npm install mypackage的时候,程序默认运行npm install mypackage@latest.

当你把你的版本标上不同的标签,比如beta的时候,用户就需要专门强调他或她要安装的是beta版本:npm install mypackage@beta .

看到这里,我再问你一次,你想好要把你宝贵的时间投入到开源社区了吗?

04 如何写文档

这一部分内容基于一个基础,这就是:你已经有一个开源软件了,它已经在GitHub上了。

为什么要写文档,文档包括哪些?

一个没有文档的开源软件是没前途的。没有前途是因为没有人会仔细研究你的代码来搞清这个软件应该怎么用,甚至连你的软件是干什么用的都不知道。

你的文档应该包含两样最基本的东西– 用途 和 操作方法。这些是文档的必要构成部分。

如何写项目说明(description)

说明是人们进入到你的GitHub仓库看到的第一则信息,所以一则好的说明应当简明扼要地描述软件的用途。

提供以下三个例子给你作为参考:

React的说明:A declarative, efficient, and flexible JavaScript library for building user interfaces. https://reactjs.org

Moment.js的说明:Parse, validate, manipulate, and display dates in JavaScript. http://momentjs.com

Angular builders(这是原文作者的项目)的说明:Angular build facade extensions (Jest and custom webpack configuration)

你可以在仓库的About部分修改说明。

如何写自述文件README.md

README.md是一个在你项目的根目录里,用标记语法(markdown syntax)书写的包含用户所需的全部信息的文本。

README.md文本应当包括软件用途更细节的描述以及详细操作方法。操作方法应当包含公共应用编程接口(public API)的每一个部分,而且最好是结合实例。

以下是写好的API接口文档的几点建议:

简洁:你的API和实例写的越简单,用户就能越轻松地理解它是干什么的以及怎么用。

排版清晰:每一个API函数都用同样的模板和可视化排版。通过这种方式,你也在形成自己与用户传递API信息的特定语言风格。

用户视角——确保你是从用户视角来写API描述的。假装你不知道该软件的内在构造而你所有的信息都来自这个文档。

及时更新——随着你的项目的迭代,API可能也跟着有改变。确保你的自述文件反映最新的API和例子。

自述文件可以(但不一定)包含以下几个元素:

Link to a contribution guide

List of contributors

Link to a change log

Latest version

License

Build status

Downloads counter

Link to a chat for fast feedback

在这个链接里你可以看到一个比较好的自述文件的例子:aws-amplify/amplify-js: A declarative JavaScript library for application development using cloud services. (github.com)

善用徽章(badge)

使用徽章是一种很好的曝光项目重要信息的视觉呈现方式,项目重要信息包括:构建状态(build),发行版(release),许可证(license)等等。有不少徽章的选择,但我推荐你使用shields.io徽章,它们的徽章几乎涵盖了所有需求。

在自述文件里加入徽章非常简单:

打开Shields.io: Quality metadata badges for open source projects;

选择相应的类别;

点击你想要添加到自述文件里的徽章;

填写必要信息(适用的话);

从下拉菜单里选择“Copy Markdown”;

把markdown粘贴到你的自述文件里。

徽章通常是放在自述文件的最开头,在具体说明之前。以下是一个例子:

一定要有测试例子

API文档虽然重要,但还是API的实际代码更重要。

文档不够,测试来凑。其实很多时候具有说明性的测试比文档能更好地解释你的代码是如何运行的。

总结一下,在这一部分里,我们主要是讲了如何写说明和自述文件,但其实还有一些其它的文档。比如说,随着你的项目不断地发展壮大,它的议题(issues)也会不断增多,这些议题会成为文档的有机组成部分。不过,一个涵盖了API接口的自述文件是最基本的。

05 如何发布一个开源项目

我们已经讨论了如何更好的启动一个项目以及如何写好文档,那么我们现在来聊聊如何吸引用户,和如何更好的吸引和正确地管理贡献者。

这一部分内容是基于一个前提,这就是:你已经有一个开源软件了,它已经在GitHub上了,它的文档很完善,用户可以轻松地通过安装包注册表来“消费”这个软件。

如何向世界宣传你的项目?

我在这里一定要说一句,在你的项目发展的过程中,你自己会很快没有时间处理所有事情的。这个时候,如果你想要你的项目持续性发展壮大的话,你需要更多的人来为这个项目做出贡献。想要更多的人参与到这个项目中,你就需要更多的人知道这个项目的存在,并且对这个项目有信念。

从我的经验来看,让更多相关受众知道你的项目的办法就是使用大平台发布消息,或者是发表关于项目的博文。平台可以是开发相关的(比如dev.to)也可以是无关的(比如Medium.com).这些平台共通的优点就是,它们都拥有固定的受众群体,而且这些受众是你的项目的相关受众。

如果你使用Medium.com的话,我强烈建议你把你的博文提交到一些大的发布者(publications)那里。虽然你需要多花一点功夫让你的文章符合相应发布者的要求,但这样能确保你的通稿能被推送到更广且相关的受众那里,比起广度,受众的相关程度更重要。

你也可以考虑把博文投放到付费的半开放媒体(你还可以从中获利):使用付费媒体的情况还包括(热门)话题投放– 这是一种强大的把文章推送到媒体首页、我们门户网站或手机应用里的每日摘要的做法。

我说不上哪种方式更好,但我个人偏好通过发布者发博文,因为这种方式能确保博文的曝光,而不只是一个”可以被投放“的模糊名词。

如果你的博文被广泛传播就能带来一系列连锁效应,为你的开源项目带来更多的优势。举一个例子,如果你在发布了一篇博文后,你的GitHub项目在一天内收获了不少星然后因此登上GitHub热门搜索页的话,这本身又形成另一种宣传项目的途径。

写好一篇博文的几点建议:

以你的软件所能解决的问题开篇。你甚至也可以用这个问题当作通稿的标题。通常人们是在寻找一个解决问题的软件,所以在他们决定要不要花时间继续读下去之前他们要能判断这篇文章和他们的问题是否相关。这个链接是我写的一篇文章。你可以看到,我在文章的标题里清晰地指出了我的软件解决的是什么问题。如果你搜索“customizing Angular builder“的话,谷歌搜索的第一条结果就是我的这篇博文。

描述你的软件为什么以及是如何解决相关问题的。

提供详细的分步骤指南,从安装开始,一直到应用实例。

创建一些示例项目,然后在你的博文里提供这些示例项目的链接。比起一篇博文,很多开发者更乐于见到一些应用实例。

发布之前,搜集一些反馈意见。先别告诉朋友关于项目的任何信息,看看他们是否能通过读你的博文搞清楚,如果不能,那说明你的文章写得还不够清楚,你需要修改。

当你发布了博文之后,记得在社交网络上分享,并扩散给你的亲人,朋友,甚至是陌生人。这可以增加项目的曝光度。

除了增加曝光度之外,你还想要吸引贡献者。

如何让你的项目对贡献者有吸引力?

最好的办法就是和别人一起启动一个开源项目。这样的话,从一开始你就是一个团队,就有人和你分享负担。

但并不是每次都能从一个团队开始,如果你一开始是一个人,那你就需要吸引贡献者。以我的经验来看,贡献者一般来自两类人:

在寻找一个项目做出贡献,以发挥影响力的人(这类人很少,但还是存在的)。

使用了你的软件然后发现了bug或者缺失的功能的用户。

对于这两类人来说,仅仅在GitHub上分享你的代码和一篇简单的博文是不够的。你需要多做几件事情,让这些人想要贡献,这几件事情就是:

提供待办清单

这个清单可以包含一些bug,一些计划中的功能,等等。这个清单会让第#1类贡献者清晰明了的看到他\她可以做的事情然后从中选择合适的来提供拉取请求。这个清单可以是一个单独的文本,或者你可以(或应该)用GitHub上的议题标签。

提供贡献者手册

基础版的贡献者手册包括仓库结构解释,分步骤的构建、运行和测试的操作说明。进阶版的手册也可以涵盖体系结构(architecture),设计决策,行为守则等等。

Atom的贡献者手册是一个很好的例子。不要低估,这很有价值!这是在你的项目发展壮大的过程中,你会花很多时间来做的一件事。我后悔我没有在一开始就写好这个手册,然后在项目迭代的过程中再逐渐完善这个手册。很遗憾,没有人在我刚开始的时候告诉我贡献者手册的总要性。所以,截至目前,我的项目还没有开发者手册,这一直在我的待办清单里。

认可贡献者

把贡献者的名字放在开源项目的首页会成为贡献者的动力之一。

把贡献者的用户名放在首页还不够,我建议你使用All Contributor(另外一个有用的开源项目)。这个工具不仅能帮你做一个漂亮的区域来展示所有贡献者的头像和徽章,还会通过创建拉取请求的方式自动地把新的贡献者加到这个区域里。

总结以下,这一部分,我们讨论了一些关于如何增加项目曝光度和给潜在贡献者提供创建拉取请求和议题的动力的事情。但这些还不足以维系贡献者,或者确保他们从头到尾完成一件他们开始的事务。

06 如何管理议题和拉取请求

这一部分,我们来聊聊贡献者– 开源项目的圣杯(不可缺少、非常重要的)。

什么是对开源项目的贡献?

一个对开源项目的贡献是任何除了项目持有者以外的人做出的修改。在实践中,贡献一般有两种形式:

议题(issues)

GitHub对议题的说法:

您可以在仓库中使用议题收集用户反馈,报告软件漏洞,并且组织要完成的任务。议题不只是一个报告软件漏洞的地方。

简而言之,议题是一条需要对之采取某种行动的信息。

拉取请求(PR)

GitHub对拉取请求的说法:

拉取请求可让您在GitHub上向他人告知您已经推送到仓库中分支的更改。在拉取请求打开后,您可以与协作者讨论并审查潜在更改,在更改合并到基本分支之前添加跟进提交。

简而言之,拉取请求是一则对项目的实际修改。

如何处理议题和拉取请求?

以我自己为例子

我个人能给你最好的建议就是使用一种工作方式。这个方式就是说,当你开始写一个新的特征的时候你应该创建一个拉取请求,然后当它满足你的要求的时候尽快合并它。如果你发现这个拉取请求中有错或者没有涵盖必要的功能,你应该创建一个相应的议题。这样的工作方式不仅把工作整理得井井有条,更给贡献者们提供一个参考,让他们能相应地调整议题和拉取请求。

还有,如果你提出很高的标准(比如要求每个拉取请求都要有合适的文档,测试,等),你也应该以相同的标准要求自己。你不能提出一些连你自己不会遵循的标准。有时,你应该对贡献者比对自己宽容些,尤其是在项目初创阶段。这也引出我要说的下一点。

感恩所有工作

和他人合作完全是建立在相互尊重之上的。你应该尊重贡献者们。耐心地回答问题(哪怕问题很简单),礼貌地提出有建设性的批评。

记住:尊重贡献者是很关键的。

如果有人创建议题(哪怕没有经过深思熟虑,没有仔细调研,是偶发性不可复制的问题),感谢他们。他们已经多花了功夫,把凳子前移打了一段觉得会对你有用的文字。谢谢他们,如果必要,再礼貌地问问其它细节。

如果有人创建了一个拉取请求,但达不到你设定的高标准,谢谢他们。礼貌地提出修改意见,给他们提供一个你自己的拉取请求以及贡献者手册的链接作为参考。

有建设性和正面的对话会成为贡献者创建贡献的额外动力。

质量还是数量

事情总有个取舍(除非你拥有的是一个像Angular和React那样的大型开源项目)。(这里有两个极端:)如果你决定不放宽标准,哪怕一点点也不放宽,那么你很可能最终得自己来实现所有工作。或者,如果你决定为贡献者降低标准,那么你指定标准这个事就变成徒劳的了,因为标准没能被遵从。

我学到每个贡献者都需要不同的方式,这取决于每个人的特点以及他们对贡献的兴趣。你应当考虑议题的紧急性,贡献者的经验,你的代码的复杂性,需要修复或添加的功能的复杂度还有贡献者的动机,等等这些因素。

通常来说,如果是相对紧急的议题,我会礼貌地请求修改,如果等上几天没有动静,我就自己做。对于相对不太重要(锦上添花)的修复或新特征,我通常就会完全把它们留给开源社区。

慢慢地,议题和拉取请求的数量就会变得越来越多,对那么多的议题和拉取请求进行跟踪,排优先顺序,分门别类就会变成一件心有余而力不足的事情。这就意味着标签将起到举足轻重的作用。

善用标签

GitHub标签是整理议题和拉取请求的利器。它不仅可以让你根据标签来搜索和筛选,我发现最有用的是它可以把一个项目的进度状态图像化地呈现出来。

当你进入“议题”页面,如果看到大部分的议题都被帖上了bug标签的话,就意味着你不能再不停地推新功能了,而是应该停下来专注把这些bug修好。相对应的另一种情况就是,大部分的议题被贴上了enhancement(改进)或者required feature(需要新功能)的标签。

Priority是另一类有用的标签,它可以告诉管理者和贡献者该优先专注在什么议题上。

除此之外,标签也能帮助到贡献者。比如,当潜在贡献者进入到“议题”页面,他们可以通过help-wanted和pr-welcome这样的标签明了的看出哪些议题需要社区的帮助。

除了使用单一责任的标签(比如bug和enhancement),我还建议你使用描述范围和程度的标签,比如:

priority: low, priority: high

required: investigation, required: tests, required: docs

packages: package1 , packages: package2 etc.

以下用我的一个项目中的议题页截图作为例子:

标签让你和贡献者一眼就能清晰地看出哪些议题需要你的关注,这些议题跟哪些部件相关,以及需要采取什么行动。

使用议题和拉取请求的模板

我强烈建议你多花几分钟来定义issues和PR模板。

你能通过模板定义和规范贡献者在仓库里创建议题和拉取请求时应该包含的信息。

模板会为你节省很多时间,因为你之后就不会需要通过回复议题和拉取请求去询问额外的信息或修改。虽然这偶尔也会发生,因为有的贡献者会没注意到模板的存在,但相比起没有模板,这种事情的概率会大大降低。

下面是一个议题模板的典型例子:

10C93

使用GitHub应用和GitHub Actions

有很多不少GitHub应用和actions可以帮你管理issues和PR,以下这个列表是我个人觉得有用的:

Stale bot

WIP

Autoapproval

PR labeler

及时回复

如果我创建了一个议题或拉取请求,然后对方花了“无穷无尽“的时间来回复我,那么我就会换一个项目。

这是我遇到的一个例子:

刚开始的回复比较快,用了两天;

讨论也算有成效;

但这个PR现在还没关闭,PR里指出的错误和缺失没有任何更新。

就这样,我换到了另外一个软件。

角色转换,情况也是一样的,如果你两个多星期才回复用户创建的PR,那么你就会失去这些用户了(用户是潜在的贡献者)。

所以,一定要及时回复– 帮助别人就是帮助你自己。你不用立即提供解决方案,哪怕是回复别人你收到了,你预计下星期研究这个问题,你也给了对方一些确定的信号和时间线。坏消息就是,你要信守承诺。但如果你时不时地无法做到每次都按你自己承诺的来做,也不要担心,我们都有自己的生活,大家也都理解也许你遇到了紧急的事情而需要推后开源项目的工作。如果这样的事情发生了,给个简短的更新,这样用户们就知道他们在等待的功能被推迟了。

如何给议题排优先顺序?

这里我给你提供几个能帮你排优先顺序的方法。

首先,怎么确定哪个议题最重要呢?最重要的议题就是用户们最想要的,无论这是一个新功能,一个bug修护还是别的要求。有的时候用户们会在提issues的时候就表达他们的关切程度,但大部分时候他们是不会的。这种情况下,我教你一个可以知道用户们对那些议题感兴趣的途径:

每一个项目都会有一个“Insights”按键,其之下有一个“traffic”区域。

A90D

打开traffic你就可以看到哪些页面是用户访问最多的。见下图:

DF6C

被访问最多的议题很可能就是用户需求最大的。在知道了需求最大的议题之后,你就需要强调这个议题,这通常有两种方法:

置顶议题:在每个仓库中,你可以置顶最多三个议题,被置顶的议题会出现在议题页面的最顶端,很显眼,很难被错过,如下图:

CF3F

添加标签:我在前页讲过善用标签的话题了,这就是一个最合适使用help-wanted和priority: high的时候了,这样的标签可以让贡献者们看到这个议题很重要而且很欢迎社区的帮助。

总而言之,保持你的项目条理工整是很重要的。。条理工整不仅使得管理更加高效,更会提升整个项目给人的印象。议题和拉取请求也是你开源项目不可分割的一部分,不要低估它们的价值。

07 如何实现软件开发自动化

一个自然地管理贡献(也就是议题和拉取请求)的方法是自动化管理,这也是OSS(开源软件)管理的一个重要方面。

为何选择自动化管理?

如果说我从我这么多年的开源软件管理经历中学到了什么经验的话,那就是:你每天在日常工作中花的时间越少,你就有更多的时间能去花在真正的工作中,比如修bug,开发新功能,等等。所以,我就会尽可能地是开发过程自动化。

我将会这样来展开我的叙述:我们先来看看非自动化和自动化两种工作流程的区别,从中我们来看看日常工作会花费多少时间,然后我们就来讨论该如何优化我们的工作流程以腾出更多的时间来修bug.

最坏情况:完全没有自动化

CD6F

你可以看到,在没有自动化的情况下,你一个人要做全部的事情,光修一个bug你就需要做很多的工作,并且,每修一个bug你都要做一遍这个流程里的所有工作。

再让我们看看另一种情况。

最好情况:完全自动化

EAFF

在这种情况里,你只需要做你必须做的部分,检查代码,以及(时不时地)同意拉取请求,其它部分都是自动化的。

这听起来像科幻小说吧?不是的,这个过程叫做持续集成(continuous integration, CI)和持续部署(continuous deployment, CD)。我就不再这里深入展开关于写脚本和与系统相关的配置问题了,我给大家介绍一下实现CI和CD所需的相关工具,细节就由你自己决定了。

什么是持续集成(CI)?

持续集成(CI)是一种需要频繁提交代码到共享仓库的软件实践。频繁提交代码能较早检测到错误,减少在查找错误来源时开发者需要调试的代码量。频繁的代码更新也更便于从软件开发团队的不同成员合并更改。这对开发者非常有益,他们可以将更多时间用于编写代码,而减少在调试错误或解决合并冲突上所花的时间。(GitHub中文文档)

一个非常基本的CI运行将包括构建和单元测试,但它不仅限于这两个。它还可能包括各种静态代码分析工具、linter等。这是你可以定义标准的地方。

为何选择端到端测试(E2E)?

构建和单元测试能给你提供的代码更改提供快速的反馈,花费的时间相对较短,但在出现问题也很快就会出现失败。但是端到端(E2E)测试在CI中占有特殊的地位。

E2E测试不仅应涵盖代码的正确性,还应涵盖部署流程、包的完整性等。我是在不小心发布了一个不包含任何代码的包的新版本时,才意识到这一点的。我发布那个不包含任何代码的包的时候,竟然构建通过了,单元测试和E2E测试都是绿色的(构建,单元测试和E2E测试是在一次从测试项目里连接构建输出目录时安装的)。哪里失败了?是在打包阶段。

为了实现这一点,我有如下建议:

在CI运行期间,启动一个本地包注册表。每种语言/生态系统都有一些选项,例如对于Java或Scala项目,有Nexus仓库,对于JavaScript,有Verdaccio(这是我在@angular-builders项目中使用的)

令开启一个项目来使用你的包(这个项目可以在同一个仓库中)。这个项目中的测试应该测试你的包的功能。

将此项目配置为使用本地包注册表。

构建包后,将其发布到本地包注册表(在CI系统中启动)。

在你的测试项目中安装最新版本的软件包(你在上一步中刚刚发布的)

运行测试。

以上这些步骤不仅能测试包完整性和可靠性,而且还会在持续部署方面为你节省一些工作。

持续集成系统是如何工作的?

有很多为开源项而设计的CI系统免费计划,其中包括Travis CI、CircleCI、AppVeyor、Github Actions等。这些,这些计划基本上的功能都差不多,它们在虚拟机上检测你的代码,运行您定义的脚本(通常运行构建和测试),然后向GitHub报告成功或失败。

所有这些系统都在GitHub上用于CI的应用程序,并且所有这些系统的集成流程都非常相似:

在平台上注册。

在您的GitHub帐户中安装相应的应用程序。

配置对选定仓库的访问权限。

创建定义构建矩阵、所需构建链和CI脚本的配置文件(如travis.yaml)。

推送给master

这将使您的CI在每个PR上运行并向GitHub报告状态——但这还不够。您真正想要的是在一个PR通过所有检查之前不让它被合并到主分支。这是通过定义分支保护规则来完成的。为了定义这些,您应该转到仓库“Setting”中的“Branches”部分,然后按“Add rule”按钮:

2E018

然后选中“Require status checks to pass before merging” 复选框。

9F4C

如你所见,相应的Github Apps复选框已经出现在这里,所以剩下的唯一事情就是启用它们。

确切的构建脚本实际上取决于您的生态系统、您的项目所用的语言、您使用的框架等等。因此,我们不会在这里介绍它——你需要自己查看CI系统的文档才能了解细节。不过,你先现在对CI是什么以及它如何自动执行PR应该有了很好的了解,所以让我们继续下一部分的内容。

什么是持续部署(CD)?

持续部署(CD)是一个软件发布过程,它使用自动化测试来验证对代码库的更改是否正确且稳定,以便在一个生产环境中快速、自主地部署。

在我们的例子中,生产环境是一个包在注册表中公开可用的环境。这是一个不可逆的状态,因为一旦你发布了一个包,你就无法撤回了,因为它已经公开了(也就是很可能已经被用户使用了)。

持续部署有多种策略,这实际上取决于项目及其复杂性。但在我看来,发布应该只从主分支发布,因为这样能简化工作流程。只从主分支发布的CD策略步骤如下:

每个PR代表一个错误修复或一个新功能。

代码在到达主分支之前已经经过测试(包括E2E)。

主分支是一个受保护的分支,只要你不合并失败的PR,主分支就会保持稳定。

每个PR合并到主分支的事件都会触发master CI运行,最终发布一个新版本。

这将确保所有发行版本的连续性,并且可以很容易地将某些PR与特定版本相关联。

我们需要做以下一些事情来自动化软件包的发布:

基于提交消息(commit message)的自动版本升级。

基于提交消息(commit message)的自动CHANGELOG更新。

自动将包发布到公共包仓库。

在Github上自动发布。

好消息是:所有这些都有语义发布(semantic-release)的支持了。坏消息是:你必须投入一些时间才能让它正常运作(但这是值得的)。

语义发布(semantic-release)是如何工作的?

语义发布使整个包发布的工作流程自动化,包括:确定下一个版本号、生成发行说明以及发布安装包。这消除了人的主观情绪和版本号之间的直接联系,严格遵循语义版本控制规范。

关于集成,有很好的文档,我们就不在这里赘述了。不过我要提几点:

在开始使用Semantic Release之前,确保你已经了解语义版本控制规范和常规的提交格式。

为了使语义发布良好运行,你应该强制执行某些提交消息的格式。为此,你可以将commitlint作为husky precommit hook运行。当有人创建本地提交时,它会强制执行常规提交,但它不能对直接从GitHub Web UI执行的提交做任何事情(当人们想要快速修复他们的PR时经常这么做)。因此我建议你使用commitlint Github Action做备份。

在您将语义发布设置为工作流程的一部分后,你就接近大功告成了,你不再需要将时间花在这些常规流程上,不过,你还可以额外进行一项优化。

如何使项目保持最新?

如果你的项目没有外部依赖——你就可以跳过这一部分了。但是,大多数项目通常依赖于其他包,而其他包往往会发生变化。

使您的项目与其依赖项保持同步很重要,但也很耗时。对我们来说幸运的是,有不少帮助我们解决这个问题的工具,例如Greenkeeper、Renovate和Dependabot。

它们的工作原理几乎相同,所以我将引用Dependabot的“它是如何工作的”部分作为说明:

Dependabot查找有没有任何更新

Dependabot会提取您的依赖文件并查找任何过时或不安全的需求。

Dependabot开启拉取请求

如果您的任何依赖项已过期,Dependabot会创建一个单独的拉取请求以更新每个依赖项。

交由你审核并合并

你检查测试否通过,阅读包含的变更日志和发行说明,然后信心满满地点击合并。

以上内容,正如您可能已经知道的那样,只有当您拥有一个正常运行的CI系统时才有意义。

总结一下,如果你有一个完全自动化的CI/CD周期,如果现在你的开源项目仓库中出现了一个新问题,您可以在几分钟内提供错误修复。事实上,你可以用你的手机GitHub应用,修复一两条错误的代码,然后提交代码。其余的都是自动完成的,你的用户会立即获得一个新版本。我已经多次这样,快速而轻松地为我的客户提修订过的新版本。

使用自动化并不是要腾出一些休闲时间,而是要将您的时间用于真正重要的事情并提高您的响应能力。

08 版本管理

在这个指南的最后,我想谈谈版本管理,这是一个对于任何拥有大量用户的开源项目都很相关的话题。你将通过这一部分的内容了解版本符号、破坏性变更、向后移植等。

什么是软件版本控制?

我们先来看看维基百科对软件版本控制的定义:

软件升级版本控制是为计算机软件的某个独特状态分配的相对应的唯一版本名称或唯一版本号的过程。我们通常使用两种不同的软件版本控制方案来追踪现代的计算机软件的版本变更:

可能在一天内多次递增的内部版本号,例如修订控制号

通常更改频率要低得多的发布版本,例如语义版本控制[1]或项目代号。

事实上,有多种方法可以给你的软件产品版本一个唯一的标识。最广为人知的方法是给它一个名字。

地球上的绝大多数人,即使是那些间接接触到技术的人,可能都听说过Android Ice Cream Sandwich和Marshmallow或Mac OS Leopard、its frozen cousin Snow Leopard和Big Sur。

译者李霄2012年拍摄于加州Mountain View谷歌总部

程序员可能听说过Eclipse及其天体Luna、Mars和Photon。这些都是软件产品的主要版本的名字。

给软件版本取名字固然对市场营销有好处,但名字有时候也会制造困惑。事实上,谷歌已经放弃使用candies这个安卓版本的名字,因为多年来从用户那里听到的反馈是,并不是全球社区中的每个人都能直观地理解这些名称。

理所当然,我们还没有进化到足以从动物物种推断版本号,即使雪豹比豹酷得多。天体和糖果是更容易掌握的概念,但前提是你按字母顺序命名它们(如Android和Eclipse这个例子)。但有一件事是肯定的——没有比数字能更好确定演替顺序的方式。因此,如果你将软件产品的第一个版本命名为“产品1”,将第二个版本命名为“产品2”,那么第二个版本是最新的版本这件事就非常直观了,不是吗?

但是,与不涉及API的独立软件产品不同,其他软件(如大多数开源软件产品使用的软件需要更好的版本控制,而不仅仅是数字序列。例如,如果我们仅使用简单的数字序列进行版本控制,那么用户怎么能区分bug修复和破坏现有API的更改呢?答案是:语义版本控制。

什么是语义版本控制?

语义版本(也称为SemVer)是一种广泛采用的版本方案,它使用以下格式的3个数字序列来表示一个版本:MAJOR.MINOR.PATCH。

规则很简单——以版本号MAJOR.MINOR.PATCH为例:

Major:进行不兼容的API更改时的版本

Minor:以向后兼容的方式添加功能时的版本

PATC:以向后兼容的方式进行错误修复时的版本

预发布和构建元数据的附加标签可以加在MAJOR.MINOR.PATCH格式的后缀扩展里。

语义版本控制提供了一种把软件产品的更改用清晰简洁的方式来传达给用户的方法。

但更重要的是,语义版本控制被各种允许用户依赖特定范围的版本而不是特定版本的包管理器和构建工具(如NPM和Maven)广泛采用。例如,指定版本范围^2.2.1而不是明确地规定版本2.2.1将让用户接受任何向后兼容的错误修复或在版本2.2.1之上发布的新功能。这就相当于,构建工具和包管理器听从于用户和包所有者之间的合同——一种由SemVer定义的合同。

这意味着这一切都是你的责任——你要定义什么是重大变化以及什么是微小变化。如果你不小心将破坏性更改作为错误修复(补丁版本)发布,它将会破坏依赖于版本范围的构建。破坏性构建是一件可怕的事情,因此我建议您使用带有预定义消息格式的语义发布以及强制规定提交格式的工具。你可以参考语义版本控制官网[1]获取更多信息。

我们已经谈到什么是破坏性变更了,现在我们来谈谈如何引入它们。

什么是破坏性变更(breaking changes)?

破坏性更改是对公共API的更改,这种更改以不兼容的方式删除、重命名或更改您与用户的合同。理想情况下,你应当在代码中保持向后兼容性,并且永远不引入任何破坏性更改。然而现实是残酷的。

软件和代码都在不断地迭代,用户的需求会发生变化,API也会发生变化。你作为一名开发人员在不断成长,你的软件作为一件产品也是同样在成长。因此,特别是作为一个没有报酬的开源开发人员,你不可能自己一个人一直维护着项目中的所有祖传代码。而且有时,你需要摆脱它们。

问题是怎么做?

与往常一样,你需要权衡利弊。权衡利弊之后,你会更好地了解此更改或其他更改如何影响用户。你不必不惜一切代价保持向后兼容,也不必在每个旧版本中实现所有新功能。但这当然是您应该考虑的事情。

如果用户的迁移成本相对较低,那么进行重大更改是可以的,并且在旧版本中不支持此功能也很合理。但是,如果迁移成本很高并且绝大多数用户负担不起这项工作,您可能应该首先考虑使此更改向后兼容并发布弃用警告。弃用警告通常与新API一起发布,而旧API仍受支持。这样用户就有时间迁移。一旦他们迁移了,在下一个主要版本中,就可以安全地删除弃用警告和旧API了。

每次引入破坏性更改,你都应该确保你提供一份迁移指南,其中包含迁移的分步说明。此外,出于礼貌,你最好让用户有时间为重大更改做准备,特别是在你不提供宽限期(新旧API都支持的期限)的情况下。提前一点的解释破坏性变更,其背后的原因以及预期时间表的提示信息对于用户来说是非常有帮助的。它可以是一个推文,一个博文或者是在一个微小变更的新版本里给出的弃用警告。一定要记住,重大变化是一种负面体验,而突然的重大变化是一种极其负面的体验。

自动迁移

我们可以把破坏性变更分为两种类型:非确定性的和确定性的。

非确定性重大变更是指你无法预测迁移工作的结果的变更,例如当你完全删除API的某个部分时。在这种情况下,是用户来自行决定是用其它第三方库替换它,自己实现它,还是完全忽略这个版本。

确定性更改是在给定代码X和用户输入I的情况下,允许你将其转换为代码Y的更改。例如,更改函数名称或导入语句。在这种情况下,你可以编写一个自动化程序来更改用户的代码库并将其调整为新的API。有了这种自动化,你就不必关心向后兼容性和详细的迁移指南。这是一种让用户以零努力升级其代码的方法,这在软件更新中至关重要。

然而,这里你也需要权衡一个固有的利弊。编写代码需要时间,编写迁移指南也需要时间。自然而然地,写将复杂代码流迁移到新API的代码比写用新函数名替换老函数名的代码花费更多时间。有时候,你就是负担不起这种付出。

如果你还是决定去做,有一些工具可以帮助你。最广为人知且与语言无关的是Facebook的Codemode:

Codemode是一个工具/库,可帮助你进行大规模代码库重构,这些重构可以部分自动化,但仍需要人工监督和偶尔干预。

还有更复杂的工具使用AST,可用于执行比find and replace更复杂的任务.例如,另一个名为JSCodeShift的Facebook库(特定于JS/TS)。又例如,代码迁移——一个工具(同样是JS/TS特定的),它允许你相对容易地编写引导迁移,并为用户提供很好的基于CLI的提示。

EAAD

一些大型开源软件项目甚至有自己的解决方案。比如Angular schematic,一种支持复杂逻辑的基于模板的代码生成器。

自动代码迁移可以作为单独的包(如my-cool-oss-migrate-v4-v5)发布,并在迁移指南中的一个步骤进行相关描述。或者,迁移可以是包含重大更改的主要版本的一部分,并在用户代码库中安装此版本时执行。以上都是你可以选择的方式。

向后推送(back-porting)

另一种常见的做法是将重要更改向后推送到以前的版本。例如,在主要版本(具有重大更改)发行之后发现了一个严重错误,而这个错误也适用于以前的版本。

在这种情况下,您不能期望用户因为一个错误而执行繁琐的迁移。另一方面,检查旧版本,在其上实施修复,并将其作为旧版本的小问题发布可能很麻烦。

解决方案是:每个主要版本都有一个受保护的分支。

每次你计划发布一个主要版本时,你都应该从主分支创建一个名为c.x.x的分支,其中c是当前的主要版本号。然后你把所有此类分支都设置为受保护的(就像主分支一样),这样你就不会意外破坏它们。然后,无论何时你必须从较新的主要版本向后移植功能或错误修复,你要么在此分支上重新实现它(如果可能的话),要么就从主分支中挑选提交。

此外,值得一提的策略是给下一个新的主要版本也预留一个单独的分支(这个分支的作用和上面提到的为老版本预留分支的作用是相反的)。

这种做法通常与在每个新的主要版本中都有很多变化的大型项目(如Webpack或Babel)相关。为即将发行的主要版本创建一个单独的分支,这种做法不仅允许开发者对其进行处理并将其发布以进行测试,同时仍然在主分支中保留最相关的版本(并对其进行处理)。当新的主要版本发布了以后,我们就应把这个单独的分支变成主分支,并为下一个主要版本创建一个新分支。

结语

我希望你喜欢这个指南,并且读完以后对拥有开源项目的意义有了比较好的理解。

最后,我想与你分享一个点,你在运营一个开源项目时应该始终牢记这一点。这就是:兼听则明(“听”是指倾听软件用户的反馈)。

这听起来可能有悖直觉,但事实就是如此——你不是唯一定义路线图的人,用户也可以定义它。事实上,用户定义了大部分。你运营一个开源项目的目的是帮助别人,而不是你自己。你要提供多个反馈渠道。有些用户只有一个小问题,您可以在一秒钟内提供答案。有一些潜在的贡献者想要讨论路线图,但不想公开进行,这种情况你要给他们留一种联系方式。比如Slack或Discord的链接,分享你的Twitter帐户,等等。渠道越多越好。(中文世界就是微信,B站,微博啦。)

参考资料

【1】Semantic Versioning 2.0.0 | Semantic Versioning (semver.org)

文章来源:深度势能

转载本网文章请注明出处

版权所有©北京大学大数据分析与应用技术国家工程实验室 京ICP备05065075号-1 京公网安备 110402430047 号