第 1 章 构建优先

第 1 章 构建优先

本章内容

  • 现代应用设计面临的问题

  • 什么是构建优先原则

  • 构建过程

  • 管理应用中的复杂度

使用正确的方式开发应用可能很难,我们要合理规划。我曾只用一个周末就开发出了应用,但应用设计得可能并不好。创建随时会扔掉的原型可以即兴发挥,但是开发一个可维护的应用则需要规划,要知道怎么把脑海中设想的功能组织在一起,甚至还要考虑到不久之后可能会添加的功能。我曾付出无数努力,但应用的前端还是差强人意。

后来我发现,后端服务通常都有专门的架构,专门用于规划、设计和概览这些服务,而且往往还不止一个架构,而是一整套。可是前端开发的情况却完全不同,前端开发者会先开发出一个可以运行的应用原型,然后运行这个原型,希望在生产环境中依然正常。前端开发同样需要规划架构,像后端开发一样去设计应用。

以前,我们会从网上复制一些代码片段,然后粘贴到页面中,就这样收工了。可是这样的日子早已过去,先把JavaScript代码搅和在一起,事后再做修改,不符合现代标准了。如今,JavaScript是开发的焦点,有很多框架和库可以选择,这些框架和库能帮助我们组织代码,我们不会再编写一整个庞大的应用了,更多的是编写小型组件。可维护性不是随意就能实现的,我们从开发应用伊始就要考虑可维护性,并在这个原则的指导下设计应用。设计应用时如果不考虑可维护性,随着功能的不断增加,应用就会像叠叠乐搭出的积木塔一样慢慢倾斜。

如果不考虑可维护性,最后根本无法再往这个塔上放任何积木。应用的代码会变得错综复杂,缺陷越来越难追查。重构就要中断产品开发,业务可经不起这样折腾。而且还要保持原有的发布周期,根本不能让积木塔倒下,所以我们只能妥协。

1.1 问题出现了

你可能想把一个新功能部署到生产环境,而且想自己动手部署。你要用多少步完成这次部署?八步还是五步?为什么你要在部署这样的日常工作中冒险呢?部署应该和在本地开发应用一样,只需一步就行。

可惜事实并非如此。我以前会手动执行部署过程中的很多步骤,你是不是也是这样?当然,你一步就能编译好应用,或者可能会使用服务器端解释型语言,根本不用事先编译。如果以后需要把数据库更新到最新版本,你甚至可能会编写一个脚本执行升级操作,但还是要登入数据库服务器,上传这个脚本文件,然后自己动手更新数据库模式。

做得不错,数据库已经更新了,可是有地方出错了,应用抛出了错误。你看了下时间,应用已经下线超过10分钟了。这只是一次简单的升级啊,怎么会出错呢?你查看日志,发现原来是忘记把新变量添加到配置文件里了,真是太傻了。你立即加上了新变量,抱怨着这次与代码基的斗争。你忘记在部署前修改配置文件,在部署到生产环境前忘了更新配置。这种情况是不是听起来很熟悉?不要害怕,这种情况很常见,在很多应用中都存在。我们来看看下面这个危险的案例。

1.1.1 45分钟内每秒损失17万美元

我敢肯定,一个严重问题导致损失几乎五亿美元的案例会让你打起精神。在骑士资本公司就发生过这样的事。1他们开发了一个新功能,让股票交易员参与一个叫“零售流动性计划”(Retail Liquidity Program,简称RLP)的项目中。RLP的目的是取代已经停用九年的“权力限定”(Power Peg,简称PP)功能。RLP的代码中重用了一个用来激活PP功能的标志,添加RLP时,他们把PP移除了,所以一切都正常运行着,至少他们认为是正常的。但是,当他们打开这个标志时,问题出现了。

1关于骑士资本公司这次事件的详情,请访问http://bevacqua.io/bf/knight

他们在部署时没有采用正式的过程,而且只由一个技术人员手动执行。这个人忘记把代码改动部署到八个服务器中的某一个,因此,在这个服务器中,这个标志控制的是PP功能,而不是RLP功能。直到一星期后他们打开这个标志时才发现问题:他们在七个服务器中激活了RLP,却在最后一个服务器上激活了停用九年的PP功能。

这台服务器上处理的订单触发执行的是PP代码,而不是RLP。这样一来,发送到交易中心的订单类型是错误的。他们试图补救,但情况进一步恶化了,因为他们从已经部署了RLP的服务器中把RLP删除了。长话短说,他们在不到一小时的时间内损失了差不多4亿6千万美元。他们只要使用更正式的构建过程,就能避免公司的衰败。想到这一点,就会发现这整件事都是那么不可思议,不负责任,其实又应该是很容易避免的。当然,这是个极端案例,但明确表明了我的观点:自动化的过程能尽量避免人为错误,至少也能更早发现问题。

1.1.2 构建优先

我写这本书的目的是教你使用构建优先原则,在还未编写任何代码之前就做好设计,让应用的结构清晰,易于测试。你会学习过程自动化的知识,减少人为出错的可能性,避免重蹈骑士资本的覆辙。构建优先原则是设计结构清晰、易于测试的应用之基础,使用这一原则开发出来的应用易于维护,也易于重构。构建优先原则的两个基本要素是过程自动化和合理的设计。

为了教你使用构建优先原则,本书会向你展示能改进软件质量和Web开发流程的技术。在第一部分,首先要学习如何建立适用于现代Web应用开发的构建过程,然后示范能提高日常开发效率的最佳实践,例如修改代码后执行的任务、在终端里只输入一个命令就部署应用的方式,以及如何监控生产环境中的应用状态。

本书第二部分讲管理复杂度和设计,专注于应用的质量。在这一部分,我会比较当前可用的一些模块化方案,介绍如何更好地编写模块化的JavaScript组件。JavaScript中的异步流越来越复杂,越来越长,因此我单独准备了一章,让你深入了解如何编写简洁的异步代码,此外还会学习用来提升异步代码质量的不同工具。Backbone是入门首选的客户端MVC框架,我会介绍一些足够你开始使用JavaScript开发MVC应用所需的知识。前面我提到过,易于测试对应用来说很重要,虽然我们已经实现了模块化,向正确的方向迈出了一大步,但还是要在单独的一章中说明测试。最后一章剖析一个流行的API设计思想,即REST(Representational State Transfer的缩写,即“表现层状态转换”),我会帮助你设计自己的API,还会深入说明服务器端的应用架构,不过仍然会密切关注前端。在探索构建过程之前,我们再来看一个危险的案例。这个案例遇到的问题,只要遵循构建优先原则,通过实现过程自动化就能避免。

1.1.3 繁琐的前戏

如果新成员加入团队后的设置步骤太复杂,也表明自动化程度不高。我以前参与的一些项目,首次搭建开发环境要一周时间,真是痛苦。在你想弄明白代码的作用之前,竟然要浪费一周时间。

我要下载大约60 GB的数据库备份,还要创建一个数据库,配置一些以前从未听说过的选项,例如排序规则,然后还得运行一系列脚本,升级模式,可这些脚本甚至不能完全正常运行。解决这个问题之后,还要在自己的环境中安装指定的过时已久的Windows媒体播放器的解码器,那感觉就像把一头猪塞进放满东西的冰箱一样,纯属徒劳。

最后,我冲了一杯咖啡,试图一次编译好130多个大型项目。可是,忘了安装外部依赖,我想,安装依赖就行了吧,但不行,还要编译C++程序,这样解码器才能重新运行。我再次编译,又过了20分钟。还不行!真烦。或许我可以问问身边的人,可是没人确切知道该怎么做。他们一开始都经历过这样痛苦的过程,但都不记得具体应该怎么做了。查查维基百科?当然可以,但信息记得零零散散,并不能用来解决遇到的具体问题。

公司从未制定正式的初始化流程,事情变得越来越复杂,也就很难再去制定一个流程。他们不得不处理巨量的备份、升级脚本、解码器和网站所需的多个服务,哪怕是改动一个分号也要花一小时编译项目。如果他们从一开始就自动执行这些步骤,遵循构建优先原则,这个过程会顺利得多。

骑士资本的溃败和这个过度复杂的设置故事有一个共同点:如果他们能提前做好计划,自动执行构建和部署,就能避免问题。提前计划,自动执行应用相关的操作,这是构建优先原则的两个基本要素,下一节会详细说明。

1.2 遵守构建优先原则,提前计划

在骑士资本的案例中,他们忘了把代码部署到其中一个服务器,即使有一步部署方案能自动把代码部署到所有服务器,也无法避免这个公司破产。这个案例深层次的问题是代码质量,因为他们的代码基中存在已经差不多十年不用的代码。

不增加功能的彻底重构对产品经理没有吸引力,他们的目标是提升面向客户的可视化产品,而不是底层的软件。不过,你可以逐渐改进代码基,重构你接触到的代码,为重构后的功能编写测试,把过时的代码包装到接口中,以后再重构——这样做能不断提升项目中代码的平均质量。

不过,单单重构还不够。好的设计在一开始就要带入项目中,不能等出现问题后才试图强行用于糟糕的结构中。除了前面提到的构建过程之外,本书要阐述的另一个基本要素就是设计。

在我们深入构建优先这个未知领域之前,我要强调一点,构建优先并不只适用于JavaScript。多数人通常在后端语言(例如Java、C#或PHP)中使用我要介绍的原则,但在这里我把这些原则应用到了JavaScript应用的开发过程中。正如我前面提到的,客户端代码往往没有得到应有的关注和尊重,常常没有适当地测试代码,导致代码有缺陷,或者致使代码基难以阅读和维护,最终受影响的是产品(以及开发者的工作效率)。

对JavaScript来说,因为这门语言不需要编译器,天真的开发者或许就以为根本不需要一套构建过程。这样的想法就像在黑暗中射击一样:在浏览器中执行代码之前,开发者不知道代码是否能运行,也不知道代码是否能像预期那样做该做的事。然后,这些人可能还要手动把应用部署到线上环境,再远程登录服务器,调整一些配置选项,让应用能运行。

构建优先原则的核心法则

构建优先原则的核心法则不仅鼓励建立一套构建过程,还鼓励使用简洁的方式设计应用。下面概述了使用构建优先原则能获得的好处:

  • 减少出错的可能性,因为交互过程中没有人类参与;

  • 自动执行重复性的任务,能提高工作效率;

  • 模块化、可伸缩的应用设计;

  • 能降低复杂度,让应用易于测试和维护;

  • 让发布版本符合性能方面的最佳实践;

  • 部署的代码在发布前都经过了测试。

在图1-1中,从上到下分为四个部分。

  • 构建过程:使用自动化方式编译和测试应用。构建的目的是便于持续开发,还能调校应用,让发布版本得到最好的性能。

  • 设计:你的大部分时间都要用到设计上,在开发的过程中实现并改进架构。在设计的过程中,你可能要重构代码,更新测试,确保组件能按照预期的方式运行。制定好构建过程或准备好部署时,就要设计应用的架构,并在代码基中迭代开发。

  • 部署和环境:这两部分的目的是自动执行发布过程和配置不同的主机环境。部署过程的作用是把代码变动传送到主机环境中,而环境配置的作用是定义与应用交互的环境和服务,也包括数据库。

图 1-1 概览构建优先原则关注的四个方面:构建过程,设计,部署和环境

从图1-1可以看出,使用构建优先原则开发应用主要涉及两方面:项目相关的过程,例如构建和部署应用;应用代码本身的设计和质量,这方面在日常开发新功能时要不断提升。这两方面同等重要,而且二者之间相互依赖,这样才能得到最好的结果。如果应用设计得不好,过程再好也不管用。类似地,没有合适的构建和部署步骤,再好的设计也不能挽救前面所述的那种危机。

和构建优先原则一样,本书也分为两部分。第一部分介绍构建过程(开发和发布都适用)和部署过程,还会介绍如何配置环境。第二部分探讨应用本身的问题,说明如何实现简洁明了的模块化设计,还会介绍开发现代应用时需要考虑的实用设计因素。

下面两节概述这两部分要讨论的概念。

1.3 构建过程

构建过程涵盖自动完成重复性的任务,包括安装依赖、编译代码、运行单元测试,以及执行其他重要的操作。能一步执行完所有需要执行的任务(一步构建)非常重要,这么做优势明显。只要制定好了一步构建方案,想执行多少次就能执行多少次,而且效果不变。这种特性叫幂等:不管执行多少次,结果都一样。

图1-2更详细地列出了组成自动构建和部署过程的重要步骤。

图 1-2 构建优先原则中的构建和部署过程

自动构建过程的优缺点

自动构建过程的最大优点是只要需要随时都能部署。功能开发完毕后立即就让用户使用,有利于收窄反馈循环,这样我们就能更好地预见应该开发什么样的产品。

自动构建过程主要的缺点是在真正获益之前,要花一定的时间制定这个过程,可是自动化过程的好处绝对物超所值,例如我们能自动测试,得到的代码质量更高,开发流程更精益,而且部署流程更安全。一般来说,这个过程只需设置一次,以后随时都能再次执行,而且在开发的过程中还可以适当调整。

1. 构建

图1-2的上半部分是构建过程(如图1-1所示)中构建这一步的详细说明,包含开发和发布两方面的内容。如果你关注的是开发,就专注“调试”能力,我保证你想要一个无需干预就知道何时应该执行这些任务的构建过程。这叫持续开发(Continuous Development,简称CD),在第3章中介绍。构建过程中的“发布”和持续开发没有关系,不过你应该花时间优化静态资源,尽量让应用在生产环境中运行得更快。

2. 部署

图1-2的下半部分是图1-1中部署过程的详细说明,这部分将调试或发行版作为应用发行版(我在操作流程中使用“发行版”这个词是有特殊目的的,全书都会这样用)部署到主机环境。

打包代码得到的发行版会和环境相关的配置(用于安全存储机密信息,例如数据库连接字符串和API密钥,第3章会讨论)一起,服务于应用。

第一部分专门讨论构建优先原则中构建方面的话题。

  • 第2章说明构建任务,教你如何使用Grunt编写和配置任务。Grunt是任务运行程序,第一部分会一直使用这个工具。

  • 第3章介绍环境,如何安全配置应用,还会介绍开发流程。

  • 第4章讨论发布构建版本时应该执行的任务。然后介绍部署方面的知识,如何每次推送到版本控制系统后都运行测试,以及如何在生产环境中监控应用。

3. 构建过程的好处

读完第一部分后你就能自信地在自己的应用中执行下述操作了。

  • 自动执行重复的任务,例如编译、简化和测试。

  • 制作图标子图集表单,把对图标的HTTP请求数减少到只有一个。这种子图技术和其他的HTTP 1.x优化技巧在第2章讨论,目的是提升页面的加载速度和应用的交付性能。

  • 轻松搭建新环境,忽略开发环境和生产环境之间的区别。

  • 相关文件改动后自动重启Web服务器,以及重新编译静态资源。

  • 通过灵活的一步部署方案,支持多个环境。

处理繁琐的任务时,构建优先原则能节省人工,而且从一开始就能提升工作效率。构建过程在构建优先原则中有重要意义,能打造出可维护的应用,还能不断减弱应用的复杂度。

本书第二部分会讨论如何实现简洁的应用设计和架构,涵盖应用内的复杂度管理,以及设计时为了提升质量要考虑的因素。下面概述第二部分的内容。

1.4 处理应用的复杂度和设计理念

不管使用什么语言开发,如果想保证代码在具有一定规模时仍能正常运行,就一定要做到以下几点:模块化、管理依赖、理解异步流、认真遵守正确的模式和测试。在第二部分你会学到不同的概念、技术和模式,运用这些知识后你的应用就会变得更模块化、更专注、更易于测试也更易于维护。在图1-3中,从上到下就是第二部分的行文顺序。

图 1-3 第二部分要讨论的应用设计和部署方面的内容

1. 模块化

你会学习如何把应用分成不同的组件,如何再把组件分成不同的模块,然后在模块中编写作用单一的简洁函数。模块可以由外部包提供,由第三方开发,也可以自己开发。外部包应该交给包管理器处理,让管理器管理版本,执行升级操作,这样就不用我们手动下载依赖了(例如jQuery)——整个过程都自动完成。

在第5章你还会学到,模块的依赖能在代码中声明,而不用从全局命名空间中获取——这样做能让模块更加独立。模块系统会利用这些信息,解析出所有的依赖,因此,为了能让应用正常运行,我们就不必按一定顺序维护一长串<script>标签了。

2. 设计

你会学习如何分离关注点,使用“模型-视图-控制器”模式分层设计应用,进一步增强应用的模块化。在第7章我会告诉你关于共享渲染的知识,这个技术首先在服务器端渲染视图,然后同一个单页应用中的后续请求都在客户端渲染视图。

3. 异步代码

我会教你使用不同的异步代码流技术,包括回调、Promise对象、生成器和事件,帮你驯服异步这头猛兽。

4. 测试实践

第5章会讨论模块化的方方面面,学习闭包和模块模式,还会讨论不同的模块系统和包管理器,并尝试找出每种方案的优势。第6章会深入介绍JavaScript中的异步编程,告诉你如何避免编写一周后就会让人困惑的回调,然后再学习Promise对象和ES6中的生成器API。

第7章专门介绍各种模式和做法,例如如何写出最好的代码,对你来说jQuery是不是最好的选择,以及如何编写在客户端和服务器中都能使用的JavaScript代码。然后介绍Backbone这个MVC框架。记住,Backbone只是我用来向你介绍MVC知识的工具,并不是这方面唯一可用的框架。

在第8章我们会介绍测试方案、自动化和很多客户端JavaScript单元测试实例。你会学到如何为单个组件编写单元测试,如何为整个应用编写集成测试。

本书最后一章介绍REST API设计,如何在前端使用REST API,以及为了充分发挥REST架构的功能而推荐使用的结构。

5. 设计时要考虑的实际问题

本书的目的是让你在开发真正的应用时考虑一些设计方面的实际问题,充分考虑后再选择最合适的工具,始终注重过程和应用本身的质量。当你准备开发应用时,首先要确定规模,选择一个技术栈,再制定一个最小可行的构建过程,然后开始开发应用。你可能会使用MVC架构,或者在浏览器和服务器中都能使用的视图渲染引擎,这些话题在第7章讨论。在第9章你会学习开发API的重要知识,还会学习如何定义服务器端的视图控制器和REST API都能用到的后端服务。

图1-4简要说明了使用构建优先原则开发时应用的典型组织方式。

图 1-4 务实的架构方式

6. 构建过程

从图1-4的左上角开始看,可以看出,我们首先要制定一个构建过程,这样有助于开始着手架构应用,还要决定如何组织代码基。定义一个模块化的应用架构对可维护的代码基来说是至关重要的,在第5章你会看出这一点。然后还要实现过程自动化,提供持续开发、持续集成和持续部署功能,以此增强架构。

7. 设计和REST API

设计应用本身,以及能显著提升可维护性的REST API时,一定要明确每个组件的作用,让组件之间形成正交关系(意思是,组件之间在任何方面都不会争夺资源)。在第9章我们会探讨一种设计应用的多层方式,我们会严格定义各层以及层与层之间的通信路径,把Web界面与数据和业务逻辑明确地隔开。

8. 积极测试

设计好构建过程和架构后,我们要积极测试,关注可靠性方面的问题。我们要探索持续集成,每次把代码推送到版本控制系统后都要执行测试;或许还要探索持续开发,每天多次把应用部署到生产环境。我们还会讨论容错方面的知识,例如记录日志、监控和搭建集群。这些内容会在第4章概述,为的是让生产环境更稳健,至少在出问题时能提醒你。

在这个过程中我们会编写测试,调整构建过程,还会微调代码。对你来说,这是个好机会,能让你仔细审视构建优先原则。驾轻就熟后,再开始学习构建优先原则的细节。

1.5 钻研构建优先原则

质量是构建优先原则的基石,这个原则采取的每项措施都是为了一个简单的目标,即提升代码的质量,并使用更合理的方式组织代码。在本节你要学习代码质量方面的知识,以及如何在命令行使用检查代码质量的工具:lint程序。衡量代码的质量是向编写结构良好的应用迈出的第一步。尽早这么做容易让代码基符合一定的质量标准,所以接下来我们就要来做这件事。

学会使用lint程序后,在第2章我会介绍如何使用Grunt。本书会一直使用这个构建工具制定自动化构建过程。使用Grunt能在构建过程中检查代码质量,以防你忘记做这件事。

Grunt:实现自动化的工具

第一部分会大量使用Grunt,第二部分也会适量使用。我们使用这个工具实现构建过程。选择Grunt是因为它很流行,而且易于学习,能满足大多数人的需求:

  • 完全支持Windows;

  • 使用时只需少量的JavaScript知识,而且易于安装和运行。

记住,Grunt只是一种工具,使用它能轻易实现本书介绍的构建过程,并不是说Grunt始终是最佳选择。为了明确这一点,我会把Grunt和另外两个工具做对比:一个是npm,这是一个包管理器,也能当作简单的构建工具使用;另一个是Gulp,这是一个由代码驱动的构建工具,和Grunt有很多共同点。

如果你对其他构建工具(例如Gulp)好奇,或者想把npm run当成构建系统使用,请阅读附录C,其中详细说明了如何选择合适的构建工具。

lint程序是检查代码质量的工具,特别适合用来检查使用解释型语言(例如JavaScript)编写的程序。我们不用打开浏览器检查代码是否有句法错误,在命令行中执行lint程序就能找出代码中潜在的问题,例如未声明的变量、缺少分号或句法错误。不过lint程序也不是万能的,它检测不到代码中的逻辑问题,只能提醒句法和风格错误。

1.5.1 检查代码质量

lint程序能判断给定的代码片段中有没有句法错误,还能实施一些JavaScript编程的最佳实践规则。第二部分的开头第5章,在讨论模块化和依赖管理时会介绍这些最佳实践。

大约10年前,Douglas Crockford发布了JSLint。这个工具检查代码时很严格,会报告代码中所有的小问题。lint程序的作用是帮助我们提升代码的整体质量。lint程序直接在命令行中执行,能报告代码片段或文件中潜在的问题。这么做有个额外好处,我们甚至不用执行代码就能找出问题。对JavaScript代码来说,这个过程特别有用,因为在某种程度上,lint程序可以当做编译器,尽量确保代码能被JavaScript引擎解释。

除此之外,我们还能配置lint程序,让它发现太复杂的代码时提醒我们,比如说行数太多的函数,可能会让别人困惑的晦涩结构(对JavaScript来说,例如with块,new语句,或者过度使用this),诸如此类的代码风格问题。以下述代码片段为例(位于在线示例的ch01/01_lint-sample文件夹中):

function compose_ticks_count (start) {
  start || start = 1;
  this.counter = start;
  return function (time) {
    ticks = +new Date;
    return ticks + '_'  + this.counter++
  }
}

这么一小段代码中有很多问题,不过可能很难发现。使用JSLint分析这段代码时,既会得到预料之中的结果,也会得到意料之外的结果。JSLint会提醒你,变量在使用之前必须先声明,而且缺少分号。如果使用其他lint程序,可能还会抱怨你使用了this关键字。大多数lint程序都会抱怨你使用了||运算符,而没使用更易于阅读的if语句。你可以在线检查这段代码。2图1-5是使用Crockford的工具检查得到的结果。

2访问http://jslint.com/,然后输入这段代码。这是最先出现的JavaScript lint工具,由Crockford维护。

图 1-5 在一段代码中发现的错误

对编译型语言来说,这些错误类型在编译代码时就能捕获,因此不需要使用lint工具。而JavaScript没有编译器,这是由这门语言的动态特性决定的。这种方式无疑很强大,但和编译型语言相比却更容易出错,一开始代码甚至无法执行。

JavaScript代码无需编译,由引擎解释执行,例如V8(Google Chrome使用的引擎)和SpiderMonkey(Mozilla Firefox使用的引擎)。虽然有些引擎(最著名的是V8引擎)会编译JavaScript代码,但在浏览器之外享受不到静态代码分析的好处。3像JavaScript这样的动态语言有个缺点,执行代码时无法确保代码一定能正常运行。虽然如此,但是使用lint工具能大大降低这种不确定性。而且JSLint还会建议我们不要使用某种编程风格,例如使用了eval,没声明变量,语句块缺少花括号等。

3在终端使用Node.js能获得这个功能,但V8引擎检测到句法问题时已经太晚了,此时程序会崩溃。Node.js是服务器端JavaScript平台,也运行在V8引擎之上。

你发现前面代码片段中的函数有什么问题了吗?看一下本书的配套代码示例(ch01/01_lint- sample文件夹),验证一下自己的答案。提示:问题是有重复。修正后的版本也在源码示例中,你一定要看一下好的写法。

对本书配套源码的说明

本书的配套源码包含很多重要的信息,例如上述示例函数有一个调整后的版本,能通过lint程序的验证,而且有很多注释,便于理解改动的部分。这个示例也证明了lint程序不是万能的。

本书配套源码中的其他代码示例也有类似的建议和重要的信息,所以一定要看一下!配套源码中的示例按章组织,而且和在书中出现的顺序一致。很多示例在书中只有简单讨论,不过在配套源码中所有代码示例都有完整的注释,拿来就可以使用。

书中的代码和配套源码之间出现这种差异是因为,有时我想说明某个话题,但可能涉及的代码太多,在书中不能全部列出。遇到这种情况时,我不想太过偏离要讲解的概念,又想给你提供真实的代码。使用这种方式,在阅读本书的过程中能让你集中精力学习,浏览代码示例时再集中精力去试验。

通常,写完代码后第一件事就是使用lint程序检查,lint程序发现不了的问题则交给单元测试。这并不意味着没必要使用lint程序,而是说仅使用lint程序是不够的。单元测试的作用是确保代码的表现与预期一样。单元测试在第8章讨论,你会学习如何为第二部分编写的代码编写测试。第二部分的内容旨在说明如何编写模块化、可维护和可测试的JavaScript代码。

接下来我们要从零开始制定一个构建过程。我们从简单的任务开始,先编写一个运行lint程序检查代码的任务,然后在命令行中运行这个任务,就像使用编译器编译代码的过程一样。你会学着养成习惯,每次修改代码后都执行这个任务,查看代码是否能够通过lint程序的检查。第3章会教你如何自动执行这个任务,这样就不必每次都手动执行了。不过现在可以手动执行。

“如何直接在命令行中使用JSLint这样的lint工具呢?”我很高兴你能提出这个问题。

1.5.2 在命令行中使用lint工具

把任务添加到构建过程最常见的方式之一,是在命令行中执行这个任务。如果能在命令行中执行任务,那么这个任务就能轻易集成到构建过程中。下面介绍如何使用JSHint4检查你的软件。

4关于JSHint更多的信息,请访问http://jshint.com

JSHint是一个命令行工具,使用Node.js编写,用于检查JavaScript文件和代码片段。Node.js是一个使用JavaScript开发应用的平台,如果你想简单了解Node.js的基础知识,可以翻到附录A,在这篇附录中我说明了什么是模块,以及模块的工作方式。如果你想深入学习Node.js,可以阅读Mike Cantelon等人写的《Node.js实战》。掌握Node.js的知识也有助于使用下一章我们选定的构建工具——Grunt。

Node.js简介

Node.js是相对较新的平台,你肯定听说过。Node最初于2009年发布,遵从事件驱动和单线程模式,能高效并发处理请求。从这方面来看,Node和Nginx的设计理念一致。Nginx是高度可伸缩的多用途反向代理服务器,非常流行,作用是伺服静态内容,以及把请求转发给应用服务器(例如Node)。

Node.js广受赞誉,尤其是对前端工程师来说,特别容易上手,因为大致而言,它只不过是在服务器端运行的JavaScript。Node.js还能把前端完全从后端抽象出来5,只通过数据和REST API接口交互。我们在第9章就会使用这样的方式设计和开发应用。

5关于把前端从后端抽象出来的更多信息,请访问http://bevacqua.io/bf/node-frontend

1. 安装Node.js和JSHint

安装Node.js和JSHint命令行界面(Command-line Interface,简称CLI)的步骤如下。安装Node.js的其他方式和排除故障的方法参见附录A。

访问http://nodejs.org,点击页面中的“INSTALL”按钮(如图1-6),下载最新版Node.js。

图 1-6 Node.js的网站

运行下载得到的文件,按照安装说明安装。

安装完成后会得到一个命令行工具,名为npm(Node Package Manager的简称),因为这个工具和Node.js是捆绑在一起的。npm是个包管理器,在终端里使用,用于安装、发布和管理Node.js项目用到的模块。包可以安装在各个项目中,也可以全局安装——这样更便于从终端调用。其实,这两种安装方式之间的区别是,全局安装的包存放在环境变量PATH对应的文件夹中,而另一种安装方式把包存放在一个名为node_modules的文件夹中,而这个文件夹位于执行安装命令所在的文件夹中。为了让项目自成一体,都推荐把包安装在项目中。不过,对JSLint这样的实用工具来说,我们希望在整个系统中都能使用,因此全局安装更合适。修饰符-g能让npm全局安装JSHint。使用这种方式安装,我们能在命令行中通过jshint命令使用JSHint。

打开你最喜欢的终端,执行npm install -g jshint命令,如图1-7所示。如果安装失败,可能要使用sudo提升权限,例如sudo npm install -g jshint

此行文本用于列表编号,不因该出现在正文中。

图 1-7 使用npm安装JSHint

执行jshint --version。这个命令应该输出JSHint的版本号,如图1-8所示。你看到的版本号可能和图中不一样,因为开发活跃的包经常会变更版本号。

图 1-8 在终端里验证jshint可用

下一节说明如何检查代码。

2. 检查代码

你现在应该在系统中安装好了JSHint,而且已经确认可以在终端里调用。如果想使用JSHint检查代码,可以使用cd命令进入项目的根目录,然后输入jshint .(点号告诉JSHint检查当前文件夹里的所有文件)。如果执行的时间太长,或许要加上--exclude node_modules选项,告诉JSHint只检查自己编写的代码,忽略通过npm install安装的第三方代码。

命令执行完毕后,你会看到一份详细报告,说明代码的状况。如果代码中有问题,这个工具会报告预期的结果和出现问题的行号,然后退出,返回一个错误码。如果通不过检查,我们可以使用这个错误码中断构建过程。只要有构建任务没得到预期的输出,整个构建过程就应该中止。这么做有很多好处,出错后不会继续运行,在问题解决前不会完成整个构建过程。图1-9显示的是检查某段代码后得到的结果。

图 1-9 在命令行中使用JSHint检查代码

安装好JSHint之后你可能就想收工了,因为这是你唯一的任务。可是,如果想在构建过程中增加任务,还不方便。你或许想在构建过程中增加一步,运行单元测试,这时就会遇到问题,因为你现在至少要执行两个命令:一个是jshint,另一个是运行测试的命令。这样做的伸缩性不好,你要记住如何使用jshint,还有很多其他命令及其参数,太麻烦,难记,而且容易出错。你肯定不想损失五亿美元吧!

那么你最好把构建任务放在一起,虽然现在只有一个任务,但很快就会变多。制定构建过程时要考虑自动化,避免重复各个步骤,以节省时间。

每门语言都有多个专用的构建工具,而且多数情况下都有一个工具比较出众,使用范围比其他工具广。对JavaScript来说,Grunt是最受欢迎的构建工具之一,有成千上万个插件(辅助构建任务)供使用。如果你要为其他语言制定构建过程,或许需要自己搜索,找到合适的工具。虽然本书编写的构建任务是针对JavaScript的,而且使用Grunt,不过我讲的原则应该能应用于任何语言和构建工具。

翻到第2章,看看如何把JSHint集成到Grunt中,以此开启制定构建过程的旅程。

1.6 总结

本章概览了本书后面几章要深入探讨的概念。下面列出你在本章学到的内容。

  • 现代JavaScript应用开发是有问题的,因为缺少对设计和架构的重视。

  • 使用构建优先原则能得到自动化的过程,设计出可维护的应用,而且鼓励思考你所开发的应用。

  • 学会了使用lint程序检查代码,不使用浏览器就提升了代码质量。

  • 在第一部分你会学习构建过程、部署和环境配置的所有知识。你将使用Grunt开发构建过程,在附录C中还能学习可以使用的其他工具。

  • 第二部分专门说明应用设计的复杂性。模块化、异步代码流、应用和API设计,以及可测试性都有一定的作用,会在第二部分介绍。

说到使用构建优先原则设计应用的好处,现在你只看到了皮毛,还有很多知识要学。下面我们进入第2章,讨论构建过程中最可能要执行的任务,再通过示例说明如何使用Grunt实现这些任务。

目录

  • 版权声明
  • 献词
  • 前言
  • 关于本书
  • 关于封面
  • 致谢
  • 第一部分 构建过程
  • 第 1 章 构建优先
  • 第 2 章 编写构建任务,制定流程
  • 第 3 章 精通环境配置和开发流程
  • 第 4 章 发布、部署和监控
  • 第二部分 管理复杂度
  • 第 5 章 理解模块化和依赖管理
  • 第 6 章 理解JavaScript中的异步流程控制方法
  • 第 7 章 使用模型-视图-控制器模式
  • 第 8 章 测试JavaScript组件
  • 第 9 章 REST API设计和分层服务架构
  • 附录 A Node.js的模块
  • 附录 B 介绍Grunt
  • 附录 C 选择合适的构建工具
  • 附录 D JavaScript代码质量指南