Archive for the ‘ readings and thoughts ’ Category

包管理器杂谈

前言

今天我想思维跳跃一下,讲讲包管理器,这是一个相当有趣的话题。因为程序员年复一年的写程序,大多是为了娱乐他人,好吧,有的人管那叫创造价值,有的人叫养家糊口。其实没所谓了,本质上来说大部分程序员并不是自己程序的受害者,所以你知道为什么这个世界上有那么多烂程序了吧。让我们回来,聚焦在那些极少的程序员写给自己的程序,除了编译器,版本控制工具以外还有一种非常非常重要,那就是包管理器。

你知道的这个世界上真正了解编译器的人据说坐不满一间教室。我想了解包管理器得人也多不到哪去。首先我们要看看今天要出场讨论的一些候选人。

  • apt 具有超级牛力Debian系猛男
  • yum 为Redhat延寿20年的黄狗
  • homebrew Mac下的青年才俊
  • rubygems Ruby下的魔法大师
  • npm Node.js的超级保镖
  • elpa Emacs的小宝宝

我今天要忽略一些内容,很可能引起某些人的不满。比如像Perl,Python,PHP,Haskell,Lua,R都有一些类似的设施,所以就不一一讨论了。明眼人也许看出来了,六个男嘉宾可以分为两组。一组是系统级别的(system-level),一组是应用级别的(application-level)。维基百科基本上对系统级别的包管理器做了一些介绍,应用级别的仅仅是列举了几个,并没有详细的介绍。但我认为包管理器都是完成类似的工作所以放到一起来讨论。

验证

从单个包的角度来说,一定要确保其来源,否则会轻易受到中间人攻击。所以一般的包管理器都具备简单的验证环节。常见的手法是MD5校验,或者其他Hash校验,homebrew提供的大部分安装包都是用这样的方式校验的。较为严格的应做签名验证,apt和yum都有相应的机制来验证软件包是否来自可信的源。综合来讲Hash的方式加密强度较低,而且严格说来这种校验只应作为下载的完整性检查,并属于授信操作。

依赖性

可以说包管理器解决最为重要的问题就是依赖性关系的问题。我相信在没有yum的RedHat中世纪,大家需要参考n多资料才能正确编译内核。那个时候他们是真心羡慕Debian的。在elpa出现之前,配置一个贴心的emacs环境简直就被视为高手的验证标准。依赖性关系其实并不像看上去的那样容易解决。很多年来make为了解决编译依赖性关系遭受不少的冷嘲热讽。尤其是这种依赖性关系比较松散的时候,看上去就不那么好解决。A依赖于B的3.2.1以上但不超过3.3的版本,这种有时候行有时候不行的条件判断构成了一个非常复杂的逻辑关系。但包管理器必须处理好这个关系,知道哪些依赖条件没有得到满足。依赖性关系的管理是一个生态系统的核心价值。Perl有了CPAN所以带来了一个非常美好的世界,甚至有人说CPAN比Perl的语法更吸引他。依赖性可以让程序员不那么纠结去手工寻找所需要的包,而这个过程可能是相当费时,步骤非常繁琐,而且极容易出错的。对,就好比不使用make工具来编译内核,不是不可能,但阿汤哥会做一些更有利于世界和平的事情。依赖性带来的好处就是开包即用,开袋即食,完全不必担心由于依赖性造成的破损。我猜测由于包管理器提供了一个可信的,可重复的,健壮的依赖性管理,每年程序员节省下来更多时间用于创造新的垃圾代码。

多版本

传统上的包管理器的互斥关系管理有点粗放,apt和yum在处理升级之类的问题时基本就是粗暴地将老的版本替换掉。当然这样设计是比较简单的,可是我们的系统并不美好,经常会有一些遗留软件需要老版本的库。而且从实践的角度来讲这也是一个不太难解决的问题,很多人的机器上都有各种版本的软件,所以后来设计的包管理器就注意了这方面的改进,比如homebrew和rubygems都可以支持同一个软件的多个版本,相互不影响,用户可以随意指定使用软件的版本。多版本是松散型依赖关系必然会出现的特性,也是简单粗暴转向优雅设计的拐点。一个随之而来的问题就是清理不用的版本。这是原来简单粗暴方式比较好解决的一个问题。将原来的是非判断变成了一个寻求最优解的优化问题了。

相对独立的环境

有的时候我们希望严格限制程序的运行环境,但由于包管理器为了便于升级和追新设定了比较宽松的限定条件,使得构建相同的运行环境在相当长的一段时间都是一个非常困难的任务。随着云计算的兴起,基础设施代码化这种思潮逐渐深入实践,诸如bundler这样精巧的工具诞生了。环境配置再也没有手工操作了,而且可以确保所有系统完全一样,简直帅呆了。如果你不觉得帅,那你定没有半夜被狗血电话叫起来去修复线上bug,结果发现这是某个库包与开发者的环境不一样导致的。相对独立的环境在开发者之间也非常重要,任何差异都可能导致惨痛的教训,真金白银的教训啊。相信越来越多的开发者会在云端部署自己的应用,构建相同的环境今后会成为开发者的基本要求的。

发布

有的包管理工具还提供非常便利的构建工具,可以快速构建自己的软件包,并将其发布。开发者可以随时贡献自己的智慧。rubygems就是这样的典型。homebrew可以本地构建一个配方,然后发起pull request,一旦获准别人就能使用了。这个对于未来的开发模式非常重要。Node.js社区的蓬勃发展就是很好的例证。实践证明降低参与者的参与成本特别有利于大家为社区做贡献。

源码还是二进制

我倾向于homebrew的源码编译方式,虽然有点慢,遭到一些诟病,不过我觉得看着代码打补丁,编译是我的个人乐趣。

传输问题

考虑到中国网络的多样性问题,在此我讨论四个方面:网速测试,断点续传,多点传输,下载调度。无疑elpa在这几方面做得奇差无比,简直就是各个方面的反面教材。yum提供插件可以检测最快的镜像源,apt做得也还可以。但都不能同时从多个源下载同一个包,有时候我们还是很需要迅雷或者BT的功能的。断点续传不用多说了,算是一个基本功能。同时开启多少个并发就不能智能点么,非得一个或者n个,就不能自动调节么。

甚至我觉得包管理器应该构建于BT或者电驴之上。

关于源

私自搭建一个源应得到鼓励,应该几乎没有成本地加入到整个全球分布式的系统中,源之间的同步应该也是开箱即用的。这方面做得最好的我认为是全球的公钥分发系统,BT和eDonkey次之。

结语

生态系统无论对一门语言还是对一个系统来说都是非常重要的,人们不会凭着一门语言良好的语法就蜂拥而至的。良好的生态系统为他们提供可靠而丰富的工具链,库包,最佳实践。而位于生态系统核心的正式管理依赖性关系的包管理器。本人杂乱地讲述了对各种包管理器一些思考,希望能“抛砖引砖拍”!

火星程序员带人指南

本来我是想写一本JavaScript方面的书,介绍一些这个领域内关于编程方法学和整体生态环境进展的书。但是去年与图灵的编辑们探讨过后,他们一致认为我应该先写出来一点,看看市场的反应。虽然建议不怎么好听,但毕竟只有真心说实话的人才会赢得我的尊敬。他们的担心体现在了两点,第一就是我作为一个在JavaScript领域并没有什么影响的人写出来的东西到底能不能卖,第二考虑到我妻子临产,我是否有充足的时间完成所规划的内容。至于内容方面我也是有些担心的,主要是貌似国人关心语言生态圈的并不多,他们把语言简单地当做一门工具,无论是赚钱的工具还是娱乐的工具,所以几乎看不到人谈论一门编程语言的流派,演进,思潮,展望。这些字眼儿也许过于风花雪月,但我觉得其实并没有那么地不切实际。在大家都在谈论某一具体的库,某一个具体的网站,某一种具体的新技术时,我总是感觉缺少些什么。似乎在这些疯狂逐利的群氓中,我更希望看到的是一个能沉静下来,多问几个为什么的人。

我最近带了两个新人,他们不是初学编程,基础也比较好。带的过程中我一直在想如何才能不误人子弟,如何能让他们获得内心的平静与强大。考虑再三,我决定让他们通过阅读一些经典代码,然后我提问题和演示的方式展开一开始的学习。理由如下:

第一,阅读源码是绝对必要的。虽然编程是创造性工作,但实际所有创造性工作的绝大部分都不怎么有创造性。这和艺术创作是类似的,大部分艺术家要数十年如一日地修炼自己的各种基本技巧,而这类基本技巧的训练经常是极度乏味,毫无创造性可言的。但是程序员往往以为自己跟别人不一样,想当然地认为大部分工作都是创造性的。说句实在话,我看到的大部分程序员都是缺少基本技巧的训练的,有的甚至说极度缺乏也不为过。最令人不解的是,很多人不愿承认这一点,或者即便承认也对此丝毫不以为意。其中我觉得代码阅读就是基础中的基础,一个看不懂别人写的代码的人,自己的写代码水平也必定有限。这个道理很好证明,一个人可以读懂自己写的任何代码,所以说他对代码的理解力一定大于自身的创造力,反过来说一个人的创造力的上限也就是他的理解力了。写代码和写文章非常类似,我以前也说过,就不罗嗦了。这里只是再一次重申代码阅读能力的重要性非常之高。所以我看程序员好坏,除了让他写一段程序,还可以让他读一段程序,甚至我更倾向于后者。读这个基础打好了,才能为写做一个良好的基础。

第二,要读经典才能学得更多。这简直就是废话。听音乐的要听经典的,读书也要名家名作,临摹字帖得是千古流传的……但程序员大部分时间都在读质量并不怎么高的代码。就好比我们读到的和我们息息相关的文字多数也没有什么美感一样。一定要刻意地找寻经典,话又说回来了,这就需要对一门语言的生态圈十分了解才可以。不但要知道那些产品非常有名,而且要知道为什么有名,谁在用这个产品,怎么用的。经典的代码和经典的音乐有点不一样,经典的代码一般还都处于活跃的维护期,不像音乐好多已经上百年没人更新了(呵呵,有点大不敬啊)。所以经典代码是活的范例,解决的是具体问题,不但可以学到编程的各种规范和技巧,也能学习到比人是如何思考改进代码的。好的代码更易于读懂,更清晰,有利于学习清晰的设计思想。这些都是普通教科书或者一般技术书籍所无法比拟的。学会跟踪趋势,知道流行也是程序员基本技能,况且额外的还可以通过读代码知道很多圈内八卦,这个是一般人不知道的乐趣。

第三,问题可以查看细节的理解是否到位。首先要说明的是无论是提问还是回答问题都是非常鼓励的。我一般会事先布置一些问题,然后让他们一起去研究,然后回答也可以问我新的问题。这种方式经过实践证明是非常有效的。学徒能够更集中精力,对问题的理解也会更加深入。透过他们的回答,我还能更清楚地判断哪些细节掌握的欠佳,我会毫不留情地抛出更多的问题。回答出所有问题只能得到及格,要提出问题才能获得高分。鼓励他们在提问的同时设计实验来检验自己的命题。这种苏格拉底式的修炼,一开始着实让新同学吃不消。因为我的问题无处不在,他们似乎第一次面对一个如此不会掩饰自己无知的家伙。这种方式使得他们渐渐抛弃了那些我们习以为常,却丝毫经不起验证的各种假设和误解。他们渐渐试着怀疑权威的观点,他们也会主动去验证自己的观点。编程是门实用艺术,一定要经得起别人问。

第四,演示促进理解和沟通。我发现大部分人对于把问题说明白这件事儿看得并不是很重要,尤其是工作经验比较少的同学。作为训练的一个科目,我要求不能只讲给我听,要演示给我看,要让我不经意间就能相信你说的都是对的。一开始有的同学觉得我比较扯,什么都演示多浪费时间啊。但如果你看过《完美软件》或者《人月神话》就会明白,就留沟通不行,问题都说不清楚,项目的失败只是时间问题,并没有回天的余地。程序员都觉得以“宅”为美,这就是狗屁好莱坞大片看多了的结果。我们不能相信好莱坞大片中的技术宅男,就好比我们不能相信国产电视剧中的八路军一样。我接触过很多大牛程序员,没见过哪个宅的。在一个团队,就是不能有信息的孤岛。在这个过程中我也发现了新手的一些常见问题,列举一些:

  • 倾向于说服,而不是证明。如果你要说明别人是错的,最好的办法就是举一个反例。如果你要说明你是对的,请回忆一下中学老师关于说明文的各种技巧。其中包括举例子,作类比,打比方等等。记住重复阐述自己的观点对理解几乎没有任何帮助。
  • 证明过程跳跃不完整。你说这两个对象是同一个的时候,就要演示一下是否全等,长得一样是远远不行的。
  • 思路不清晰,经常把自己绕进去了。课下的功夫做得不够。
  • 不cool。很多人说什么?!这也算是缺点!?当然,记得大学里上过的那些死气沉沉政治课吧。很多人上学的时候都对乏味的课程大放厥词,但是轮到自己演示的时候却是一个模样。我为什么要听你讲?你得有趣,人要有趣,讲的东西要有趣,要酷才行。
  • 不能即兴演示。对于观众提出的突发问题,没有即刻的反应。互动效果差。

好了,这些就是我对带人的一些实践。今后我会写一个专题,把我和他们之间讨论的问题记录下来(对,有可能是那种《论语》风格的),名字就暂定为《火星人JavaScript问题集锦》。起初几期讨论一下underscore和backbone的源码,之后会讨论jquery,coffeescript,less,欢迎大家参与讨论。