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 }  

布尔值用truefalse表示。

  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。

四、缺点

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

五、总结

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

(完)

文档信息