您的当前位置:首页正文

Google 是如何开发 Web 框架的

来源:华拓网

** 转载请注明出处,保留原文链接以及作者信息**



(这毫无疑问是世界上最大的代码仓库。)

Google 的代码库在 Google 在全世界几十个办事处,共 25000 多名软件工程师之间共享。在一个普通的工作日里,就有 16000 多次代码提交。


(“Human users” 意味着谷歌的软件工程师提交代码)

只有一个版本

当你在一个单仓库项目中使用基于 trunk 的开发模式时,你的所有东西都只有一个版本。例如, Google 不会发生一个叫做 FooBar 的 App 使用 AngularDart 2.2.1 版本,而另一个叫做 BarFoo 的 App 使用 2.3.0 版本。所有的 App 都用同一个版本——而且是最新版本。


(基于 trunk 的开发模式时示意图,来源: )

这就是为什么 Google 的工程师说所有的 Google 软件都活在“失血边缘”。

如果现在你的心里在大喊“太危险了!”,那就对了。在你的生产环境代码里面依赖另外一个库的 trunk(相当于 git 里面的 master)分支上面的代码听起来确实很危险,但是你要知道,故事在这之前还有别的情节。

每次 commit 前的 74000 次测试

测试越多越好。

这里面体现出真正的价值是,这些测试都是真实产品的测试。这些测试不仅数量庞大,同时它们也反映了开发者是如何使用的这个框架(不仅仅是框架的作者使用)。这样做的意义是:框架的作者并不总是能够正确地预估别人是怎么使用他的框架。

对正在生产环境运行的 App 也是有好处的,毕竟上面每个月有数十亿美元的流水。开发者不是在一个业余时间拿来玩玩的 Demo App 上使用框架,而是一个成千上万人投入在上面的线上产品使用。如果 Web 是和未来息息相关的,那么作为 Web 框架开发者,我们更应该更好地支持后者的开发。

所以,如果一个框架导致依赖于它的一些 App 崩溃,会发生什么?

谁搞砸,谁修复

如果 AngularDart 的某位成员引入了一个引起崩溃的改动,那么他必须要为他们的用户修复它。因为大家都使用单一的仓库,因此 bug 很容易被发现,并且可以立马修复它。

因为 bug fix 的时候可能带来新的 bug,因此 bug 和 fix 有可能是同时入代码库。当然,在入库之前它们都会被做代码审查。

我们来给一个具体的例子。当 AngularDart 团队里的某个成员修改了代码影响了 AdWords 程序的运行,那么他们就要跑到 AdWords 去修复它们。在修复代码过程中,他们会跑 AdWords 已有的测试,也可以新建测试。然后他们会把测试和 bug fix 都放入变更列表,并且申请代码检查。因为这个变更列表中包含了 AngularDart 代码和 AdWords 的代码,因此系统会自动地将代码发送到两个团队进行审查,只有两边都通过了才能被提交入库。

这是一个很好的防止闭门造车的方法。AngularDart 框架的开发者们可以访问到数以百万计行的代码,这些代码都依赖于该框架。他们不需要去假想其他人会怎么使用这个框架。(当然他们只能访问到 Google 的代码,而访问不到世界上其他依赖于 AngularDart 的代码。)

别人用你的框架,但是你要升级别人的代码,这样听起来会使得开发变慢。但是也没想象中那么慢(可以看一下 AngularDart 10月份的进度),但是也确实让开发进度慢了一些。这是一把双刃剑,怎么来看待这个问题取决于你想要从这个框架中获得什么。等下我们再来讨论这个问题。

所以,如果有个 Google 的家伙跟你说,他们 Alpha 版本的库已经稳定了并且可以应用到生产环境了,你就知道是什么回事了。

大规模改动

那么,如果 AngularDart 要做一个大规模的变动(如版本从 2.x 升级到 3.0)并且会命中 74000 个测试该怎么办呢?团队要修复所有的问题吗?难道他们要去更改上千个跟根本就不是他们写的源文件吗?

是的。

当类 Foo 中的一个方法bar()变成了baz(),你可以创建一个工具,它可以遍历整个 Google 仓库,找到所有 Foo 类的实例以及它子类的实例,并且把所有的bar()改成baz()。有了类型安全,你可以确定这个改动不会使任何地方崩溃。如果没有类型安全,甚至这么一个简单的修改都可能变得非常麻烦。


(根据 Dart 代码规范,一键格式化代码)

性能指标

正如我上面所说的,AngularDart 得益于依赖于它的产品的测试。但仅仅只是测试还不够,Google 对于 App 的性能要求的很严格,几乎所有的产品都有基准测试套件。

所以,如果 AngularDart 引入了一个导致 AdWords 加载慢了 1% 的改动,上线这个改动之前他们都会知道。如果十月份 AngularDart 团队宣布他们自从八月份以来 AngularDart App 比原来体积小了40%,速度快了10%的时候,他们并不是在讨论什么 TodoMVC 这样的小事。他们谈论的是真实世界中里面上百万级别用户的产品和上兆字节的业务逻辑代码。

顺带提一下:封闭式编译工具

在这种代码规模下,你不可能写 Shell 脚本来编译所有文件,这样做会很不靠谱而且会慢的令人发指。这时候你需要的是一个封闭式编译工具。

“封闭式”(Hermetic)的意思有点类似于函数式编程里面纯函数的”纯“。也就是说,你的编译过程不能有副作用(如临时文件,改变环境变量 PATH 等),他们也必须是确定性的(相同的输入必须得到同样的输出)。这样的话不管你什么时候在你的机器上编译和运行测试,你会得到一致的输出结果。你不需要make clean。因此你可以发送你的编译/测试到编译服务器上并行编译。

多亏了有这样的基础设施,内部测试工具才能够自己来决定编译和测试哪些受影响的部分,并且在恰当的时候运行他们。

以上种种意味着什么?

AngularDart 的目标非常明确,就是要在构建大型 Web 应用领域,做到一流的效率、性能和可靠性。这篇文章所讲述的就是后一个部分——可靠性。同时也解释了为什么 Google 的关键产品,如 AdWords 和 AdSense 都使用这个框架。不是 AngularDart 自己吹嘘自己有多么厉害的用户,就正如上面所说的——就是因为有这么多的内部用户,AngularDart 几乎不可能引入一些随意的变更。这也使得这个框架更加的可靠。


(如果你觉得这些听起来太商业化了,你可以看一下我的非商业化 AngularDart 项目 或者 )

如果你想要一个每几个月就有一次大的改版或者重大功能升级的框架,很明显 AngularDart 并不是你要找的。即使我们想这么干,你看完这篇文章就只知道,这么严格的开发流程是不可能开发出这样的框架的。但我们真心相信,一个不是很流行但是却很稳定的框架肯定会它有发展和生存的空间。