下载本文的PDF版本 PDF

打破入门壁垒

我们必须选择构建一个每个人都能访问的网络。


Rich Harris

一场战争正在 Web 开发领域中展开。一方是工具制造者和工具使用者的先锋,他们以摧毁糟糕的旧观念(在这个环境中,“旧”意味着任何在一个月前在 Hacker News 上首次亮相的东西)以及关于转译器和类似事物的激烈辩论为乐。

另一方是越来越多的开发人员,他们声称——并非完全没有道理——创新令人眼花缭乱的速度使得跟上时代变得不可能,并且 Web 正在瓦解成一堆基于观点的黑客行为,其中大多数是错误的,并且所有这些都将在 hot-new-thing.js 达到 1.0.0 版本时发生改变。

第二组提倡回归基础,摒弃现代 JavaScript 库和框架,转而支持未经驯服的 DOM API(DOM 是我们这些不修边幅的 Web 开发人员最接近“裸机”的东西)。让我们称之为回归田园运动。回归田园者认为,工具会拖慢 Web 速度,损害可访问性,并增加脆弱性。你经常可以在编程博客的评论中看到他们链接到 vanilla-js.com。

这是 quirksmode.org 的创建者 Peter-Paul Koch 在最近一篇文章6 中写道的(原文强调):

“对工具链和越来越多库的追求,以完成越来越少有用的事情,已经变得歇斯底里,而且随着每一天的过去,我越来越为我 2006 年忽略工具并继续前进的决定感到高兴。工具不再解决问题,它们已经变成了问题。

撇开许多评论中“滚开我的草坪”的语气不谈,这场运动确实有合理的担忧。但我们对 Web 的期望比过去更高——实时协作、个性化应用、丰富的交互性。我们不能指望软件工程师在没有工具的情况下构建这些体验,就像我们不能指望土木工程师徒手建造悬索桥一样。正如 Facebook 的 Sebastian Markbåge 在对 Koch7 的直接回应中说,“只有当你在为昨天的 Web 构建时,你才能说 Web 已经‘足够好’了。”

正如任何战争一样,都存在虚假的二分法(简单与强大)、伪善(放弃库,然后编写大量应用代码来做同样的事情,尽管没有文档或测试),以及伤亡。我想谈谈的是伤亡。

前端开发者:濒危物种?

直到最近,“前端开发者”还是一个略带嘲讽的术语,用来形容那些可以拼凑一些 HTML 和 CSS,并在其上撒上一些 JavaScript 的人,也许是在 Stack Overflow 上搜索“如何用 jQuery 隐藏元素”之后。前端开发者负责将 Google Analytics 脚本代码段添加到 CMS 文章模板中,并可能添加一个滑动图像轮播(营销部门对主页上放什么内容犹豫不决的传统疗法),但从未被信任处理任何特别重要的事情。

然后 Backbone1 出现了,这是迈向越来越精细的 JavaScript 应用程序框架竞赛的发令枪。许多现代 Web 应用程序几乎将所有逻辑都推送到客户端,结果是,随着应用程序变得越来越复杂,工具以及使用工具的人员也必须如此。

因此,许多评论员已将传统前端开发者列入灭绝观察名单。Ember.js 团队(Ember 是上述客户端应用程序框架之一)的核心成员 Trek Glowacki 在回应关于构建工具的抱怨时写道:

“我知道 Ember 核心团队的每个人都同情那些职业生涯始于‘下载一个 zip 文件,添加一些 script 标签,FTP 到生产环境’时代的 Web 开发者,他们现在感到有点惊讶,因为他们所有喜欢的工具都变得越来越复杂。但是,事实仍然是,那个时代正在结束。”5

换句话说,“跟上潮流”。Glowacki 没有错,就像 Koch 没有错一样,但是现代工具存在一个问题——新手进入这个领域后,在被大量的选择淹没之后,他们需要学习一系列令人眼花缭乱的新概念(此处插入关于“transclusion”的笑话)才能真正构建任何东西。这些工具的强大功能实际上只对少数人可用——那些有决心攀登陡峭的学习曲线,并且有时间和意愿跟上我们社区疯狂创新步伐的人。

“学习编程”不是答案

当 Web 还是一个更简单的地方时,它对新手程序员来说是一个友好的环境。工具更少,我们拥有的工具也不那么复杂,但我们用“查看源代码”的力量弥补了这一点。在那些狂野的西部时代,在我们关心最佳实践之前,逆向工程许多 Web 软件出奇地容易。

在短短几年内,Web 开发取得了惊人的成熟。但是,取代“查看源代码”(在转译、压缩代码的时代,它毫无用处)的工具,对于绝大多数人来说是不可访问的。

这不仅仅是为那些想成为专业软件工程师的人提供更好培训的问题。Web 的力量和美妙之处始终在于,任何人都可以作为创造者和消费者参与其中——科学家、学者、艺术家、记者、活动家、演艺人员、教育工作者——他们中的大多数人尚未解锁现代 Web 技术令人兴奋的可能性。

我们尝试解决这个问题的一种方法是“学习编程”运动,它催生了一个由初创公司组成的完整行业(初创公司文化本身就是学习编程的主要驱动力之一)。政治家们喜欢它,因为它让他们看起来具有前瞻性,尽管没有人完全确定迈克尔·布隆伯格是否完成了他的 Codecademy 课程2

当然,学习编程有很多值得称赞的地方。许多人已经掌握了原本遥不可及的技能。但是,这场运动基于两个奇怪的假设——首先,我们的首要任务应该是培养更多的程序员人才,而不是让编程更易于访问;其次,“学习编程”包括吸收关于编程语言的事实并练习正确语法的形成。

实际上,学习如何编程是一个培养建模问题能力的过程,以便计算机能够解决这些问题——这只能通过经验来实现。你不是通过学习如何变位动词和名词复数来学习外语的;你通过学习短语并练习它们,以及阅读和听母语人士的讲话,直到它变得自然。每位语言教师都知道这一点,但在很大程度上,这不是我们教授编程语言的方式。

我们不需要第 1437th 个关于原型继承或 JavaScript 的“this”关键字的解释。我们需要的是允许新手表达他们的想法的工具,而无需完全了解其发生的过程。

进入 Ractive.js

几年前,我需要这样一个工具,当时我刚加入 theguardian.com 的互动新闻团队。新闻互动通常包含大量状态,以几种不同的视觉丰富形式表示,并且必须处理许多不同的用户交互模式——这是错误代码的配方,尤其是在新闻行业截止日期之前编写时(我们嘲笑“敏捷”这个词)。我非常清楚我的 jQuery 意大利面代码随时可能崩溃,但像 Angular 这样更高级的工具既令人生畏,又在某种程度上不足以胜任手头的任务。

我一直期待着有人告诉我正确方法的秘密,但那一天从未到来。根本没有任何工具旨在让我的工作更轻松,所以我决定自己创建一个。

坦率地说,这个问题相对容易阐明。在任何给定时刻,Web 应用程序 UI 的状态都可以描述为应用程序状态的函数,而我们的任务是操作 DOM,直到现实与意图相符。

在服务器端,这很容易:编写一个模板,使用模板引擎将其编译成一个函数,使用一些数据调用它,并将生成的 HTML 提供给客户端。但是,一旦你进入浏览器,字符串模板就是一个糟糕的技术。重复生成 HTML 并将其插入文档意味着破坏现有的 DOM,这会给垃圾回收器带来压力并破坏状态(例如哪个元素被聚焦,以及光标在哪里)。因此,开发人员通常将其应用程序分解为微小的块,使用专用的自定义模型和视图类,并通过事件系统将它们连接在一起。MVC 胶带是新的 jQuery 意大利面。

Ractive.js10 的设计目的是允许开发人员充分利用模板的声明性能力,而无需牺牲基于字符串的模板系统带来的牺牲。这个想法在当时是新颖的(尽管现在已经不那么新颖了,因为其他工具也采用了类似的方法),即一个理解 HTML 和模板标签的模板解析器可以生成一个树结构,数据绑定引擎稍后可以使用该结构以手术般的精确度来操作 DOM。开发人员只需偶尔提供新数据即可。

这不是 React.js 和其他类似库使用的虚拟 DOM diffing 技术。这种方法有一些非常有趣的特性,但数据绑定——即更新已知与已更改的特定值相对应的 DOM 部分,而不是重新渲染所有内容并且不更新没有更改的部分——通常性能更高。

从那时起,Ractive 添加了(并且在某些情况下率先使用了)许多新功能:组件系统、声明式动画和过渡效果、完整的 SVG 支持、封装的 CSS、服务器端渲染等等。就思想份额而言,我们与 Angular、Ember、Meteor 和 React 等巨头相比只是小鱼小虾,尽管我们拥有来自世界各地的贡献者,并且 Ractive 被用于各种网站,从电子商务到企业监控软件。

但是,团队和我最自豪的是,它让经验不足的开发人员能够在 Web 上将他们的想法变为现实。

杂志文章不是展示交互式 UI 库的最佳场所,但如果你好奇,你应该访问 http://learn.ractivejs.org 以获取交互式教程。

经验教训

当我们在构建 Ractive 时,“这会使新手开发人员更容易入门还是更难?”这个问题始终萦绕在我们的脑海中。有趣的是,我们从未发现这需要我们为了更有经验的开发人员而牺牲功能——软件开发中没有“降低难度”,只有清晰的 API 与复杂的 API 之分。通过关注初学者体验,我们让所有用户的生活变得更好。

多年来,我们将这种心态提炼成一个工具制造者的清单。其中一些要点坦率地说只是理想化的。但即使我们做得不够好,我们也发现它们是有用的指导原则,它们适用于各种工具。

Readme 驱动开发

通常,当我们编写旨在供其他人使用的代码时,我们首先关注实现,然后将其作为最后一步附加一个接口。这是很自然的——毕竟,弄清楚正确的算法和数据结构才是有趣的部分——但这完全是本末倒置。

当 API 是事后才考虑的时候,十有八九你会搞错。实现也是如此,但有一个关键的区别——你可以在后续版本中修复糟糕的实现,但更改 API 意味着破坏其他所有人的代码,从而阻止他们升级。(更糟糕的是,你可以尝试同时容纳旧的和新的 API,在必要时打印弃用警告,并导致 Zalgo 出现在你的代码库中。我以亲身经历说话。)

相反,尝试在编写任何代码之前编写 README 的初稿,包括代码示例。你通常会发现这样做会迫使你更清晰地阐明你试图解决的问题。你的起始词汇将更丰富,你的想法将安排得更好,并且你最终会得到一个更优雅的 API。

Ractive 用于获取和设置数据的 API 就是一个很好的例子。我们非常清楚,我们希望允许用户使用普通的旧 JavaScript 对象 (POJO),而不是坚持让他们将值包装在 Ractive 特定的可观察类中(想想 'Backbone.Model' 或 'ko.observable')。这带来了一些实现上的挑战,但这绝对是正确的举动。我们目前正在进行内部架构的全面改造,这将为许多用户带来显着的性能提升,而不会破坏他们的应用程序。

“Readme 驱动开发”这个短语是由 Tom Preston-Werner9 创造的,或者至少是普及开来的。

消除依赖项

JavaScript 中的依赖项管理非常痛苦,即使对于专家来说也是如此——尤其是在浏览器中。有一些旨在使情况更容易的工具,例如 Browserify 和 RequireJS(或 Webpack、Esperanto 和 JSPM,如果你是革命先锋队的一员),但它们都具有陡峭的学习曲线,并且有时会以非常难以调试的方式出错。

因此,沉默的大多数开发人员使用经过考验的解决方案,即手动添加 <script> 标签。这意味着库必须在其依赖项(以及它们的依赖项,等等)之后包含在页面上。忘记在 backbone.js 之前包含 underscore.js?给你,菜鸟,给你一个神秘的“无法读取未定义的属性 'extend'”错误。

通常,依赖项实际上是不必要的——例如,非常常见的情况是看到库为了一个或两个易于实现的方法而依赖 jQuery。(是的,它可能已经在页面上了。但是哪个版本?)当它们确实必要时,库作者应提供一个包含依赖项的版本,以及一个不包含依赖项的版本。不要担心潜在的重复;在这个阶段,这至少不是我们担心的。

不要过度模块化

自从 node.js 和 npm 出现以来,一群直言不讳的开发人员一直在宣扬代码应该仅以执行非常特定任务的小模块形式发布的想法。这至少是 npm 拥有比任何其他软件包管理器更多软件包的部分原因。

从表面上看,这似乎是一个极好的想法,也是减少应用程序或库中导入但未使用的代码量的好方法。但最终结果是,严格思考架构问题的负担从工具制造者转移到了应用程序作者身上,应用程序作者通常必须编写大量的胶水代码才能使各种小模块相互通信。

没有人会构建下一个 jQuery,因为他们会立即受到模块化羞辱(React.js 团队前成员 Pete Hunt 创造的一个绝妙短语)。这真是太可惜了,因为这意味着我们将不再有任何具有相同可学习性和哲学连贯性的库。

如果你认为我言过其实,那么 npm 上确实有一个名为“no-op”的软件包。它的源代码如下:

module.exports = function noop() {}

它已经发布了三个版本。它有一个测试套件!至少它没有像“max-safe-integer”软件包那样使用 Travis-CI 进行持续集成,后者导出的数字是 9007199254740991。这些软件包不是玩笑。它们是由 JavaScript 社区的领导成员非讽刺地创建的。

小模块可能和单体框架一样糟糕。与往常一样,我们应该追求一个快乐的中间地带。

通用模块定义 (UMD)

说到模块,你最好尽可能以多种不同的方式使你的代码可消费。三种最常见的格式是 AMD(通过 RequireJS 及其各种克隆使用)、CommonJS(在 node.js 中或通过 Browserify 使用)和浏览器全局变量。

通用模块定义允许你定位所有这三种环境。有几个不同的版本,但基本模式如下:


(function (global, factory) {
  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory()
  typeof define === 'function' && define.amd ? define(factory)
  global.MyLibrary = factory()
}(this, function () {
  var MyLibrary = {};
  /* 一些代码发生了... */
  return MyLibrary;
}));

第一部分检测 CommonJS 环境,第二部分检测 AMD,如果两者都未找到,则回退到创建浏览器全局变量。

醒目的下载链接

如今,如果你想发布一个开源库,它应该存在于公共 VCS 存储库(GitHub 是事实上的标准)中并发布到 npm,这已是不言而喻的。这两者都是正确的,但重要的是要为不习惯使用 git 或 npm 的用户,或者想要快速试用库而无需使用 package.json 和构建步骤来设置新项目的用户提供下载链接。

这不需要涉及大量的手工劳动或复杂的自动化(尽管使用 cdnjs.com 等服务很容易设置)。提供下载链接的一种简单方法是将构建的库包含在 GitHub 存储库中(例如,dist/my-library.min.js),并标记特定的提交,以便轻松链接到特定版本:


# 创建 dist 文件(npm run 是一个很棒的任务运行器!)
npm run build

# 创建版本 0.2.0 标签并将其添加到
# 存储库上的“releases”选项卡
git tag -a v0.2.0 -m ‘version 0.2.0'
git push origin v0.2.0

良好的错误消息

错误和警告消息永远不会是快乐的源泉,但它们至少可以成为启迪的源泉。一条精心设计的错误消息价值连篇的文档,因为它会在开发人员需要它时立即出现。

在 Ractive 团队中,我们几个月前决定,试图保护开发人员免受错误的影响弊大于利。现在,我们在控制台中打印详细的警告,解释他们如何防范常见错误并使他们的应用程序性能更高。(如果开发人员愿意,可以禁用此功能。)在有意义的地方,我们在错误消息中包含指向相关文档的链接。在大多数浏览器中,这些链接会变成可点击的超链接。

在一个阶段,我们遇到了一类非常难以解开的错误。我们不太清楚是什么原因导致了这个问题,但我们能够检测到导致该问题的状态,因此我们开始在该状态达到时抛出错误,其中包含友好的“请提出包含重现步骤的问题!”消息,并链接到我们的问题页面。用户感到有能力对原本会是非常令人沮丧的体验做些什么(在某些情况下成为 GitHub 的首次贡献者),我们收集了解决该错误所需的测试用例。

避免命令行

此指南仅真正适用于基于浏览器的工具,但它是一个重要的指南:如果你的入门说明涉及使用命令行,那么你已经失去了一半的受众。

除非你花了很多时间与新手开发人员相处,否则这听起来可能很夸张。但试着回忆一下你第一次打开终端时感到多么迷茫。GUI 使我们正在处理的事物——文件夹、文件、驱动器和服务器——变成几乎是物理的、有形的事物,我们的头脑非常擅长理解这些事物,而命令行则迫使你构建一个复杂的心理模型。

你有没有在去洗手间的路上走错了路,最终走到了后台?这就是大多数人在打开终端窗口时的感受——就像他们躲在幕后,而且感觉不好。

例子,例子,例子

邀请人们查阅 API 文档是礼貌的开发者语言,意思是“RTFM”(请阅读手册),但没有人想阅读“精细的”手册。人们真正想要的——尤其是那些还不是你所在领域的专家,并且尚未形成正确的心理词汇的人——是例子。

我无法比 d34 的创建者 Mike Bostock 更好地表达它,所以我不会尝试。相反,我只想推荐他的文章“For Example”3。可复制粘贴的示例的激增是 d3 取得巨大成功的主要原因之一。

消除术语

命名事物很难,所以不要为此烦恼。尽可能坚持人们已经熟悉的词汇(但不要对先前的知识做任何假设)。偏爱稍微冗长但普遍易懂的词汇,而不是简洁的术语。

你可能需要更复杂的词汇来描述你工具内部的原语,但你越少强迫用户熟悉它,就越好。

同理心

虽然这是清单上最模糊的项目,但它也是最重要的。付出额外的努力,并尝试帮助你不认识的人充分利用你的开源软件的动力源于同理心。

如果你的同理心储备需要补充,请尝试阅读一篇你不熟悉的领域的论文。对于大多数凡人来说,从头到尾阅读 通讯应该就足够了;你,亲爱的读者,可能需要更强的。试试 Papers We Love8。你感受到的困惑与普通人试图学习 Web 开发时感受到的困惑非常相似——或者,就此而言,与一位经验丰富的开发人员第一次接触你的专业领域时感受到的困惑也非常相似。

我们必须构建我们想要的未来

听到人们认为 Web 平台日益增长的复杂性是不可避免的,是我们为进步付出的代价,这真是令人沮丧。这是一个经典的自我实现的预言——一旦我们决定 Web 开发最好留给专业人士是真实的(或更糟,正确的),我们将停止努力使其对其他人更易于访问。

如果这种情况发生,那将是最高级别的悲剧。Web 一直是一代程序员(包括你现在的通讯员)的入门毒品,他们中的许多人原本永远不会体验到计算机科学的纯粹乐趣。没有任何内在的原因表明它不能继续如此。但这取决于我们:我们必须选择构建一个每个人都能访问的网络。

参考文献

1. https://backbone.npmjs.net.cn

2. Bloomberg, M. 2012. https://twitter.com/mikebloomberg/status/154999795159805952

3. Bostock, M. 2013. For example. http://bost.ocks.org/mike/example/

4. https://d3js.cn/

5. Glowacki, T. 2015. Comment on "Will there be continued support for people that do not want to use Ember-CLI?" http://discuss.emberjs.com/t/will-there-be-continued-support-for-people-that-do-not-want-to-use-ember-cli/7672/3

6. Koch, P.-P. 2015. Tools don't solve the Web's problems, they are the problem. http://www.quirksmode.org/blog/archives/2015/05/tools_dont_solv.html

7. Markbåge, S. 2015. Tooling is not the problem of the web. https://medium.com/@sebmarkbage/tooling-is-not-the-problem-of-the-web-cb0ae1fdbbc6

8. http://paperswelove.org/

9. Preston-Werner, T. 2010. Readme driven development. http://tom.preston-werner.com/2010/08/23/readme-driven-development.html

10. http://ractivejs.org

喜欢它,讨厌它?请告诉我们

[email protected]

Rich Harris 是 theguardian.com 的一位屡获殊荣的互动记者,他在那里使用 Web 技术通过互动性和数据可视化以新的方式讲述故事。他是许多旨在让 Web 开发人员的生活更轻松的开源项目的创建者和主要作者,并且偶尔在技术会议上发表演讲。他最初来自英格兰北部,与妻子艾玛住在布鲁克林。偶尔可以在 twitter.com/rich_harris 上发现他语无伦次地胡言乱语。

© 2015 1542-7730/15/0500 $10.00

acmqueue

最初发表于 Queue vol. 13, no. 5
数字图书馆 中评论这篇文章





更多相关文章

Shylaja Nukala, Vivek Rau - 为什么 SRE 文档很重要
SRE(站点可靠性工程)是一种工作职能、一种思维模式以及一组工程方法,用于使 Web 产品和服务可靠地运行。SRE 在软件开发和系统工程的交叉点运作,以解决运营问题并设计解决方案,从而可扩展、可靠且高效地设计、构建和运行大规模分布式系统。成熟的 SRE 团队可能拥有与许多 SRE 职能相关的定义明确的文档体系。


Taylor Savage - 组件化 Web
在当今的软件工程中,没有哪项任务比 Web 开发更艰巨。Web 应用程序的典型规范可能如下:该应用程序必须跨各种浏览器工作。它必须以 60 fps 的速度运行动画。它必须立即响应触摸。它必须符合一组特定的设计原则和规范。它必须在几乎所有可以想象的屏幕尺寸上工作,从电视和 30 英寸显示器到手机和手表表面。它必须在长期内得到良好的工程设计和可维护性。


Arie van Deursen - 超越页面对象:使用状态对象测试 Web 应用程序
Web 应用程序的端到端测试通常涉及通过 Selenium WebDriver 等框架与网页进行棘手的交互。隐藏此类网页复杂性的推荐方法是使用页面对象,但首先要回答一些问题:在测试 Web 应用程序时,应该创建哪些页面对象?应该在页面对象中包含哪些操作?给定你的页面对象,应该指定哪些测试场景?


Alex Liu - JavaScript 和 Netflix 用户界面
自推出以来的二十年中,JavaScript 已成为 Web 的事实上的官方语言。在野生环境中运行时环境的数量方面,JavaScript 胜过所有其他语言。如今市场上几乎所有消费类硬件设备都以某种方式支持该语言。虽然这最常见的方式是通过集成 Web 浏览器应用程序来完成的,但许多设备现在也以本机方式支持 Web 视图,作为操作系统 UI(用户界面)的一部分。





© 保留所有权利。

© . All rights reserved.