2016年8月5日星期五

阮一峰的网络日志

阮一峰的网络日志


布尔代数入门

Posted: 04 Aug 2016 05:17 PM PDT

布尔代数是计算机的基础。没有它,就不会有计算机。

布尔代数发展到今天,已经非常抽象,但是它的核心思想很简单。本文帮助你理解布尔代数,以及为什么它促成了计算机的诞生。

我依据的是《编码的奥妙》的第十章。这是一本好书,强烈推荐。

一、数理逻辑的起源

18世纪早期,英国数学家乔治·布尔(George Boole,1815-1864)突发奇想:人的思想能不能用数学表达?

此前,数学只用于计算,没有人意识到,数学还能表达人的逻辑思维。

两千年来,哲学书都是用文字写的。比如,最著名的三段论:

所有人都是要死的,
苏格拉底是人,
所以,苏格拉底是要死的。

乔治·布尔认为,这种推理可以用数学表达,也就是说,哲学书完全可以用数学写。这就是数理逻辑的起源。

二、集合论

乔治·布尔发明的工具,叫做"集合论"(Set theory)。他认为,逻辑思维的基础是一个个集合(Set),每一个命题表达的都是集合之间的关系。

比如,所有人类组成一个集合R,所有会死的东西组成一个集合D。

所有人都是要死的

集合论的写法就是:

R X D = R

集合之间最基本的关系是并集和交集。乘号(X)表示交集,加号(+)表示并集。上面这个式子的意思是,R与D的交集就是R。

同样的,苏格拉底也是一个集合S,这个集合里面只有苏格拉底一个成员。

苏格拉底是人
// 等同于
S X R = S

上面式子的意思是,苏格拉底与人类的交集,就是苏格拉底。

将第一个式子代入第二个式子,就得到了结论。

S X (R X D)
= (S X R) X D
= S X D
= S

这个式子的意思是,苏格拉底与会死的东西的交集,就是苏格拉底,即苏格拉底也属于会死的东西。

三、集合的运算法则

前面的三段论比较容易,一眼就能看出结论。但是,有些三段轮比较复杂,不容易立即反应过来。

请看下面这两句话。

"鸭嘴兽是卵生的哺乳动物。鸭嘴兽是澳洲的动物。"

你能一眼得到结论吗?

鸭嘴兽 X 卵生 = 鸭嘴兽
鸭嘴兽 x 澳洲 = 鸭嘴兽

将第一个式子代入第二个,就会得到:

鸭嘴兽 X 卵生 x 澳洲 = 鸭嘴兽
// 相当于
卵生 x 澳洲 = 鸭嘴兽 + 其他

因此,结论就是"有的卵生动物是澳洲的动物",或者"有的澳洲的动物是卵生动物"。

还有更不直观的三段论。

"哲学家都是有逻辑头脑的,一个没有逻辑头脑的人总是很顽固。"

请问结论是什么?

这道题会用到新的概念:全集和空集。集合A和所有不属于它的元素(记作-A)构成全集(I),这时A和-A的交集就是一个空集(0)。

A + (-A) = I
A X (-A) = 0

因此,有下面的公式。

B
= B X I
= B X (A + -A)
= B X A + B X (-A)

回到上面那道题。

哲学家 X 逻辑 = 哲学家
无逻辑 X 顽固 = 无逻辑

根据第一个命题,可以得到下面的结论。

哲学家 X 无逻辑
= (哲学家 X 逻辑) X 无逻辑
= 哲学家 X (逻辑 X 无逻辑)
= 哲学家 X 0
= 0

即哲学家与没有逻辑的人的交集,是一个空集。

根据第二个命题,可以得到下面的结论。

无逻辑 X 顽固
= 无逻辑 X 顽固 X (哲学家 + 非哲学家)
= 无逻辑 X 顽固 X 哲学家 + 无逻辑 X 顽固 X 非哲学家
= 0 X 顽固 + 无逻辑 X 顽固 X 非哲学家
= 无逻辑 X 顽固 X 非哲学家
= 无逻辑

也就是说,最终的结论如下。

无逻辑 X 顽固 X 非哲学家 = 无逻辑
// 相当于
顽固 X 非哲学家 = 无逻辑 + 其他

结论就是顽固的人与非哲学家之间有交集。通俗的表达就是:一些顽固的人,不是哲学家,或者一些不是哲学家的人,很顽固。

由此可见,集合论可以帮助我们得到直觉无法得到的结论,保证推理过程正确,比文字推导更可靠。

四、 集合论到布尔代数

既然命题可以用集合论表达,那么逻辑推导无非就是一系列集合运算。

由于集合运算的结果还是集合,那么通过判断个体是否属于指定集合,就可以计算命题的真伪。

一名顾客走进宠物店,对店员说:"我想要一只公猫,白色或黄色均可;或者一只母猫,除了白色,其他颜色均可;或者只要是黑猫,我也要。"

这名顾客的要求用集合论表达,就是下面的式子。

公猫 X (白色 + 黄色)
+ 母猫 X 非白色
+ 黑猫

店员拿出一只灰色的公猫,请问是否满足要求?

布尔代数规定,个体属于某个集合用1表示,不属于就用0表示。 灰色的公猫属于公猫集合,就是1,不属于白色集合,就是0。

上面的表达式变成下面这样。

1 X (0 + 0)
+ 0 X 1
+ 0
= 0

因此,就得到结论,灰色的公猫不满足要求。

这就是布尔代数:计算命题真伪的数学方法。

五、布尔代数的运算法则

布尔代数的运算法则与集合论很像。

交集的运算法则如下。

1 X 1 = 1
1 X 0 = 0
0 X 0 = 0

并集的运算法则如下。

1 + 1 = 1
1 + 0 = 1
0 + 0 = 0

集合论可以描述逻辑推理过程,布尔代数可以判断某个命题是否符合这个过程。人类的推理和判断,因此就变成了数学运算。

20世纪初,英国科学家香农指出,布尔代数可以用来描述电路,或者说,电路可以模拟布尔代数。于是,人类的推理和判断,就可以用电路实现了。这就是计算机的实现基础。

六、布尔代数的局限

虽然布尔代数可以判断命题真伪,但是无法取代人类的理性思维。原因是它有一个局限。

它必须依据一个或几个已经明确知道真伪的命题,才能做出判断。比如,只有知道"所有人都会死"这个命题是真的,才能得出结论"苏格拉底会死"。

布尔代数只能保证推理过程正确,无法保证推理所依据的前提是否正确。如果前提是错的,正确的推理也会得到错误的结果。而前提的真伪要由科学实验和观察来决定,布尔代数无能为力。

(完)

文档信息

2016年7月26日星期二

阮一峰的网络日志

阮一峰的网络日志


母鸡与前端工程师

Posted: 25 Jul 2016 05:12 PM PDT

(说明:本文原载2016年第29期《财新周刊》)

1.

新闻说,今年全国高校毕业生达到空前的756万,又赶上很多传统行业压缩产能,就业压力很大。

很多曾经的"明星专业",都已经就业困难。我考大学的时候,国际贸易是最热门的专业之一,大家认定这个专业容易赚钱。但是现在这个专业的毕业生,想找一份好工作会很难,上海将它列入10大预警专业,即最难就业的十个专业之一。

但是,并非所有行业都不景气。至少有一个行业的用工需求极其旺盛,到处都是招聘广告,工作岗位是应聘者数量的好几倍,通常你都有好几家公司可以挑。每周都有朋友发来消息,能不能帮忙介绍几个人过来,我们实在是缺人啊。

这个行业就叫做互联网开发。

2.

互联网行业的劳动力需求,可以用"用工荒"来形容。

只要你会做网页,尤其是手机App的页面,或者微信的活动页面,就不愁找不到工作。哪怕你刚刚学会几个月,或者刚从培训班毕业,只要能拿出作品,就会有比其他行业高得多的起薪。等到有了一两年工作经验,工资就可以达到大学教授的水平。

这样的就业行情,怎不令人趋之若骛。尽管每年都有好几万新人加入,互联网公司还是在喊,工程师严重短缺。

我曾经不太理解,为什么网页开发工程师(或称"前端工程师")这么抢手。直到有一天,看到了一组鸡蛋的统计数据,才想通了这个问题。

3.

中国是世界鸡蛋第一大国。据统计,2013年全国产量5750亿枚,一个中国人平均一年要吃掉400多枚鸡蛋。

那么中国需要多少只母鸡,才能达到这样的产量?

据说,普通母鸡一年大概生200~250枚鸡蛋。养鸡场里面最优秀的母鸡,一年可以达到320枚。以250枚计算的话,中国至少需要有23亿只母鸡,才能满足全国人民吃蛋的需求。

4.

如果把鸡蛋换成网页,同样的问题就是,中国一年需要生产多少张网页,才能满足人民消费的需要?

去年,我国手机用户超过13亿,智能手机用户超过6亿。就算其中只有一半人上网,那也是3亿多人。这么多人,每天都有几十分钟或者几个小时,要使用手机上网。全体中国人一年消费的网页和App的数量,是一个天文数字。

鸡蛋是母鸡生出来的,网页从哪里来?归根结底,所有页面都需要工程师做出来。那么多互联网公司,每家公司都需要前端工程师。而全国的前端工程师,目前可能总共有几十万人,对比那么大的内容消费量,肯定是远远不够的(想一想吧,全国的母鸡有23亿只)。这样一想,工程师抢手就不奇怪了。

还有一个很重要的原因,学校不教前端开发,可能会有一些相关课程,但不会系统地教,所有前端工程师都是靠自学的。这也导致了供给偏少。

5.

由于工作好找和工资较高,前端工程师现在成了一个热门职业。很多不是搞计算机的人,也在考虑转行加入。社会上的培训班,每个周末的各种讲座和大会,都已经人满为患。

我经常收到电子邮件,咨询是否应该改行。

"我是一名会计/教师/导游,现在的工作没有任何成就感,感到没有发展空间。如果我拿出一年左右的时间去自学前端类的课程,将来能走上程序员这条路吗?"

这可怎么答复?

6.

前端编程入门,确实不难,可以短期速成。只要你对计算机有基本的理解,哪怕编程零基础,经过三四个月的培训,也能做出网页和App。

如果你确实想改行,我觉得,这基本上是一件好事,你应该选择那些更有前景的职业。但问题是,并非每个人都适合编程。现在那么多人一窝蜂学习互联网开发,肯定有人将来会后悔。

你最好事先知道下面三件事,再考虑加入这个行业。

7.

首先,你应该热爱编程。

职业程序员每天都必须长时间地坐在电脑前面,与机器对话的时间,远超过与人对话。如果不是真心热爱编程,这会很难忍受,简直像是一种惩罚。让一个人在他不喜欢的事情上面,筋疲力尽地干上几年甚至几十年,那是多么痛苦的人生。

其次,编程本身虽然是一种智力活动,但是中国的现实却更像一种体力劳动。

由于运营活动太多,开发是做不完的,App必须不断地推出新版本。工作量常常是超负荷的,任务排期一个接着一个,中间根本没有喘息时间,同时做多个项目也是家常便饭。每个项目都有截止期,做不完只能加班。这样说吧,制作网页本身是有趣的,但是像流水线一样的"制造"网页是乏味的,好比养鸡场的母鸡不停得下蛋,每周必须完成5个蛋的指标。

最后,这个行业的新陈代谢很快。

快速的技术更新和极大的工作强度,使得年轻人具有天然的优势。等到职业生涯后期,你的开发速度开始慢下来,就是你被更年轻的人取代的时候。一只母鸡一生中,大约总共可以生2000枚鸡蛋,你的一生中可以制作的网页(或者 App),大概也是一个常数。

8.

如果你不喜欢编程,体会不到代码的乐趣和成就感,只是为了一份好的薪水,就跑来干,那就是很糟糕的选择。想一想如果十年前,你听说国际贸易很兴旺,高考志愿就填了国际贸易,今天会怎样呢?

你应该选择,那些让你产生最大兴趣和热情的职业。因为未来所有行业,低端的、低技能的岗位都会被机器取代,只有技能最强、最有创造性的人不会被淘汰。兴趣,也只有兴趣,才会让你产生不倦的热情,钻研下去,变得更优秀。

(完)

文档信息

2016年7月22日星期五

阮一峰的网络日志

阮一峰的网络日志


如何识别图像边缘?

Posted: 21 Jul 2016 04:39 PM PDT

图像识别(image recognition)是现在的热门技术。

文字识别、车牌识别、人脸识别都是它的应用。但是,这些都算初级应用,现在的技术已经发展到了这样一种地步:计算机可以识别出,这是一张狗的照片,那是一张猫的照片。

这是怎么做到的?

让我们从人眼说起,学者发现,人的视觉细胞对物体的边缘特别敏感。也就是说,我们先看到物体的轮廓,然后才判断这到底是什么东西。

计算机科学家受到启发,第一步也是先识别图像的边缘。

加州大学的学生 Adit Deshpande 写了一篇文章《A Beginner's Guide To Understanding Convolutional Neural Networks》,介绍了一种最简单的算法,非常具有启发性,体现了图像识别的基本思路。

首先,我们要明白,人看到的是图像,计算机看到的是一个数字矩阵。所谓"图像识别",就是从一大堆数字中找出规律。

怎样将图像转为数字呢?一般来说,为了过滤掉干扰信息,可以把图像缩小(比如缩小到 49 x 49 像素),并且把每个像素点的色彩信息转为灰度值,这样就得到了一个 49 x 49 的矩阵。

然后,从左上角开始,依次取出一个小区块,进行计算。

上图是取出一个 5 x 5 的区块。下面的计算以 7 x 7 的区块为例。

接着,需要有一些现成的边缘模式,比如垂直、直角、圆、锐角等等。

上图右边是一个圆角模式,左边是它对应的 7 x 7 灰度矩阵。可以看到,圆角所在的边缘灰度值比较高,其他地方都是0。

现在,就可以进行边缘识别了。下面是一张卡通老鼠的图片。

取出左上角的区块。

取样矩阵与模式矩阵对应位置的值相乘,进行累加,得到6600。这个值相当大,它说明什么呢?

取样矩阵移到老鼠头部,与模式矩阵相乘,得到的值是0。

乘积越大就说明越匹配,可以断定区块里的图像形状是圆角。通常会预置几十种模式,每个区块计算出最匹配的模式,然后再对整张图进行判断。

(完)

文档信息

2016年7月4日星期一

阮一峰的网络日志

阮一峰的网络日志


YAML 语言教程

Posted: 03 Jul 2016 03:07 PM PDT

编程免不了要写配置文件,怎么写配置也是一门学问。

YAML 是专门用来写配置文件的语言,非常简洁和强大,远比 JSON 格式方便。

本文介绍 YAML 的语法,以 JS-YAML 的实现为例。你可以去在线 Demo 验证下面的例子。

一、简介

YAML 语言(发音 /ˈjæməl/ )的设计目标,就是方便人类读写。它实质上是一种通用的数据串行化格式。

它的基本语法规则如下。

  • 大小写敏感
  • 使用缩进表示层级关系
  • 缩进时不允许使用Tab键,只允许使用空格。
  • 缩进的空格数目不重要,只要相同层级的元素左侧对齐即可

# 表示注释,从这个字符一直到行尾,都会被解析器忽略。

YAML 支持的数据结构有三种。

  • 对象:键值对的集合,又称为映射(mapping)/ 哈希(hashes) / 字典(dictionary)
  • 数组:一组按次序排列的值,又称为序列(sequence) / 列表(list)
  • 纯量(scalars):单个的、不可再分的值

以下分别介绍这三种数据结构。

二、对象

对象的一组键值对,使用冒号结构表示。

  animal: pets  

转为 JavaScript 如下。

  { animal: 'pets' }  

Yaml 也允许另一种写法,将所有键值对写成一个行内对象。

  hash: { name: Steve, foo: bar }   

转为 JavaScript 如下。

  { hash: { name: 'Steve', foo: 'bar' } }  

三、数组

一组连词线开头的行,构成一个数组。

  - Cat  - Dog  - Goldfish  

转为 JavaScript 如下。

  [ 'Cat', 'Dog', 'Goldfish' ]  

数据结构的子成员是一个数组,则可以在该项下面缩进一个空格。

  -   - Cat   - Dog   - Goldfish  

转为 JavaScript 如下。

  [ [ 'Cat', 'Dog', 'Goldfish' ] ]  

数组也可以采用行内表示法。

  animal: [Cat, Dog]  

转为 JavaScript 如下。

  { animal: [ 'Cat', 'Dog' ] }  

四、复合结构

对象和数组可以结合使用,形成复合结构。

  languages:   - Ruby   - Perl   - Python   websites:   YAML: yaml.org    Ruby: ruby-lang.org    Python: python.org    Perl: use.perl.org   

转为 JavaScript 如下。

  { languages: [ 'Ruby', 'Perl', 'Python' ],    websites:      { YAML: 'yaml.org',       Ruby: 'ruby-lang.org',       Python: 'python.org',       Perl: 'use.perl.org' } }  

五、纯量

纯量是最基本的、不可再分的值。以下数据类型都属于 JavaScript 的纯量。

  • 字符串
  • 布尔值
  • 整数
  • 浮点数
  • Null
  • 时间
  • 日期

数值直接以字面量的形式表示。

  number: 12.30  

转为 JavaScript 如下。

  { number: 12.30 }  

布尔值用true和false表示。

  isSet: true  

转为 JavaScript 如下。

  { isSet: true }  

null用~表示。

  parent: ~   

转为 JavaScript 如下。

  { parent: null }  

时间采用 ISO8601 格式。

  iso8601: 2001-12-14t21:59:43.10-05:00   

转为 JavaScript 如下。

  { iso8601: new Date('2001-12-14t21:59:43.10-05:00') }  

日期采用复合 iso8601 格式的年、月、日表示。

  date: 1976-07-31  

转为 JavaScript 如下。

  { date: new Date('1976-07-31') }  

YAML 允许使用两个感叹号,强制转换数据类型。

  e: !!str 123  f: !!str true  

转为 JavaScript 如下。

  { e: '123', f: 'true' }  

六、字符串

字符串是最常见,也是最复杂的一种数据类型。

字符串默认不使用引号表示。

  str: 这是一行字符串  

转为 JavaScript 如下。

  { str: '这是一行字符串' }  

如果字符串之中包含空格或特殊字符,需要放在引号之中。

  str: '内容: 字符串'  

转为 JavaScript 如下。

  { str: '内容: 字符串' }  

单引号和双引号都可以使用,双引号不会对特殊字符转义。

  s1: '内容\n字符串'  s2: "内容\n字符串"  

转为 JavaScript 如下。

  { s1: '内容\\n字符串', s2: '内容\n字符串' }  

单引号之中如果还有单引号,必须连续使用两个单引号转义。

  str: 'labor''s day'   

转为 JavaScript 如下。

  { str: 'labor\'s day' }  

字符串可以写成多行,从第二行开始,必须有一个单空格缩进。换行符会被转为空格。

  str: 这是一段    多行    字符串  

转为 JavaScript 如下。

  { str: '这是一段 多行 字符串' }  

多行字符串可以使用|保留换行符,也可以使用>折叠换行。

  this: |    Foo    Bar  that: >    Foo    Bar  

转为 JavaScript 代码如下。

  { this: 'Foo\nBar\n', that: 'Foo Bar\n' }  

+表示保留文字块末尾的换行,-表示删除字符串末尾的换行。

  s1: |    Foo    s2: |+    Foo      s3: |-    Foo  

转为 JavaScript 代码如下。

  { s1: 'Foo\n', s2: 'Foo\n\n\n', s3: 'Foo' }  

字符串之中可以插入 HTML 标记。

  message: |      <p style="color: red">      段落    </p>  

转为 JavaScript 如下。

  { message: '\n<p style="color: red">\n  段落\n</p>\n' }  

七、引用

锚点&和别名*,可以用来引用。

  defaults: &defaults    adapter:  postgres    host:     localhost    development:    database: myapp_development    <<: *defaults    test:    database: myapp_test    <<: *defaults  

等同于下面的代码。

  defaults:    adapter:  postgres    host:     localhost    development:    database: myapp_development    adapter:  postgres    host:     localhost    test:    database: myapp_test    adapter:  postgres    host:     localhost  

&用来建立锚点(defaults),<<表示合并到当前数据,*用来引用锚点。

下面是另一个例子。

  - &showell Steve   - Clark   - Brian   - Oren   - *showell   

转为 JavaScript 代码如下。

  [ 'Steve', 'Clark', 'Brian', 'Oren', 'Steve' ]  

八、函数和正则表达式的转换

这是 JS-YAML 库特有的功能,可以把函数和正则表达式转为字符串。

  # example.yml  fn: function () { return 1 }  reg: /test/  

解析上面的 yml 文件的代码如下。

  var yaml = require('js-yaml');  var fs   = require('fs');    try {    var doc = yaml.load(      fs.readFileSync('./example.yml', 'utf8')    );    console.log(doc);  } catch (e) {    console.log(e);  }  

从 JavaScript 对象还原到 yaml 文件的代码如下。

  var yaml = require('js-yaml');  var fs   = require('fs');    var obj = {    fn: function () { return 1 },    reg: /test/  };    try {    fs.writeFileSync(      './example.yml',      yaml.dump(obj),      'utf8'    );  } catch (e) {    console.log(e);  }  

九、参考链接

(完)

文档信息

2016年7月2日星期六

阮一峰的网络日志

阮一峰的网络日志


谷歌的代码管理

Posted: 01 Jul 2016 05:15 PM PDT

谷歌和 Facebook 都只有一个代码仓库,全公司的代码都放在这个库里。

我一直很困惑,为什么要这样做,不同语言的项目放在一个库有什么好处?

最新一期的《ACM通信》(59卷第7期)有一篇论文《为什么 Google 要把几十亿行代码放在一个库?》,作者是谷歌基础设施小组的工程师,可以看作官方对这个问题的详细解答。我读后感到收获很大,下面就是摘录。

一、概况

谷歌最早使用 CVS 进行代码管理,1999年改为 Perforce。那时是一台 Perforce 主机,加上各种缓存机。

当时,全公司的代码就在一个仓库里面,后来一直沿用这种做法。由于规模不断增长,Perforce 已经无法满足需求,谷歌就开始使用自己开发的版本管理系统 Piper。

Piper 架设在谷歌自己的分布式数据库系统(以前叫 Bigtable,现在改名 Spanner)之上,分布在全世界10个数据中心,保证世界各地的谷歌员工都有良好的访问速度。

目前,这个代码仓库包含10亿个文件、3500万次提交记录,大小为86TB,用户达到几万人。工作日每秒有50万次请求,高峰时80万次,大部分来自自动构建和测试系统。

谷歌90%以上的代码,放在 Piper 里面。对于那些开源的、需要外部协作的项目,代码放在 Git,主要是 Android 项目和 Chrome 项目。Git 的特点是,所有历史记录都会复制到用户的本地机器,所以不适合大型项目,必须拆分成更小的库。以 Android 为例,该项目一共包含800多个独立的仓库。

二、Piper 的设计

2.1 结构

整个仓库采用树状结构。每个团队有自己的目录。目录路径就是代码的命名空间。每个目录都有负责人(owner),他负责批准该目录的文件变动。

2.2 权限控制

Piper 支持文件级别的权限控制。99% 的代码对所有用户可见,只有少部分重要的配置文件和机密的关键业务,设有访问限制。

如果机密信息不小心放上了 Piper,文件可以被快速清除。并且,所有的读写都有日志,管理员能够查到谁读过这个文件。

2.3 工作流

Piper 的工作流(workflow)如下图。

开发者先创建文件的本地拷贝,这叫做"工作区"(workspace)。完成开发后,工作区的快照共享给其他开发者进行代码评审。只有通过了评审,代码才能合并到中央仓库。

2.4 客户端

大多数开发者通过一个叫做 CitC 的客户端,访问 Piper。

开发者通过 CitC 浏览和同步 Piper 上的文件,但是编辑和修改是在自己工作区,里面只保存有变动的文件(一个工作区一般不超过10个文件)。CitC 带有云储存机制,每个工作区就是云上的一个目录。通过代码评审以后,这些文件才从 Citc 合并进 Piper。

不使用 CitC 也是允许的,所有代码保存在本地,最终用 Git 客户端提交到 Piper。不过,由于 CitC 提供更多的功能,目前使用率达到 80% 。

2.5 主干开发

Google 采用"主干开发"(trunk-based development)。代码一般提交到主干的头部。这样保证了所有用户看到的都是同一份代码的最新版本。

"主干开发"避免了合并分支时的麻烦。谷歌一般不采用分支开发,分支只用来发布。大多数时候,发布分支是主干某个时点的快照。以后的除错和功能增强,都是提交到主干,必要时 cherry-pick 到发布分支。与主干长期并行的开发分支,在谷歌极少见。

由于不采用"分支开发",谷歌引入新功能,一般在代码中使用开关控制。这避免了另起一个分支,也使得通过配置切换功能变得容易,一旦新功能发生故障,很容易切换回旧功能。等到新功能稳定,再彻底删除旧代码。谷歌有类似A/B测试的路由算法,评估代码的表现,由于存在配置开关,这种测试很容易实现。

2.6 代码评审

所有代码合并进仓库之前,都必须进行代码评审。大部分评审对所有人开放,任何谷歌员工都可以对代码提意见或者提交变动。

代码评审的依据是《Google 代码风格指南》。谷歌有一个叫做 Critique 的工具,可以查看每一行代码的历史演变。

2.7 自动测试

评审完成后,会自动运行测试。通过测试以后,代码就合并进了 Piper 仓库,整个过程不需要人工干预。

三、优点

单一代码仓库主要有以下优点。

(1)统一的版本

整个公司的代码,有统一的版本和路径,不存在找不到文件的最新版本这样的问题。

(2)广泛的代码共享和复用

任何人都可以浏览和使用全公司的代码,这大大促进了代码的共享和复用。

(3)简化的依赖管理

如果你是库文件或者 API 的作者,因为所有人的代码都在一个库里面,所以很容易找到依赖你的所有下游代码。

每当代码变动,所有依赖你的代码都会自动构建。如果有大量的构建失败,那么系统会自动撤销这次提交。这样也保证了所有代码依赖的都是最新版本,避免依赖不同的版本所导致的冲突。

另外,由于代码的边界很清楚,所以不会发生循环依赖。而且,API的作者也很容易发现,别人怎么使用他的API。

(4)原子性变动

由于每次代码变动所导致的影响,都在一个仓库里面,所以都属于原子性的变动。因此,很容易撤销,或者预先测试它所造成的影响。

为了防止错误提交,谷歌引入了"预提交"(即在提交之前,先分析一下依赖它的代码是否会构建失败)。

(5)大规模代码析构

单一代码仓库为查找和分析代码,提供了巨大的方便。

Google的静态分析引擎 Tricorder 定时运行,对代码进行分析。比如,C++ 11 标准公布以后,很容易找到所有需要改进的变量声明语句,进行性能优化。该引擎还对许多错误提供"一键修正"的功能,同时产出大量的统计数据。

此外,编译器团队也会对不同语言的所有代码进行分析,找出不合理的代码和过时的API。

四、缺点

单一代码仓库的主要缺点是,所有工具都必须自己写,因为市场上没有能够管理这种规模的代码仓库的软件。

五、总结

单一代码仓库,适合提倡透明开放的大型软件公司,不适合小公司和有大量私密代码的公司。

(完)

文档信息