这篇文章写于4月18日的X-lab周一论文分享会后,点击此处可查看Slides。

背景

这是我读研来第一次分享论文,作为一名早已习惯对任何新造访的GitHub仓库检索它们的Hypercrx图表的Hypercrx用户及开发者,我欣然于任何一种具有普适性的开源软件项目指标计算方法。在OpenResearch中浏览一番后,我选择了《基于贡献分配的开源软件核心开发者评估》,因为这个标题似乎在告诉我,它有一种“算”出任何开源软件项目核心开发者的本事。

此论文发表于2018年第29卷第8期的软件学报上,作者为浙江工业大学的吴哲夫、朱天潼、宣琦和国防科技大学的余跃。关键词有:开源软件核心开发者外围开发者贡献分配支持向量机

寻找核心开发者的3个类型阶段

如何找出一个开源软件项目的核心开发者?研究者们对该问题的研究历程大致可分为3个类型阶段:统计计数研究阶段、 网络分析研究阶段以及定量分析研究阶段。

在早期的研究中,人们通过统计诸如编译次数、代码行数和邮件数目,并根据20/80法则来简单、快速地找出核心开发者。随着网络科学的发展,许多研究通过建立开发者网络,观察作为节点的开发者在网络中的特征表现来区分不同类型的开发者。在近几年的软件工程领域,一种较为流行的做法是以个人访谈、问卷调查、经验汇总等定量分析的形式来描述两种开发者的行为特性,以此作为区分两种开发者的基准。

此文与上述3种类型的方法又有所不同,将重点关注开发者对项目文件本身的操作记录,设计文件贡献分配算法分析开发者对各个文件的贡献来建立新的“核心-外围开发者”分布。

实验框架

下图为此文的实验框架,主要包括 4 个部分:首先是开发者开发记录、项目数据信息收集;其次是核心文件组的选取;随后,使用贡献分配算法计算开发者在核心文件组中的贡献大小;依据 20/80 法则,选取将贡献度较大的开发者选取为核心开发者,并进一步通过与现实中的地位名单进行比较来实证。最后,结合其他各类指标,建立支持向量机进行综合分析。实验环境选用 Linux 下的 Python2.7 和 R 语言。 image.png

数据集

对于数据集,我能介绍的不多,因为作者在此处用墨甚少,并且在软件学报的官网上也没有找到相关数据集和代码的下载地址。唯一能知道的,是被分析的9个项目数据全部来自apache.org,并且在初步处理后,得到了两方面的信息。一是开发者的开发记录,这包括各开发者在项目中的总编译次数、具体操作文件、 总的代码行数以及与其他开发者之间邮件通信记录。二是项目源文件的调用记录,这涵盖了从项目开始至数据采集结束为止项目源文件相互调用以及调用次数的记录。

核心文件组

许多编程语言都有模块引用机制,最常见的保留关键字就是“import”。因此,一个软件项目中存在文件间的引用关系。此文将核心文件组定义为占所有引用次数和的前90%所覆盖的文件。举个例子,若一个项目有100个文件,总共的引用次数为200,其中被引用次数最多的前8个文件的引用次数为180,则这8个文件就构成了核心文件组。

为什么被引用次数多的文件会被认为是核心文件呢?作者给出了他的观察:在目前的协同软件开发中,尤其是当采用面向对象的语言时,高层次的开发者通常在立项初期将核心技术部分以接口、 封装类的形式写入命名空间进行包装,提供给一般开发者让其来频繁调用并将之具体实现,以应对具体的业务需求。说得简单点,作者观察到被引用次数多的文件很可能是诸如提供“抽象类”的文件,而这样的文件一般都是高层次开发者写的,所以把这样的文件作为核心文件,就能顺藤摸瓜找到核心开发者。

当然,事情没那么简单,核心文件组的作者并不直接被认为是核心开发者。此文在核心文件组的基础上,设计了贡献度分配算法,进而得到开发者贡献分配向量,由此确定核心开发者。详情,见下。

贡献度分配算法

\(input\)

  • 核心文件组
  • 各文件引用情况
  • 各文件作者情况

\(output\)

  • 贡献度分配向量\(c\)

这是此文的核心内容。算法的输入部分在“数据集”和“核心文件组”这两节中已经得到。算法的输出很简单易懂,贡献度分配向量\(c\)是一个\(m\)维列向量,其中\(m\)表示所有文件作者的数目,向量\(c\)的每个分量\(c_i\)表示对应的作者在项目中的贡献度。\(c_i\)越大,说明该开发者的地位越核心。下图为贡献度分配算法的应用案例,我将结合此图对该算法进行介绍。 image.png 可用两行字概述该算法的流程:遍历核心文件组,对每个核心文件建立它的3层关联网络并求出该核心文件对应的贡献分配向量。得到所有核心文件的贡献分配向量后,取平均即得算法的输出——贡献度分配向量\(c\)。

对于遍历中的当前核心文件,首先是根据文件引用关系建立它的3层关联网络。第1层是该核心文件本身,即图中\(P_0\);第2层为所有调用\(P_0\)的文件集合\(D=\{d_1,d_2,d_3,d_4,d_5\}\);第3层为所有\(D\)中文件调用的非核心文件集合。为了方便描述,我将第1、3层中的文件合称为集合\(P=\{P_0,P_1,P_2,P_3,P_4\}\)。

接下来,根据3层关联网络求出文件关联向量\(s\),\(s\)的每个分量\(s_i\)为\(D\)内文件引用\(P_i\)的次数。例如,\(P_0\)被\(D\)中每个文件都引用了,故\(s_0=5\);\(P_4\)只被\(d_3\)和\(d_5\)引用,故\(s_4=2\);以此类推。之后,再结合各文件作者情况,求出分配矩阵\(T\)。\(T\)中的每一行代表当前核心文件的其中一位作者,图中\(P_0\)有两位作者\(a_1\)和\(a_2\),分别用圆形和三角形表示,因此对应的分配矩阵\(T\)有2行;\(T\)中的列代表开发者在该列所代表的文件作者中的人头占比,其列数与关联向量\(s\)的维数相等,都为\(\|P\|\)。例如,矩阵元素\(T_{11}=0.50\)表示开发者\(a_1\)占文件\(P_0\)的所有作者数量的\(\frac{1}{2}\);矩阵元素\(T_{25}=0.00\)表示开发者\(a_2\)不是文件\(P_4\)的一个作者。最后,当前核心文件对应的贡献分配向量由\(c_{current}=Ts\)求得。

在遍历完核心文件组后,我们已经求得所有核心文件对应的贡献分配向量\(c_{current}\),现在只需要将它们求个平均值就可以得到算法的输出——贡献度分配向量\(c\)。但这里还有个小问题,即每个\(c_{current}\)的长度并不一定相等,因为\(c_{current}\)的长度为对应核心文件的作者数。解决的方法是使用一个长度为所有作者数目的向量来表示文件的作者情况,分量为1代表对应的开发者是文件的作者,分量为0则相反。如此一来,所有的\(c_{current}\)的长度都为\(m\),这样就能求它们的平均值了。

核心开发者

根据20/80法则,贡献度分配向量\(c\)中贡献度总和的前80%覆盖的开发者为核心开发者。

算法修正

上述算法仅仅按人头比来计算贡献度,作者在实践中观察到这样的方式容易造成偏差。为此,引入全局影响因子\(\lambda\)进行修正:

\[c^{'}_i=c_i\lambda_i\] \[\lambda_i=\frac{I_i}{N_i}\]

其中,\(I_i\)是开发者\(i\)编辑过的所有文件在整个项目中被引用的次数,\(N_i\)是开发者\(i\)在整个项目中编辑过的文件数。

实验结果

image.png 从实验结果可以看出,与传统分类方案相比,基于文件贡献的分类法在总体上有着更高的真实名单相似度。但在部分项目中,该算法的真实名单相似度与传统方法相比较低。据作者了解,这是因为开源软件项目组中人事地位的变动所造成的,属于不可控的误差。

支持向量机分类预测

简单来说,在贡献度分配向量这一度量的基础上,再综合若干个其他指标,通过模型验证发现可以更好地识别核心开发者。

论文“盲点”

个人认为此文对数据集的制作过程介绍得过少。从数据获取的角度看,在逛了一天的ASF后,我并没有找全作者提到的数据的可能来源。从数据处理的角度看,我很希望知道遇到文件存在重命名或更改路径的事件该如何处理,因为这在以Git为CVS的项目中会影响到文件作者的判定。

另外,此文对文件引用网络的构造过程也涉及不多。虽然作者没有明确指出,但我发现实验中分析的9个项目全为Java项目。但事实上,不同语言的引用机制不尽相同,若能稍加分析便更好了。此文定义的核心文件都是在文件引用网络中的文件,而事实上项目中还有一些不在引用网络中的文件也很重要,例如与项目构建相关的文件、配置文件等。对这些文件的忽略也许会导致一些核心开发者的遗漏。

收获与启发

由于作者对数据集的介绍不详,我花了许多时间逛了apache.org的角角落落,发现了ASF项目的主战场大多不在GitHub,在GitHub上的同名项目仅仅是一个镜像,Issues和PR等功能都被关闭——ASF使用自部署的Jira或Bugzilla来追踪Issues。这也提醒我们,在基于GitHub全域日志数据对项目进行分析时,要提防这些有多个平台的项目,不要“以偏概全”了。ASF使用Snoot来展示与项目相关的数据,Snoot与Hypercrx有类似之处,它的订阅收费模式也许在未来某一天能够参考。

基于GitHub日志的分析方法是宏观的,基于Git历史记录的方法是微观的。前者可以构造开发者-仓库网络,后者可以构造开发者-文件网络,若将两者结合起来,也许能更好地度量开源软件项目。希望在这个开放协作的世界里,开发者无论来自热火朝天的大社区,或是安静幽僻的小角落,都能看到属于他们自己的credit。

References

讨论环节

毕枫林:“关于GitHub日志与Git的提交记录的联合分析,我之前对这两个数据是这样思考的。GitHub的行为我们就理解为日志,理解为一个行为数据,而commit就理解为项目数据。研究行为数据的话是和人挂钩的,然后研究Git记录的话是和项目挂钩的。我认为把两者分开研究更清晰点,研究人的话用社会学;研究项目用软件工程学的知识。这就是我的思考。

我:“赞同。用Git分析的话还有一个好处就是它可以对单个仓库进行分析。像我们基于日志的方法每次都可能要从整个全域网络上分析;但是基于Git的话,你可以以仓库为单位进行。”

毕博:“嗯。不过单个项目也可以从日志里面摘出来的。”

我:“嗯。哦我还想说的是,基于Git的话你还得看它是个什么项目。你可能要对不同语言不同类型的项目单独研究过,这更复杂。如果你想知道这些文件到底有意义的话,需要对每种语言和每种项目都有一套单独的分析机制。”

Frank:“我想问一下,刚才你在有几个地方说是文件引用,有几个地方是调用,是一个含义吗?”

我:“是一个含义。”

Frank:“所以它本身论文里面也是两个词混用是吗?”

我:“这。。我不确定哈哈我不确定。”

Frank:“嗯。引用和调用是两个意思嘛。”

毕博:“我刚才大致猜也是import那个文件的意思。 ”

我:“对,它其实没有细致到函数调用那个级别。”

毕博:“对。那样的话,写工具包的最核心了。但是,还有没有一个方法考虑到这个问题。比如说,写一个工具包,写一个时间转化函数。这篇文章的方法认为这就是核心代码吗?”

我:“对,它这里就是这么算的。文章为什么要使用这个占引用次数的前90%为核心文件呢?它有提到,但它没说这就是原因奥。它举例,说是在面向对象的语言中,那些比较厉害的开发者在一开始会定义抽象类,这个抽象类就是单独一个文件,然后后来的那些普通开发者就会去实现这些抽象类里的函数,那么他们肯定会先import这个抽象类文件,所以作者认为这些被引用的抽象类的文件是比较重要的,因为它相当于有一种设计感在里面的。核心开发者或高阶开发者他们先设计好,然后让普通开发者实现,作者认为这些具有设计性的文件是比较重要的。”

毕博:“那我理解了。他是相当于把abstract类的文件当做核心了。”

我:“对。是核心文件的一种形式。”

毕博:“他没从语言特性。。(被我打断了n次,sorry)”

我:“所以这种方法,可以说,更适用于面向对象的项目。”

毕博:“嗯是的。”

Frank:“对,我也分享几个关于Git数据相关的思考吧。其实,这个问题从一开始我们实验室在做的时候大家就提过。除了一部分Git仓库本身的内容数据以外,行为数据其实本身也属于git log的一部分。所以其实也是能够基于它去构建一些统计或一些网络中的数据。就像周添一,最早就做过基于Git去构建整个文件之间的协作网络关系的一个图。这种也属于行为层面的。数据层面的当然也是有的,但是,其实Git有个好处就是一个仓库一个仓库做。但它不好的也是这个地方,你在全域上想做的话是非常困难的事情。之前也想采集全域的代码数据或者说是git log数据,但是git log没办法单独抽取的,你需要把整个仓库clone下来。当时应该是,预估过这个存储量是比较大的。而且你还需要做类似于world of code那种给他做结构化、索引这类的,你不能直接使用Git原始的blob去做一些分析的话,其实是非常慢的,尤其是数据量大的时候。所以这块在工程上是有一定难度的。大概是这样一个背景。我就简单补充一下。”

我:“谢谢Frank!谢谢毕博!”

王老师:“我这边再问你一个。其实里面有一个非常关键的点,就是那个核心开发者的认定。你是说apache官方有个列表,来作为核心开发者真实的对比情况是吧?”

我:“是的。作者说上面有个核心开发者的名单的。”

王老师:“我觉得不管是他的这个文章,还是后续我们在这个基础上做我们自己的,其实有个很重要的事,要搞清楚究竟他这个名单是怎么定的。这个你有深度了解过吗?”

我:“这个没有深度了解。”

王老师:“对。因为如果目标不清楚的话,即便这里他有这样的方法,那是不是合适其实都还是可以让人质疑的。这个开发者名单究竟是怎么定的其实决定了你要采取什么样的方法或是采取什么样的特征,或是用图用什么,对。如果他这个名单压根不是通过什么样的方式,不考虑什么因素的话,那你这里其实是会有一种南辕北辙的方面对吧。所以说呢,我估计那篇文章里肯定没写,因为你没写嘛。所以我觉得,最好还是好好的看一下所谓的核心开发者List究竟是怎么确定的。他们是真的也会看贡献度啊还是什么别的指标吗?因为他们看什么或是以什么样的方式列上去就意味着你要考虑什么因素,用数据分析的方式去建模它。分类方法可以提几十种都没问题的,但是如果你和他本身的核心开发者不是很match的话结果肯定会低的。那我肯定去找一个按他定的标准,比如说找一个特征,哪怕是简单的打分机制,也肯定会比其他方式要好。第二,其实很明显,这里的分数普遍都不是特别高。高的话也就百分之八十九,低的话也很低,百分之三十都有。我想肯定有原因的,规模肯定也有限。”

我:“对,这作者提到了。”

王老师:“我是对他核心开发者的认定这件事情还是很感兴趣的。因为这对我们来说还蛮有意义的,也是一种ground truth嘛。”

我:“然后说那个apache名单是怎么来的是吧?”

王老师:“如果知道这个名单怎么来的话,包括他怎么列上去,其实肯定可以构造一些方法去判定,或者是分类或怎么样。甚至可以把这个模型迁移到其他项目去,甚至不是apache的项目。把这个模式迁移过去也能区分核心开发者和非核心开发者。”

Frank:“我猜他这里用的就是committer的名单。以apache的社区治理模型来讲的话就是PMC+committer。再往下都是contributor没什么区分了。所以应该用的就是committer的名单,这是公开的。然后这确实是一个很好的ground truth的名单,因为这些人都是社区选出来的,他并没有一个特定的基于什么指标来定的。就是,大家觉得这个人不错然后去提名,然后投票通过这个人就是committer了。”

王老师:“好。那这就是我们平常在报道里听到的例如中国的谁谁谁又成为了哪个apache项目的committer了。就是说apache下面有个标准的选举的流程,是这个意思吧。”

Frank:“对,它是基于流程而不是数据的。由某一些人去提名,然后大家投票,只要通过就可以,就是这样一种方式。”

王老师:“OKOK,对。我觉得这个东西还是蛮关键的。如果有机会,去深挖一下他们是怎么去投票,怎么去选出来的整个过程,以及投票的时候会说些什么话,比如说会去考虑一些什么特性。我觉得还是比较好的容易总结出一些道理,而且这些东西在某种程度上还是一种经验性的东西。对那这种经验性的东西,可以看看有没有一种好的方式用数据去进行拟合。这样的话就可以去建模迁移到其他项目去。总的来说,这个label我觉得是比较有价值的,它肯定花费了比较大的共识机制去达到这种选举committer的形式。”

Frank:“对。但是apache的committer也有一些问题。第一个是,对于大厂贡献出来的项目,会有一些initial committer。就是类似于创始人这样的身份,这些人中很多一般都是大厂自己的工作人员。然后这些人员呢,他们可能是早期的项目成员,但是开源以后就不参与了。然后以及说,包括外部晋升进来的,有一些也是为了拿到committer这个身份,拿到后就不参与了。又或者说他自己因为工作变化,逐渐退出。但是由于Apache没有committer退休机制,所以导致很多committer遗留下来,就是三五年都不活跃了,完全脱离项目,但是依然是committer的身份。所以我猜测,准确率比较低可能也会和这个有点关系。就是,有些人早就流失了但是他依然在这个名单里。其实这也一直是apache比较被诟病的一个点。在云原生社区里,有些项目是有退休机制的,他会有一个叫名誉committer的身份,当你不活跃后, 你会从committer退为名誉committer的身份,相当于给你保留一个名誉,但是你没有相关权限了。对,反正这是挺有意思的一个点,但如果直接拿数据来用的话,我猜测会有一点问题。”

王老师:“ok这个没问题啊。我觉得这个完全可以根据你刚才说的一些知识进行一些数据清洗的工作。把一些不活跃的,可以认为他就是退休了,我就把这部分去掉,然后省下的那部分就很好的可以用数据来建模的是吧。”

Frank:“对对是的。”

王老师:“我觉得挺好的。这种工作是挺有价值的。你既懂数据又是这方面的经验专家,你通过经验把一些数据进行清洗,这是蛮data science的工作,就是需要这种domain knowledge,你没有这种领域知识的话,你看这种比较粗糙的,就是参差不齐,如果加上领域知识,用一些数据清洗的东西,就说用一些规则吧,把这些东西除掉后,我觉得这个模型准确率就会比较漂亮了。”

我:“好的!谢谢王老师。”

娄泽华:“这是一个二分网络吧。我觉得二分网络应该有这种推荐。他没有提到这方面的东西,我觉得可以往这方面再找找。”

我:“二分网络是。。。?是二部网络吗?”

娄博:“是二部图网络。”

我:“这里好像没有吧。这里要说能叫网络的就只有一个引用网络吧。引用网络都是文件,好像没有异构。”

娄博:“对。他这里就考虑了人对文件的一个贡献对吧。就是文件和文件之间是没有边的,人和人之间是有边的吗。。?”

我:“是没有边的,人和人之间是没有的。但是文件和文件之间是有边的。每个文件包括了它的作者信息。但人和人之间是没有什么网络的。”

娄博:“哦。我感觉应该是有现成的算法。他那个按照90%把一些人除掉可能不是很合理。就比如说二分网络,它就有社团划分这一类的。比如说,一个文件它形成了比较小的社团,向它贡献的那个人贡献度可能并不高,但是把他筛掉,对这个小社团的影响还是很大的。我是这么感觉的,就是如果把他筛掉,解释性就不是很强了。其他没了,就是考虑到二分网络可能会有类似的算法吧。”

我:“好的好的,谢谢娄博。”

王老师:“永雷老师有什么想聊的吗?永雷老师好久没聊了。(空白)永雷老师好像不方便说话。没事,后面还可以聊。邱博士有什么想聊的吗?”

邱娟:“我其实还好。我刚刚非常赞成王老师聊的那个点。就是很多我们本来想当然的东西,就是那些经验性的东西,可能往往背后更多就是个性化的经验,反而是值得去做一些模型的。王老师刚才讲的,我可以把这些经验,不管通过什么样的方式,定性的方式,或定量的方式就像我们之前做survey,或者说,用一些数据采集的方式然后通过建模的方式去评估它。然后这里面会有一些我们已经知道的规则嘛,用我们知道的know-how去做一些清洗的规则,这确实是一个可以考虑的点。其他的我觉得,整个论文里的东西都讲的挺技术的。当然就是说如果去创新的话,可以通过一些其他的建模方式对吧。这篇论文我没有非常细致地去看,但是刚才我看那位同学的分享还是觉得蛮好的。这种能够在软件学报发的论文应该还是很不错的。其他没有什么要补充的,好的,老师。”

王老师:“好的!谢谢邱博。”

我:“谢谢邱博!”

王老师:“好的最后还有个问题哈。那个ppt的制作成本高吗?”

我:“高!高!哈哈哈。太高了,这不适合做这种论文分享啊,要画箭头啊这种的,不大合适。仅仅用于那种展示的不大要用脑的还可以。像你要做这种分好几栏的,有点艰难。这是用reveal.js做的,它的好处是你甚至可以在里面放网页。”

王老师:“我是挺喜欢这种模式的。我是在想有没有傻瓜方式也能用,我会考虑迁移到这方面来的,哈哈。”

我:“我还在探索中。这个reveal.js提供了一个官方的在线制作工具slides.com,它可以导出html,单页的html,但是就比较大,有2M。我这个slides就是一个文件,我可以给大家看看源码。(共享画面转移到编辑器界面)是这个样子的,这一次ppt一共有四百行,我选择了reveal.js标准的html形式,它还有markdown的,但是markdown没有html这么灵活。”

邱娟:“我感觉看着很geek,很好。”

王老师:“其实我感觉按道理来说他应该有这样的工具才对啊,让我用低代码的方式来玩h5的slides。”

我:“对,有,它有这么个平台。但是它部署起来有点冗余,它把那些js全都写到一个文件,你也不好拆,它是一个大文件,一个2M的文件,如果用这里的导出功能生成的话。”

王伟:“okok。因为前面Frank也用过的,因为我们有个需求,就是做开源的课件。特别是如果有机会用markdown的形式去做slides的话,其实可以做开源课件的,以开源项目的方式去迭代。”

我:“对是的。我现在就用GitHub在管理这个,它属于我个人网站的一部分嘛。”

王老师:“对,希望后面有一些成熟的工具能够降低制作成本,体验上去了,我觉得是非常期待的。”

Frank:“唐烨男用的这个是哪一个框架?”

我:“是reveal.js。”

Frank:“奥。和我之前用的不是同一个。”

我:“我看过你的!我上次在issue上面看到了。”

Frank:“对我用的是slidev去做的,然后是基于vue的。但这种就是,很难去做成ppt那种。它还是需要在开发的灵活性和可协作性、易用性去做平衡。一般来说,像低代码拖拽的方式形成的代码基本都是不可读的,不可读意味着协作性变低。可读就意味着要自己去写代码。这几种框架开发成本都很高。”

王老师:“诶我是这样想的,markdown不是有很好的格式化功能吗?只要把markdown的版式变成ppt的版式,然后我一页markdown一页markdown的写,是不是可以这样做了呢?”

Frank:“纯文本是没有问题的。但是图会有问题,布局会有问题。其实,这个有点类似我之前用svg做图的情况。它分为3个部分,一个是内容,一个是布局,一个是样式。然后目前的框架来看,至少我用的slidev这3个东西是没有抽开的,东西都混在一起。相当于是,你markdown本身包含了一定的样式,然后你要用html做布局,同时你要把文字填充进去,这样的话其实很凌乱的,都写在一个文件里面。我不知道reveal.js是什么情况?”

我:“非常类似。”

王老师:“哈哈好吧。”

张翔宇:“我之前也是用过类似的一个框架叫MDX。然后我用过,只用过它的基本功能,就是感觉还用起来挺顺的。只要在一个文件里就可以。”

王老师:“哦回头发群里啊~”

此处开始到会议结束,是一段约20分钟的关于如何区分企业开源项目的内外部开发者的精彩讨论!