大公司、小公司、自由开源软件社区的代码管理

大公司里我经历过摩托罗拉,里面的代码管理非常奇葩,用的一个工具叫clearcase,非常地神奇,非常适合脑子锈掉的人使用,我在里面呆了三年,一直没有整明白过这个东西是怎么用的,当时我还曾很傻地想整明白,后来发现智商不够,就放弃了。中间在瞎折腾的过程中,还因为创建了一个自己的vob,被CM发现打电话过来数落一顿,说我不懂规距。我去,这样的东西,会极大的扼杀你的创造力,所以我一点也不后悔,自己压根儿不懂clearcase。恰相反,如果我很懂的话现在才需要后悔——这得浪费了多少时间在这个没用的玩意儿上啊!所以我并且很庆幸这辈子应该可以不用跟这样的东西打交道了。

小公司里也有一些奇葩的做法,那就是压根儿没有代码管理。我有个朋友,毕业以后就去了一个这样的公司,后来公司黄了,他就去了新浪,两个月之后辞职不干了,说受不了代码都要提交到服务器,要让人review,于是出来自己成立了一个公司,仗着之前的小公司拿在手里的代码,继续做同样的项目,从大公司里转包出来做。估计管理代码版本的方法就是按开发日期复制备份一下整个源代码文件夹。

其实到这个年代了,很多大公司、小公司,都已经开始学习自由开源软件社区的代码管理方式——不学也不行啊,Linux Kernel这个牛逼得不要不要的项目就是一个自由软件,大公司想要参和进来可以,但得按社区的规距来😁。

1 开源社区的代码管理

所以这里重点讲一下我理解的社区代码管理、工具、工作方式等,学识浅薄,错误难免,欢迎指正…

社区里的代码,一般都会有两个概念,比较重要,一个概念叫上游“upstream”,另一个概念叫发行版“distribution”。有可能只是我杜撰出来的😁。

什么是上游呢?比如我们大家都知道的Kernel,以Linus为核心的社区,就是上游。

什么是发行版呢?比如大家用的Debian、Redhat、Ubuntu等,就是发行版。

针对大家常用的软件,发行版会有相关的维护者,有些是员工,有些是志愿者。他们的工作是从上游拿到一个软件的初始版本,根据自己的发行版的需求加入一些合适的改动,然后出一个版本给它的用户使用。当然,也可能存在完全不用加任何改动的情况,那种情况下,这个软件加入发行版相关的工作就比较少,维护者会比较轻松。

1.1 发行版的维护工作

重点来了,这些维护者,他们是怎么进行“维护”的呢?

一般来说,他们会维护一个“patch list”,也就是一个patch列表,对这个patch列表,他们会进行以下操作:

  • 增加一个patch

    发现一个新的bug,实现一个新的与自己的发行版相关的feature,这种情况下,需要新增加一个patch,patch list就会变长。

  • 删除一个patch

    一般发现bug,如果不是只与自己的发行版相关的,那也就是说是与上游相关的,这种情况下,维护者会把patch再提给上游社区。上游社区如果接受这个patch的话,那么下一次维护者拿到上游社区的新版本,就需要把相应的patch从自己的patch list里删掉了。

  • 合并patch

    维护者发现了一个Bug,加了一个patch修正。但很遗憾,过了一阵子之后,发现同一个Bug并没有被之前的patch完全修正,还留着点尾巴,需要再改一点代码。那这种情况下,维护者可以选择把新改的代码做成一个独立的patch,也可以将其与上次的patch合并,因为毕竟从逻辑上来讲,合并是更合理的,它们是在修复同一个bug。并且这样合并之后也更容易维护。

  • 拆分patch

    从理论上来讲,是可以存在这样的操作的。也就是当发现一个patch同时修复两个bug的情况,那为了让逻辑更合理,patch更容易理解,维护者可以选择把一个patch分成两个。

如前所说,patch list里的patch越短,维护者越轻松,如果一个patch也没有,那就直接拿上游的代码用就可以,是最省事儿的。但patch list也不能盲目的让它变短,逻辑上没有关系的两个patch,无论如何不可以把它们合并了来减少patch list里patch数量——要那样的话把所有patch都合成一个岂不是最省事儿?

要保持patch独立性的一大原因,也是为了逻辑独立,便于理解,从而维护起来更方便。因为有时候上游的代码改动,会导致你的patch list里某些patch在新版上游代码里打不上去,这种情况下维护者就要仔细研究上游代码到底改啥了,我这个打不上去的patch又是在改啥,然后重新评估该怎么处理这个patch。处理的方法不外乎上面几种,以及下面这种:

  • 修改、更新patch——上游代码更新时可能导致一个patch需要改一下才能打上去

    有时候一个patch在新的代码版本上还能打上去,但已经开始给出警告了,这种情况下只要对patch更新一下就好了——有专门的工具做这个操作

1.1.1 维护工具

一般来说,目前大多数的开源社区都已经开始使用git工具来管理自己的代码了。那么,作为维护者的话,用什么工具来维护patch list比较好呢?

哈哈,我知道的一个工具,也是Linux Kernel社区里另一个大神,Linus的左膀右臂之一,Andrew Morton最先发起的,叫quilt(可以参考其man手册)。以前的Kernel社区,不知道现在还是不是这样,提patch主要的方法是通过给邮件列表里发邮件,这样社区相关模块的负责人们最后手里会累积很多patch,于是需要写一个专门的工具来打到git里头,所以Andrew Morton开始写了这么一个工具。那么这个工具后来也被Debian社区借来管理自己对各上游社区的patch lists。

那么最近debian社区是不是还在用quilt管理patch lists呢?不知道,但已经有人提出来 git is the better quilt ,所以可能这些patch lists也已经开始用git来管理了。有兴趣的同学不访去研究一下。

2 社区众生相

这是发行版的维护者的工作,一种比较特别的工作。那么既然已经说到这儿了,我们不访看看开源社区里还有哪些角色。

  1. Maintainer/benevolent dictator

    也就是项目的负责人,有些项目里甚至是精神领袖,说一不二的人物。比如Linux Kernel社区里的Linus Torvalds、Python社区里的Guido等等人物。他们能获得这样的地位,有些是因为他们是项目的创始人,有些是因为在项目里做出过突出的贡献,比如Emacs的维护者,现在已经不是其发起人Richard Stallman了,而是社区里另外推选出来的人物。有一点非常关键的是,项目的领导人必须是从技术水平,为人,能力等方面来说,能获得大家信任的才能担任,否则的话,在开源社区,我们其他开发者完全可以另起炉灶,把你的项目给fork了,然后号召其他开发者加入到这个新的社区里来。这样就造成社区的分裂了,你可能会认为这是一种很不好的事情,但事实上,这种可能性的存在才是社区健康发展的唯一强有力保障。如果所有代码都在一个邪恶的公司手里掌握着,谁也没法反抗它,那才是真正的可怕的事情。

  2. Core contributor

    有些社区成员,因为长年对社区的贡献,取得了大家的信任,从一般的contributor成为了core contributor,标志一般是比如获得了代码仓库的提交权限等。

  3. Contributor

    并不是所有人都有兴趣长期在一个社区里贡献,有些可能临时起意,发现了一个Bug,随手修复一下,给maintainer、社区邮件列表、社区bug系统等提了一个Bug、提了一个patch,等等等等,打一枪就换一个地方走了,这种都是社区不可获缺的力量的一部分,都是社区的贡献者。

  4. User

    社区存在的意义,就是为user服务的。一些user,随着对社区、代码、产品越来越熟悉,也会慢慢成长为contributor,比如可以从写文档、报Bug开始。

  5. Distributor

    Distributor也就是上面提到的类似ubuntu、debian等发行版。它们与社区的关系更是错综复杂。因为作为一个发行者,它不会只发行比方一个Linux Kernel,只与Linux Kernel这一个社区打交道——相反,它需要与所有它希望包含在自己的发行版里的社区打交道——有些自由开源软件甚至都没有形成社区。

    发行版也可能自己发起一些开源项目,比如Ubuntu之前会做一个自己的桌面,与Gnome、Kde等社区抢用户。这种情况下,社区与发行版的关系、定义更复杂了。

    某些发行版的员工本身可能是某个社区的负责人、Core contributor、contributor、用户,这都是有可能的,对吧…

3 如何借鉴

这个时代要开公司,已经无法避开社区这个话题了。这里结合我自己的工作经历,谈一些经验感受之类的吧。

  1. 别一不小心fork一个社区,一般情况下你搞不定的

    以安卓开发为例,目前安卓“社区”掌握在谷歌手里,其余的各家rom,从最早的播思通讯的oms,到开源的CM社区,到小米的MiUI,阿里云的云OS,我司的Smartisan OS,无一例外,还是必须紧跟谷歌的步伐。

    深度定制最怕的就是定制的越来越深,最后谷歌一升级,因为你跟上游之间的差异太大,导致你想跟着升级时的成本也越来越大。不处理好这个问题,底下的工程师平时开发的吭哧吭哧挺来劲儿,一旦要升级的话,就会特别痛苦。

    播思通讯的oms基本上就是属于最早开始定制的,定制应非常的深,但很可惜,现在已经日益势微了。跟他们定制的太深了有关系吗?我不知道…

    这种深度定制,实质上跟fork一个社区没有太大的区别了。因为自己深度修改的代码,一般来讲是肯定不会提给上游社区的,即使提的话上游也不见得会接受。Emacs编辑器开发的过程中发生过一次重大的fork,就是XEmacs这个软件。当时Richard Stallman对XEmacs的License有疑虑,不停地与XEmacs的开发者进行骂战,不接受对方提的patch,导致XEmacs的代码与Emacs的代码之间差异越来越大,最后Richard Stallman也承认,自己已经看不懂XEmacs的patch了…

    Fork一个社区本身并没有什么问题,前面也说了,是社区得以健康发展的进化机制保证。痛苦一般是出现在Fork完了你又想合回去的时候,比如谷歌升级你想跟着升的时候。

    那么如何预防这种定制太深,最后不可收拾呢?我在这里大胆的胡说一下,但愿能抛砖引玉。

    • 不要定制太深。这是最根本的解决方案。能不改底层的东西,尽量不要改;改了的话,如果有可能,尽量考虑一下能否回馈给社区。

      要做到这一点,要求做代码有比较深的了解,抑制住自己上去就开始按自己本能觉得可以这么改的冲动,仔细考虑一下有没有更好的办法,尽量把影响降到最低。

      比如Kernel,它能支持那么多的设备,为什么一直没有被fork?因为它本身就提供了非常好的可裁减机制,把你可能想要改的地方全部抽出来允许你改,以一种可配置的方式,也有叫裁减的。这样就免去了很多不必要的代码修改。

      安卓本身也是提供了一些比较完善的可配置机制的,如果想进行深度定制的话,最好能对这些机制有比较全面的了解。

      当然,也不是说一点也不可以、绝对禁止改谷歌底层的东西,那样的话这些做定制的公司都无法存在了。既然是开源的,就是可以改的,并且最后可能拼的就是看谁改得好,改得妙,当然,还要改得轻松。

    • 维护自己的patch list

      如果有可能,还是要维护一下自己的patch list,并不是说一旦提交上去了的patch,就必须至死不渝的——这样的话随着你的开发越来越深入,每次随着谷歌升级的痛苦也是越来越大的。如果能发现一些总是产生冲突的patch,或者以前的做法觉得有问题的patch,那么趁着升级的机会,应该考虑一下做一些必要的清理。

      当然,这个行为无论如何,不应该影响日常的开发的稳定性才对。

      具体如何维护,可以参考之前提到的发行版的维护者们会进行何种操作来维护自己的patch list。毕竟,我们很多时候也是一个维护者。

  2. 要敏捷

    其实这跟上面的“不要fork”是一回事儿。定制太深之后,基本就没法敏捷了。定制太深之后,实质上就相当于fork了。

    比如 Linux 社区里,有一条相当严肃的规则,就是要求各大公司与Kernel开发的时候,要尽量“敏捷”,刚开始某个新特性的开发,就要让社区知道,involve进来,大家一起讨论。最起码一个特性到底要不要做能够一开始就敲定下来。然后在开发的过程中,每当有一些阶段性的成果,就拿出来让社区review。罗马不是一天建成的,把事情分成一步一步的,实现起来会容易很多。Intel公司在这方面吃过一个大亏,工程师们吭哧吭哧花了不知道多少个人月,搞出一个非常牛的feature,biaji一下发到Kernel邮件列表,来,给我把这个超级大patch review一下,然后进去吧。Linus直接就怒了,妈个逼我们社区不是这么玩儿的,你这么大一个ptach发过来,根本就没法review!这样的做法一点也不敏捷,并且对社区缺乏尊重,把社区当啥了。

    要敏捷,才是紧跟社区的节奏。

    所以这里所谓的敏捷,还可以理解在公司内部也不要随便形成大的dev branch,与主线之间差异过大,到时候以哪条线为准?出了Bug怎么查?一旦差异过大,形成实质的fork,就是对社区力量的减半,因为需要维护的effort double了,本来只要开发、管理、测试一条线,现在变成两条线了。

    所以git的工作流里一直鼓励随心所欲的branch吧,fork吧,但是——很重要的一个但是——要尽快merge回去。一个branch从主线上拉出来之后,不尽快merge回去,差异就越来越大,中间维护和最后merge的effort也都越大;最后不merge的话,那你当初为何要接这个branch哩——这不白忙活了吗…

    一般的公司或者社区,都会有一条主线,然后可能会有一条stable线,或都根据不同版本,有N条stable线。这个可以参考一下Linux Kernel。

  3. 多参考社区现有的成熟的优良的做法/流程

    比如我们做安卓平台开发,升级的时候很痛苦,那是不是可以参考一下CM(cyanogen mod)社区的做法?

    当然,社区里很多时候没有很强的deadline之类的项目压力压着,有些东西可以慢慢来精雕细琢,这个是学不来的…