版权信息

书名：码农 第14期

作者：图灵社区出品

本书由北京图灵文化发展有限公司发行数字版。版权所有，侵权必究。

您购买的图灵电子书仅供您个人使用，未经授权，不得以任何方式复制和传播本书内容。

我们愿意相信读者具有这样的良知和觉悟，与我们共同保护知识产权。

如果购买者有侵权行为，我们可能对该用户实施包括但不限于关闭该帐号等维权措施，并可能追究法律责任。

目录

编者的话

专题：Web App不权威指南

Web 的基础：HTTP 原理图解

“一切皆服务”时代Web 应用的安全隐患

Web 应用为什么要组件化开发

开源Web 应用Moodle架构细节分析

Google性能专家Ilya Grigorik：速度是我们的核心信条

数据绑定和第一个AngularJS Web 应用

读《码农》 吐吐槽 还能赚银子！

人物

周爱民：真正的架构师是没有title的

践行

如何开始一个模块化可扩展的Web App

鲜阅

统计学家的黑客技艺两则

九卦

去加勒比海做IT 项目的几点回忆

出版的未来

从传统行业进入互联网行业的得与失

书榜

看看大家都在读什么？

电子书榜

成书手记

《第一行代码——Android》封面诞生记

妙评

看完这本书整个人都不好了——《写给大家看的设计书》读后感

《Web 性能权威指南》，一年一遇的好书

封底

编者的话

编者 / 李盼

在因特网发展的早期阶段，万维网（World Wide Web）仅由Web站点构成，这些站点基本上是包含静态文档的信息库。随后人们发明了Web浏览器，通过它来检索和显示那些文档。这种相关信息流仅由服务器向浏览器单向传送。如今的万维网与早期的万维网已经完全不同，Web上的大多数站点实际上是应用程序。它们功能强大，在服务器和浏览器之间进行双向信息传送。

Web浏览器、服务器和相关的Web应用程序都是通过HTTP相互通信的。在本期中我们将追本溯源一起剖析一下这种现代全球因特网中使用的公共语言，从而了解Web应用程序背后的基本原理。利用这些原理，人们在开源世界迸发出无限的创意，Moodle就是这样一款为教育系统构建的优秀Web应用，其背后的设计也颇具匠心。本期我们将有机会管中窥豹，从细节处领略一下这个开源Web应用的成功奥秘。

应用程序各不相同，所包含的漏洞也各不相同。许多应用程序是由开发人员独立开发的，但是很多code maker对他们所编写的代码可能引起的安全问题只是略知一二。预先了解可能发生的问题，就可以为辛勤的码农们提个醒。对于开发者来说，效率和速度都是他们的思考重点。适合的方式（组件化开发）和聪明的工具（AngularJS），会让开发Web应用变得非常简单，本期我们也将向你介绍个大概。

架构师对于很多码农来说是一种职业理想，本期码农周爱民是豌豆荚的动力引擎，也是一位真正的架构师。关于选择和架构，他有一些独到的感悟。架构师，从一个哄人的title，到知其然，再到知其所以然。这不仅是一位架构师的进化，也是中国软件业的进化。

关于Web App我们说的不够多，也不够深，只是希望聪明的码农们能够藉此吸取自己需要的养分，从管中窥到另一片天地。

专题：Web App不权威指南

Web 的基础：HTTP 原理图解

作者/ Anshu Aggarwal

Anshu Aggarwal现任1 efficiency公司的技术副总裁。他有20年编程和技术领导经验，他创过业，也进过大公司。Anshu的专长是设计、开发、部署高性能的系统。2008年，Anshu创立了Zebek公司，这是世界上第一个以对象为基础的个人化搜索引擎。随后在2011年，Zebek被Quewey收购。在此之前，Anshu在Inkomi任职，并编写了HTTP处理引擎。Anshu取得了科罗拉多大学博德分校的计算机科学博士和硕士学位，并有波士顿大学电气工程硕士学位和计算机工程的学士学位。他是《HTTP权威指南》的合著者之一。

Web 浏览器、服务器和相关的 Web 应用程序都是通过 HTTP 相互通信的。HTTP 是现代全球因特网中使用的公共语言。

本文是对 HTTP 的简要介绍。在本文中可以看到 Web 应用程序是如何使用 HTTP 进行通信的，这样就可以对 HTTP 如何完成其工作有个大概印象。我们将特别介绍以下方面的内容：

- Web 客户端与服务器是如何通信的；

- （表示 Web 内容的）资源来自何方；

- Web 事务是怎样工作的；

- HTTP 通信所使用的报文格式；

- 底层 TCP 网络传输；

- 不同的 HTTP 协议变体；

- 因特网上安装的大量 HTTP 架构组件中的一部分。

我们有很多话题要讨论，就此开始 HTTP 之旅吧。

HTTP——因特网的多媒体信使

每天，都有数以亿万计的 JPEG 图片、HTML 页面、文本文件、MPEG 电影、WAV 音频文件、Java 小程序和其他资源在因特网上游弋。HTTP 可以从遍布全世界的 Web 服务器上将这些信息块迅速、便捷、可靠地搬移到人们桌面上的 Web 浏览器上去。

HTTP 使用的是可靠的数据传输协议，因此即使数据来自地球的另一端，它也能够确保数据在传输的过程中不会被损坏或产生混乱。这样，用户在访问信息时就不用担心其完整性了，因此对用户来说，这是件好事。而对因特网应用程序开发人员来说也同样如此，因为这样就无需担心 HTTP 通信会在传输过程中被破坏、复制或产生畸变了。开发人员可以专注于应用程序特有细节的编写，而不用考虑因特网中存 在的一些缺陷和问题。

下面，就让我们来近距离地观察一下 HTTP 是如何传输 Web 流量的。

Web 客户端和服务器

Web 内容都是存储在 Web 服务器上的。Web 服务器所使用的是 HTTP 协议，因此经常会被称为 HTTP 服务器。这些 HTTP 服务器存储了因特网中的数据，如果 HTTP 客户端发出请求的话，它们会提供数据。客户端向服务器发送 HTTP 请求，服务器会在 HTTP 响应中回送所请求的数据，如图 1-1 所示。HTTP 客户端和 HTTP 服务器共同构成了万维网的基本组件。

图 1　Web 客户端和服务器

可能你每天都在使用 HTTP 客户端。最常见的客户端就是 Web 浏览器，比如微软的 Internet Explorer 或网景的 Navigator。Web 浏览器向服务器请求 HTTP 对象，并将这些对象显示在你的屏幕上。

浏览一个页面时（比如 http://www.oreilly.com/index.html），浏览器会向服务器 www.oreilly.com 发送一条 HTTP 请求（参见图1）。服务器会去寻找所期望的对象（在这个例子中就是 /index.html），如果成功，就将对象、对象类型、对象长度以及其他一些信息放在 HTTP 响应中发送给客户端。

资源

Web 服务器是 Web 资源（Web resource）的宿主。Web 资源是 Web 内容的源头。最简单的 Web 资源就是 Web 服务器文件系统中的静态文件。这些文件可以包含任意内容：文本文件、 HTML 文件、微软的 Word 文件、 Adobe 的 Acrobat 文件、 JPEG 图片文件、 AVI 电影文件，或所有其他你能够想到的格式。

但资源不一定非得是静态文件。资源还可以是根据需要生成内容的软件程序。这些动态内容资源可以根据你的身份、所请求的信息或每天的不同时段来产生内容。它们可以为你显示照相机中活生生的照片，也可以帮你进行股票交易，搜索房产数据库，或者从在线商店中购买礼物（参见图 1-2）。

图 2　所有能够提供 Web 内容的东西都是 Web 资源

总之，所有类型的内容来源都是资源。包含公司销售预测电子表格的文件是一种资源。扫描本地公共图书馆书架的 Web 网关是一种资源。因特网搜索引擎也是一种资源。

媒体类型

因特网上有数千种不同的数据类型，HTTP 仔细地给每种要通过 Web 传输的对象都打上了名为 MIME 类型（MIME type）的数据格式标签。最初设计 MIME （Multipurpose Internet Mail Extension，多用途因特网邮件扩展）是为了解决在不同的电子邮件系统之间搬移报文时存在的问题。MIME 在电子邮件系统中工作得非常好，因此 HTTP 也采纳了它，用它来描述并标记多媒体内容。

Web 服务器会为所有 HTTP 对象数据附加一个 MIME 类型（参见图3）。当 Web 浏览器从服务器中取回一个对象时，会去查看相关的 MIME 类型，看看它是否知道应该如何处理这个对象。大多数浏览器都可以处理数百种常见的对象类型：显示图片文件、解析并格式化 HTML 文件、通过计算机声卡播放音频文件，或者运行外部插件软件来处理特殊格式的数据。

图 3　与数据内容一同回送的 MIME 类型

MIME 类型是一种文本标记，表示一种主要的对象类型和一个特定的子类型，中间由一条斜杠来分隔。

- HTML 格式的文本文档由 text/html 类型来标记。

- 普通的 ASCII 文本文档由 text/plain 类型来标记。

- JPEG 格式的图片为 image/jpeg 类型。

- GIF 格式的图片为 image/gif 类型。

- Apple 的 QuickTime 电影为 video/quicktime 类型。

- 微软的 PowerPoint 演示文件为 application/vnd.ms-powerpoint 类型。

常见的 MIME 类型有数百个，实验性或用途有限的 MIME 类型则更多。附录 D 提供了一个非常完整的 MIME 类型列表。

URI

每个 Web 服务器资源都有一个名字，这样客户端就可以说明它们感兴趣的资源是什么了。服务器资源名被称为统一资源标识符（Uniform Resource Identifier，URI）。URI 就像因特网上的邮政地址一样，在世界范围内唯一标识并定位信息资源。

这是 Joe 的五金商店的 Web 服务器上一个图片资源的 URI：

http://www.joes-hardware.com/specials/saw-blade.gif

图4 显示了 URI 是怎样指示 HTTP 协议去访问 Joe 商店服务器上的图片资源的。给定了 URI，HTTP 就可以解析出对象。URI 有两种形式，分别称为 URL 和 URN。现在我们分别来看看这些资源标识符类型。

图 4　URL 说明了协议、服务器和本地资源

URL

统一资源定位符（URL）是资源标识符最常见的形式。URL 描述了一台特定服务器上某资源的特定位置。它们可以明确说明如何从一个精确、固定的位置获取资源。图4 显示了 URL 如何精确地说明某资源的位置以及如何去访问它。表1 显示了几个 URL 实例。

表 1　URL实例

URL

描　　述

http://www.oreilly.com/index.html

O'Reilly & Associates 公司的主URL

http://www.yahoo.com/images/logo.gif

Yahoo! 的Web 站点标志URL

http://www.joes-hardware.com/inventory-check.cgi?item=12731

一个查看库存条目#12731 是否有现货的程序的URL

ftp://joe:tools4u@ftp.joes-hardware.com/lockingpliers.gif

以密码保护的FTP 作为访问协议的lockingpliers.gif 图片文件的URL

大部分 URL 都遵循一种标准格式，这种格式包含三个部分。

- URL 的第一部分被称为方案（scheme），说明了访问资源所使用的协议类型。这部分通常就是 HTTP 协议（http://）。

- 第二部分给出了服务器的因特网地址（比如，www.joes-hardware.com）。

- 其余部分指定了 Web 服务器上的某个资源（比如，/specials/saw-blade.gif）。

现在，几乎所有的 URI 都是 URL。

URN

URI 的第二种形式就是统一资源名（URN）。URN 是作为特定内容的唯一名称使用的，与目前的资源所在地无关。使用这些与位置无关的 URN，就可以将资源四处搬移。通过 URN，还可以用同一个名字通过多种网络访问协议来访问资源。

比如，不论因特网标准文档 RFC 2141 位于何处（甚至可以将其复制到多个地方），都可以用下列 URN 来命名它：

urn:ietf:rfc:2141

URN 仍然处于试验阶段，还未大范围使用。为了更有效地工作，URN 需要一个支撑架构来解析资源的位置。而此类架构的缺乏也延缓了其被采用的进度。但 URN 确实为未来发展作出了一些令人兴奋的承诺。

除非特殊说明，否则本文的其余部分都会使用约定的术语，并且会不加区别地使用 URI 和 URL。

事务

我们来更仔细地看看客户端是怎样通过 HTTP 与 Web 服务器及其资源进行事务处理的。一个 HTTP 事务由一条（从客户端发往服务器的）请求命令和一个（从服务器发回客户端的）响应结果组成。这种通信是通过名为 HTTP 报文（HTTP message）的格式化数据块进行的，如图5 所示。

图 5　包含请求及响应报文的 HTTP 事务

方法

HTTP 支持几种不同的请求命令，这些命令被称为 HTTP 方法（HTTP method）。每条 HTTP 请求报文都包含一个方法。这个方法会告诉服务器要执行什么动作（获取一个 Web 页面、运行一个网关程序、删除一个文件等）。表2列出了五种常见的 HTTP 方法。

表 2　一些常见的HTTP方法

HTTP方法

描　　述

GET

从服务器向客户端发送命名资源

PUT

将来自客户端的数据存储到一个命名的服务器资源中去

DELETE

从服务器中删除命名资源

POST

将客户端数据发送到一个服务器网关应用程序

HEAD

仅发送命名资源响应中的HTTP 首部

状态码

每条 HTTP 响应报文返回时都会携带一个状态码。状态码是一个三位数字的代码，告知客户端请求是否成功，或者是否需要采取其他动作。表3显示了几种常见的状态码。

表 3　一些常见的HTTP状态码

HTTP状态码

描　　述

200

OK。文档正确返回

302

Redirect（重定向）。到其他地方去获取资源

404

Not Found（没找到）。无法找到这个资源

伴随着每个数字状态码，HTTP 还会发送一条解释性的“原因短语”文本（参见图5中的响应报文）。包含文本短语主要是为了进行描述，所有的处理过程使用的都是数字码。

HTTP 软件处理下列状态码和原因短语的方式是一样的。

200 OK
200 Document attached
200 Success
200 All's cool, dude

Web页面中可以包含多个对象

应用程序完成一项任务时通常会发布多个 HTTP 事务。比如，Web 浏览器会发布一系列 HTTP 事务来获取并显示一个包含了丰富图片的 Web 页面。浏览器会执行一个事务来获取描述页面布局的 HTML“框架”，然后发布另外的 HTTP 事务来获取每个嵌入式图片、图像面板、Java 小程序等。这些嵌入式资源甚至可能位于不同的服务器上，如图6所示。因此，一个“Web 页面”通常并不是单个资源，而是一组资源的集合。

图 6　复合 Web 页面要为每个嵌入式资源使用一个单独的 HTTP 事务

报文

现在我们来快速浏览一下 HTTP 请求和响应报文的结构。

HTTP 报文是由一行一行的简单字符串组成的。HTTP 报文都是纯文本，不是二进制代码，所以人们可以很方便地对其进行读写 1。图7显示了一个简单事务所使用的 HTTP 报文。

1 有些程序员会抱怨 HTTP 的语法解析太困难了，这项工作需要很多技巧，而且很容易出错，尤其是在设计高速软件的时候更是如此。二进制格式或更严格的文本格式可能更容易处理，但大多数 HTTP 程序员都很欣赏 HTTP 的可扩展性以及可调试性。

图 7　由一行行的简单文本结构组成的 HTTP 报文

从 Web 客户端发往 Web 服务器的 HTTP 报文称为请求报文（request message）。从服务器发往客户端的报文称为响应报文（response message），此外没有其他类型的 HTTP 报文。HTTP 请求和响应报文的格式很类似。

HTTP 报文包括以下三个部分。

- 起始行

报文的第一行就是起始行，在请求报文中用来说明要做些什么，在响应报文中说明出现了什么情况。

- 首部字段

起始行后面有零个或多个首部字段。每个首部字段都包含一个名字和一个值，为了便于解析，两者之间用冒号（:）来分隔。首部以一个空行结束。添加一个首部字段和添加新行一样简单。

- 主体

空行之后就是可选的报文主体了，其中包含了所有类型的数据。请求主体中包括了要发送给 Web 服务器的数据；响应主体中装载了要返回给客户端的数据。起始行和首部都是文本形式且都是结构化的，而主体则不同，主体中可以包含任意的二进制数据（比如图片、视频、音轨、软件程序）。当然，主体中也可以包含文本。

简单的报文实例

图8 显示了可能会作为某个简单事务的一部分发送的 HTTP 报文。浏览器请求资源 http://www.joes-hardware.com/tools.html。

图 8　http://www.joes-hardware.com/tools.html的 GET 事务实例

在图8中，浏览器发送了一条 HTTP 请求报文。这条请求的起始行中有一个 GET 命令，且本地资源为 /tools.html。这条请求说明它使用的是 1.0 版的 HTTP 协议。请求报文没有主体，因为从服务器上 GET 一个简单的文档不需要请求数据。

服务器会回送一条 HTTP 响应报文。这条响应中包含了 HTTP 的版本号（HTTP/1.0）、一个成功状态码（200）、一个描述性的原因短语（OK），以及一块响应首部字段，在所有这些内容之后跟着包含了所请求文档的响应主体。Content-Length 首部说明了响应主体的长度，Content-Type 首部说明了文档的 MIME 类型。

连接

概要介绍了 HTTP 报文的构成之后，我们来讨论一下报文是如何通过传输控制协议（Transmission Control Protocol，TCP）连接从一个地方搬移到另一个地方去的。

TCP/IP

HTTP 是个应用层协议。HTTP 无需操心网络通信的具体细节；它把联网的细节都交给了通用、可靠的因特网传输协议 TCP/IP。

TCP 提供了：

- 无差错的数据传输；

- 按序传输（数据总是会按照发送的顺序到达）；

- 未分段的数据流（可以在任意时刻以任意尺寸将数据发送出去）。

因特网自身就是基于 TCP/IP 的，TCP/IP 是全世界的计算机和网络设备常用的层次化分组交换网络协议集。TCP/IP 隐藏了各种网络和硬件的特点及弱点，使各种类型的计算机和网络都能够进行可靠地通信。

只要建立了 TCP 连接，客户端和服务器之间的报文交换就不会丢失、不会被破坏，也不会在接收时出现错序了。

用网络术语来说，HTTP 协议位于 TCP 的上层。HTTP 使用 TCP 来传输其报文数据。与之类似，TCP 则位于 IP 的上层（参见图 1-9）。

图 9　HTTP 网络协议栈

连接、IP地址及端口号

在 HTTP 客户端向服务器发送报文之前，需要用网际协议（Internet Protocol，IP）地址和端口号在客户端和服务器之间建立一条 TCP/IP 连接。

建立一条 TCP 连接的过程与给公司办公室的某个人打电话的过程类似。首先，要拨打公司的电话号码。这样就能进入正确的机构了。其次，拨打要联系的那个人的分机号。

在 TCP 中，你需要知道服务器的 IP 地址，以及与服务器上运行的特定软件相关的 TCP 端口号。

这就行了，但最初怎么获得 HTTP 服务器的 IP 地址和端口号呢？当然是通过 URL 了！我们前面曾提到过，URL 就是资源的地址，所以自然能够为我们提供存储资源的机器的 IP 地址。我们来看几个 URL：

http://207.200.83.29:80/index.html

http://www.netscape.com:80/index.html

http://www.netscape.com/index.html

第一个 URL 使用了机器的 IP 地址，207.200.83.29 以及端口号 80。

第二个 URL 没有使用数字形式的 IP 地址，它使用的是文本形式的域名，或者称为主机名（www.netscape.com）。主机名就是 IP 地址比较人性化的别称。可以通过一种称为域名服务（Domain Name Service，DNS）的机制方便地将主机名转换为 IP 地址，这样所有问题就都解决了。

最后一个 URL 没有端口号。HTTP 的 URL 中没有端口号时，可以假设默认端口号是 80。

有了 IP 地址和端口号，客户端就可以很方便地通过 TCP/IP 进行通信了。图10 显示了浏览器是怎样通过 HTTP 显示位于远端服务器中的某个简单 HTML 资源的。

图 10　基本的浏览器连接处理

步骤如下：

(a) 浏览器从 URL 中解析出服务器的主机名；

(b) 浏览器将服务器的主机名转换成服务器的 IP 地址；

(c) 浏览器将端口号（如果有的话）从 URL 中解析出来；

(d) 浏览器建立一条与 Web 服务器的 TCP 连接；

(e) 浏览器向服务器发送一条 HTTP 请求报文；

(f) 服务器向浏览器回送一条 HTTP 响应报文；

(g) 关闭连接，浏览器显示文档。

一个使用Telnet的实例

由于 HTTP 使用了 TCP/IP 传输协议，而且它是基于文本的，没有使用那些难以理解的二进制格式，因此很容易直接与 Web 服务器进行对话。

Telnet 程序可以将键盘连接到某个目标 TCP 端口，并将此 TCP 端口的输出回送到显示屏上。Telnet 常用于远程终端会话，但它几乎可以连接所有的 TCP 服务器，包括 HTTP 服务器。

可以通过 Telnet 程序直接与 Web 服务器进行对话。通过 Telnet 可以打开一条到某台机器上某个端口的 TCP 连接，然后直接向那个端口输入一些字符。Web 服务器会将 Telnet 程序作为一个 Web 客户端来处理，所有回送给 TCP 连接的数据都会显示在屏幕上。

我们用 Telnet 与一个实际的 Web 服务器进行交互。我们要用 Telnet 获取 URL http://www.joes-hardware.com:80/tools.html 所指向的文档（你可以自己尝试一下这个实例）。

我们来看看会发生什么情况。

- 首先，查找 www.joes-hardware.com 的 IP 地址，打开一条到那台机器端口 80 的 TCP 连接。Telnet 会为我们完成那些“跑腿儿”的工作。

- 一旦打开了 TCP 连接，就要输入 HTTP 请求了。

- 请求结束（由一个空行表示）之后，服务器会在一条 HTTP 响应中将内容回送并关闭连接。

例1 显示了对 http://www.joes-hardware.com:80/tools.html 的 HTTP 请求实例。我们输入的内容用粗体字表示。

例 1　一个使用 Telnet 的 HTTP 事务

% telnet www.joes-hardware.com 80
Trying 161.58.228.45...
Connected to joes-hardware.com.
Escape character is '^]'.
GET /tools.html HTTP/1.1
Host: www.joes-hardware.com

HTTP/1.1 200 OK
Date: Sun, 01 Oct 2000 23:25:17 GMT
Server: Apache/1.3.11 BSafe-SSL/1.38 (Unix) FrontPage/4.0.4.3
Last-Modified: Tue, 04 Jul 2000 09:46:21 GMT
ETag: "373979-193-3961b26d"
Accept-Ranges: bytes
Content-Length: 403
Connection: close
Content-Type: text/html

<HTML>
<HEAD><TITLE>Joe's Tools</TITLE></HEAD>
<BODY>
<H1>Tools Page</H1>
<H2>Hammers</H2>
<P>Joe's Hardware Online has the largest selection of hammers on the
earth.</P>
<H2><A NAME=drills></A>Drills</H2>
<P>Joe's Hardware has a complete line of cordless and corded drills,
as well as the latest
in plutonium-powered atomic drills, for those big around the house
jobs.</P> ...
</BODY>
</HTML>
Connection closed by foreign host.

Telnet 会查找主机名并打开一条连接，连接到在 www.joes-hardware.com 的端口 80 上监听的 Web 服务器。这条命令之后的三行内容是 Telnet 的输出，告诉我们它已经建立了连接。

然后我们输入最基本的请求命令 GET/tools.html HTTP/1.1，发送一个提供了源端主机名的 Host 首部，后面跟上一个空行，请求从服务器 www.joes-hardware.com 上获取资源 tools.html。随后，服务器会以一个响应行、几个响应首部、一个空行和最后面的 HTML 文档主体来应答。

要明确的是，Telnet 可以很好地模拟 HTTP 客户端，但不能作为服务器使用。而且对 Telnet 做脚本自动化是很繁琐乏味的。如果想要更灵活的工具，可以去看看 nc （netcat）。通过 nc 可以很方便地操纵基于 UDP 和 TCP 的流量（包括 HTTP），还可以为其编写脚本。更多细节参见 http://www.bgw.org/tutorials/utilities/nc.php^2。

2 该链接已失效，读者可以访问http://en.wikipedia.org/wiki/Netcat。（编者注）

协议版本

现在使用的 HTTP 协议有几个版本。HTTP 应用程序要尽量强健地处理各种不同的 HTTP 协议变体。目前仍在使用的版本如下。

- HTTP/0.9

HTTP 的1991 原型版本称为HTTP/0.9。这个协议有很多严重的设计缺陷，只应该用于与老客户端的交互。HTTP/0.9 只支持 GET 方法，不支持多媒体内容的 MIME 类型、各种 HTTP 首部，或者版本号。HTTP/0.9 定义的初衷是为了获取简单的 HTML 对象，它很快就被 HTTP/1.0 取代了。

- HTTP/1.0

1.0 是第一个得到广泛使用的 HTTP 版本。HTTP/1.0 添加了版本号、各种 HTTP 首部、一些额外的方法，以及对多媒体对象的处理。HTTP/1.0 使得包含生动图片的 Web 页面和交互式表格成为可能，而这些页面和表格促使万维网为人们广泛地接受。这个规范从未得到良好地说明。在这个 HTTP 协议的商业演进和学术研究都在快速进行的时代，它集合了一系列的最佳实践。

- HTTP/1.0+

在 20 世纪 90 年代中叶，很多流行的 Web 客户端和服务器都在飞快地向 HTTP 中添加各种特性，以满足快速扩张且在商业上十分成功的万维网的需要。其中很多特性，包括持久的 keep-alive 连接、虚拟主机支持，以及代理连接支持都被加入到 HTTP 之中，并成为非官方的事实标准。这种非正式的 HTTP 扩展版本通常称为 HTTP/1.0+。

- HTTP/1.1

HTTP/1.1 重点关注的是校正 HTTP 设计中的结构性缺陷，明确语义，引入重要的性能优化措施，并删除一些不好的特性。HTTP/1.1 还包含了对 20 世纪 90 年代末正在发展中的更复杂的 Web 应用程序和部署方式的支持。HTTP/1.1 是当前使用的 HTTP 版本。

- HTTP-NG（又名 HTTP/2.0）

HTTP-NG 是 HTTP/1.1 后继结构的原型建议，它重点关注的是性能的大幅优化，以及更强大的服务逻辑远程执行框架。HTTP-NG 的研究工作终止于 1998 年，编写本文时，还没有任何要用此建议取代 HTTP/1.1 的推广计划。

Web 的结构组件

在本文的概述中，我们重点介绍了两个 Web 应用程序（Web 浏览器和 Web 服务器）是如何相互发送报文来实现基本事务处理的。在因特网上，要与很多 Web 应用程序进行交互。在本节中，我们将列出其他一些比较重要的应用程序，如下所示。

- 代理

位于客户端和服务器之间的 HTTP 中间实体。

- 缓存

HTTP 的仓库，使常用页面的副本可以保存在离客户端更近的地方。

- 网关

连接其他应用程序的特殊 Web 服务器。

- 隧道

对 HTTP 通信报文进行盲转发的特殊代理。

- Agent 代理

发起自动 HTTP 请求的半智能 Web 客户端。

代理

首先我们来看看 HTTP 代理服务器，这是 Web 安全、应用集成以及性能优化的重要组成模块。

如图11所示，代理位于客户端和服务器之间，接收所有客户端的 HTTP 请求，并将这些请求转发给服务器（可能会对请求进行修改之后转发）。对用户来说，这些应用程序就是一个代理，代表用户访问服务器。

图 11　在客户端和服务器之间转发流量的代理

出于安全考虑，通常会将代理作为转发所有 Web 流量的可信任中间节点使用。代理还可以对请求和响应进行过滤。比如，在企业中对下载的应用程序进行病毒检测，或者对小学生屏蔽一些成人才能看的内容。

缓存

Web 缓存（Web cache）或代理缓存（proxy cache）是一种特殊的 HTTP 代理服务器，可以将经过代理传送的常用文档复制保存起来。下一个请求同一文档的客户端就可以享受缓存的私有副本所提供的服务了（参见图12）。

图 12　保存常用文档本地副本以提高性能的代理缓存

客户端从附近的缓存下载文档会比从远程 Web 服务器下载快得多。HTTP 定义了很多功能，使得缓存更加高效，并规范了文档的新鲜度和缓存内容的隐私性。

网关

网关（gateway）是一种特殊的服务器，作为其他服务器的中间实体使用。通常用于将 HTTP 流量转换成其他的协议。网关接受请求时就好像自己是资源的源端服务器一样。客户端可能并不知道自己正在与一个网关进行通信。

例如，一个 HTTP/FTP 网关会通过 HTTP 请求接收对 FTP URI 的请求，但通过 FTP 协议来获取文档（参见图13）。得到的文档会被封装成一条 HTTP 报文，发送给客户端。

图 13　HTTP/FTP 网关

隧道

隧道（tunnel）是建立起来之后，就会在两条连接之间对原始数据进行盲转发的 HTTP 应用程序。HTTP 隧道通常用来在一条或多条 HTTP 连接上转发非 HTTP 数据，转发时不会窥探数据。

HTTP 隧道的一种常见用途是通过 HTTP 连接承载加密的安全套接字层（SSL， Secure Sockets Layer）流量，这样 SSL 流量就可以穿过只允许 Web 流量通过的防火墙了。如图14 所示，HTTP/SSL 隧道收到一条 HTTP 请求，要求建立一条到目的地址和端口的输出连接，然后在 HTTP 信道上通过隧道传输加密的 SSL 流量，这 样就可以将其盲转发到目的服务器上去了。

图 14　隧道可以在非 HTTP 网络上转发数据（显示的是 HTTP/SSL 隧道）

Agent代理

用户 Agent 代理（或者简称为 Agent 代理）是代表用户发起 HTTP 请求的客户端程序。所有发布 Web 请求的应用程序都是 HTTP Agent 代理。到目前为止，我们只提到过一种 HTTP Agent 代理：Web 浏览器，但用户 Agent 代理还有很多其他类型。

比如，有些自己会在 Web 上闲逛的自动用户 Agent 代理，可以在无人监视的情况下发布 HTTP 事务并获取内容。这些自动代理的名字通常都很生动，比如“网络蜘蛛”（spiders）或者“Web 机器人”（Web robots）（参见图 15）。网络蜘蛛会在 Web 上闲逛，搜集信息以构建有效的 Web 内容档案，比如一个搜索引擎的数据库或者为比较购物机器人生成的产品目录。

图 15　自动搜索引擎“网络蜘蛛”就是 Agent 代理，可以从世界范围内获取 Web 页面

起始部分的结束语

这就是我们对 HTTP 的简要介绍。本文中，我们重点介绍了作为多媒体传输协议使用的 HTTP。概要说明了 HTTP 是怎样使用 URI 来命名远程服务器上的多媒体资源的，粗略介绍了如何利用 HTTP 请求和响应报文操纵远程服务器上的多媒体资源，最后考察了几种使用 HTTP 的 Web 应用程序。

《HTTP权威指南》由具有多年实践经验的专家编写，通过简洁、精确的语言和大量翔实的细节图解帮助读者形象地理解Web幕后所发生的事情，详细说明了Web上每条请求的实际运行情况。要想高效地进行Web开发，所有Web程序员、管理员和应用程序开发者都应该熟悉HTTP。很多书籍只介绍了Web的使用方式，而本书则深入说明了Web的工作原理。本文节选自《HTTP权威指南》。

“一切皆服务”时代Web 应用的安全隐患

作者/ Dafydd Stuttard

世界知名安全顾问、作家、软件开发人士。牛津大学博士，MDSec公司联合创始人，尤其擅长Web应用程序和编译软件的渗透测试。Dafydd以网名PortSwigger蜚声安全界，是众所周知的Web应用程序集成攻击平台Burp Suite的开发者。

Web应用程序安全无疑是当务之急，也是值得关注的话题。对相关各方而言，这一问题都至关重要。这里的相关各方包括因特网业务收入日益增长的公司、向Web应用程序托付敏感信息的用户，以及通过窃取支付信息或入侵银行账户偷窃巨额资金的犯罪分子。可靠的信誉也非常重要，没人愿意与不安全的Web站点进行交易，也没有组织愿意披露有关其安全方面的漏洞或违规行为的详细情况。因此，获取当前Web应用程序安全状况的可靠信息不可小视。

本文简要介绍Web应用程序的发展历程及它们提供的诸多优点，并且列举我们亲身体验过的在目前Web应用程序中存在的漏洞，这些漏洞表明绝大多数应用程序还远远不够安全。本文还将描述Web应用程序面临的核心安全问题（即用户可提交任意输入的问题），以及造成安全问题的各种因素。最后讨论Web应用程序安全方面的最新发展趋势，并预测其未来的发展方向。

Web应用程序的发展历程

在因特网发展的早期阶段，万维网（World WideWeb）仅由Web 站点构成，这些站点基本上是包含静态文档的信息库。随后人们发明了Web浏览器，通过它来检索和显示那些文档，如图1-1所示。这种相关信息流仅由服务器向浏览器单向传送。多数站点并不验证用户的合法性，因为根本没有必要这样做；所有用户同等对待，提供同样的信息。创建一个Web站点所带来的安全威胁主要与Web服务器软件的（诸多）漏洞有关。攻击者入侵Web站点并不能获取任何敏感信息，因为服务器上保存的信息可以公开查看。所以攻击者往往会修改服务器上的文件，以歪曲Web站点的内容，或者利用服务器的存储容量和带宽传播“非法软件”。

如今的万维网与早期的万维网已经完全不同，Web上的大多数站点实际上是应用程序（见图2）。它们功能强大，在服务器和浏览器之间进行双向信息传送。它们支持注册与登录、金融交易、搜索以及用户创作的内容。用户获取的内容以动态形式生成，并且往往能够满足每个用户的特殊需求。它们处理的许多信息属于私密和高度敏感的信息。因此，安全问题至关重要：如果人们认为Web应用程序会将他们的信息泄露给未授权的访问者，他们就会拒绝使用这个Web应用程序。

图 1　包含静态信息的传统Web站点

图 2　典型的Web应用程序

Web应用程序带来了新的重大安全威胁。应用程序各不相同，所包含的漏洞也各不相同。许多应用程序是由开发人员独立开发的，还有许多应用程序的开发人员对他们所编写的代码可能引起的安全问题只是略知一二。为了实现核心功能，Web应用程序通常需要与内部计算机系统建立连接。这些系统中保存着高度敏感的数据，并能够执行强大的业务功能。15年前，如果需要转账必须去银行，让银行职员帮助你完成交易。而今天，你可以访问银行的Web应用程序，自己完成转账交易。进入Web应用程序的攻击者能够窃取个人信息，进行金融欺诈或执行针对其他用户的恶意行为。

Web应用程序的常见功能

创建Web应用程序的目的是执行可以在线完成的任何有用功能。近些年出现的一些Web应用程序的主要功能有：

- 购物（Amazon）；

- 社交网络（Facebook）；

- 银行服务（Citibank）；

- Web搜索（Google）；

- 拍卖（eBay）；

- 博彩与投机（Betfair）；

- 博客（Blogger）；

- Web邮件（Gmail）；

- 交互信息（Wikipedia）。

如今，使用计算机浏览器访问的应用程序的功能越来越多地与使用智能手机或平板电脑访问的移动应用程序的功能重叠。大多数移动应用程序都通过浏览器或定制客户端与服务器进行通信，这些浏览器或客户端大多使用基于HTTP的API。应用程序功能和数据通常在应用程序用于不同用户平台的各种接口之间共享。

除公共因特网外，组织内部已广泛采用Web应用程序来支持关键业务功能。许多这类应用程序可以访问各种高度敏感的数据和功能。

- 用户可以使用HR应用程序访问工资信息、提供并接收绩效反馈，以及管理人员招聘和纪律处分程序。

- 连接关键体系架构（如Web和邮件服务器）的管理接口、用户工作站及虚拟机管理。

- 用于共享文档、管理工作流程和项目、跟踪问题的协作软件。这些功能通常涉及重要的安全和监管问题，而且组织结构大多完全依赖于它们的Web应用程序内置的控件来实现这些功能。

- 企业资源规划（ERP）软件等业务应用程序，这类应用程序以前使用专用厚客户端应用程序访问，现在则可以通过Web浏览器进行访问。

- 电子邮件之类的软件服务，这类服务最初需要独立的电子邮件客户端，现在可以通过Web接口（如Outlook Web Access）访问。

- 传统的桌面办公应用程序（如文字处理程序和电子表格）已通过Google Apps和Microsoft Office Live等服务转换为Web应用程序。

为降低成本，组织逐渐将各种任务外包给外部服务提供商来完成，因此，在上述所有示例中，我们所认为的“内部”应用程序正日益由外部机构托管。在这些所谓的“云”解决方案中，业务关键功能和数据向数目更庞大的潜在攻击者开放，而组织却越来越多地依赖于不受其控制的安全防御。

大多数计算机用户所需要的客户端软件仅仅是一个Web应用程序，这样的时代即将来临。到那时，用户使用一组共享的协议和技术即可执行各种功能，但随之也会出现各种常见的安全漏洞。

Web应用程序的优点

Web应用程序越来越流行的原因显而易见。若干技术因素已经与主要的商业动机相结合，从而引发了因特网使用方式上的重大变革。

- HTTP是用于访问万维网的核心通信协议，它是轻量级的，无须连接。这一点提供了对通信错误的容错性。应用HTTP，许多传统客户端—服务器应用程序中的服务器无须再向每一个用户开放网络连接。HTTP还可通过代理和其他协议传输，允许在任何网络配置下进行安全通信。

- 每个Web用户都在其计算机和其他移动设备上安装了浏览器。Web应用程序为浏览器动态部署用户界面，不必像以前的Web应用程序那样需要分配并管理独立的客户端软件。界面变化只需在服务器上执行一次，就可立即生效。

- 如今的浏览器功能非常强大，可构建内容丰富并且令人满意的用户界面。Web界面使用标准导航和输入控件，可保证用户即时熟悉这些功能，而不需要学习如何使用各种应用程序。应用程序可通过客户端脚本功能将部分处理交由客户端完成，必要时，可使用厚客户端组件任意扩展浏览器的功能。

- 用于开发Web应用程序的核心技术和语言相对简单。即使是初学者，也可使用现有的各种平台和开发工具，开发出强大的应用程序，还有大量开源代码和其他资源可供整合到定制的应用程序中。

Web应用程序安全

与任何新兴技术一样，Web应用程序也会带来一系列新的安全方面的漏洞。这些常见的缺陷也在“与时俱进”，一些开发人员在开发现有应用程序时未曾考虑到的攻击方式都相继出现了。由于安全意识的加强，一些问题已经得到解决。新技术的开发也会引入新的漏洞。Web浏览器软件的改进基本上消除了某些缺陷。

针对Web应用程序的最严重攻击，是那些能够披露敏感数据或获取对运行应用程序的后端系统的无限访问权限的攻击。这类倍受瞩目的攻击经常发生，但对许多组织而言，任何导致系统中断的攻击都属于重大事件。通过实施应用程序级拒绝服务攻击，可以达到与针对基础架构的传统资源耗竭攻击相同的目的。但是，实施这些攻击通常需要更精细的操作，并主要针对特定的目标。例如，可以利用这些攻击破坏特定用户或服务，从而在金融贸易、赌博、在线招投标和订票等领域赢得竞争优势。

在整个发展过程中，不时有报道知名Web应用程序被攻破的消息。情况似乎并未好转，也没有迹象表明这些安全问题已经得到解决。可以说，如今的Web应用程序安全领域是攻击者与计算机资源和数据防御者之间最重要的战场，在可预见的将来，这种情况可能仍将持续。

“本站点是安全的”

人们普遍认识到，对Web应用程序而言，安全确实是个“问题”。查询一个典型的应用程序的FAQ页面，其中的内容会向你保证该应用程序确实是安全的。

大多数Web应用程序都声称其安全可靠，因为它们使用SSL，例如：

本站点绝对安全。它使用128位安全套接层（Secure Socket Layer, SSL）技术设计，可防止未授权用户查看您的任何信息。您可以放心使用本站点，我们绝对保障您的数据安全。

Web应用程序常常要求用户核实站点证书，并想方设法让用户相信其所采用的先进加密协议无懈可击，从而说服用户放心地向其提供个人信息。

此外，各种组织还声称他们遵循支付卡行业（PCI）标准，以消除用户对安全问题的担忧。例如：

我们极其注重安全，每天扫描Web站点，以确保始终遵循PCI标准，并免受黑客攻击。下面的标志上显示了最近扫描日期，请放心访问该Web站点。

实际上，大多数Web应用程序并不安全，虽然SSL已得到广泛使用，且会定期进行PCI扫描。最近几年，我们测试过数百个Web应用程序。图1-3说明了在2007年和2011年间测试的应用程序受一些常见类型的漏洞影响的比例。下面简要说明这些漏洞。

- 不完善的身份验证措施（62%）。这类漏洞包括应用程序登录机制中的各种缺陷，可能会使攻击者破解保密性不强的密码、发动蛮力攻击或完全避开登录。

- 不完善的访问控制措施（71%）。这一问题涉及的情况包括：应用程序无法为数据和功能提供全面保护，攻击者可以查看其他用户保存在服务器中的敏感信息，或者执行特权操作。

- SQL注入（32%）。攻击者可通过这一漏洞提交专门设计的输入，干扰应用程序与后端数据库的交互活动。攻击者能够从应用程序中提取任何数据、破坏其逻辑结构，或者在数据库服务器上执行命令。

- 跨站点脚本（94%）。攻击者可利用该漏洞攻击应用程序的其他用户、访问其信息、代表他们执行未授权操作，或者向其发动其他攻击。

- 信息泄露（78%）。这一问题包括应用程序泄露敏感信息，攻击者利用这些敏感信息通过有缺陷的错误处理或其他行为攻击应用程序。

- 跨站点请求伪造（92%）。利用这种漏洞，攻击者可以诱使用户在无意中使用自己的用户权限对应用程序执行操作。恶意Web站点可以利用该漏洞，通过受害用户与应用程序进行交互，执行用户并不打算执行的操作。

图 3　我们最近测试的应用程序中出现的一些常见Web应用程序漏洞（基于100多个样本）

SSL是一种出色的技术，可为用户浏览器和Web服务器间传输的数据提供机密性与完整性保护功能。它有助于防止信息泄露，并可保证用户处理的Web服务器的安全性。但SSL并不能抵御直接针对某个应用程序的服务器或客户端组件的攻击，而许多成功的攻击都恰恰属于这种类型。特别需要指出的是，SSL并不能阻止上述任何漏洞或许多其他使应用程序受到威胁的漏洞。无论是否使用SSL，大多数Web应用程序仍然存在安全漏洞。

核心安全问题：用户可提交任意输入

与多数分布式应用程序一样，为确保安全，Web应用程序必须解决一个根本的问题。由于应用程序无法控制客户端，用户几乎可向服务器端应用程序提交任意输入。应用程序必须假设所有输入的信息都是恶意的输入，并必须采取措施确保攻击者无法使用专门设计的输入破坏应用程序，干扰其逻辑结构与行为，并最终达到非法访问其数据和功能的目的。

这个核心问题表现在许多方面。

- 用户可干预客户端与服务器间传送的所有数据，包括请求参数、cookie和HTTP信息头。可轻易避开客户端执行的任何安全控件，如输入确认验证。

- 用户可按任何顺序发送请求，并可在应用程序要求之外的不同阶段不止一次提交或根本不提交参数。用户的操作可能与开发人员对用户和应用程序交互方式做出的任何假设完全不同。

- 用户并不限于仅使用一种Web浏览器访问应用程序。大量各种各样的工具可以协助攻击Web应用程序，这些工具既可整合在浏览器中，也可独立于浏览器运作。这些工具能够提出普通浏览器无法提交的请求，并能够迅速生成大量的请求，查找和利用安全问题达到自己的目的。

绝大多数针对Web应用程序的攻击都涉及向服务器提交输入，旨在引起一些应用程序设计者无法预料或不希望出现的事件。以下举例说明为实现这种目的而提交的专门设计的输入。

- 更改以隐藏的HTML表单字段提交的产品价格，以更低廉的价格欺诈性地购买该产品。

- 修改在HTTP cookie中传送的会话令牌，劫持另一个验证用户的会话。

- 利用应用程序处理过程中的逻辑错误删除某些正常提交的参数。

- 改变由后端数据库处理的某个输入，从而注入一个恶意数据库查询以访问敏感数据。

勿庸置疑，SSL无法阻止攻击者向服务器提交专门设计的输入。应用程序使用SSL仅仅表示网络上的其他用户无法查看或修改攻击者传送的数据。因为攻击者控制着SSL通道的终端，能够通过这条通道向服务器传送任何内容。如果前面提到的任何攻击成功实现，那么不论其在FAQ中声称其如何安全，该应用程序都很容易受到攻击。

关键问题因素

任何情况下，如果一个应用程序必须接受并处理可能为恶意的未经验证的数据，就会产生Web应用程序面临的核心安全问题。但是，对Web应用程序而言，几种因素的结合使问题更加严重，这也解释了当今因特网上许多Web应用程序无法很好地解决这一问题的原因。

1. 不成熟的安全意识

近年来，人们对Web应用程序安全问题的意识有所增强，但与网络和操作系统这些发展更加完善的领域相比，人们对Web应用程序安全问题的意识还远不够成熟。虽然大多数IT安全人员掌握了相当多的网络安全与主机强化基础知识，但他们对与Web应用程序安全有关的许多核心概念仍然不甚了解，甚至存有误解。当前，在其工作中，Web应用程序开发人员往往需要整合数十、甚至数百个第三方数据包，导致他们无法集中精力研究基础技术。即使是经验丰富的Web应用程序开发人员，也经常会对所用的编程框架的安全性做出错误假设，或遇到一些对他们而言完全陌生的基本缺陷类型。

2. 独立开发

大多数Web应用程序都由企业自己的员工或合作公司独立开发。即使应用程序采用第三方组件，通常也是使用新代码将第三方组件进行自定义或拼凑在一起。在这种情况下，每个应用程序都各不相同，并且可能包含其独有的缺陷。这种情形与组织购买业内一流产品并按照行业标准指南安装的典型基础架构部署形成鲜明对照。

3. 欺骗性的简化

使用今天的Web应用程序和开发工具，一个程序员新手也可能在短期内从头开始创建一个强大的应用程序。但是，在编写功能性代码与编写安全代码之间存在巨大的差异。许多Web应用程序由善意的个人创建，他们只是缺乏发现安全问题的知识与经验。

近年来出现了一种显著趋势，即使用提供现成代码组件的应用程序框架来处理各种常见的功能，这些功能包括身份验证、页面模板、公告牌以及与常用后端基础架构组件的集成，等等。Liferay和Appfuse就属于这种类型的框架。使用这些产品可以快速方便地创建可运行的应用程序，而无须了解这些应用程序的运行机制或它们包含的潜在风险。这也意味着许多公司会使用相同的框架。因此，即使仅仅出现一个漏洞，该漏洞也将会影响许多无关的应用程序。

4. 迅速发展的威胁形势

Web应用程序攻击与防御研究发展相对不成熟，是一个正蓬勃发展的领域，其中新概念与威胁出现的速度比传统的技术要快得多。在客户端方面尤其如此，针对特定攻击的公认防御机制往往会在一些研究中失去作用，这些研究最终成就了新的攻击技巧。在项目开始之初就完全了解了当前威胁的开发团队，很可能到应用程序开发完成并部署后会面临许多未知的威胁。

5. 资源与时间限制

由于独立、一次性开发的影响，许多Web应用程序开发项目会受到严格的时间与资源限制。通常，设计或开发团队不可能雇用专职的安全专家，而且由于项目进程的拖延，往往要等到项目周期的最后阶段才由专家进行安全测试。为了兼顾各种要素，按期开发出稳定而实用的应用程序的要求往往使开发团队忽视不明显的安全问题。小型组织一般不愿多花时日评估一个新的应用程序。快速渗透测试通常只能发现明显的安全漏洞，而往往会遗漏比较细微、需要时间和耐心来发现的漏洞。

6. 技术上强其所难

Web应用程序使用的许多核心技术出现于万维网早期阶段，那时的状况与目前十分不同。从那以后，其功能已远远超越最初的设想，例如，在许多基于AJAX的应用程序中使用JavaScript进行数据传输。随着对Web应用程序功能要求的变化，用于实现这种功能的技术已远远落后于其发展要求，而开发人员还是沿用原有的技术来满足新的需求。因此，这种做法造成的安全漏洞与无法预料的负面影响也就不足为奇了。

7. 对功能的需求不断增强

在设计应用程序时，开发人员主要考虑是的功能和可用性。曾经静态的用户资源现在包含社交网络功能，允许用户上传照片，对页面进行“维基”风格的编辑。以前，应用程序设计人员可以仅仅通过用户名和密码来创建登录功能，而现今的站点则包含密码恢复、用户名恢复、密码提示，以及在将来访问时记住用户名和密码的选项。无疑，这类站点声称其能够提供各种安全功能，但实际上，这些功能不过是增大了该站点的受攻击面而已。

新的安全边界

在Web应用程序出现之前，主要在网络边界上抵御外部攻击。保护这个边界需要对其提供的服务进行强化、打补丁，并在用户访问之间设置防火墙。

Web应用程序改变了这一切。用户要访问应用程序，边界防火墙必须允许其通过HTTP/HTTPS连接内部服务器；应用程序要实现其功能，必须允许其连接服务器以支持后端系统，如数据库、大型主机以及金融与后勤系统。这些系统通常处于组织运营的核心部分，并由几层网络级防御保护。

如果Web应用程序存在漏洞，那么公共因特网上的攻击者只需从Web浏览器提交专门设计的数据就可攻破组织的核心后端系统。这些数据会像传送至Web应用程序的正常、良性数据流一样，穿透组织的所有网络防御。

Web应用程序的广泛应用使得典型组织的安全边界发生了变化。部分安全边界仍旧关注防火墙与防御主机，但大部分安全边界更加关注组织所使用的Web应用程序。Web应用程序接收用户输入的方式多种多样，将这些数据传送至敏感后端系统的方式也多种多样，这些都是一系列攻击的潜在关口，因此必须在应用程序内部执行防御措施，以阻挡这些攻击。即使某个Web应用程序中的某一行代码存在缺陷，也会使组织的内部系统易于遭受攻击。此外，随着“聚合”应用程序、第三方小部件及其他跨域集成技术的出现，服务器端安全边界常常会跨越组织本身的边界。而且，各种组织还盲目地信任外部应用程序和服务。前述有关该新的安全边界内漏洞发生几率的统计数据值得每一个组织思考。

注解　对一个针对组织的攻击者而言，获得网络访问权或在服务器上执行任意命令可能并不是他们真正想要实现的目标。大多数或者基本上所有攻击者的真实意图是执行一些应用程序级行为，如偷窃个人信息、转账或购买价格低廉的产品。而应用程序层面上存在的安全问题对实现这些目标有很大帮助。

例如，一名攻击者希望“闯入”银行系统，从用户的账户中窃取资金。在银行使用Web应用程序之前，攻击者可能需要发现公共服务中存在的漏洞，并利用其进入银行的DMZ，穿透限制访问其内部系统的防火墙，在网络上搜索确定大型计算机，破译用于访问它的秘密协议，然后推测某些证书以进行登录。但是，如果银行使用易受攻击的Web应用程序，那么攻击者可能只需修改隐藏的HTML表单字段中的一个账号，就可以达到这一目的。

Web应用程序安全边界发生变化的另一原因，在于用户本身在访问一个易受攻击的应用程序时面临的威胁。恶意攻击者可能会利用一个良性但易受攻击的Web应用程序攻击任何访问它的用户。如果用户位于企业内部网络，攻击者可能会控制用户的浏览器，并从用户的可信位置向本地网络发动攻击。如果攻击者心存恶意，他不需要用户的任何合作，就可以代表用户执行任何行为。随着浏览器扩展技术的兴起，各种插件不断增多，客户端受攻击面的范围也明显变大。

网络管理员清楚如何防止其用户访问恶意的Web站点，终端用户也逐渐意识到这种威胁。但是，鉴于Web应用程序漏洞的本质，与一个全然恶意的Web站点相比，易受攻击的应用程序至少给用户及其组织带来了一种威胁。因此，新的安全边界要求所有应用程序的所有者承担保护其用户的责任，使他们免受通过应用程序传送的攻击。

此外，人们普遍采用电子邮件作为一种补充验证机制，安全边界在一定程度上向客户端转移。当前，大量应用程序都包含“忘记密码”功能，攻击者可以利用该功能向任何注册地址发送账户恢复电子邮件，而无须任何其他用户特定的信息。因此，如果攻击者攻破了用户的Web邮件账户，就可以轻松扩大攻击范围，并攻破受害用户注册的大多数Web应用程序账户。

Web应用程序安全的未来

虽然经过约10年的广泛应用，但目前因特网上的Web应用程序仍然充满漏洞。在了解Web应用程序面临的安全威胁以及如何有效应对这些威胁方面，整个行业仍未形成成熟的意识。目前几乎没有迹象表明上述问题能够在不远的将来得到解决。

也就是说，Web应用程序的安全形势并非静止不变。尽管SQL注入等熟悉的传统漏洞还在不断出现，但已不是主要问题。而且，现有的漏洞也变得更难以发现和利用。几年前只需使用浏览器就能够轻易探测与利用的小漏洞，现在需要花费大量精力开发先进技术来发现。

Web应用程序安全的另一个突出趋势为：攻击目标已由传统的服务器端应用程序转向用户应用程序。后一类攻击仍然需要利用应用程序本身的缺陷，但这类攻击一般要求与其他用户进行某种形式的交互，以达到破坏用户与易受攻击的应用程序之间交易的目的。其他软件安全领域也同样存在这种趋势。随着安全威胁意识的增强，服务器端存在的缺陷首先应为人们所理解并得到解决，从而可以在进一步的研究过程中将注意力集中在客户端。本文描述的全部攻击类型中，那些针对其他用户的攻击是发展最快的攻击类型，也是当前许多研究的焦点所在。

技术领域的各种最新趋势在一定程度上改变了Web应用程序的安全状态。一些极具误导性的热门词汇使这些趋势深入人心，下面是一些最热门的词汇。

- Web 2.0。这一术语指更大范围地采用实现用户生成内容和信息共享的功能，以及采用各种广泛支持这一功能的技术，包括异步HTTP请求和跨域集成。

- 云计算。这一术语指更多地通过外部服务提供商来实施技术栈的各个部分，包括应用程序软件、应用程序平台、Web服务器软件、数据库和硬件。它也指在托管环境中大量采用虚拟化技术。

和技术领域的大多数变革一样，这些趋势也催生了一些新型攻击，并导致现有攻击产生变体。虽然这些趋势受到人们的大肆追捧，但鉴于其导致的各种问题，它们并不像人们最初认为的那样会带来颠覆性的改变。

尽管Web应用程序发生了所有这些改变，一些典型漏洞并未表现出任何减少的迹象。它们继续出现，方式与Web技术发展初期大致相同。这些漏洞包括业务逻辑缺陷、未能正确应用访问控制以及其他设计问题。即使在应用程序组件紧密集成及“一切皆服务”的时代，这些问题仍然会广泛存在。

小结

大约十几年的时间，万维网已由纯粹的静态信息仓库发展为功能强大的应用程序，能够处理敏感的数据并执行用于输出实际结果的高度功能化的应用程序。在这个发展过程中，多种因素造成了当前绝大多数Web应用程序所面临的安全保护不足的状况。

多数应用程序都面临一个核心安全问题，即用户可提交任意输入。用户与应用程序交互的每一个方面都可能是恶意的，而且在未能证明其并无恶意之前应该被认定为是恶意的。如果这个问题处理不当，应用程序就有可能受到各种形式的攻击。

当前Web应用程序安全状况的所有证据表明，这个问题尚未得到很好的解决，而且不管是对部署Web应用程序的组织还是对访问它们的用户而言，针对Web应用程序的攻击都是一个严重的威胁。

黑客攻防技术宝典是Web安全领域专家的经验结晶，系统阐述了如何针对Web应用程序展开攻击与反攻击，深入剖析了攻击时所使用的技巧、步骤和工具，条理清晰，内容详尽。第2版全面升级，涵盖了最新的攻击技巧与应对措施，此外，书中还列出了几百个“漏洞实验室”，以帮助读者对所学内容进行巩固和实战演习。本文节选自黑客攻防技术宝典。

Web 应用为什么要组件化开发

作者/徐飞

徐飞2005年至今一直从事企业应用前端架构，对富因特网应用有较深刻的认识，致力于前端的高效开发，研究过Backbone和AngularJS的源码，翻译过讲解AngularJS基本原理的文章，对脏数据检测和基于存取器两种监听方式的差异有深刻认识。图灵社区ID:民工精髓

为什么要做组件化？

无论前端也好，后端也好，都是整个软件体系的一部分。软件产品也是产品，它的研发过程也必然是有其目的。绝大多数软件产品是追逐利润的，在产品目标确定的情况下，成本有两个途径来优化：减少部署成本，提高开发效率。

减少部署成本的方面，业界研究得非常多，比如近几年很流行的“去IOE”，就是很典型的，从一些费用较高的高性能产品迁移到开源的易替换的产品集群，又比如使用Linux + Mono来部署.net应用，避开Windows Server的费用。

提高开发效率这方面，业界研究得更多，主要途径有两点：加快开发速度，减少变更代价。怎样才能加快开发速度呢？如果我们的开发不是重新造轮子，而是每一次做新产品都可以利用已有的东西，那就会好很多。怎样才能减少变更代价呢？如果我们能够理清模块之间的关系，合理分层，每次变更只需要修改其中某个部分，甚至不需要修改代码，仅仅是改变配置就可以，那就更好了。我们先不看软件行业，来看一下制造行业，比如汽车制造业，他们是怎么造汽车的呢？造汽车之前，先设计，把整个汽车分解为不同部件，比如轮子，引擎，车门，座椅等等，分别生产，最后再组装，所以它的制造过程可以较快。如果一辆汽车轮胎被扎破了，需要送去维修，维修的人也没有在每个地方都修一下，而是只把轮胎拆下来修修就好了，这个轮胎要是实在坏得厉害，就干脆换上个新的，整个过程不需要很多时间。

席德梅尔出过一款很不错的游戏，叫做《文明》（Civilization），在第三代里面，有一项科技研究成功之后，会让工人工作效率加倍，这项科技的名字就叫做：可替换部件（Replacement Parts）。所以，软件行业也应当引入可替换的部件，一般称为组件。

早期的前端怎么做组件化的？

在服务端，我们有很多组件化的途径，像J2EE的Beans就是一种。组件建造完成之后，需要引入一些机制来让它们可配置，比如说，工作流引擎，规则引擎，这些引擎用配置的方式组织最基础的组件，把它们串联为业务流程。不管使用什么技术、什么语言，服务端的组件化思路基本没有本质差别，大家是有共识的，具体会有服务、流程、规则、模型等几个层次。

早期展示层基本以静态为主，服务端把界面生成好，浏览器去拿来展示，所以这个时期，有代码控制的东西几乎全在服务端，有分层的，也有不分的。如果做了分层，大致结构就是下图这样：

这个图里，JSP（或者其他什么P，为了举例方便，本文中相关的服务端技术都用Java系的来表示）响应浏览器端的请求，把HTML生成出来，跟相关的JavaScript和CSS一起拿出去展示。注意这里的关键，浏览器端对界面的形态和相关业务逻辑基本都没有控制权，属于别人给什么就展示什么，想要什么要先提申请的尴尬局面。

这个时期的Web开发，前端的逻辑是基本可忽略的，所以前端组件化方式大同小异，无论是ASP还是JSP还是其他什么P，都可以自定义标签，把HTML代码和行间逻辑打包成一个标签，然后使用者直接放置在想要的地方，就可以了。

在这一时代，所谓的组件化，基本都是taglib这样的思路，把某一块界面包括它的业务逻辑一起打成一个端到端的组件，整个非常独立，直接一大块从界面到逻辑都有，而且逻辑基本上都是在服务端控制，大致结构如下图所示。

SPA时代，出现了新问题

自从Web2.0逐渐流行，Web前端已经不再是纯展示了，它逐渐把以前在C/S里面做的一些东西做到B/S里面来，比如说Google和微软的在线Office，这种复杂度的Web应用如果还用传统那种方式做组件化，很显然是行不通的。

我们看看之前这种组件化的方式，本质是什么？是展现层跟业务逻辑层的隔离，后端在处理业务逻辑，前端纯展现。如果现在还这么划分，就变成了前端有界面和逻辑，后端也有逻辑，这就比较乱了。我们知道，纯逻辑的分层组件化还是比较容易的，任何逻辑如果跟展现混起来，就比较麻烦了，所以我们要把分层的点往前推，推到也能把单独的展现层剥离出来。

如下图所示，因为实际上HTML、CSS、JavaScript这些都逐渐静态化，所以不再需要把它们放在应用服务器上了，我们可以把它们放在专门的高性能静态服务器上，再进一步发展，就可以是CDN（Content Delivery Network，内容分发网络）。前端跟后端的通信，基本都是通过AJAX来，也会有一些其他的比如WebSocket之类，总之尽量少刷新了。

在这张图里面可以看到，真正的前端已经形成了，它跟应用服务器之间形成了天然的隔离，所以也能够很独立地进行一些发展演进。

现在很多Web程序在往SPA（单页面程序，Single Page Application）的方向发展，这类系统通常比较类似传统的C/S程序，交互过程比较复杂，因此它的开发过程也会遇到一些困难。

那为什么大家要做SPA呢？它有很多明显的好处，最核心的优势就是高效。这个高效体现在两个方面：一是对于用户来说，这种方式做出来的东西体验较好，类似传统桌面程序，对于那些需要频繁操作的行业用户，有很大优势。二是运行的效率较高，之前集成一些菜单功能，可能要用iframe的方式引入，但每个iframe要独立引入一些公共文件，服务器文件传输的压力较大，还要初始化自己的一套内存环境，比较浪费，互相之间也不太方便通信，一般要通过postMessage之类的方式去交互。

有了SPA之后，比如一块界面，就可以是一个HTML片段，用AJAX去加载过来处理之后放到界面上。如果有逻辑的JavaScript代码，也可以用require之类的异步加载机制去运行时加载，整体的思路是比较好的。

很多人说，就以这样的需求，用jQuery再加一个异步js加载框架，不是很足够了吗？这两个东西用得好的话，也是能够解决一些问题的，但它们处理的并不是最关键的事情。在Web体系中，展现层是很天然的，因为就是HTML和CSS，如果只从文件隔离的角度，也可以做出一种划分的方式，逻辑放在单独的js文件里，html内部尽量不写js，这就是之前比较主流的前端代码划分方式。

刚才我们提到，SPA开发的过程中会遇到一些困难，这些困难是因为复杂度大为提升，导致了一些问题，有人把这些困难归结为纯界面的复杂度，比如说，控件更复杂了之类，没有这么简单。问题在于什么呢？我打个比方：我们在电脑上开两个资源管理器窗口，浏览到同一个目录，在一个目录里把某个文件删了，你猜猜另外一个里面会不会刷新？

毫无疑问，也会刷新，但是你看看你用的Web页面，如果把整个复杂系统整合成单页的，能保证对一个数据的更新就实时反馈到所有用它的地方吗？怎么做，是不是很头疼？代码组织的复杂度大为提高，所以需要做一些架构方面的提升。

架构的变更

提到架构，我们通常会往设计模式上想。在著名的《设计模式》一书中，刚开始就讲了一种典型的处理客户端开发的场景，那就是MVC。

传统的MVC理念我们并不陌生，因为有Struts，所以在Web领域也有比较经典的MVC架构，这里面的V，就负责了整个前端的渲染，而且是服务端的渲染，也就是输出HTML。如下图所示：

在SPA时代，这已经不合适了，所以浏览器端形成了自己的MVC等层次，这里的V已经变成客户端渲染了，通常会使用一些客户端的HTML模版去实现，而模型和控制器，也相应地在浏览器端形成了。

我们有很多这个层面的框架，比如Backbone，Knockout，Avalon，Angular等，采用了不同的设计思想，有的是MVC，有的是MVP，有的是MVVM，各有其特点。

以Angular为例，它推荐使用双向绑定去实现视图和模型的关联，这么一来，如果不同视图绑定在同一模型上，就解决了刚才所说的问题。而模型本身也通过某种机制，跟其他的逻辑模块进行协作。

这种方式就是依赖注入。依赖注入的核心理念就是通过配置来实例化所依赖的组件。使用这种模式来设计软件架构，会牺牲一些性能，在跟踪调试的便利性等方面也会有所损失，但换来的是无与伦比的松耦合和可替代性。

比如说，这些组件就可以单独测试，然后在用的时候随手引入，毫无压力。对于从事某一领域的企业来说，光这一条就足以吸引他在上面大量投入，把所有不常变动领域模型的业务代码都用此类办法维护起来，这是一种财富。

MV*框架的基本原理

如果我们来设计Angular这么一个前端框架，应当如何入手呢？很显然，逻辑的控制必须使用JavaScript，一个框架，最本质的事情在于它的逻辑处理方式。

我们的界面为什么可以多姿多彩？因为有HTML和CSS，注意到这两种东西都是配置式的写法，参照后端的依赖注入，如果把这两者视为跟Spring框架中一些XML等同的配置文件，思路就豁然开朗了。

与后端不同的是，充当前端逻辑工具的JavaScript不能做入口，必须挂在HTML里才能运行，所以出现了一个怪异的状况：逻辑要先挂在配置文件（HTML）上，先由另外的容器（浏览器或者Hybird的壳）把配置文件加载起来，然后才能从某个入口开始执行逻辑。好消息是，过了这一步，逻辑层就开始大放异彩了。

从这个时候开始，框架就启动了，它要做哪些事情呢？

- 初始化自身（bootstrap）

- 异步加载可能尚未引入的JavaScript代码（require）

- 解析定义在HTML上的规则（template parser）

- 实例化模型（scope）

- 创建模型和DOM的关联关系（binding, injection）

这些是主线流程，还有一些支线，比如：

- 解析url的search字符串，恢复状态（route）

- 加载HTML部件模板（template url）

- 部件模板和模型的关联（binding）

如何做组件化

HTML的组件化

SPA的一个典型特征就是部分加载，界面的部件化也是其中比较重要的一环。界面片段在动态请求得到之后，借助模版引擎之类的技术，经过某种转换，放置到主界面相应的地方。所以，从这个角度来看，HTML的组件化非常容易理解，那就是界面的片段化和模板化。

JavaScript的组件化

JavaScript这个部分有好几个发展阶段。

- 早期的共享文件，把公共功能的代码提出出来，多个页面共用

- 动态引用，消灭全局变量

- 在某些框架上进一步划分，比如Angular里面又分为provider，service，factory，controller

JavaScript组件化的目标是什么呢，是清晰的职责，松耦合，便于单元测试和重复利用。这里的松耦合不仅体现在js代码之间，也体现在js跟DOM之间的关系，所以像Angular这样的框架会有directive的概念，把DOM操作限制到这类代码中，其他任何js代码不操作DOM。

如上图所示，总的原则是先分层次，层内再作切分。这么做的话，不再存在之前那种端到端组件了，使用起来没有原先那么方便，但在另外很多方面比较好。

CSS的组件化

这方面，业界也有很多探索，比如LESS，SASS，Stylus等。为什么CSS也要做组件化呢？传统的CSS是一种扁平的文本结构，变更成本较高，比如说想要把结构从松散改紧凑，需要改动很多。如果把实际使用的CSS只当作输出结果，而另外有一种适合变更的方式当作中间过程，这就好多了。比如说，我们把一些东西定义成变量，每个细节元素使用这些变量，当需要整体变更的时候，只需修改这些变量然后重新生成一下就可以了。

开源Web 应用Moodle架构细节分析

作者/ Tim Hunt

Tim以前是一位数学家，获得剑桥大学非线性力学的博士学位。他现在是英国Milton Keynes开放大学的首席软件开发者，致力于开发基于Moodle的教学系统。2008到2009年间，他在位于澳大利亚的Moodle总部工作了一年。他的博客：http://tjhunt.blogspot.com

译者/ 李诗剑

现就读于英国牛津大学读研究生，毕业于上海交通大学，不善言辞。图灵社区ID：速冻沙漠

Moodle是一款为教育系统设计的Web应用。我会对Moodle各个部分如何运作做一个综述，同时我将专注于介绍几个我认为特别有趣的设计：

1. 用插件分割应用的方法；

2. 权限系统 —— 它控制着什么用户可以在系统不同的地方做什么事情；

3. 产生输出的方式 —— 它使得应用不同的主题来更改外观，并且把界面接口分离出来；

4. 数据库抽象层。

什么是Moodle?

Moddle 提供了一个师生之间的在线教学平台。一个Moodle站点被划分为不同的课程。特定的用户可以以不同的身份参与到一门课程中去，比如学生或者老师。每一门课程都由一系列的资源和活动组成。一个资源可以是一份PDF文档，Moodle的一个HTML页面，或者干脆是一个指向网络上其他位置的链接。一个活动可能是一个论坛，一次测验，或者是一个Wiki。在一门课程中，这些资源和活动以某种方式被组织起来。例如，它们可以按照逻辑上的话题，或者是日程上特定的周目被分配到一起。

图 1　Moodle课程

图 2　大学系统典型架构

Moodle可以作为一个单独的应用来使用。比如说，如果你只是想为软件架构课程构建一个网站，你只要在你的网站托管那里下载Moodle并且安装它，而后创建课程，等待学生自己来注册就可以了。或者说，你为一个庞大的机构工作，Moodle可以仅仅成为你所运行的众多系统中的一个。此时你很有可能已经拥有了：

- 一个管理跨系统的用户帐号身份认证服务（例如使用LDAP）。

- 一个学生信息系统。它就是一个庞大的数据库，里面记载着所有的学生信息，包括他们当前正在进行的课程，以及以后需要完成的课程；还有他们的笔记，这份笔记可以是他们对一门所完成课程的高度总结。当然，这个信息系统也可以提供其他的管理功能，比如跟踪一个学生是否上缴了学费。

- 一个文档库（比如使用 Alfresco）。它用来存储文件，以及跟踪用户合作维护文件时的工作流。

- 一个电子档案袋（ePortfolio）。 学生可以在这里存放他们自己的资料(assets)并且把它们组合起来形成其他文档。例如利用这些资料编写一篇CV（简历），或者用来证明档案所有者已经满足了一门实践课的选修条件。

Moodle专注于为所有参与到教学中的人提供一个在线平台，而不是为某一个教育组织特别设计的某个系统。Moodle仅仅为非主要功能提供了最基本的实现，所以它可以单独的作为一个应用，或者与其它系统进行集成。Moodle扮演的角色被正式地称为虚拟教学环境（VLE），或者是教学/课程管理系统（LMS，CMS，甚至LCMS）。

Moodle是一个用PHP编写的开源免费软件(GPL)。它可以在绝大多数的Web服务器和平台上运行。它需要一个数据库，目前支持MySQL，PostgreSQL，MS SQL Server以及Oracle。

Moodle项目由Martin Dougiamas在1999年开创，当时他正在澳大利亚的科廷大学工作。1.0版本于2002年发布，当时使用的语言和数据库版本是PHP 4.2和MySQL 3.23。那时的版本从一开始就限制了Moodle可能采用的框架。然而从那以后，整个软件发生了翻天覆地的变化。现在发布的版本是Moodle 2.2.x系列。

插件

和许多其它成功的开源项目一样，Moodle由许多和系统内核协同工作的插件构建起来。这是一个绝妙的主意，因为它可以使用户按照他们定制的方法来增强Moodle的功能。一个开源系统的重要优势在于，你可以根据自己的特定需求来更改它。然而，为代码增加高可定制性的同时，会在系统升级的时候引入大麻烦，即使我们已经采用了很好的版本控制系统。Moodle的插件通过定义好的API与内核交互，所以在自包含的插件中，可以允许尽可能多的用户定制与新特性被开发出来。这也方便了用户根据需求定制自己的Moodle，分享这些定制内容，同时也便于对Moodle系统内核进行升级。

有许多不同的方法可以将一个系统构建成插件化的。Moodle具有一个相对庞大的内核，并且插件是强类型的。我所说的相对庞大的内核，指的是内核提供了大量的功能。这违反了那类，由一个小型的插件启动器进行引导，其余部分都是插件的架构设计。

当我提及插件是强类型的时候，我指的是根据你想要实现的具体功能，你可能需要写完全不同的插件，实现不同的API。比如，一个新的活动模块插件会与一个新的认证插件，或者是提问插件截然不同。根据最后统计，现在我们一共有35种不同的插件（这里有一个Moodle插件类型完全列表）。这违背了那类，所有插件通过使用最基本的API，通过注册它们感兴趣的钩子和事件与内核进行交互的架构设计。

通常来说，Moodle现在有尝试把更多的功能移到插件中以减小内核的趋势。可是这并没有带来巨大的成功，因为当前一个逐渐增长的特性集趋于去扩展内核。另一个趋势是尽可能将不同种类的插件进行规范化。这样在许多公共功能上，比如安装和升级，所有类型的插件都能够按照统一的方式运行。

一个Moodle中的插件其实就是一个包含许多文件的目录。每一个插件都有一个类型和名字，这两个构成了这个插件的"Frankenstyle"组件名称。（"Frankenstyle"这个单词出自于开发者Jabber频道的一次讨论，人人都爱它，所以这个单词就被固定下来了）插件的类型和名字决定了这个插件目录的路径。插件类型给定一个前缀，目录名称就是这个插件的名字。这里有一些例子：

插件类型

插件名称

Frankenstyle

目录

mod (Activity module)

forum

mod_forum

mod/forum

mod (Activity module)

quiz

mod_quiz

mod/quiz

block (Side-block)

navigation

block_navigation

blocks/navigation

qtype (Question type)

shortanswer

qtype_shortanswer

question/type/shortanswer

quiz (Quiz report)

statistics

quiz_statistics

mod/quiz/report/statistics

最后的一个例子表明了每一个活动模块被允许声明子插件类型。只有活动模块才能做到这个，出于两点原因。首先如果所有的插件都可以声明子插件类型，这或许会带来严重的性能问题。另外活动模块是Moodle中最重要的教育活动，也是插件中最重要的类型，所以它们应该具有特殊的权限。

示例插件

我会以一个具体的插件实例来解释Moodle架构中的大量细节。作为惯例，我选择实现一个显示"Hello world"的插件。

这个插件实际上并不适合任何一种Moodle标准插件。它只是一个简单的脚本，和其他任何东西都没有联系，所以我选择把它制作成一个'local'类型的插件。这是一个catch-all的插件类型，专门处理一些杂乱的功能，所以在这里再适合不过了。我给我的插件命名为greet，所以它的Frankenstyle的名字是local_greet，路径为local/greet。（插件代码下载）

每一个插件都必需包含一个叫做version.php的文件，这个文件定义了关于这个插件本身的元数据。Moodle的插件安装系统会使用它来对插件进行安装和升级。例如local/greet/version.php包含代码：

<?php
$plugin->component    = 'local_greet';
$plugin->version      = 2011102900;
$plugin->requires     = 2011102700;
$plugin->maturity     = MATURITY_STABLE;

因为可以从路径上显然地推导出插件的名字，所以乍看之下代码里面包含组件名（component name）略显多余。而实际上，安装器需要通过组件名来验证插件是否安装在正确的位置上。版本（Version）字段定义了这个插件的版本，成熟度(Maturity）是诸如ALPHA，BETA，RC（发布候选版, release candidate）, 或者STABLE这样的标签。Requires字段标识着能和这个版本兼容的Moodle最低版本号。必要的话，你也要记录下这个插件依赖的其他插件。

这里是这个简单插件的主要脚本（存储在local/greet/index.php）：

<?php
require_once(dirname(__FILE__) . '/../../config.php');        // 1

require_login();                                              // 2
$context = context_system::instance();                        // 3
require_capability('local/greet:begreeted', $context);        // 4

$name = optional_param('name', '', PARAM_TEXT);               // 5
if (!$name) {
$name = fullname($USER);                                  // 6
}

add_to_log(SITEID, 'local_greet', 'begreeted',
'local/greet/index.php?name=' . urlencode($name));        // 7

$PAGE->set_context($context);                                 // 8
$PAGE->set_url(new moodle_url('/local/greet/index.php'),
array('name' => $name));                                  // 9
$PAGE->set_title(get_string('welcome', 'local_greet'));       // 10

echo $OUTPUT->header();                                       // 11
echo $OUTPUT->box(get_string('greet', 'local_greet',
format_string($name)));                                   // 12
echo $OUTPUT->footer();                                       // 13

Line 1：引导Moodle

require_once(dirname(__FILE__) . '/../../config.php');        // 1

这单独的一行是大多数工作都要首先完成的。我之前说过，config.php包含着Moodle如何连接数据库以及找到metadata目录的细节。然后，它以一行require_once('lib/setup.php')结束。这样：

1. 通过require_once加载所有Moodle标准库；

2. 开始处理会话；

3. 连接数据库；

4. 初始化一系列全局变量，我们一会就将看到它们。

Line 2：检查用户是否登录

require_login();

这行使得Moodle利用管理员配置过的任何认证插件来判断，当前访问用户是否已经登录。

一个与Moodle整合性更好的插件会在这里传递更多的参数，比如这个页面属于哪个课程或者活动。然后调用的require_login仍然会检查是否当前用户是否参加了这门课程或者活动。如果是，用户就可以访问这门课程，或者观看这个活动；如果不是，那么适当的错误信息会被显示出来。

Moodle中的角色和权限系统

接下来的两行代码显示出如何检查用户是否有做某件事的权限。正如你所见，从开发者的角度来说，这些API都十分的简单。但是，实际上在这下面是一个非常复杂的接入系统。这会给管理员很大的伸缩性，以控制什么人可以做什么。

Line 3: 获得上下文

$context = context_system::instance();                        // 3

在Moodle中，同一个人可能在不同的地方拥有不同的权限。比如说一个用户可能在某个课程上做一名老师，也可能是另一门课程的一位学生。这些地方被称作为上下文（context）。上下文在Moodle中构筑了一个特别像文件系统中目录结构那样的多层结构。

在系统的上下文中，有许多的上下文信息被构造出来，它们负责维护那些为了组织课程而被创建的不同分类(Category)。这些上下文可以是嵌套的，比如在一个分类里面包含有其他更多的分类。分类上下文同时也包含着课程上下文。最后，每一个课程中的活动也会拥有一个自己的Moodle上下文。

图 3　上下文

Line 4: 检查用户是否有权执行这个脚本

require_capability('local/greet:begreeted', $context);        // 4

现在我们获得了上下文 —— 与Moodle相关的领域 —— 就可以检查权限了。某个用户能否执行某个功能的信息被称作一个能力（Capability）。基于能力的检查可以提供比简单的require_login检查更加细粒度的访问检查。我们这个简单的插件，只有一个能力：local/greet:begreeted。

这个检查通过require_capability函数来完成，不过这需要这个能力的名字以及当前的上下文。就像其他require_...函数一样，如果用户没有这个能力，它不会正常返回，而是显示一个错误。在其他地方，非致命的has_capability函数，当可用的时候返回true，比如要不要在另一个网页上添加对于当前这个脚本的一个链接。

那么管理员是如何配置什么用户拥有什么权限的呢？这是在has_capability函数中通过计算得到的（至少理论上是这样）：

1. 从当前上下文开始；

2. 获得这个用户在当前上下文中所扮演的所有角色；

3. 计算出在当前上下文中，每一个角色所拥有的权限；

4. 将这些权限整合起来获得一个最终的结果。

脚本的下一个部分解释了一些繁杂的事情：

Line 5：从请求中获得数据

$name = optional_param('name', '', PARAM_TEXT);               // 5

每个网络应用都会做的一件事情就是，把数据从请求中获取出来（GET或者POST变量），而不会产生SQL注入和跨站脚本攻击。Moodle提供了两种方法来完成这件事。

上面那行代码就是一个简单的方法。它通过一个参数名（在这里的name），一个缺省值，以及一个期望类型来获得一个单独的值。期望类型用来清理掉所有带有非法字符的输入。我们定义了许多类型，诸如PARAM_INT，PARAM_ALPHANUM，PARAM_EMAIL等等。

这里你也可以用类似required_param这样的函数。这些require...函数如果发现期望的参数没有找到时会停止执行并显示一个错误信息。

另一个Moodle从请求中获得数据的机制是一个非常成熟的库。它给PEAR的HTML QuickForm库包了一层。（对于非PHP程序员来说，PEAR在PHP中就相当于CPAN）这在刚开始下决定的时候似乎是一个好主意，但是现在我们已经不维护它了。或许在未来的某一天，我们会迁移到一个新形式的库中，就像我们中许多人一直想做的，因为QuickForm有一些恼人的设计问题。不过现在，这个机制就已经足够了。表单就是一个字段的集合（比如text box，select drop-down，date-selector），每一个字段可能具有不同的类型用于前端和后端的验证（包括使用PARAM_...类型）。

Line 6：全局变量

if (!$name) {
$name = fullname($USER);                                  // 6
}

这一小段代码展示了Moodle提供的第一个全局变量。$USER保存关于执行当前脚本的用户信息。其他的全局变量包括：

- $CFG：保存常用的配置

- $DB：数据库连接

- $SESSION：封装了PHP会话（session）

- $COURSE：当前请求相关的课程

还有一些其他的，我们会在下面见到它们。

你或许觉得“全局变量”这个词十分恐怖。然而，请注意，PHP每次只处理一个请求，所以这些变量根本就不是全局的。实际上，PHP的全局变量可以被视为线程安全的注册表模式（请参看Martin Fowler的《企业级应用架构模式》），这也是Moodle使用它们的原因。让最常用的对象始终可见是非常方便的，因为你不需要把它们作为参数传入到每一个函数和方法中去。这个方法很少被滥用。

Line 7：日志

add_to_log(SITEID, 'local_greet', 'begreeted',
'local/greet/index.php?name=' . urlencode($name));    // 7

Moodle所有的关键动作都会被日志记录下来。日志被写到数据库的一个表中。这是一个折中的办法。这个方法使得复杂的分析变得容易，而且Moodle基于这些日志也提供了很多翔实的报告。但是对一个大规模、高访问量的网站来说，这却带来了一个性能问题。日志表非常大，这使得数据库备份变得异常困难，而且对于日志的查询也非常的慢。在日志表上还存在者写入竞争。这些问题可以通过不同的方法得以缓解，比如批量写，存档或者删除旧的记录，把它们从主数据库中移除。

产生输出（页面生成）

输出主要通过两个全局对象来处理：

$PAGE->set_context($context);                                 // 8

Line 8：全局变量$PAGE

$PAGE存储着要被输出的页面信息。这个信息在所有产生HTML的代码中都可以轻易获得。在这个脚本中，必须明确指明当前的上下文是什么。（在其他的情况下，它或许已经通过require_login被自动设置了）这个页面的URL也必须被明确。这看起来似乎很没必要，但是需要它的合理性在于你或许会使用不同的URL来获取同一个页面。如果你喜欢的话，传递给set_url的URL必须是一个这个页面的规范化URL —— 一个永久链接。页面的标题也要被设置。这样HTML的head元素就被构建出来了。

Line 9：Moodle URL

$PAGE->set_url(new moodle_url('/local/greet/index.php'),
array('name' => $name));                              // 9

顺便说一句，上面用过的add_to_log并没有使用这个辅助类。确实，日志API不能够接受moodle_url对象。这种不一致性是一个像Moodle一样老的code-base^1的典型特征。

1 译者注：code-base通常是指那些由人力产生的代码，许多自动生成的代码不算，比如由配置文件通过工具生成的代码就不是code-base。一般code-base的代码才有用版本控制的价值。

Line 10：国际化

$PAGE->set_title(get_string('welcome', 'local_greet'));       // 10

Moodle使用自己的系统来支持多语言。或许现在有许多的PHP国际化库，但是在它第一次被实现的2002年当时，没有任何一个可用的库能够完成这个任务。整个系统基于get_string函数。字符串被一个键和插件的Frankenstyle名字唯一确定。就像你在第12行看到的，完全可以把值插入到字符串中。（多值在PHP中通过数组和对象来处理）

字符串会在一个语言文件中被查找，这个语言文件里面其实就是一个PHP数组。这里有一个我们插件的语言文件local/greet/lang/en/local_greet.php：

<?php
$string['greet:begreeted'] = 'Be greeted by the hello world example';
$string['welcome'] = 'Welcome';
$string['greet'] = 'Hello, {$a}!';
$string['pluginname'] = 'Hello world example';

注意到，除了两个我们脚本中用到的字符串，这里还给某个能力了一个名字，还有这个插件显示在用户界面上的名字。

不同语言由两个字母的国家码唯一确定（这里的en）。语言包或许衍生于其他的语言包。比如说fr_ca（加拿大法语）语言包声明了fr（法语）作为它的母语言，所以它只需要定义不同于法语的部分。因为Moodle诞生在澳大利亚，en意味着是英式英语，并且en_us（美式英语）从它衍生过来。

同样，这个看似简单的get_stringAPI把巨大的复杂性从插件开发者面前隐藏起来，包括计算出当前的语言（这可能由用户的偏好，或者特定课程的设置来决定）以及搜索语言包以及所有母语言包来找到这个字符串。

语言包制作以及协同翻译在[http://lang.moodle.org]上管理，Moodle用它们制作了一个可定制插件（local_amos）。它使用Git和数据库作为存储语言文件的后端，保留了所有的历史版本。

Line 11：开始输出

echo $OUTPUT->header();                                       // 11

这又是一个看似平淡无奇的一行，然而它所做的工作可比看起来多得多。这里最关键的一点在于，在任何的输出之前，页面所采用的主题（皮肤）必须被计算出来。这取决于页面上下文以及用户偏好的组合。然而，$PAGE->context只在第8行被设置，所以$OUTPUT全局变量不能在脚本的一开始就初始化。为了解决这个问题，我们使用了一些PHP的小技巧，根据$PAGE的信息，在第一次调用输出方法的时候才构造合适的$OUTPUT。

另一件需要考虑的事情是，Moodle中的每一个界面都有可能包含块（blocks）。这些块是一部分可以额外配置的内容，通常被显示在主要内容的左侧或者右侧。（它们是一类插件）同时，到底哪些特定的块需要被显示出来，通过一种弹性的方式（管理员可控），由页面的上下文和其他页面的标识来决定。所以，输出的另一个准备工作就是调用$PAGE->blocks->load_block()。

当所有必要的信息都被准备好了之后，主题插件（控制页面的整体外观）被调用以产生页面的整体布局，包括任何标准需要的头部和页脚。这个调用同时也负责在HTML中对应的位置填入块中的内容。在布局的中间，会有一个div，这个页面特定的内容会显示在这里。当HTML的布局产生之后，在主要内容的div上一切两半。在第一半完成后，其他的部分被存储起来，由$OUTPUT->footer()返回。

Line 12：输出页面Body

echo $OUTPUT->box(get_string('greet', 'local_greet',
format_string($name)));                               // 12

这一行输出了整个页面的主体。这里仅仅把我们的问候显示在一个盒子里。这一句问候，同样，是一个本地化过的字符串，因为这时我们已经用了一个值替换掉了占位符。内核渲染器$OUTPUT提供了许多像box这样方便的方法，以高级术语来描述我们所需要的输出。不同的主题可以控制什么样的HTML元素真正地被用来构建这个盒子。

首先输出的内容是通过format_string函数处理过的用户的信息（$name）。这是XSS（Cross-Site Scripting，跨站脚本攻击）保护的另一部分。另外这也使文本过滤器产生作用。使用过滤器的一个例子就是LaTex过滤器，它把像$$x + 1$$这样的输入转换成一个公式的图片。我会简单的提到，但是不会进行详细的解释，实际上，这里有三个不同的函数（s，format_string和format_text）进行字符串处理。具体使用哪个取决于输出内容的具体类型。

Line 13：结束输出

echo $OUTPUT->footer();                                       // 13

最后，页脚被输出。这个例子并没有显示出来，但是Moodle会记录所有这个页面需要的JS文件，然后把它们都添加到页脚上。这是一个经典的好实现。这样用户就可以先看到页面，而不必等待所有的JS加载完成。一个开发者可以像$PAGE->requires->js('/local/greet/cooleffect.js')这样用API添加JS 。

数据库抽象

这个"Hello World"脚本太简单了，所以它根本不需要进行数据库访问。然而，一些Moodle的库调用确实进行了一些数据库查询。现在，我简单地介绍一下Moodle的数据库层。

在过去，Moodle的数据抽象层基于ADOdb库，但是这给我们带来了种种麻烦。并且这个库的代码中多出来了额外的一层，对我们的性能产生了严重的影响。所以，在Moodle 2.0中，我们将之转换到我们自己的数据抽象层，它只不过把不同PHP的数据库库封装起来。

moodle_database类

整个库的核心是moodle_database类。它定义了$DB全局变量提供的用于连接数据库的接口。一个典型的用法是：

$course = $DB->get_record('course', array('id' => $courseid));

把它翻译成SQL语句就是：

SELECT * FROM mdl_course WHERE id = $courseid;

这将返回一个公开属性的PHP对象，所以你可以简单地像$course->id，$course->fullname等等来获取相应的属性。

这样简单的方法可以处理最基本的查询、更改和插入。有些时候，做一些更加复杂的SQL查询是很必要的，比如生成报告。在这样的情况下，有许多方法来执行任意的SQL语句：

$courseswithactivitycounts = $DB->get_records_sql(
'SELECT c.id, ' . $DB->sql_concat('shortname', "' '", 'fullname') . ' AS coursename,
COUNT(1) AS activitycount
FROM {course} c
JOIN {course_modules} cm ON cm.course = c.id
WHERE c.category = :categoryid
GROUP BY c.id, c.shortname, c.fullname ORDER BY c.shortname, c.fullname',
array('categoryid' => $category));

这里有几点需要注意：

- 表名需要被{}包起来，这样库函数才能找到它们，并且加上前缀。

- 库函数使用占位符来将值填入SQL语句中。在某些场合，我们使用了底层数据库驱动的功能。在其他情况下，这些值必须通过转译，然后利用字符串操作将其插入到SQL语句中。库函数支持两种填充方法，一种使用命名占位符（就像上面那样），还有一种使用?作为占位符的匿名方式。

- 为了让查询在我们所有支持的数据库中都得以实现，只有标准SQL中的一个安全子集被筛选出来以供使用。比如，你能看到我使用了关键字AS来对列名作别名处理，但是从来没有对表名的别名。这两个用法规则都十分的重要。

- 即使是这样，仍然有一些情况，没有任何一个标准SQL的子集可以在所有我们需要支持的数据库上都能运作。比如说，每一种数据库都用完全不同的方式来处理字符串拼接。在这些时候，我们提供一些兼容功能来产生正确的SQL语句。

定义数据库结构

另一个数据库系统之间差异很大的地方就是，创建表的SQL语法。为了克服这个问题，每一个Moodle插件（包括Moodle内核）都在一个XML文件中定义了需要的数据库表。Moodle安装系统会解析install.xml文件，并且利用它们包含的信息来创建所需的表和索引。有一个Moodle内建的叫做XMLDB的工具，它可以用来帮助开发者创建和编辑这些安装文件。

如果在两个不同的Moodle发布版（或者是一个插件）中数据库结构需要更改，那么开发者就需要负责编写代码（使用一个额外提供DDL方法的数据库对象）来更新这些数据库结构，同时必须保持所有的用户数据。所以，Moodle总是在版本升级的时候总是进行自我更新，简化了管理员的维护成本。

另一个有争议的地方是，鉴于Moodle最开始使用的是MySQL 3这个版本的事实，Moodle数据库没有使用外键。这就有可能使得许多容易产生BUG的行为很难被检测到，但是现代数据库却很容易检测到它们。困难在于，我们的用户不用外键使用Moodle站点已经很多年了，所以现在几乎肯定有数据不一致性存在。如果现在要添加这些键，不进行一次非常困难的清理工作是不可能的。尽管如此，自从XMLDB系统加入到Moodle 1.7（在2006年！）以来，这些install.xml文件已经规定了外键可以存在的定义。我们始终希望，总有一天，通过必要的工作，可以允许我们在安装过程中创建这些键。

经验教训

一个关于Moodle非常有趣的话题是，Moodle实际上产生于一个科研项目。Moodle使得（但是并不强制）社会性构建教育法成为可能。它的意思是说，我们最好的学习方式是实实在在地创造一些东西，并且从社区中其他人那里获得知识。Martin Dougiamas的PhD答辩并没有回答这种模式对于教育是否有效，但是却肯定了它作为运作一个开源项目的方法是十分有效的。也就是说，我们可以把整个Moodle项目本身就看作一个建造和使用一个VLE的尝试。同时，我们尝试把Moodle作为一个社区来构建和使用，看看我们的教师、开发者、管理员和学生是否能在其中互相学习呢？我发现这是一个思考开源软件项目开发的绝妙模型。开发者和用户相互学习的主要场所，其实就是Moodle项目的论坛和BUG数据库。

《开源软件架构：卷 1》

《开源软件架构：卷 2》

本文选自《开源软件架构》，欢迎加入该翻译计划！在建筑师的培养过程中，他们要看上千座建筑，并研究大师们对这些建筑的评议。但在软件行业，开发者却缺乏这样的经验，使得他们无法利用彼此的成功，只能重复彼此的错误。这两本书恰恰提供了一个了解历史的机会。48 位开源软件作者站出来，解说他们的软件是如何架构的，以及为什么这么做。更多开放书翻译计划，所涉书籍采用 Creative Commons Attribution 3.0 Unported 署名 3.0 Unported (CC BY 3.0) 许可发布。

Google性能专家Ilya Grigorik：速度是我们的核心信条

Ilya Grigorik

Ilya Grigorik 是谷歌“Web加速”（Make The Web Fast）团队的性能工程师、开发大使。他每天的主要工作就是琢磨怎么让Web应用速度更快，总结并推广能够提升应用性能的最佳实践。在加入Google之前，他创立了一家研究实时用户行为分析的公司PostRank。PostRank成立4年后被Google Analytics团队收购。Ilya Grigorik著有《Web性能权威指南》，另外，他还在Udacity上教授一门公开课：The Critical Rendering Path。

你是如何变成一个开发者的？你又是如何成为一位web性能工程师的？

我是先变成“搞web的人”然后才成为一位开发者的，也就是说，建造和设计网页把我一点点带到了这个领域。首先，只是学学HTML，CSS，再加上一点JavaScript，随着时间累积，我开始越来越深入地学习web相关知识，然后我又逐渐开始学习计算机科学。而剩下的，就像他们所说，已经成为了历史。

性能是一直以来让我着迷的东西，我想一部分原因在于要想谈论性能，你必须要建立一种敏锐的直觉，要知道所有的东西是如何在一起工作的——我不喜欢黑盒子，当我能用简单的词语解释这一切是如何发生的时候，我感觉到深深的满足感。

你写《Web性能权威指南》的动力和原因是什么？

当我学习web性能的时候我曾希望能有这样一本书。关于网络栈 （TCP, HTTP, 等等）的具体部件已经有很多书了，但是我无法找到一本能把所有这些部分拼合在一起，并且以讨论如何交付快速浏览器体验为背景的书：TCP是如何影响页面性能的，HTTP 1.x 的局限性在哪里，我们为什么需要HTTP 2.x，不同浏览器API （XHR, EventSource, WebSocket, 等等）之间有什么不同，诸如此类的问题。

另外，我知道在我写作的时候我能学习得最好，因为写作迫使我梳理思绪。在HPBN上工作的经历让我弄明白了很多我曾经忽视的重要细节，我可以重新学习我自认为已经理解（实际上并没有）的课题，并且学习很多新材料。我希望对于任何拿起这本书的人都是这样。

Google是HTTP2.0的积极倡导着。你认为部署HTTP 2.0的主要困难是什么？

随着更多的服务商和供应商接受了这个标准，部署HTTP/2每天都会变得更加简单。今天最大的障碍就剩下升级老旧基础设施，让它们可以接受新的协议，但是这个问题终将会被解决。这也是IETF一直以来紧密推动标准的原因：业界会藉此获得很大的利益，所有人都想要去实现一个稳定并且大家都能接受的协议。

WebRTC什么时候才能在移动设备上成熟地应用？Google计划在未来如何应用这个技术？

1B+设备上已经启用了WebRTC！我不能透露各种Google产品的具体计划，但是你肯定可以想象很多团队都在寻找能够利用这些新能力的办法。这就是说，WebRTC更让人着迷的方面并不仅仅是Google计划如何使用它，而是日常开发人员现在将如何构建和部署那些有条件出现的应用！你现在只需要用浏览器里的API来建立一个P2P会话就可以交换音频、视频，以及数据。这是web平台的重要一步！

Chrome并不是最快的浏览器，但是却有最大的市场占有率，这是为什么？这意味着速度并不是最重要的因素吗？

确实，Chrome并不是最快的，这是个bug，如果你有具体的例子，请告知我们——在crbug.com上提出bug！速度是Chrome的核心信条。

Google依靠Web，但是从某些角度来说Web也很脆弱，比如，DNS污染。Google现在在中国已经基本消失了。对于这样的情况Google会有什么对策吗？

我无法评论Google在中国的表现，但是可以肯定的是，我们有大型专业的安全团队和基础架构团队，他们唯一的工作和责任就是要确保我们的服务可以有效正确地运行。更广义地说，我认为Google的每个人对于任何能让web变得更加可靠和安全的机会都充满了兴趣——Vint Cerf是我们的“首席互联网大使”，他在很多方面都是带头冲锋的！

在开发移动web应用的时候，不同平台不同版本的浏览器表现千差万别，再加上要考虑各种兼容方案，必然会极大影响到性能。这是否意味着原生应用仍然是主要的手段？

Web和原生平台都一直在进化：新的API和特性，bugfixes，偶发性回归，诸如此类。在此之上，我们还有各种各样的硬件，从低端单核手机到最新的有大量RAM和GPU带宽的多核设备。所以，无论你是要构造原生还是web应用，都需要依赖于这些设备。要想构建一个性能糟糕的本地应用，和构建一个糟糕的web应用一样简单。

如今Web发展的瓶颈在哪？未来又将如何？

一直以来，我对最新最好的智能电话的硬件参数和能力都感到惊喜。现在很多最新设备所携的计算能力要高于我几年前使用的笔记本电脑！所以，这同时也激发和挑战了软件的新能力：新的API，不同的处理管道，等等。因此，我们需要重新思考如何构建浏览器架构的设想，应该把什么样的能力显示给开发者。换句话说，并不是非此即彼，而是两者互相驱动，并且在未来很长一段时间内都将如此。

历史上出现了很多重要的网络应用，电子邮件，新闻组，万维网。Web似乎有着经久不衰的魅力。你能想象未来会出现一个具有和web同等影响力的网络应用吗？

预测未来是一桩危险的买卖！我能预测吗？我也想！万维网很棒，要是未来能出现一个更棒的东西挤掉web，我想很多人都愿意做这样有趣的思维试验：它是什么样的，它能做什么事，它是如何工作的，等等。网络已被证明是很有弹性的，我相信仍然有很多东西有待我们发现，在架构内仍然有很多东西可以提高……能成为这个时代的开发者感觉真的不错！

有一部叫做《硅谷》的热播美剧，故事的主人公创造了一种叫做pied piper的算法，这种算法可以减少数据传输时间，你认为这在现实中是可能的吗？

必须得承认，我并没有看这部剧，对于"pied piper"也不太熟悉。即便如此，几年前我们确实从CERN那里收到一些关于比光速更快的传输报告……只不过，经过进一步调查，这只是一根有缺陷的电缆。:)

这件事有可能吗？这个问题我只能留给物理学家。与此同时，更加现实的答案是“不可能”。

《Web性能权威指南》的作者是世界顶尖的Web性能工程师，他在书中深入浅出地讲解并演示了针对TCP、UDP和TLS协议的性能优化最佳实践，以及面向无线和移动网络进行优化时的特殊要求。随后，他全面剖析了浏览器技术的几项重大革新，包括使用这些新技术时在性能方面需要的独到考量。革命性的HTTP 2.0、XHR客户端网络脚本、基于SSE及WebSocket的实时数据流，以及通过WebRTC实现P2P通信，对这些面向未来的重大浏览器技术，本书都从性能优化的角度给出了详尽的解读和分析。

数据绑定和第一个AngularJS Web 应用

作者/ Ari Lerner

Ari Lerner是一位全栈工程师，拥有多年AngularJS经验，自办并运营AngularJS电子报ng-newsletter.com，在著名硅谷工程师培训学校Hack Reactor担任AngularJS讲师。他的工作涉及软件开发的各个层次，包括基础设施开发、前端应用开发和性能优化。他目前住在旧金山一个阳光明媚的地方，还是FullStack.io创始人。

AngularJS是什么

AngularJS的官方文档是这样介绍它的。

完全使用JavaScript编写的客户端技术。同其他历史悠久的Web技术（HTML、CSS和JavaScript）配合使用，使Web应用开发比以往更简单、更快捷。

AngularJS主要用于构建单页面Web应用。它通过增加开发人员和常见Web应用开发任务之间的抽象级别，使构建交互式的现代Web应用变得更加简单。

AngularJS的开发团队将其描述为一种构建动态Web应用的结构化框架。

AngularJS使开发Web应用变得非常简单，同时也降低了构建复杂应用的难度。它提供了开发者在现代Web应用中经常要用到的一系列高级功能，例如：

解耦应用逻辑、数据模型和视图；

- Ajax服务；

- 依赖注入；

- 浏览历史（使书签和前进、后退按钮能够像在普通Web应用中一样工作）；

- 测试；

- 更多功能。

Hello World

写一个Hello World应用是开始学习AngularJS的最基本途径，让我们从一段简单得不能再简单的HTML开始吧。

随着学习的深入，我们会逐渐深入到AngularJS的内部原理中。现在，让我们先来写一个Hello World应用。

<!DOCTYPE html>
<html ng-app>
<head>
<title>Simple app</title>
<script
src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.13/angular.js"></script>
</head>
<body>
<input ng-model="name" type="text" placeholder="Your name">
<h1>Hello {{ name }}</h1>
</body>
</html>

虽然这个例子不怎么有趣，但它展示了AngularJS最基本也最令人印象深刻的功能之一：数据绑定。

请注意，在本文的示例代码中为了方便展示第一个核心概念，我们并没有用最佳方式编写控制器。

AngularJS中的数据绑定

在Rails等传统Web框架中，控制器将多个模型中的数据和模板组合在一起形成视图，并将其提供给用户，这个组合过程会产生一个单向视图。如果没有创建任何自定义的JavaScript组件，视图只会体现它渲染时模型暴露出的数据。在写这篇文章时，已经出现了好几个可以在视图和模型之间自动进行数据绑定的框架。

AngularJS则采用了完全不同的解决方案。它创建实时模板来代替视图，而不是将数据合并进模板之后更新DOM。任何一个独立视图组件中的值都是动态替换的。这个功能可以说是AngularJS中最重要的功能之一，也是让我们只用10行代码，并且在没有任何JavaScirpt的情况下就可以写出Hello World的关键。

要实现这个功能，只要在HTML页面中引用angular.js，并在某个DOM元素上明确设置ng-app属性即可。ng-app属性声明所有被其包含的内容都属于这个AngularJS应用，这也是我们可以在Web应用中嵌套AngularJS应用的原因。只有被具有ng-app属性的DOM元素包含的元素才会受AngularJS影响。

视图中的插值会在计算一个或多个变量时被动态替换，替换结果是字符串中的插值被变量的值替代。

例如，如果有一个叫做name的变量，它的值是“Ari”，那么视图中的"Hello {{ name }}"字符串会被替换成“Hello Ari”。

自动数据绑定使我们可以将视图理解为模型状态的映射。当客户端的数据模型发生变化时，视图就能反映出这些变化，并且不需要写任何自定义的代码，它就可以工作。

在MVC（Model View Controller，模型-视图-控制器）的世界里，控制器可以不必担心会牵扯到渲染视图的工作。这样我们就不必再担心如何分离视图和控制器逻辑，并且也可以使测试变得既简单又令人愉悦。

MVC是一种软件架构设计模式，它将表现从用户交互中分离出来。通常来讲，模型中包含应用的数据和与数据进行交互的方法，视图将数据呈献给用户，而控制器则是二者之间的桥梁。

这种表现分离能将应用中的对象很好地隔离开来，因此视图不需要知道如何保存对象，只要知道如何显示它即可。这也意味着数据模型不需要同视图进行交互，只需要包含数据和操作视图的方法。控制器用来存放将二者绑定在一起的业务逻辑。

1 http://martinfowler.com/eaaDev/uiArchs.html

AngularJS会记录数据模型所包含的数据在任何特定时间点的值（在Hello World例子中就是name的值），而不是原始值。

当AngularJS**认为**某个值可能发生变化时，它会运行自己的事件循环来检查这个值是否变“脏”。如果该值从上次事件循环运行之后发生了变化，则该值被认为是“脏”值。这也是Angular可以跟踪和响应应用变化的方式。

这个事件循环会调用$digest()循环。

这个过程被称作脏检查（dirty checking）。脏检查是检查数据模型变化的有效手段。当有潜在的变化存在时，AngularJS会在事件循环时执行脏检查来保证数据的一致性。

如果使用KnockoutJS这种通过在数据模型上绑定事件监听器来监听数据变化的框架，这个过程会变得更复杂且低效。处理事件合并、依赖跟踪和大量的事件触发（event firing）是非常复杂的，而且会在性能方面导致额外的问题。

2 这有些言过其实，低效是真，复杂未必。——译者注

尽管存在更高效的方式，但脏检查可以运行在所有浏览器中并且是可预测的。此外，很多在速度和效率方面有要求的软件都会使用脏检查的方案。

3 比如在游戏开发中就大量使用脏检查技术。——译者注

借助AngularJS，不需要构建复杂和新的JavaScript功能，就可以在视图中实现类自动同步的机制。

为了表示内部和内置的库函数，Angular使用$预定义对象。尽管这类似于全局的jQuery对象$，但它们是完全无关的。只要遇到$符号，你都可以只把它看作一个Angular对象。

简单的数据绑定

审阅一下上面写的代码，我们使用ng-model指令将内部数据模型对象（$scope）中的name属性绑定到了文本输入字段上。

这意味着无论在文本输入字段中输入了什么，都会同步到数据模型中。

数据模型对象（model object）是指$scope对象。$scope对象是一个简单的JavaScript对象，其中的属性可以被视图访问，也可以同控制器进行交互。如果不理解这个概念也没有关系，后面的例子将会对这个概念进行详细说明。

双向数据绑定（bi-directional）意味着如果视图改变了某个值，数据模型会通过脏检查观察到这个变化，而如果数据模型改变了某个值，视图也会依据变化重新渲染。

在输入字段上使用ng-model指令来实现数据绑定，如下所示：

<input ng-model="person.name" type="text" placeholder="Yourname">
<h1>Hello{{ person.name }}</h1>

这样绑定就设置好了（没错，就是这么简单）。我们可以观察一下视图是如何更新数据模型的。当输入字段中的值发生变化时，person.name会被更新，而视图将反映出这个更新。

我们仅通过视图就实现了一个双向数据绑定。为了从其他角度（后端到前端）解释双向数据绑定，后面会深入介绍控制器。

正如ng-app声明所有被它包含的元素都属于AngularJS应用一样，DOM元素上的ng-controller声明所有被它包含的元素都属于某个控制器。

为了解释这个概念，我们将前面的例子修改成如下的样子：

<div ng-controller='MyController'>
<input ng-model="person.name" type="text" placeholder="Your name">
<h1>Hello {{ person.name }}</h1>
</div>

在这个例子中，我们会创建一个每秒钟走一步的时钟（时钟通常都是这样的），并更新clock变量上的数据：

function MyController($scope, $timeout) {
var updateClock = function() {
$scope.clock = new Date();
$timeout(function() {
updateClock();
}, 1000);
};
updateClock();
};

在这个例子中，MyController函数接受两个参数，即该DOM元素的$scope和$timeout。

在这个例子中，当定时器触发时会调用updateClock()函数，将$scope.clock的值设置为当前时间。

- $timeout对象

可以在视图中将clock变量用{{ }}包起来，以显示$scope中的clock的值：

<div ng-controller="MyController">
<h5>{{ clock }}</h5>
</div>

下面是完整的示例代码：

<!doctype html>
<html ng-app>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.13/angular.js"></script>
</head>
<body>
<div ng-controller="MyController">
<h1>Hello {{ clock }}!</h1>
</div>
<script type="text/javascript">
function MyController($scope, $timeout) {
var updateClock = function() {
$scope.clock = new Date();
$timeout(function() {
updateClock();
}, 1000);
};
updateClock();
};
</script>
</body>
</html>

在线示例：http://jsbin.com/uHiVOZo/1/edit?html,output。

尽管我们可以将所有代码都写在一个文件中，但由于需要将不同的组件分开开发，将代码写在一个文件中会使协同工作变得非常困难。通常情况下，更好的选择是将JavaScript放在单独的文件中，而不是index.html中。

上面的代码可以修改成：

<!doctype html>
<html ng-app>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.13/angular.js"></script>
</head>
<body>
<div ng-controller="MyController">
<h1>Hello {{ clock }}!</h1>
</div>
<script type="text/javascript" src="js/app.js"></script>
</body>
</html>

将前面例子中的JavaScript代码放在js/app.js文件中，而不是将它直接写在HTML中。

// 在app.js中
function MyController($scope, $timeout) {
var updateClock = function() {
$scope.clock = new Date();
$timeout(function() {
updateClock();
}, 1000);
};
updateClock();
};

数据绑定的最佳实践

由于JavaScript自身的特点，以及它在传递值和引用时的不同处理方式，通常认为，在视图中通过对象的属性而非对象本身来进行引用绑定，是Angular中的最佳实践。

如果把这个最佳实践应用到上面时钟的例子中，需要把视图中的代码改写成下面这样：

<!doctype html>
<htmlng-app>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.13/angular.js"></script>
</head>
<body>
<div ng-controller="MyController">
<h1>Hello {{ clock.now }}!</h1>
</div>
<script type="text/javascript" src="js/app.js"></script>
</body>
</html>

在这个例子中，相比每秒钟都更新$scope.clock，更新clock.now的值会是更好的选择。有了这个优化后，我们将反映数据变化的逻辑做如下修改：

// 在app.js中
function MyController($scope, $timeout) {
var updateClock = function() {
$scope.clock = {
now: new Date()
};
$timeout(function() {
updateClock();
}, 1000);
};
updateClock();
};

将所有绑定都通过这样的形式放在视图中，是个非常好的主意。

AngularJS权威教程是资深全栈工程师的代表性著作，由拥有丰富经验的国内AngularJS技术专家执笔翻译，通俗易懂、全面深入，是学习AngularJS不可错过的经典之作。无论是出于工作需要，还是好奇心的驱使，只要你想彻底理解AngularJS，本书都会让你感到满意。本文节选自AngularJS权威教程。

读《码农》 吐吐槽 还能赚银子！

活动规则：在吐槽贴中留言，选出任何一期中你最喜欢的文章和最不喜欢的文章，即可获得图灵社区银子 2 两！凡吐槽吐得掷地有声者，加赠银子 3 两（共 5 两）！（怎样使用银子兑换图书）

活动时间：本活动长期有效。

人物

周爱民：真正的架构师是没有title的

周爱民，现任豌豆荚架构师，国内软件开发界资深软件工程师。从1996年起开始涉足商业软件开发，历任部门经理、区域总经理、高级软件工程师、平台架构师等职，有18年的软件开发与架构、项目管理及团队建设经验，曾任盛大网络平台架构师、支付宝业务架构师，是Borland Delphi 产品技术专家，也是 Qomo 开源项目（JavaScript）的发起者。2003年5月被美国 Borland 公司授予「Borland Delphi 产品专家」称号，并授予「论坛特别贡献奖」。著有《大道至简——软件工程实践者的思想》、《大道至易——实践者的思想》、《Delphi 源代码分析》、《JavaScript 语言精髓与编程实践》等专著。

关于选择

“既然我已经答应夫人要走，就不想做别的选择了。”

问：您从什么时候开始接触编程？

我从1994年的时候开始学习编程，最开始接触电脑是学习WPS，DOS之类的，其中有一门课程是DBase，这是早期的编程语言。我的专业不是编程，我学的是机械电子。当时我其实更喜欢文学，但是我认为专业作家迟早会被饿死，所以我就选择了计算机，至少搞计算机不会饿死。

问：我发现您的职业经历也很丰富，在成为架构师之前，您好像还获得过Borland公司发的奖？

我在去盛大之前的经历都是在Delphi圈子里，当时Borland一下发了两个奖给我。一个是Borland产品专家奖，因为那个时候我已经出了第一本书《Delphi源代码分析》。那本书是讲Borland产品内核的，从对Delphi的了解程度来讲我是可以拿这个奖的。另外一个奖叫做社区专家奖，这是因为我是Delphi早期社区的活跃者。我去盛大之前面临了两个Offer，一个是Borland China，请我去做产品工程师，一个是盛大的。Borland在中国没有开发，所以他们只好叫做产品工程师，可以理解成售后服务。我当时觉得虽然Borland公司的title可以，但不是我想做的事，所以我决定去盛大。

问：您曾经是一位Delphi专家，也曾作为架构师就职于盛大和阿里巴巴，您职业转换的原因和动机是什么？

我在郑州待了9年，我在那里的最后一份工作是软件部的负责人，带项目兼做开发。我离开郑州的原因其实很简单。当时我有一位非常好的朋友，他也是我的开发团队中的主力工程师。他说他拿到了微软的Offer，想问一下我的意见，我就告诉他：作为朋友我建议你立即去微软，这对你来讲是事业发展的大好机会。他向我提出离职之后，第二天我就向老板提出了离职。那个时候，我突然想明白了一件事，对于软件开发人才来说，郑州不是一个好环境。当我们把一个人培养得很成熟了，他就会受到更大的环境（比如北京）的影响，人才会流失。在郑州这样的环境下搞软件或者软件企业受限太大了，而这样的现实不是我能改变的。我今天会面临朋友的离职，我如果继续在郑州做下去，过一两年还会面临同样的问题。

我开始意识到整个产业环境对局部地方的影响，对于人才的影响，对于公司企业规模的影响。当时我们还在做一些项目，面向移动、金融、电信这些企业。非常明显，我们作为本地企业，拿到的永远都是小骨头。从北京来的其他公司，人没有你多，素质不一定有你高，但是报价会比你高很多。从北京来的公司只要说他们后面有怎样的一家大公司在支持他，他就会拿到比你更大的东西。

离开郑州后，我去了盛大，盛大对于我来说，是作为架构师的第一个阶段。为了处理一些家务事，我2008年离开盛大。2009年去支付宝，2011年8月份左右离开支付宝，因为保密协议的问题我等了一年，没有在工作。

对于选择这件事，除了离开郑州之外，我都没有更充分的动机和理由。我从盛大离开是迫不得已，因为有私事必须要处理。从支付宝走的时候是想换一个环境，不想在那样的环境下生活，不想在那样的工作背景里面工作。

问：您能概括讲一下为什么不喜欢那样的环境吗？

这只是诱因，不是结果。我夫人当时在杭州病得特别厉害，她严重失眠，一个多月只能睡到20个小时，基本上每天一个小时都不到，已经抗不住了。我就跟她讲我们回郑州吧，因为我们家安在郑州。之后我们就搬家了，她的病也在渐渐好转。当时阿里金融给了我一个offer，让我去阿里金融做架构。但是既然我已经答应夫人要走，就不想做别的选择了。

问：您来豌豆荚的时候有没有什么特别的原因呢？

我在来到豌豆荚之前也有一些其他的offer，最后促使我选择豌豆荚的原因也很简单。我来豌豆荚面试的时候跟创始团队一起沟通过，我的感觉是他们很诚恳，这种诚恳的具体表现就是：知道就是知道，不知道他会告诉你。你会从他们那里听到：我们正在创业，我们面临了很多的竞争，有些问题我们也许没有想清楚，但是我们需要像你这样的人过来帮助我们一起做事。大多数创始人不会这样做，这样诚恳的人是很难得的，这是我当时很感动的地方。

第二，当时的豌豆荚还有很多事情没有人拎起来做。很多事情就直接打包到一个团队里面，由团队往下推进，或者打包到一件事里面当成一件事做。在我看来整个体系不成形，杂乱无章。我感觉在这样的背景下面有很多架构工作要做，我架构的能力和背景一定能够帮到这家公司，有我施展的地方，于是我就选择了豌豆荚。

架构师

“真正的架构师是没有title的。”

问：您是如何成为一位架构师的？

人生有很多巧合，不一定非得要预测一个途径。我离开郑州的时候就有一份简历是投到盛大的，面试通过之后他们就发了一份offer，上面写了架构师。原因是我在Delphi圈子里比较资深，盛大给我发offer的时候，觉得高级工程师已经不太适合了，就只能是架构师。

我去盛大的时候没有架构师，包括我本人在内没有人知道怎么做架构。当时是2005年，架构师在国内还是比较陌生的名词。CTO给我安排的活儿是希望我能给某个东西做架构，把底层做起来。我到了之后就按照这样的思路去做，按照自己的理解去推进、实施，用了2个多月，初步做出了原型。虽然最后这个数据底层处理系统没有用，但是这个过程让大家看到，架构的做法跟原来的开发方式不太一样。所以他们很快把我调去了盛大机顶盒的系统，做平台架构。在去盛大之前我已经工作9年了，我基本上把过去工作的所有东西全部汇总，变成了我技术架构上的基础。

如果说在我做平台架构之前，已经大概知道如何做架构了，但是什么叫做平台架构还是没人知道的事。那个时候国内可以出来讲平台架构的人还没有几个，相关的理论知识也没有。我该如何定义什么是平台？想要的东西是什么？我怎么去做？我怎么影响那些决策者？我如何把他们想要的东西描绘出来推进下去？所以，做平台架构也是摸索着往前走，在盛大这件事我大概做了两年。后来盛大为未来5到10年规划的是叫做OMMO的大型多人在线游戏项目（也就是后来的“零世界”项目），于是我在盛大工作的最后半年时间，就在做这个项目的平台设计。

那个时候开始，我已经在做架构了，但是没有范本，没有理论和数据告诉我怎么做。我按照自己的想法去实施，按照自己的方法形成自己的体系，不断地去修改、适应它。

问：您对架构的理解经历了几个阶段？

最早的架构师就是某家公司没有合适你的title，就给你一个架构师做。坦率地说,这是很多公司架构师的出处。我在盛大前两个月左右的时候，第一个项目做得不错，正有一点自得，当时被称为盛大首席架构师的一位同事就要离职了。他跟我聊了一件事，他说他其实半年前就想走了，但是CTO找到他说：你看盛大还有很多事情，你也有很多能量没有发挥出来，我们真的不希望你走，接下来你做盛大的首席架构师吧，把你的能力释放下来。他当时觉得可以留下来，等过了半年再来看这事的时候，他突然醒悟了，他说不是公司想要做架构，也不是我能做架构，而是公司想把他留下来，又找不到适合的理由，于是需要一个合适的title把他留住。

我当时听了他的解释就一阵透心凉，我来盛大做架构也是这样的，找不到一个可以给我的title，但是又想把我留到盛大，于是让我来做架构师了。我跟盛大或者整个行业都经历了这样的阶段。架构师就是一个title，为了挽留一些人，或是为了在某些场合下可以跟别人递一张名片。

在盛大做了两年多之后，我进入到第二个阶段，从架构的结果到架构过程的领悟。最早做架构怎么做呢？去Google搜各种各样的架构的文章、各种各样架构的图和材料，例如要做数据的架构只要搜一些国外大公司数据架构的文章和图就行了。我最初的两年时间也经历了这样的过程。最终我从学习架构的做法得到了突然的领悟：如果那些东西是架构，那么它们是怎么得来的？后来《架构之美》这本书出版的时候，我写了一篇序，里面有我关于架构的第一个感悟：架构是一个过程，而非结果。水管滴下来一滴水，从它滴落到裂开的过程，中间有多少形态的变化？大家只会看到水在地上绽开的结果，不会想到在中间的过程是什么。我们只看到了架构师画出来的东西，而如何得到它才是架构师最重要的部分。这是我2008年左右从盛大离开时的感悟。

从支付宝离开的时候，我又感受到了变化。过程即便也是可以被描述和展现的，也不足以说明架构本身，也就是说，这个过程是不具有灵魂的。假设，我是一个老石匠，我有一位学徒小石匠，我雕石狮子的过程他看得清清楚楚，请问他照着我做一遍就可以成为老石匠了吗？这个过程是可以被仿造的，可以被形式化，但是并不具备灵魂。我的第二个感悟是：过程是不具备灵魂的，真正要学的应该是架构师思考方式。我在《大道至易》里面花了不少工夫去分析架构的过程和结果，但是核心的部分是给大家讲什么是架构的思维，怎么用架构的思维思考。所以后来孟岩写序的时候，说爱民是在用架构的思维看这个，看那个。管理也好，技术也好，架构本身也好，我都是站在架构思维的视角去看的，能够看到这本书的后半部才能把这本书看明白。这是第三个阶段，思考架构在思维阶段怎么形成，而不是怎么把石狮子雕出来，或者雕出来的结果漂不漂亮，这两者已经偏向于末端了。

问：孟岩曾在《大道至易》的序中提出了一个有趣的问题：组织架构跟技术架构之间的搭配如何才是合适的。但是您在书里面并没有深入地讨论这件事。您现在对这个问题有没有更深的理解？

这个问题是没有通解的。任何一个系统，我将要实施的架构和该架构所在的这家公司中间一定存在着整体系统的视角，系统中不同的东西肯定会有矛盾和冲突。这种矛盾和冲突在不同的背景下表现是不一样的。这个东西本身没有通解，但是问题是完全一致的：如何能让你实施的架构跟这家公司的组织结构及其推动方法协调。你要么改变A，要么改变B，采用的手法是在实施的技术架构上做调整。

举个例子，某个架构的实施需要500人，但是这家公司本来才200人，显然是实施不下去了，但是你还得想办法实现怎么办？你可以去买一个500人实施出来的产品把这个坑填掉，你的架构本身是没有变化的，只是实施的手段变了。第二，我们不要这个架构了，能不能做一个更简洁、轻便，能够适应200人规模的架构。第三，调整公司和架构，这200人不是不够吗？我能不能再买一个500人的公司过来，如果能，我可以把这个放进去。所以调整哪一个都有可能，这是实施手法的问题。

我跟孟岩无法形成共识的问题是：到底什么是冲突的本身？孟岩认为系统可能是不可预设的，也许一开始做架构这件事就错了，因为你预设了系统。而我的观点是系统是可以预设的，我认为架构的发展和需要做的事情是可以按照这个设计往下走的。如果我假设系统可以预设，必然会产生了刚才所讲的矛盾，我们解决矛盾的方法要么是演化，要么是改变。

问：很有趣的分析。您来到豌豆荚之后遇到最大的技术挑战是什么？

这个问题的答案其实也是我刚才问题的。也就是说，如果技术框架跟公司层面存在了一个矛盾，它会有挑战吗？其实没有。所以在架构的层面讨论刚才的问题，是永远不存在技术障碍的。因为我永远可以有其他选项，我可以不做它，或者我可以不用现在做它，我可以用各种方法把这个问题消化掉。所以如果在架构层面上存在技术挑战或者技术上不可解决的问题，就是你架构做错了，只有架构做错了才会存在这样的矛盾。

所以从技术的角度上讲，总是能够解决，尽管确实永远存在能力的问题。人多了之后就会存在的第二个问题，就是组织问题。人少不存在这样的问题，一个人开发永远不会打架。所以从这个角度考虑，技术上存在的最大挑战往往第一是需要人来解决的部分，我们的人力以及相应的能力不够。第二，人多了之后，存在的管理问题是需要通过非技术方法来解决掉的，比如说组织结构的调整，管理方法等等。

问：如果想从一个普通的程序员成为一位架构师，需要学习什么？

跟你老板搞好关系，让他给你一个title（笑）。

问：那要成为一位真正的架构师呢？

其实真正的架构师是没有title的。首先你要想清楚你在做的东西是不是架构，你是不是会做架构。问题本身就是要让一个愿意学习的同学（不一定是工程师）能够理解到怎样做架构。第一，认识架构最重要的事：你要知道你所面对的是一个系统而不是一件事。你可能每天会面对一堆待处理的事，如果你看到的只是事的过程和结果，而非事情本身，你就仅仅是工程师，一位实施者。跳出这个框子，你面临的其实是一个系统，你看清楚这个系统之后，还要看清楚这个系统里面的关键要素。

我常用过河来作例子。一个人在河的前面想过这条河，有一条船放在那里，如果你认为过河是一件事，你的第一件事是跳到船上想办法把船划过去。你遇到的第一个问题可能是你没有划船的技能。但是如果你是一个架构师，你的第一个问题是：这是什么东西？你可以定义其为一个障碍，河这个东西对你来讲是阻碍，你跨过阻碍的方法不一定是划船。我架一个桥不行吗？我直接跳下去，游泳过去不行吗？另外，这条河是不是障碍还是一个问题，如果它很浅呢？你非要认为它是障碍就制造了矛盾，系统中也许不存在这样的矛盾。

所以从架构思维的角度来讲，第一件事是要看到这是一个系统，第二件事是定义问题，第三件事是看到这个问题是不是真实存在。找到这个问题跟方案之间的关系在哪里，你就会做架构了。

至于做多大范围的架构，就是我在《大道至易》里面提到的领悟、领袖和领域能力的问题了。第一个部分就是领悟的能力，知道划船不是第一要素，思考问题和定义问题以及否定这些问题的整个过程就是架构思维。第二个部分是领域能力的部分，你得知道河流，你得知道船，你得知道桥，这些东西是属于领域的部分。第三部分是领袖能力，你真的想要造一座桥的话，你得组织一群人把桥造出来；如果你们是4、5个人，你的决策是我往上走一公里尝试一下寻找源头，那么你怎么让其他4个人跟着你往那一公里的上游走，这是领袖能力。否则你就会自己变成排头兵，“你们大家等着，我去上游看看”。如果你是领袖的话，可以安排一些人去上游看看，另一些去下游看看，半个小时后在这里集合。这就是领袖，把你设想的解决问题的架构实施下去。

豌豆荚的动力引擎

“也许今天这艘船很小，是一叶扁舟那么小，我也给了它一个超大能力的引擎，有一天当它成长为非常大的船的时候，一样是可以用的。”

问：您提出的架构师的三种能力（领袖，领域，领悟）反映在您在豌豆荚做架构的过程之中，您更倚重于哪种能力？

我认为是平衡力的问题，不是多少的问题。假设你做技术架构在你的团队使用，你选择了一个插件的框架或者数据流的框架，总共就影响4、5个人，你需要的领袖能力无非是你站起来拍板说我已经调查过的，这个技术绝对没有问题，大公司都已经用过了。这就是你的领袖能力。但是你要想影响到一个行业，比如我们现在正在做的应用内搜索，这是整个行业的一部分，你可能要跳出来说以后整个搜索就是以应用内搜索为核心的，但你怎么能够用领袖能力使得所有人相信将来应用内搜索在这个领域里面是一个方向呢？这就是领袖能力。

你需要在你的能力之间找到一个平衡，而不是考量解决多少问题。领域能力也是如此，所有这部分的能力的要素其实都是可以放在别的角色或者是别人身上的。举个例子，豌豆荚要做应用内搜索，并不等于说CEO需要把应用内搜索所有的领域知识了解得非常清楚。最终他在这个领域的能力是如何构建的呢？是豌豆荚的所有工程师、各个角色在这个领域的集合，支撑了他来讨论应用内搜索可不可以成为行业方向。对于他来讲，这个领域能力就被分解在不同的角色和不同的团队上了。

真正不可或缺的部分变成了领悟能力。比如在决策上豌豆荚为什么要选择应用内搜索作为我们在移动领域上面的方向，在这样的问题上，领悟能力的部分才是架构思索不可或缺的部分。一定要有一个理论体系来支撑这个决策，而其它两个（领域能力和领袖能力）只是在实施手段中所需要找到的、平衡整体能力的部分，可以把它嫁接到别的团队，别的人身上，这会形成一种结构。

我在架构师的能力上面提到的三种能力并不是要集于某一人身上，而是你一定要理解架构所需要的能力，需要多大能力的集合才能把这个系统消化掉。

问：您初到豌豆荚时曾提过您对很多豌豆荚的组织和文化的来源不是很清楚，而您有一个观点是认为组织结构和文化是跟架构有关系的，对这些来源，您现在有没有更清楚？

确实是更清楚了。组织方法和文化氛围的形成，都是有历史渊源的。一个公司如何成长为现在这样？用什么方法来推进它的事和产品？这些都有习惯性的方法和思维在里面。

一家公司做事方法的历史根源，是值得你花一定的时间和精力追本溯源的，有一些信息是需要你去挖掘的，这也是我在豌豆荚一年多的工作里面比较关注的事。豌豆荚因为特别公开、透明，我能够从文档上面看到两三年前，或者是更早时期他们的一些工作过程。这是一个关于信息的问题。

第二部分是文化的形成，这部分不像刚才那么容易。我在《大道至简》那本书里面说到过，一个公司或者一家企业或者一个组织的形成是与那些核心人物有密切关系的，那些创始者和初期团队的构建者的行为、方法、习惯和思维方式都会留下痕迹，这是由人带来的一种气质和影响。所以，更多的是要通过了解这些创始者、创始者团队和早期的团队来理解公司，理解方向，理解他们在做的事业。这个东西不是信息，不是你在过去的文档里面能搜到的东西了，需要你跟他们碰撞、了解、接受、感悟。这一年多以来我在团队融入上的投入使得我能够对豌豆荚的文化和现状有更深的了解。

问：您曾把豌豆荚的组织结构形容为 “开放、透明”，这样的组织对您的技术架构有影响吗？

有影响。不管你怎样做架构，最终是在系统里做实施，这个系统的文化、组织，以及工作方法，一定程度上都影响了你如何去设定这个架构，你不能做出这里面的人无法接受的架构来。一定要掉过头来看这些人习惯并想要用什么样的方法做这些事，在架构实施推进过程中要能够再去调整它。

做架构这件事跟公司的组织和文化有一个漫长的协调过程，你需要在你设定目标和为了达到目标而演化出解决方案的过程中思考。所以一个好的做架构的人一定是对组织、文化、工作方法、项目管理，对整个系统的各个方面都非常了解的，他不一定会做，但是要看到方方面面。

处在扩张阶段的豌豆荚用人需要很大，很多应届毕业生也对豌豆荚很感兴趣，你们需要什么样的年轻开发者？ 只要有才，唯才是举。首先我觉得要对软件，产品，或者是对你所选择的方向、视野有热心和激情。在一家公司里面不仅仅是要招架构师，也要招软件工程师，各个方向都要招各种各样的人才。人才的定义可能会受到外界的影响，但是由内到外的孜孜不倦的精神，别人一眼就能看得出来。如果你在这件事上具有那样的信心、欲望和力量，这可能会激发每个人的创造力、动力和想要达到一切目标的原始驱动。这是我所希望的定义。

虽然我觉得能力很重要，但是你可以承认自己的不足。我们不会要求刚毕业的学生有多么强的能力，因为这件事本来就不靠谱，我认为在你的背景下面具有合适的能力就行了。拿学生的背景和履历跟经验更丰富的人比较是不公平的。我觉得如果你对自己所做的事，对现有的履历有足够的信心和热情，你愿意去了解豌豆荚并加入到我们的事业里来，这就够了。

问：如果把豌豆荚比喻成一艘大船，您在上面的角色是什么？

一艘船由很多部分组成，船上的所有人都是不可或缺的，抛掉了谁都是不可以的，但同时，也不是缺了谁就不行，不是缺了厨师长大家都没有饭吃，缺了船长就没有人知道方向了。对于我来说，我愿意定义的是我能够做到什么，我的能力是哪个方面，我不想把它拟人化、职务化。

我觉得我应该是动力引擎中的组成部分，因为架构师这个角色所提供的是能力。我不提供这个船的船体，船体不是我工作的目标和对象。也许今天这艘船很小，是一叶扁舟那么小，我也给了它一个超大能力的引擎，有一天当它成长为非常大的船的时候，一样是可以用的。但是如果一开始做得很大的话，这个小舟可能装不下，我愿意做其中的一部分，从小舟开始一直到船非常大的时候都存在，并且起到它的作用，这就够了。我不希望的是，今天我们的引擎是动力0.1版的，只作用于一艘小船，明天我把这个东西扔到海里，换一个大的，那不是我做动力引擎的方法。架构师更倾向于构造一个可扩展的，可以长期影响这个结构的引擎。

豌豆荚2015校园招聘

豌豆荚一直希望证明，一群单纯地喜欢自己做的事情的年轻人，是可以亮亮堂堂地把事情做好、做大的。对，即使在中国。在这里没有一个职位是为了满足别人的需求而存在，请永远对工作保有热情，勇敢向前。纵横移动互联网，豌豆荚的大航海时代，等你加入！

践行

如何开始一个模块化可扩展的Web App

作者/ Allo

六年Web开发：两年于日本工作，三年半联合创办公司，现入职华尔街见闻并继续创业。目前仍然以互联网做为谋生的行业，仍然在不断的尝试作出能改变人们生活的产品，仍然在努力让自己能有时间做爱做的事。对任何技术不追捧，不贬低。关注互联网项目、Web快速开发、前端、W3C标准等。 略懂PHP & Zend Framework。技术Blog : http://avnpc.com/

虽然从没有认为自己是一个前端开发者，但不知不觉中也积累下了一些前端开发的经验。正巧之前碰到一道面试题，于是就顺便梳理了一下自己关于Web App的一些思路并整理为本文。

对于很多简单的网站或Web应用来说，引入jQuery以及一些插件，在当前页面内写入简单逻辑已经可以满足大部分需要。但是如果一旦多人开发，应用的复杂程度上升，就会有很多问题开始暴露出来：

1. 数据源一般都与页面分离，那么App启动一般都需要等待数据源读入。

2. UI交互复杂时，需要将逻辑通过面向对象抽象后才能更好的复用。

3. 功能间一般都存在依赖关系，需要引入支持依赖关系的模块加载器。

那么如何解决这些问题，就以一个简单的订餐App为例，从零开始一个模块化可扩展Web App。

这个简单的App基于HTML5 Boilerplate、requireJS、jQuery Mobile、Underscore.js，后端逻辑用jStorage模拟实现。完成后的成品在此。所有代码可以在github查看。下文将逐一介绍实现的思路与方法。

从选择一个好模板开始

开始一个Web项目，HTML的书写总是重中之重，一个好的HTML能从根源上规避大量潜在问题，所以Web App应该全部应用一个标准化的高质量HTML模板，而不是将所有页面交由开发人员自由发挥。

这里推荐使用HTML5 Boilerplate项目作为App的默认模板以及文件路径规范，无论是网站或者富UI的App，都可以采用这个模板作为起步。

可以使用

git clone git://github.com/h5bp/html5-boilerplate.git

或者直接下载HTML5 Boilerplate项目代码。HTML5 Boilerplate的文件结构如下，

.
├── css
│   ├── main.css
│   └── normalize.css
├── doc
├── img
├── js
│   ├── main.js
│   ├── plugins.js
│   └── vendor
│       ├── jquery.min.js
│       └── modernizr.min.js
├── .htaccess
├── 404.html
├── index.html
├── humans.txt
├── robots.txt
├── crossdomain.xml
├── favicon.ico
└── [apple-touch-icons]

从上向下看

- css 用于存放css文件，并内置了Normalize.css作为默认CSS重置手段（其实Normalize.css不能算是CSS reset）。

- doc 存放项目文档

- img 存放项目图片

- js 存放javascript文件，其中第三方类库推荐放在vendor下

- .htaccess 内置了很多对于静态文件在Apache下的优化策略，如果Web服务器不是Apache则可以参考其他Web服务器配置优化。

- 404.html 默认的404页面，

- index.html 项目模板

- humans.txt 相对于面向机器人的robots.txt，humans.txt更像是小幽默，这在里可以写关于项目/团队的介绍，或者放置一些彩蛋给那些喜欢对你的应用刨根问底的用户们。

- robots.txt 用于告诉搜索引擎蜘蛛爬行规则

- crossdomain.xml 用于配置Flash的跨域策略

- favicon.ico apple-touch-icon.png等小图标。

如果是一个主要面向移动设备，还有更具针对性的Mobile Boilerplate可供参考。

制定统一的编码规范

在正式开始编码之前，无论是多大规模的应用，多少人的团队，一定要有一个统一的规范，才能保证后续的开发不会乱套。

前端规范其实又要分为三部分：HTML、CSS、Javascript应该分别由自己的规范。HTML/CSS主要约定id/class的命名规则、属性的书写顺序。JavaScript可能需要细化到缩进、编码风格、面向对象写法等等。

最省事的方法当然还是参考已有的规范，比如Google的HTML/CSS风格指南、Google的Javascript编码指南等等。

HTML篇

HTML5 Boilerplate的模板核心部分不过30行，但是每一行都可谓千锤百炼，可以用最小的消耗解决一些前端的顽固问题：

使用条件注释区分IE浏览器

<!DOCTYPE html>
<!--[if lt IE 7]>      <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
<!--[if IE 7]>         <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
<!--[if IE 8]>         <html class="no-js lt-ie9"> <![endif]-->
<!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]-->

之所以要这样写

1. 可以使用class作为全局条件区分低版本的IE浏览器并进行调整，这显然要优于使用CSS Hack。

2. 可以避免IE6条件注释引起的高版本IE文件阻塞问题，原文的解决方法是在前面加一个空白的条件注释，但是这里显然将原本无用空白的条件注释变得有意义了。

3. 仍然可以通过HTML验证。

4. 与Modernizr等特征检测类库使用相同的class，更具备通用性。

no-js标签是需要与Modernizr等类库配合使用的，如果你不想在项目中引入Modernizr，需要在Head部分加入一行使no-js标签变为js，代码来自Avoiding the FOUC：

<script>(function(H){H.className=H.className.replace(/\bno-js\b/,'js')})(document.documentElement)</script>

通过上面的条件注释，就可以在CSS中针对不同情况分别处理

.lt-ie7 {} /* IE6等版本时 */
.no-js {} /* JavaScript没有启用时 */

meta标签的书写顺序

为了让浏览器识别正确的编码，meta charset标签应该先于title标签出现。

meta X-UA-Compatible标签可以指定IE8以上版本浏览器以最高级模式渲染文档，同时如果已经安装Google Chrome Frame则直接使用Chrome Frame渲染。而指定渲染模式的meta X-UA-Compatible标签同样需要优先出现

<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title></title>

设置移动设备显示窗口宽度

<meta name="viewport" content="width=device-width">

这是移动设备专属的标签，具体设置需要根据项目实际情况调整。

使用Modernizr做浏览器差异检测

Modernizr常做前端的应该都不陌生。引入Modernizr后，html标签的no-js将会被自动替换为js，同时Modernizr会向html标签添加代表版本检测结果的class。

对于低版本浏览器的向上兼容需要根据项目实际需求处理，Modernizr也非常周到的给出的绝大多数HTML5功能的兼容方法。

CSS篇

CSS重置及增强功能

HTML5 Boilerplate选择Normalize.css重置CSS。如果项目计划引入Twitter Bootstrap、YUI 3这些前端框架的话则可以移除，因为这些框架已经内置了Normalize.css。

同时HTML5 Boilerplate又引入了一个main.css，内置了一些基本的排版样式以及打印样式。

使用LESS或Sass生成CSS

在复杂应用中，如果还手写CSS的话将是一件痛苦的事情，大量的class前缀，复用样式需要来回copy等等。为了更好的扩展性，这里建议在项目中引入LESS或Sass。这代表着：

- 支持变量与简单运算

- 支持CSS片段复用

- class/id样式嵌套

等一些更像是编程语言的特性。这对于提高开发效率是效果非常明显的。

以LESS为例，简单介绍一下LESS在Windows下如何应用到这个项目中：

1. 下载Nodejs并安装，nodejs会自动将自己加入系统路径。

2. 在cmd运行npm install -g less

3. 然后就可以通过lessc指令将less源文件编译为css lessc avnpc.less avnpc.css

4. 如果不使用nodeJs作为后端，最好在写LESS时采用watch模式，每次保存自动编译为css。这里需要安装一个辅助模块recess：npm install -g recess 然后运行watch recess avnpc.less:avnpc.css --watch

Javascript篇

使用requireJS按需加载

模块加载器的概念可能稍微接触过前端开发的童鞋都不会陌生，通过模块加载器可以有效的解决这些问题：

1. JS文件的依赖关系。

2. 通过异步加载优化script标签引起的阻塞问题

3. 可以简单的以文件为单位将功能模块化并实现复用

主流的JS模块加载器有requireJS，SeaJS等，加载器之间可能会因为遵循的规范不同有微妙的差别，从纯用户的角度出发，之所以选requireJS而不是SeaJS主要是因为：

- 功能实现上两者相差无几，没有明显的性能差异或重大问题。

- 文档丰富程度上，requireJS远远好于SeaJS，就拿最简单的加载jQuery和jQuery插件这回事，虽然两者的实现方法相差无几，但requireJS就有可以直接拿来用的Demo，SeaJS还要读文档自己慢慢折腾。一些问题的解决上，requireJS为关键词也更容易找到答案。

requireJS 加载jQuery + jQuery插件

可能对于一般Web App来说，引入jQuery及相关插件的概率是最大的，requireJS也亲切的给出了相应的解决方案及动态加载jQuery及插件的文档及实例代码。

在最新的jQuery1.9.X中，jQuery已经在最后直接将自己注册为一个AMD模块，即是说可以直接被requireJS作为模块加载。如果是加载旧版的jQuery有两种方法：

1. 让jQuery先于requireJS加载

2. 对jQuery代码稍做一点处理，在jQuery代码包裹一句：

define(["jquery"], function($) {
// $ is guaranteed to be jQuery now */
});

requireJS的示例中，直接将requireJS与jQuery合并为一个文件，如果是采用jQuery作为核心库的话推荐这种做法。

同样对于jQuery插件来说也有两种方法

1. 在插件外包裹代码

define(["jquery"], function($){
// Put here the plugin code.
});

2. 在使用reuqireJS代码加载前注册插件（比如在main.js）中

requirejs.config({
"shim": {
"jquery-cookie"  : ["jquery"]
}
});

requireJS加载第三方类库

在实例的App中还用到了jQuery以外的第三方类库，如果类库不是一个标准的AMD模块而又不想更改这些类库的代码，同样需要提前进行定义：

require.config({
paths: {
'underscore': 'vendor/underscore'
},
shim: {
underscore: {
exports: '_'
}
}
});

CSS文件的模块化处理

在requireJS中，模块的概念仅限于JS文件，如果需要加载图片、JSON等非JS文件，requireJS实现了一系列加载插件。

但是遗憾的是requireJS官方没有对CSS进行模块化处理，而我们在实际项目中却往往能遇到一些场景，比如一个轮播的图片展示栏，比如高级编辑器等等。几乎所有的富UI组件都会由JS与CSS两部分构成，而CSS之间也存在着模块的概念以及依赖关系。

为了更好的与requireJS整合，这里采用require-css来解决CSS的模块化与依赖问题。

require-css是一个requireJS插件，下载后将css.js与normalize.js放于main.js同级即可默认被加载，比如在我们的项目中需要加载jQuery Mobile的css文件，那么可以直接这样调用：

require(['jquery', 'css!../css/jquery.mobile-1.3.0.min.css'], function($) {
});

不过由于这个CSS本质上是属于jQuery Mobile模块的一部分，更好的做法是将这个CSS文件的定义放在jQuery Mobile的依赖关系中，最终我们的requireJS定义部分为：

require.config({
paths: {
'jquerymobile': 'vendor/jquery.mobile-1.3.0',
'jstorage' : 'vendor/jstorage',
'underscore': 'vendor/underscore'
},
shim: {
jquerymobile : {
deps: [
'css!../css/jquery.mobile-1.3.0.min.css'
]
},
underscore: {
exports: '_'
}
}
});

在使用模块时，只需要：

require(['jquery', 'underscore', 'jquerymobile', 'jstorage'], function($, _) {
});

jQuery Mobile的CSS文件就会被自动加载，这样CSS与JS就被整合为一个模块了。同理其他有复杂依赖关系的模块也可以做类似处理，requireJS会解决依赖关系的逻辑。

数据源的加载与等待

Web App一般都会动态加载后端的数据，数据格式一般可以是JSON、JSONP也可以直接是一个JS变量。这里以JS变量为例

var restaurants = [
{
"name": "KFC"
},
{
"name": "7-11"
},
{
"name": "成都小吃"
}
]

载入这段数据:

$.getScript('data/restaurants.json', function(e){
var data = window.restaurants;
alert(data[0].name); //KFC
});

单一的数据源确实很简单，但是往往一个应用中会有多个数据源，比如在这个实例App中UI就需要载入用户信息、餐厅信息、订餐信息三种数据后才能工作。如果仅仅靠多层嵌套回调函数的话，可能代码的耦合就非常重了。

为了解决多个数据加载的问题，我习惯的解决方法是构造一个dataReady事件响应机制。

var foodOrder = {

//数据载入后要执行的函数暂存在这里
dataReadyFunc : []

//数据源URL及载入状态
, dataSource : [
{ url : 'data/restaurants.json', ready : false, data : null },
{ url : 'data/users.json', ready : false, data : null },
{ url : 'data/foods.json', ready : false, data : null }
]

//检查数据源是否全部载入完毕
, isReady : function(){
var isReady = true;
for(var key in this.dataSource){
if(this.dataSource[key].ready !== true){
isReady = false;
}
}
return isReady;
}

//数据源全部加载完毕，则逐一运行dataReadyFunc中存放的函数
, callReady : function(){
if(true === this.isReady()){
for(var key in this.dataReadyFunc){
this.dataReadyFunc[key]();
}
}
}

//供外部调用，会将外部输入的函数暂存在dataReadyFunc中
, dataReady : function(func){
if (typeof func !== 'function') {
return false;
}
this.dataReadyFunc.push(func);
}

, init : function(){
var self = this;
var _initElement = function(key, url){
$.getScript(url, function(e){
//每次载入数据后，将数据存放于dataSource中，将ready状态置为true，并调用callReady
self.dataSource[key].data = window[key];
self.dataSource[key].ready = true;
self.callReady();
});
}
for(var key in this.dataSource){
_initElement(key, this.dataSource[key].url);
}
}
}

用法为

foodOrder.dataReady(function(){
alert(1);
});
foodOrder.init();

dataReady内的alert将会在所有数据载入完毕后开始执行。

这段处理的逻辑并不复杂，将所有要执行的方法通过dataReady暂存起来，等待数据全部加载完毕后再执行，更加复杂的场景此方法仍然通用。

使用JS模板引擎

数据载入后，最终都会以某种形式显示在页面上。简单情况，我们可能会这样做：

$('body').append('<div>' + data.name + '</div>');

如果页面逻辑一旦复杂，比如需要有if判断或者多层循环时，这种连接字符串的方式就相形见绌了，而这也就催生出了JS模板引擎。

主流的JS模板引擎有underscore.js，Jade，EJS等等，可以横向对比一下这些JS模板引擎的优缺点。

对于相对简单的页面逻辑（只需要支持if和for/each）来说，我更倾向选用轻巧的underscore.js或者JavaScript Templates。

在当前例子中，使用underscore.js生成列表就非常简单了，页面模板为：

<ul data-role="listview" data-inset="true">
<script id="tmpl-restaurants" type="text/template">
<% _.each(data, function(restaurant) { %>
<li>
<a href="#" data-rel="back" data-value="<%- restaurant.name%>"><%- restaurant.name%></a>
</li>
<% }); %>
</script>
</ul>

调用引擎

$("#tmpl-restaurants").replaceWith(
_.template($("#tmpl-restaurants").html(), {
data : restaurants
})
);

面向对象与模块化

通过上面这些工具的组合，我们有了模块的概念，有了模板引擎，有数据的加载。最终还是要通过javascript将这一切组织在一起并加入应用所需要的逻辑。为了能最大限度的复用代码，用面向对象的方式去组织内容是比较好的选择。

JavaScript虽然原生并不支持面向对象，但是依然可以通过很多方式模拟出面向对象的特性。例子中采用了我个人比较喜欢的一种方式是：

var foodOrder = function(ui, options){
//构造函数
this.init(ui, options);
}
foodOrder.prototype = {
defaultUI :  {
form : '#form-order'
}
, defaultOptions : {
debug : false
}
, init : function(ui, options){
this.ui = $.extend({}, this.defaultUI, ui);
this.options = $.extend({}, this.defaultOptions, options);
}
}
var order = new foodOrder({
form : '#real-form'
}, {
debug : true
});

将页面的UI元素以及配置项目抽象出来，在实际构造对象时则可以通过入口参数复写，可以分离整个项目的逻辑与UI，使处理的方式更加灵活。

鲜阅

统计学家的黑客技艺两则

作者/ Bruce Frey

博士，连环画收藏家和电影爱好者，现任堪萨斯大学教育心理与研究项目助理教授，教授研究生统计学，在教学过程中屡获殊荣。Bruce获得的主要成就包括：青少年时期荣获堪萨斯州大富翁锦标赛第三名，读大学时荣获堪萨斯州电影节第二名，中年时在堪萨斯州劳伦斯举办的德州扑克锦标赛中获得不错的第三名。

统计学家用来探索世界、回答问题和解决难题的工具其实并不多，关键在于他们利用概率或者正态分布知识的方法，让他们能在千变万化的情境中解决问题。本文将为你介绍这些基本的Hack两则。

将已知的分布信息表述成概率[Hack #1]，这是统计Hack常用的基本技艺，与之类似的还有用小样本数据来准确描述数量较大总体中个体的分值[Hack #2]。

不可不知的秘密

统计学家怎么让自己看起来比其他人聪明？

统计学作为一种科学方法，主要目的是对分数样本做概率解释。在深入学习前，需要简单了解一些术语，以便理解这个Hack，也为理解其他Hack打下基础。

样本是你目前收集到，就摆在你眼前的数值，用来表示既不在你眼前也没收集到的更大的数值总体。因为这些值几乎总是用来表示某一特征的存在或程度的数字，所以测量界把这些值称为分数。概率解释是对某件事情发生的可能性的解释。

概率是统计学的核心和灵魂。实际上，对统计学家的一种普遍看法就是，他们主要计算某些备受关注的事情（比如中彩票或是被雷击）发生的精确可能性。经验告诉我们，有办法计算骰子游戏结果可能性的人，同样有办法用为数不多的汇总统计数据来描述一大群人。

所以，通常统计学的教学中至少会花点时间来讲述概率的原理：计算不同组合的出现概率或者各种可能结果排列的方法。但是，统计学中更为常见的是描述性统计或是推断性统计，前者用以描述分数群组，后者仅用样本中包含的少量信息对分数总体进行估计。在社会科学中，“分数”常被用来描述人或是发生在人身上的事件。

当然，研究人员和测量人员（现实生活中最有可能使用统计的人）不局限于计算某种组合和排列的可能性。他们根本不需要计算连续3次扔一对骰子得到7的概率，他们能够运用不同的统计程序来回答复杂程度不同的问题。

如果你刚刚拿起骰子，那么这个概率是0.005或是1%的1/2。如果你已经扔到了两个7，就有16.6%的概率扔到第三个7。

准确地讲应该是1/(6×6×6)，0.00462963，约等于0.005。——译者注

秘密

概率对统计学家的工作如此重要，关键原因是他们喜欢对实际或理论分布的分数进行概率解释。

分数的分布会列出一系列不同的值，有些情况下，还会给出每个值的数量。

比如，假设你知道刚刚参加的一次测验中，分数分布是25%的人得到10分，那我可能会说，我不需要认识你，也不需要了解与你有关的任何情况，就能知道你有25%的可能性得到10分。我同样可以说，你有75%的几率不得10分。我所做的只是获得关于某些值分布的已知信息，将其表述成概率。这是一种技巧，是所有统计学家都知道的秘密。实际上，这几乎是统计学家们所做的一切！

统计学家获取关于某些值分布的已知信息并将其表述成概率。我们有必要再次强调这句话（严格来说，这是第三遍）：统计学家获取关于值分布的已知信息并将其表述成概率。

天啊！这我们都能做到！这有何难？假设有一个空咖啡罐，里面有三个弹珠。再假设你知道其中只有一个弹珠是蓝色的。现在分布信息包含三个值：一个蓝色弹珠还有另外两个其他颜色的弹珠，这三个值构成了一个样本。三个弹珠里有一个是蓝色。噢，统计学家，闭上你的眼睛，请问我首次取出蓝色弹珠的几率是多少？1/3。33%。

说句公道话，统计学家最常用的值及其分布通常比刚才那个从咖啡罐里取弹珠的场景稍微复杂或抽象一些，所以统计学家的工作看起来不是那么浅显易懂。比如，应用社会科学领域的研究人员总是用“值”来表示不同群体平均分之间的差异，或者两个或两个以上分数集的关系度量。其内在过程和刚才所举的咖啡罐的例子并无不同，不过是参考已知的值分布信息，做出对值的概率解释。

当然，关键是怎样才能知道这些奇特的、让统计学家感兴趣的值的分布？怎样才能了解平均差的分布或两组变量间关系程度的分布？方便的是，研究人员和数学家前辈已经发明或发现了多种公式和定理、经验法则、思想体系和假说，让我们了解到研究者最常用到的复杂值的分布情况。这些工作前人已经为我们做好了。

不太光彩的小秘密

统计学家获得分数分布的已知信息、将其表述为概率的方法中，多数必须满足一些前提，才能够确保概率解释的准确。其中一个几乎永恒不变的必要前提就是：样本值必须从分布中随机抽取。

请注意，在叙述咖啡罐的例子时，我插了一句“闭上你的眼睛”。如果抽样过程不是随机的，而是被其他一些因素所引导，那么得出的相应概率就错了；最糟糕的是，我们无法了解错误的程度。现今，也许绝大部分应用心理学和教育学研究都不是随机采样的。

比如，选修《心理学导论》课程的大学生，构成了很多心理学研究的样本。由于贪图便利，教育研究人员常用自家附近的小学生充作样本。这是社会科学研究者常常容忍、忽略或担心的一个问题，但不管怎样，非随机抽样是很多社会科学研究中存在的一个局限。

仅用两个数字描述世界

《有趣的统计》介绍的大部分统计解决方案和工具之所以行之有效，只因为你能通过样本对总体进行精确推断。获得以上推断技巧需要用到的元工具、主要指导方针和所有秘密之最就是中心极限定理。

每当你试图描述一组分数时，统计学都能为你提供解决方案。有时你想描述的整组分数全都摆在眼前，这时完成该任务的方法称作描述性统计。更常见的情形是，你只能看到欲描述的一组分数中的一部分，但仍想描述整个组。这种概括性方法称作推断性统计。在推断性统计中，你想要推断的整个分数群组叫做总体，其中能看到的那一部分叫做样本。

从定义上看，不经直接观察就能有把握地描述由多个值构成的总体，想来颇似一种奇妙的把戏。然而，你只消运用三条信息——两个样本值和一个总体分数分布形态的假设，便可自信而准确地描述那不可见的总体，其结果准得令人称奇。这样一套推断程序就是所谓的中心极限定理。

统计学基础一点通

推断性统计用两个值来描述总体：平均数和标准差。

1. 平均数

若要描述值的样本，报告一组分数的合理概要比展示每个分数更高效。这个数值应该能够代表群组中所有分数以及它们的共性。因此，这个数值被视为一组分数的趋中趋势。

由于种种原因，通常情况下对趋中趋势的最佳度量是平均数[Hack #21]。平均数是所有分数的算术平均，即把群组中所有值相加求和并除以群组中值的数量。相比其他趋中度量（比如中位数、众数等），平均数可以提供关于群组分数的更多信息。

实际上，从数学上看，平均数具有一个有趣的属性。其计算方式（所有分数相加并除以分数的数量）所导致的副效应就是产生了一个和其他所有分数尽可能接近的数字。这个平均数会和群组中的一些分数比较接近，和另一些分数距离较远。但是如果你将这些距离相加，得到的总数是最小的。其他任何数字，无论是真实的还是想象的，与群组中各个分数的距离总和都不会比它更小。

2. 标准差

仅仅知道某一分布的平均值还不够，我们还需要知道有关分数变异性的信息。是多数接近平均数还是多数远离平均数？两个非常不同的分布可能有着相同的平均数，但变异度却大为不同。最常用的变异度量概括了每个分数和平均数的距离。

像平均数一样，承载更多信息的变异度量能用到分布内的所有数值。标准差就是这样一种变异度量。标准差是每个分数和平均数的平均距离。它统计某一分布中所有的距离并算出平均值：这里的“距离”是每个分数和平均数的距离。

另一个经常用于概括分布变异性的值是方差。方差是标准差的平方，在描述单一分布时并不是特别有用，但对比较不同分布的差异性很有帮助。方差常用作统计运算值。

标准差公式看起来复杂得超出必要，但求和距离（当平均数被当做分割点时，负向距离总会抵消正向距离）在数学上的确有些复杂。故而有以下的方程式：

中心极限定理

中心极限定理非常简单，但非常强大。该定理表述如下：

如果你从总体中随机抽取多个样本，那么每一样本的平均数趋于正态分布。

由此定理衍生出一系列的数学规则，用以准确估计上述虚构的样本平均数分布的描述值。

- 这些平均数的平均数（念起来真拗口）等于总体的平均数。凭借单样本的平均数，可对平均数的平均数做出很好的估计。

- 这些平均数的标准差等于样本标准差除以样本量的平方根（以字母n表示）：

样本集合内的样本数量越大，这些数学规则产生的结果越准确，分布也更接近于正态曲线。

当样本数为30或30以上时，应用中心极限定理似乎足以得出准确结果。

那又如何

好吧，中心极限定理看起来有那么一点儿智力趣味性，并且毫无疑问能让统计学家们兴奋不已，但那又意味着什么呢？怎样才能用它来做点酷酷的事？

正如[Hack #1]中讨论的，这个所有统计学家都知道的有效秘诀是：获取一些值分布的已知信息，并将其表述为概率解释。当然，关键是怎样才能知道引起统计学家兴趣的不同类型值的分布？又该如何得知平均差异的分布或是两组变量关系大小的分布？答案是：中心极限定理。

比如，为了估计任何两个群组在某个变量上出现一定差异的概率，我们需要知道样本对应的总体平均数的分布。而当总体平均数不可见，甚至只是理论存在时，如何能够了解分布的状态？小伙子，答案就是中心极限定理！当样本可能从无限可能相关性的总体中抽取时，如何能够知道相关性（衡量两个变量间相关强度的指标）的分布？听说过中心极限定理吗，老兄？

既已知道正态曲线上值的比例，中心极限定理又告诉我们这些概括性的值为正态分布，因此我能对每个统计结果标出概率。我能在我的结论和决策中用这些概率表示统计显著性水平（置信水平）。如果没有中心极限定理，我几乎无法做出任何关于统计显著性的解释。那将是何等乏味而悲哀的生活。

中心极限定理的实际应用

该定理在实际应用中，只需从总体中随机抽取几个样本值。例如，假设我手下新增了8个童子军。我的职责是教会他们打绳结。我猜在我指导过的童子军学员当中，这一批孩子并不是最聪明的。

在开口要求增加学费之前，我想要判断他们是否真的有点笨。我想知道他们的智商。我知道童子军的总体平均智商是100，但我注意到这8个童子军学员里没有一个智商超过100的。按理说，总该有个别超出这一水平的。这一组人是从平均总体里刻意选出的吗？也许，只是我的样本有点不同，并不代表所有童子军？如果使用中心极限定理的统计方法，就会提问道：

这个样本所代表的总体平均IQ可能是100吗？

如果我想知道我这组童子军是从什么样的整体中抽出来的，可以使用中心极限定理相当准确地估计总体的平均IQ和总体的标准差。我同样可以计算出样本平均IQ和总体平均IQ有多大差异。

我需要从手下的童子军那里获得一些数据以便进行以上计算。表1提供了一些不错的信息。

表1：童子军聪明程度

这8个IQ分数样本的描述性统计是：

- 平均IQ=91.75

- 标准差=4.53

于是我知道在我的样本组中，大部分个体的IQ分数在91.75的4\frac{1}{2}左右。不过，我更感兴趣的是他们所来自的那个未知的总体。利用中心极限定理我能够估计这一总体的平均数、标准差，更重要的是，能估计样本平均数在多大程度上偏离总体平均数。

- 平均IQ

我们的样本平均数可作为最好的估量依据，所以总体平均数很可能接近91.75。

总体中IQ分数的标准差

计算样本标准差的公式是专为估计总体标准差而设计的，所以推测总体标准差是4.53。

平均数的标准差

这才是真正关注的值。我们知道样本的平均数小于100，但那可能是偶然吗？当从总体中随机抽取这含有8个数的样本时，样本的平均数会在多大程度上偏离总体平均数？这里要用到之前提过的方程式。输入样本值计算平均数的标准差，这通常称为平均数的标准误差：

由于中心极限定理，我们现在知道，8个童子军中大多数样本的平均数是在总体平均数±1.6个IQ点的范围内。所以，这个平均数为91.75的样本不太可能是从平均数为100的总体中抽取出来的。总体平均数为93或者94，但不是100。

因为我们知道这些平均数是正态分布，所以可以利用关于正态分布形态的知识来生成一个精确的概率，即从平均数为100的总体中抽取出平均数为91.75的样本的概率。这种情况发生的概率低于1/100000。看来我手下这批学习打绳结的孩子要比普通人难教一些。我也许可以多收一点学费。

其他生效领域

中心极限定理的一个模糊版本指出：

受很多随机力和无关事件影响的数据最终呈正态分布。

因为这几乎适用于我们度量的所有事物，所以可以应用正态分布特征对多数可见和不可见概念做概率解释。

迄至现在，我们还没有说到中心极限定理最厉害一条的推论：无论总体分布形态如何，从总体中随机抽取的平均数均呈正态分布。想想这个，即便你从中抽取样本的总体是非正常的——甚至走到了正常的反面（就像我的叔叔Frank那样），样本的平均数仍会是正态分布。

这是自然界相当了不起和便利的特征。不管我描述的总体是正常还是非正常、在地球上还是在火星上，这一要诀始终有效。

概率在我们的日常生活中扮演着重要角色。每个人独特的基因组成，学生的在校成绩，赌博游戏中的下注，体育比赛的结果等，到处都有概率的身影。《有趣的统计》一书分为6章，共包括75个“黑客技艺”（Hack），揭示了如何通过推论统计学来理解事物的运作方式，发现变量之间的相关性，以及透过局部样本分析推断出总体特征，做出异常精准的预测。本文节选自《有趣的统计》。

九卦

去加勒比海做IT 项目的几点回忆

作者/ jiaj999

程序员一枚，现居于加拿渔人村。

今年正当世界杯足球赛巴西队与墨西哥队进行得如火如荼之际，我的一个现定居于美国亚特兰大的大学同学，转发了一篇有关美国家庭日常备用的非处方药的文章，“你不可不知的美国十种家庭常用药”，它使得俺想起了一些往年的事。

14年前的刚好今天，我来到加拿大，开始了曲折而又不算太短的寻找网络方面专业工作的历程。经过努力，包括考出CCIE（R/S），被一家思科金牌认证公司录用，开始从事思科语音方面的项目工作。不久有几次机会去加勒必海的多米尼加和波多黎各做项目（客户是全球一著名企业），有几个事情是常常经历的。

（1）多米尼加海关人员将我这个当时持中华人民共和国护照之人要无缘无故停留半个到一个小时时间，一是他们很少见到中国护照似的（他们要我传你，你传他，说：“This is a Chinese passport！”），二是他们当时与台湾建交。

（2）虽然酒店吃的东西很好，但每次都是肠胃不适，好在买了文中提到的止泻药Repto Bismol，立竿见影，所以必备之。

（3）跟着那个阿根廷籍同事，去阿根廷餐馆大块吃牛肉。过了几天梁山好汉的日子。

Dominica Hotel

我的同事Andrian

（4）还是跟着他，胆战心惊地去参加舞会：人挤人，手碰手，脸擦脸，连木楼梯上都是玩得很High的人，一碰到他们的胯部，你没准会感受到一个硬绑绑的家伙——人家是可带武器进场的。

（5）一人独吃好几磅的大龙虾（二吃），价格不菲，将我吓晕，200$美金。但既然客户同意，俺也吃得心安理得。

（6）去看哥伦布发现新大陆时的城堡。

（7）天气炎热，床上没准爬上蚂蚁，将你咬得痒死。

（8）阿根廷籍同事很快凭借西班牙语与当地公司两美女搞熟，周末去海滨，俺跟上，拍照，此时他会在水中警告俺说：“Don't let my wife know those pictures.”俺点头并蒙头喝大颗椰子里的汁。[呲牙]

（9）大概一年后, 我考出了第二个CCIE（VOICE），大概当时是全球前200个思科的语音专家认证（跳槽后考出）。

（10）考出语音认证不久，被原来公司纠缠连续一个月，又被挖回，吃了回头草（不管，只要给的条件好，后来觉得不必在意）。其后公司来了个刚从国内移过来的母亲，在国内曾在Nortel干过。我带队去波多黎各同一公司做同样项目，她跟上。她工作很勤奋，爬上爬下、蹬上蹬下安装语音电话。周末一块去海滩及热带雨林，和San Juan游玩，只是觉得她老是要时不时休息。做好项目回到加拿大后的一天，她对俺说：其实那时我已经怀孕二个月，因为刚找到第一个工作，不好意思对公司说（注：加拿大法律保护不准炒孕妇的鱿鱼）。我听了后，很是吃惊，对她说：我的天，好在你沒事，要有事我这辈子永远内疚让你一孕妇干那样的活，你应该早让我知道。由此可见国人在加拿大工作的艰辛。（39岁孕，这是第二個孩子，其老大已经11岁。）

总之，花若盛开，有蝶来舞；你若精彩，天有安排。俺的这些同事一直幸福地生活着。

出版的未来

从传统行业进入互联网行业的得与失

作者/ 谢工

现就职于百度，负责文库和阅读运营。图灵社区发起人之一，从事IT图书出版传播近20年。向往自由、快乐的生活。新浪微博：@谢工在百度

已经入职百度近3个月了（本文作于2014年4月），在个人作者写作系统即将上线之际，由衷的想写点什么。从传统行业进入互联网后，究竟有什么收获，有什么失落，有什么可以分享的？

一个偌大的公司，对每个新入职的员工来说，肯定是很新鲜的，但一个百度这样的互联网大公司，对我们这样工作了20多年的传统行业人来说，是充满了挑战的，同时也有各种疑惑和不确定性。幸运的是，我在图灵的三年中，不断的和互联网行业的作者打交道，参与很多技术型公司的会议和沙龙，也报道了很多大公司背后的人和故事，所以这一切，还好，不算太陌生。

我能总结到在互联网行业工作的感受，集中在这几个方面：工作节奏极快、思路或习惯不断被打断或改变、心态需简单开放、创新无处不在。而从互联网产品的内容层面看：产品实现过程中接触到的技术手段、数据分析和运营能力等都是传统行业所没有的，这种感受和震撼，我想可能有些人一辈子都遇不到，也许是种遗憾。

1. 工作节奏

在上班的这几个月中，平均每天要同时处理十几项正在进行的事儿，比如招聘、与运营开会、与产品经理讨论设计方案、与各小组同事交流思路、与研发技术人员研究项目进展情况、与横向其他部门协调等等。如果你所在的部门是一个产生内容的核心部门，在一个新项目上线或优化迭代过程中，涉及到的产品层面的复杂性和交互体验都是极为复杂的。

这与在传统行业有着天壤之别。

2. 思路、方法或习惯

要知道在传统出版行业，一本书从选题到出版需要近一年的时间，一个编辑一年大约可以看10本稿子，一个策划编辑大约一个月策划几本书，一个市场发行或销售人员可以接触到几个到几十个渠道或书店等。

而在互联网不是，一天要接触非常多的数据反馈，要受到产品形态的多种变化反馈，要面对几万、几十万、上百万或上千万的用户和流量的反馈信息，需要不断考虑产品、用户之间的匹配关系，一切基于用户的需求出发，面对的是无法回避的问题，最直接和最真实的数据放在眼前，你没有任何借口。

在传统出版呆久了，都会有固定的工作习惯和作息时间，有墨守成规的套路，有各种可以转移问题的借口。一本书卖好了，理由无处不在，卖不好，各种不靠谱的分析和推策，而在这之中，编辑或营销人员的价值无法体现得很充分，因为人人都可以成为事后诸葛亮。

而我自己就是一个编辑做不好，市场做不好，销售做不好等各种做不好的出版从业者，所有的做不好的原因和理由，可以说在丧失信心之后，都归结于自己也许一开始就不适合这种行业。真心不知道一本书为什么能火，为什么就卖不动，是我的某个想法或运作起了作用，还是就是撞了几次好运而已。

但我一直认为，图书的关健还是一头是在作者身上，另一头是在读者身上，在这两头下功夫，都是有回报的。

而互联网就是有这样的平台，你可以看到各种数据真实的反馈，书的价值体现，在哪几个纬度上最起作用，哪些纬度无关紧要，哪个段落导致了读者的购买行为，哪个标题停留时间最长，一切基于阅读需求而产生的动作，实在让人跃动不已。

一本书，图片很好看，内容也不错，但就是没有人翻阅，为什么呢？其实答案你完全可以自己找到了。

3. 心态决定一切

在传统行业，感觉自己挺牛逼，有时也很清高，我是某某书的编辑，我策划了一本好书，某某书卖得很好，有我一份功劳。你装做很懂书的样子，不断在推销你认为的好书，然后你会有一群粉丝，天天围着你。独自坐在一个很大的办公室屋子里，自我感觉不错吧。

而在一个大公司里，你是一个部门的一个产品经理，你今天坐在这里，但明天你坐在哪里就不知道了，你可能有自己的兴趣和喜好，对某个产品极有感觉，但能有多长时间来成就你的兴趣或喜好，完全取决于你自己在和时间和速度赛跑的能力。你能驾驭一个产品的能力，取决于你对最终实现结果的过程中，你的自信力，决策力、协调力和掌控能力，说得更直接点，你能为结果负责的实力，决定了你能在这个团队存留多久。

互联网牵着两头，一边是需求，一边是内容；一边是作者，一边是读者；你想要的结果，充满了不确定性和可变化性。今天的需求，可能到明天就OVER了，你的心态就是不断的适应需求的变化，不断完善自己的产品设计，不断接受自己所犯的错误带给其他部门的困扰，也不断在否定自己的过程中学到很多做事和做人的道理，得到很多有智慧的人的帮助。

在传统行业积累的所有的经验和能力，在互联网产品中显得不值一提，从头开始，从新开始，几乎是我来这里唯一能做的事了，而这之前的经验，全部象颗粒或面包屑一样，时常会调用一点点，但大部分时间很悲催，几乎没有一件事，是可以随手可得的，经验反而成了鸡肋，不断被人置疑，或不断的否定过程，会让自尊心受到各种侵蚀，还能HOLD得住不？

在面试传统出版社的编辑过程中，有很多有经验的编辑会提出一连串的问题：是不是电子书的阅读体验做的不好，所以没人买；是不是电子书不够丰富，品种太少，所以没人问津；是不是中国的读者对电子书的消费能力就是不行，所以这个产品可能真没希望了。

当然有自信的编辑会说，我觉得加上某个功能，一定就行.......此时，我只能打断并问一句：电子书的阅读人群在哪里，有购买付费习惯的人群在哪里，需求究竟是哪个品类或领域？大而全还是少而精，谁能说明白呢？

在相当长的一段时间内，出版物电子书的模式都还在探索中，只有在实践过程中才能找到答案。此时只有放下一切，拥抱变化，不断迭代产品和读者需求的匹配度，离真相才有可能更近一步。

4. 创新无处不在

在相当长的一段时间内，每天都在面临改版和创新的需求，实现这些需要很多的驾驭能力，一旦决定，结果会马上呈现出来，这也是很多时候，需要大量数据积累，才能完善一套产品的想法的原因。没有时间和数据积累，往往结果都不尽如人意，而这些环节需要的靠谱的产品分析、设计能力，这背后都是靠人来实现的。

而做成一个既和传统相关，又很互联网的产品，对人才的需求极具复杂性。不仅要了解书，还要了解作者和读者，不仅要了解传统行业的规律，更要了解互联网产品的规律。不权要了解PC端的需求，更要了解无线端的需求，不仅要知道产品，更要了解实现产品的一切手段和技术实现。

所有的一切，全靠创新的思路来实现，没有参照物，没有可以借鉴的，一切都是未知数。

可以想象，失去的是成就感。因为今天不知明天的结果，不象一本书，你想什么操作都可以，你自己可以决定。在互联网，要的就是让人平等的获得信息，可信息就是这样变化莫测。

所以将自己置身于所有需求之中，成为各种信息中的一个小小的颗粒，也许积累到一定时候，自己可能会成为某个结点，并形成某个需求的关键点。我有时一个人做事，有时候和一群人做事，但有时可能就是大家围绕着我做事了。

接下来，可能会很好玩了.......

你可能会问，还有什么是失去的，没有个人的可支配时间，没有悠哉的清闲状态，没有时间陪儿子或朋友闲聊了，没时间煲电话粥了。

但在没有成就感面前，这些失去的，都不重要了。

书榜

看看大家都在读什么？

谷歌和亚马逊如何做产品

作者：Chris Vander Mey

译者：刘亦舟

书号：978-7-115-34931-6

图灵社区推荐：

“在移动互联网时代，产品的用户体验比概念更重要。我们要靠技术给用户带来体验上的优势，而不是靠概念。本书不仅着墨于产品的极致体验，也为互联网从业者总结了一整套关于产品如何迭代、发布、市场推广等的简单框架，适合实际操作，且易于学习。”

——傅盛，猎豹移动CEO

Android编程权威指南

作者：Brian Hardy， Bill Phillips

译者：王明发

书号：978-7-115-34643-8

图灵社区推荐：

“不管是你刚刚迈进Android开发的大门，还是准备掌握更多高级开发技术，本书都非常值得看。”

——James Steele，《Android开发秘籍》作者

只是为了好玩：Linux之父林纳斯自传

作者：Linus Torvalds， David Diamond

译者：陈少芸

书号：978-7-115-36164-6

图灵社区推荐：

林纳斯唯一亲笔自传，充满各种笑料以及对技术和软件的严肃思考。

算法帝国

作者：克里斯托弗•斯坦纳

译者：李筱莹

书号：978-7-115-34900-2

图灵社区推荐：

人类正在步入与机器共存的科幻世界？看《纽约时报》畅销书作者讲述算法和机器学习技术如何悄然接管人类社会，带我们走进一个算法统治的世界。

R语言实战
作者：Robert I. Kabacoff
译者：高涛 肖楠 陈钢
书号：978-7-115-29990-1
图灵社区推荐：

Python基础教程（第2版•修订版）
作者：Magnus Lie Hetland
译者：司维 曾军崴 谭颖华
书号：978-7-115-35352-8
图灵社区推荐：

算法（第4版）
作者：Robert Sedgewick， Kevin Wayne
译者：谢路云
书号：978-7-115-29380-0
图灵社区推荐：

第一行代码——Android
作者：郭霖
译者：曾文斌
书号：978-7-115-36286-5
图灵社区推荐：

Linux就是这个范儿
作者：赵鑫磊， Jie Zhang
书号：978-7-115-35936-0
图灵社区推荐：

互联网思维的企业
作者：Dave Gray ， Thomas Vander Wal
译者：张玳
书号：978-7-115-34930-9
图灵社区推荐：

电子书榜

1. AngularJS权威教程
——AngularJS领域的里程碑式著作，既是一部权威教程，又是一部参考指南

2. 数据结构与算法JavaScript描述
——用JavaScript描述数据结构与算法的开山之作

3. 深入浅出Node.js
——阿里巴巴一线Node开发者最真实的经验

4. iOS开发指南：从零基础到App Store上架（第2版）
——全新iOS 7 API，详细介绍iOS 7相关的知识点

5. Android编程权威指南
——Android私人导师在你身边随时为你答疑解惑

6. 互联网思维的企业
——用互联网思维决策企业运作，让企业更灵活、便捷

7. 谷歌和亚马逊如何做产品
——软件项目管理人士的最佳基本功指南

8. 微交互：细节设计成就卓越产品
——设计大师唐·诺曼作序，Gmail、Google Reader设计师Kevin Fox等鼎力推荐

9. 简约至上：交互式设计四策略
——赢得最大多数主流用户的内功心法

10. 如何变得有思想？——阮一峰博客文集Ⅰ
——多年以后翻到，不要忘了曾经的感悟

成书手记

《第一行代码——Android》封面诞生记

作者/ 陈冰

曾任清华大学出版社计算机图书策划编辑和华中科技大学出版社大众类图书事业部主任。在清华社工作期间策划了畅销书《大话设计模式》。做过多年软件开发（高级程序员）。并创作了《Flash第一步》、《电脑使用说明书》等图书。

《第一行代码——Android》最终的封面：

每一本好书的封面的诞生都不是轻松的事情。不仅不是轻松的事情，事实上正相反，几乎无一例外都是艰难的过程。但如果最终得偿所愿，一本书找到了它的封面，那这个艰难的过程就会有一个愉快的结果。

什么叫一本书找到了它的封面？就是当你拿到这个封面时，你大脑中有一个强烈的意识，不会有任何一个其他封面更适合这本书了。

这本书，我最初给它这个书名时，我就意识到这是个好书名，但对于给这个书名设计一个好封面而言，却是个极难的课题。“第一行代码”是我策划的一个给编程初学者看的系列，本书是这个系列的第一本书。

在策划这本书（这个系列）时，我就想好要有一个独一无二的形式上的突破，要给读者以新意和独特的阅读体验。为此，我决定给它引入一个经验值、升级与宝物系统，这是在此前的任何计算机图书（据我所知也包括其他所有类别的图书上）上都没有过的事情。

基于这些考虑，这本书的封面必须同时满足以下四个特点：

一，应该庄重、可信、能让读者感到有一股内在的力量，让读者感到这本书所讲述的知识是可信赖的。

二，有一种神秘感、带有一些魔幻现实主义的色彩，让读者感到这本书与其它书明显不同；

三，让初学者有亲切感，IT感也必须在其中体现，让读者感到这一场景他非常熟悉，有认同感。

四，要有绝对的视觉冲击力，在网店上销书，都是邮票大小的展示区，不可能让你捧到手里仔细端详，如果封面不能在邮票大小的面积上给你造成视觉吸引力，但这个封面无论设计得多么绝妙都是失败的。 设计一个封面不难，但要设计一个好看的封面就相当不易，而要设计一个既好看又能卖的封面则是极难的事情。

为了达到这个效果，我开始构思这个封面。在“第一行代码”这几个字的字体上做文章的念头曾在我脑海中一闪而过，但立马就被我否定了，那样出来的效果会过于张扬，同时又不免落入俗套，而且弄不好就会搞得像本财经类图书。

我告诉自己，既然是本给初学者看的书，那首先就要让初学者有亲切感，要设计一个典型的元素，让初学者一看到它亲切感和熟悉感就立马油然而生。八年前，我在清华时曾策划过一个“第一步”系列，当时我设计的封面元素就是在画面中央有一个“Hello，World”字样，很醒目。

Hello，World对于初学者而言无疑是最熟悉的元素，但我不喜欢重复自己，对于这个“第一行代码”系列，我要重新琢磨一个元素。我想到了我当年刚刚学习编程时，在深夜灯下电脑前工作的情景，也想起来以前深夜写计算机书时的情景，不管是写代码，还是写计算机书，深夜电脑前工作的情形是每个程序员，无论新手还是老手都非常熟悉有亲切感的画面，甚至此时此刻的你就是这样。所以我决定就用这个画面这个场景来做这个封面关键识别元素。

那么这个识别元素具体要有哪些物件组成呢？我开始进一步构思这个场景具体要包含哪些元素。我凭着直觉，让脑海中自发产生的图像带我前行，我看到一个小程序员坐在书桌前，他背对着我，面前是一台ThinkPad笔记本电脑，左手侧有一台iPad，右手侧有一台三星Note大屏手机，还有一碗刚泡好正待吃的方便面，一杯果汁，一摞书，因为正在从事Android开发，所以书上还有一个Android小机器人的手办，还有一盆绿植（我又在脑海中仔细看了看，原来是盆仙人掌）。我把这些元素在纸上画出来。为了让画面更丰富，我瞅了瞅我自己的书桌，然后又在画面中加了一个水杯，几张纸和一支笔。为了让画面更生动和富有趣味，我在桌旁的地面上加了一只小猫，它正好奇的望向桌底某个方向，似乎发现了什么异次元的东西。

这是当时我画的草图。

当时我在主画面周围还画了一些家具、一个窗户，以及窗前的天文望远镜（望远镜的出现有两种含义，这是这个小程序员的爱好，同时也意味着梦想），后来我意识到，整个画面很像我房间的样子。

我让与我合作多年的插画师巫俊武来画这个插画。这是插画的第一个版本。

不萌，而且过于写实，欠缺一些魔幻感，于是继续修改，得到下面这个版本。

有点萌了，有点味道了。于是，我让俊武先进行一下着色，看看效果。我当时给了俊武一幅插画效果（这是《梦想还是要有的，万一实现了呢》一书中的一幅插画），让他就按照这幅插画的效果来进行着色。因为我当时希望这本书的画面要温暖、明亮、色彩丰富，看着让人心生喜悦，带给人阳光向上的感觉，同时时间是晚上，所以这幅插画非常符合我要求。

结果，俊武一番神勇发挥后，得到下面这个让我大跌眼镜的效果。

当时又进行了一些着色的调整，但始终离我要的效果相去甚远（俊武不太擅长我要的那种着色风格），于是我果断放弃着色，决定就用黑白色来实现这幅插画。

我开始思考怎样才能让黑白的插画产生足够的力度，那就必须有足够的细节，同时笔触要加粗，粗笔触本身就会带来细节感和质感。笔触太细，必然会有单薄感、缺乏力度。因为缺少细节而导致缺少力度，所以我给插画师找了一些素描风格的插画，让他往那种效果上靠拢，几经修改，得到下面这个版本。

画面开始有一些厚重感和力量感了，但人物的脑袋有问题，不够大不够圆，继续修改。几经修改，得到满意的版本（我当时以为的最终版本）。

然后，我又让俊武照着我的另一副草图画好了封底用的插画。

这个封底的插画是和封面插画相呼应的，封面插画是在家里（屋内）看到的情景，而封底插画是同一时间在屋外（小区里）看到的这家的情景，整栋楼的灯光都熄灭了，所有人都入睡了，只有这个小程序员的房间还亮着灯，正在认真地努力地写着代码。他的梦想是成为优秀的程序员。

因此，我在封底写下了这样一句宣传语：

我挥舞着键盘和本子，

发誓要把世界写个明明白白。

两行字下面的那个小灰块代表正在闪烁的光标。

然后，我把全套封面设计资料（包括插画、封面宣传语、目录、样章等等）交给我的设计师绿豆（真名潘建永），并电话中跟他沟通了这个封面的设计要求。一周后，我拿到了下面的版本，我在已经坐在椅子里的状态下又进一步跌坐在了椅子里。

简直乱成一锅渣了，而且画面很单调，毫无让人去翻阅的念头。绿豆跟我说，他感到这幅黑白的插画限制了他的设计发挥，因为为了配合一幅黑白的画，封面的底色几乎只能是黑白的（至少我和他当时异脑同思都是这么认为的）。于是，事情几乎又回到了起点，还是必须搞出一幅着色的插画才行，才好后续设计的发挥。我安慰了一下绿豆受伤的心，决定继续去寻找合适的插画师。我在chuangyiren.cn网站上翻阅了几百页，找到了多位优秀的插画师，然后我从中选了一位插画师，和他电话沟通后，请他开始画这幅插画。

我这时对这幅插画的画风应该是怎样的，已经有了一些想法上的变化，我感到如果画面和上面提到的《梦想》那本书的画风一样的话，会显得过于卡通，有些低幼，而且缺少神秘感，与这本书中所包含的这套令人耳目一新、匪夷所思的升级与宝物系统的感觉不是很搭，同时，这种画面缺乏厚重感和力度，由于颜色过多，在网店小图上展示时也很难形成视觉冲击力。因此，我决定放弃这种画风，转而寻求一种能满足神秘、厚重、且具有强力的视觉冲击力的画风。

这时，我突然想到了我以前看过的一部动画片电影《凯尔经的秘密》。这部动画片的风格正适合这本书。

但最终拿到的插画效果和我想要的仍相去较大。

我这时感觉多少有点崩溃，我觉得我最初制定的设计方案或许很不错，但实现难度较大，而且即使完全按照我的设想实现了，画面冲击力很可能仍然不够大。

在寻找新的插画师，以及让新的插画师试画的期间，我也同时在让俊武把原来的素描版插画进行完善，因为此时我猛然想到，我不能仅仅只在封面上写上一句“第一本引入经验值、升级与宝物系统的计算机书！”就想当然的认为读者会信了，并且会感受到这句话意味着什么。我必须要在封面上直接就以画面的形式把这本书所加入的这套独一无二的经验值和升级系统体现出来，所以只画出小程序员显然是不够的，还要有这套系统中所营造的那个Android神界的人物、角色、物品。

所以，我让俊武在画面上增添了这样七个元素：

一，Android前辈遗失的修行卡

二，外围守护者

三，信心增强大力丸

四，上古奇书

五，神树

六，粗布Android战袍

七，界面砖家（法师造型）

我画了草图，并跟俊武说明了这些元素各自的形象。

第二天，我得到了一个身材矮小的牛魔王（俊武想象中的外围守护者），以及忘记从哪个软件里跑出来的Wizard（俊武想象出的巫师）。

我平静了一下心情，调整好思路，继续与俊武沟通。我从网上找到了这七个元素中每一个我满意的造型，然后让俊武参考它们进行绘画，果然，这次的接近了不少。

但守护者的神态不够威武，而且身高不够，感觉不到是神界的人物，更像是个脾气和善的在自由市场卖猪肉的胖商贩，巫师的身高也不够，感觉像西游记里的土地或是小矮人之类的角色。我让俊武想象希腊、埃及那些神庙中的顶天立地的神像，感受他们的体积和庞大，体会他们的威严，经过数次修改后，我拿到了满意的版本。

插画很完美了，但封面的总体设计方案的问题依然没有解决。我集中精力只想一个问题，怎样让画面有视觉冲击力，显然必须保持单色色块尽可能的大，因为一旦画面被多种颜色和色块分散后，在网店上邮票大小的面积上就更形不成什么视觉面积了，所以我想到必须采用整个封面用单一色调的方案，可选的颜色有红色、黄色、金色、蓝色、绿色，充分琢磨和体会后，我选了红色，蓝色也很有冲击力，但不管是读过本书内容还是这套升级系统，你都不会在脑海中产生蓝色这个颜色。你的脑海中会产生绿色这个颜色，但绿色在网页上的视觉冲击力太弱，在催人向上的力度上也较差。红色最合适，但不能是大红，大红太刺眼，而且略显俗气，所以我用了暗一些的红色，绿豆说他是往红色中加了20的黑，出来的这个效果非常棒。

为了尽量让红色占有更大的视觉面积，我想了一个方案，在画面前景色中只保留小程序员和那只小猫是白色的，而把其他元素（守护者、神树、古书、巫师等）都放入背景的红色中，作为底纹呈现出来，这样就不会喧宾夺主，而且有一种那些神界的人物是这个小程序员的脑海中的想象或者说回忆的韵味。此外，单独的红色，即使加上这些神界的元素做底纹还是略显缺乏一点层次，我又让绿豆在底纹中加入了一些植物的叶子。我选了恐龙时代就存在的蕨类植物的叶子，因为这些叶子有一种与生俱来的神秘感和沧桑感，若隐若现间，烘托出本书应有的神秘和庄重的味道。

此前，由于封面上宣传语太多，不方便设计，所以我让绿豆把其中一部分宣传语设计到了腰封上。腰封对这些重要的宣传语能起到更好的宣传作用，同时封面本身也干净了很多。

至此，整个封面设计完成。

最后，感谢绿豆和俊武的出色工作。虽然每个封面的创作过程中经常充满曲折，但与你们合作，最终总能获得出色的封面。

PS：前几天看过这本书封面的读者可能会发现现在这个最终的封面和前几天公布的版本有一点面积不大、却立竿见影的不同。那就是封面上的那个小程序的周围被点亮了。原因是我感到之前的版本虽然视觉冲击力很足，但看久了会有一点冷，缺少一点温度，而且也无法让人感到是在夜里。所以我引入了这个黄色的光晕，虽然改动很小，但整个画面立马变得温暖了，而且一下子就会让人感受到是在晚上，同时，会让人感到这个小程序员正在散发出奋斗的光芒，周围的Android神界的英雄们也在光芒的映衬下更有神明的味道了。

《第一行代码——Android》是Android初学者的最佳入门书。全书由浅入深、系统全面地讲解了Android软件开发的方方面面。第1章带领你搭建Android开发环境，完成你的第一个Android程序。第2至13章完整地讲解了Android开发中的各种基本知识和关键技术，包括四大组件、UI、碎片、广播机制、数据存储、服务、多媒体、网络、定位服务、传感器，以及分布式版本控制系统Git的使用等等。在部分章节会穿插相关技术的高级使用技巧。第14章和第15章则将带领你编写一个完整的项目，教会你如何打包、上架、嵌入广告并获得盈利。本书内容通俗易懂，既适合初学者循序渐进地阅读，也可作为一本参考手册，随时查阅。

妙评

看完这本书整个人都不好了——《写给大家看的设计书》读后感

在设计大师RobinWilliams看来，设计其实很简单。在这部畅销全球多年、影响了一代设计师的经典著作中，RobinWilliams将优秀设计的秘诀归纳为对比、重复、对齐和亲密性四条基本原则，并用简洁通俗、幽默生动的文笔，同时配以大量经过修改进行前后对比的实例图解和设计练习（并提供解答），直观清晰地传授给读者。通过《写给大家看的设计书》，普通读者很快就能够自信地设计出专业级别的作品，而专业设计师也将从中获得灵感和解决问题的途径。

作者/李晶

从文档排版到ppt设计，从制作网页到写程序，作为一名计算机专业的学生不敢想象不懂点设计怎么混下去，可谓不懂设计的码农不是好黑客。

其实不仅仅是计算机专业的同学们，哈代说过，丑陋的数学在世界上无法生存；凯利·约翰迅相信外观优美的飞机飞得漂亮。每次看到一个设计的很烂的网页的时候我都会觉得这网站是骗人的，每次看到特别丑的应用的时候就想直接卸掉，每当我看到别人给我排版特别恶心的文档或设计的不忍直视的ppt的时候，我都希望把这本设计圣经推荐给他们。

设计在生活的各个方面，一个人的设计能力可以影响别人对你的印象甚至是你的能力，信誉度......

这本书不论从内容还是装帧设计来说都可以说上是精品。从简装到精装，排版、封面设计的飞跃想必也是出版社受到了这本书的影响。

图灵的精装书不多，这本书的编辑在社区也说过，只有那些经得起时间考验的书才会出精装版。这就充分证实了这本书的价值，Paul Graham在《黑客与画家》中说过“好的设计是经得起时间的考验的”，这也恰恰证实了这本书的价值。

人人可以懂得设计，设计师知道如何哪里应该怎么设计，普通人靠运气尝试，有时候也能做出不错的设计。Robin Williams用通俗易懂的语言将设计原理概括为“亲密、重复、对齐、对比”这四个原则，同时还介绍了一些色彩搭配与字体搭配的原理，虽然看完这本书不能取代设计学院四年的学习，但也足以让屌丝逆袭，麻雀变凤凰，让你的设计拿的上台面。

看完这本书，用挑剔的眼光来看这个世界。

《Web 性能权威指南》，一年一遇的好书

《Web性能权威指南》的作者是世界顶尖的Web性能工程师，他在书中深入浅出地讲解并演示了针对TCP、UDP和TLS协议的性能优化最佳实践，以及面向无线和移动网络进行优化时的特殊要求。随后，他全面剖析了浏览器技术的几项重大革新，包括使用这些新技术时在性能方面需要的独到考量。革命性的HTTP 2.0、XHR客户端网络脚本、基于SSE及WebSocket的实时数据流，以及通过WebRTC实现P2P通信，对这些面向未来的重大浏览器技术，本书都从性能优化的角度给出了详尽的解读和分析。

作者/ Berlinix

现在我看书的时间远不如从前了，1年大概只能精读2-3本书，泛读20本左右。但即便是把这个数字扩大1倍，我也能断定《Web性能》在我的书架上也绝对能侧身一年一遇的好书之列。说实话，我并没有特别认真地读，更没有从头到尾地读，当我看完这本书的第1章（延迟和带宽）和第5-6章（无线网络概览&Wifi）后，就因感到我的人生已发生极大变化，而把它遗之一旁。和那种人云亦云的推荐不同，接下来我将详细地告诉你本书是如何改变我的人生的。

那阵子我正好遇到一个棘手的事。我有个Mac笔记本，打算从另一台装有Win7的机子上复制一些文件。我极为惊讶地发现，两台电脑的传输速度只有可怜的1.5M/s。要知道我上传文件到网络硬盘也轻松突破这个数字啊。换句话说，我从Win7把文件上传到网盘，再在Mac上把它下载，也不会比这样的传输更慢。

起初我屈服了，用U盘充当媒介，在两者间复制文件。并忍受了几天这样的速度。我Google了许久，尝试了一些解决方案，以下是我当时的一些笔记：

据说OSX 10.7后由于避免版权问题，没有使用Windows的smb协议实现，而用了自己写的替代方法，很不成熟，速度比PC间的共享慢很多。

有建议使用CIFS。在Finder里输入cmd+k，把smb的前缀替换为cifs，如：

cifs://192.168.1.102/backup

很遗憾，都行不通。我尝试过其他方法，如通过应用软件如飞鸽传书等来传输文件，依然慢得恐怖。

我感到有些绝望。难道在接下来的岁月里，我都要忍受1.5M/s的传输速度吗？绝不能如此。

没有用tcpdump/Wireshark来帮助诊断，而急匆匆跑去求救Google，我对自己有些失望。不过当时没有意识到自救的问题。从一开始就认为是个小问题，并深陷在各种失败的尝试之中。直到最后我翻开《Web性能》。

这本书第1章就开宗明义地说明了影响网络性能的2大因素：延迟和带宽。我知道延迟，也知道带宽。我知道带宽非常重要，也很贵（对购买带宽的公司而言）。但为何把延迟和带宽放一起，甚至还放在前面？

看完第1章的短短几页后，我感到毛骨悚然。如今这个年代，1M以下的带宽还真是罕见。我朋友笑话我还停留在20M的落后阶段，他早就上50M了。但即便是这样的带宽，即便看视频非常流畅，有时你仍会发现，打开某些网页有点卡。带宽反正越来越便宜了，为何不上更大的带宽呢？反正电信公司的人是这样说。

如果不是出自无知的话，电信公司的人一定是故意绝口不提延迟的事。延迟的本质是说，数据的传输是需要时间的，光从A地到B地需要时间，这个时间就是延迟。光速在真空中的传输速度约为300,000km/s（30万公里每秒）。我有一个Linode VPS在达拉斯，从北京到达拉斯的直线距离：11,233km，光从北京到达拉斯的时间为：11,233 / 300,000 = 0.0374s = 37.4 ms （37.4毫秒）。我多次ping过这个VPS，但并没有深刻的意识到这个东西是“延迟”。而这个“延迟”几乎无法优化。

我跳到第5章无线网络以及第6章Wifi，醍醐灌顶。首先，作者明确指出无线网络是不可靠的。我知道我的Mac和Win7都是通过无限网络传输的，但从未考虑过它们的可靠性问题。插上一块无线网卡就能上网，没有布线的烦恼，但是作者说：无线网络是不可靠的。我小吃一惊。然后看到Wifi的几个标准，也就是802.11 a/b/g/n/ac标准。我快速检查了自己的设备：

- Mac。自带的无线网卡，支持802.11 a/b/g/n/ac所有标准。

- Win7。台式机，TPLink无线网卡，802.11g 标准。

- 路由器。TPLink无线路由器，支持“最新”的 802.11g标准。

失望，绝望，愤慨什么情绪都涌上来了。路由器是4-5年前购买的，网卡是3年多前购买的。它们真的是太老了。按书中所言，我认真地观察了Win7 ping路由器的输出，几乎达到恐怖的10ms延迟，且大概有1-2%的丢包率；同时期另一台PC通过网线连接路由器，ping的延迟小于1ms。

我立即购买了1个新路由器与1个新网卡，双双升级到802.11n标准。然后重新测速，大概稳定在4.5M/s-5.5M/s。可惜我现在的无线网络还运行在2.4GHz，还没有支持5GHz。但对这个结果已经比较满意了。

在我重新意识到“硬件”的存在后，改进了Linode VPS的部署。曾经我对速度的优化始终停留在软件上，以为软件就是一切。如今，我真正意识到硬件，地理位置对性能的影响。我对这些东西并非毫不知情，也知道要把内容（不仅仅是内容）部署到离用户更近的地方。然而，总有许多现实因素把你的双眼蒙蔽起来，让你看不清事实的真相。但这位好心的作者已经开门见山地指出了：关注带宽，但对Web而言更重要的是延迟。

最后仍要大力推荐此书。即使是第1章“延迟和带宽”，就值书的价钱了。更何况第5、6章带来的价值。话说到这里，我真得好好研究下其他章节呢。同时也但愿你也有颗好心脏来承接作者带给你的惊喜。

封底

图灵社区 出品

出版人：武卫东

编辑：李盼

顾问：杨帆

设计：大胖

本刊只用于行业交流，免费赠阅。

署名文章及插图版权归原作者所有。

地址：北京市朝阳区北苑路13号院领地OFFICE C座603室

电话：010-51095181

微博：weibo.com/ituring

Email: ebook@turingbook.com