编者按:InfoQ开设新栏目“品味书香”,精选技术书籍的精彩章节,以及分享看完书留下的思考和收获,欢迎大家关注。本文节选自Casimir Staternos著、王群锋和杜欢翻译《全端Web开发:使用JavaScript与Java》中的章节“快速开发实践”,讨论了快速开发Web应用的一些指导思想和经验。
“在一句话中找寻我的家,一个简洁的句子,犹如金属锻造。并非为了迷惑什么人。并非为了在后代之中获取永恒的声名。一个未命名的事物需要秩序、韵律、形式,这三个词反对混乱和虚无。”
——切斯拉夫·米沃什
简洁、高效、简单,是当代文化所高度推崇的。或许这是因为和以前相比,现代社会变得相对丰富和复杂了。代码简洁、流程高效的Web应用会让最终产品易于维护、易于修改,最终会帮助获取更高的利润。同样,程序员的工作流程和工具也应该高效,避免无谓的复杂。由于选择太多,开发者暂时从编码中抽身,认真思考一下工作流程是否优化、是否高效,就变得特别重要了。
随着Web开发方式向客户端-服务器端架构的转变,开发流程也面临不断的改变和提高。通过去掉很少用到的配置选项、使用合理的缺省值,减少了大量不必要的工作。简化的工作流程带来更紧凑及时的反馈循环,频繁的反馈让人及时发现和解决产品中的问题。及早发现和修复缺陷提高了生产效率——也让程序员更加开心。更进一步说,它让原本需要大量时间和资源才能开发维护的复杂且高质量的软件,现在有可能花费较少的时间和资源就能做到。
大多草草了事 ……
众所周知,衡量程序员的生产效率很难。工作时间、每日代码量或者解决的缺陷数目,这样的指标虽然可量化,但不是很有用。由于项目的目标、时间和其预期寿命的不同,很难对它们做比较。没有一个指标能适用于所有项目,足够客观、公正,准确地反映出程序员的工作效率。在实践中,大多数软件开发经理根据开发者给出的估计和实际产出,“艺术地”维护着一张电子表格以准确估计这两者之间的关联度。这样就能理解,为什么通过改进流程会提升个人和组织的效率这个假设会被大家所接受了。
敏捷开发最早是以纠正瀑布式开发的缺陷的形式出现的,瀑布式开发倾向于一开始就负重前行,花费大量精力,组织大量活动,项目反而不会取得有意义的进展。这种趋势再发展就是“分析瘫痪”,当敏捷开发被初次引入时,其立即着手开发一个可工作的产品的观点令人耳目一新。遗憾的是,随着时间的推移,这一概念被发展成为一种“做了再说”的方式,早期的很多活动都没有一个定义清晰的目标。因此,如果想要生产提高效率需要,可能这样说有违直觉,需要先 放下工作 。显然,这不是指让你真的放下工作,这是为了项目的质量和长期生产效率,而对暂时的衡量指标和可见进度做出牺牲的能力。
这样做很有挑战性。管理层希望看见手头的项目进展,开发者喜欢编写代码,客户希望看到前者真的着手开始工作了。但是过早开始可能会导致工具和开发方式选型错误(或者不是最理想的)。将不适用的约定或传统引入项目遗害无穷。早期的一点点偏差,都会让项目偏离正确的轨道,随着项目的进展,问题会越积越多。认识到暂时 放下工作 ,先做一些分析,会让工作效率更高,产品质量更高,这是需要一些远见的。
预先分析因为采用 伪敏捷开发 (pseudo-agile)而饱受诟病(“伪”字是指这里所讲的并不违背敏捷开发实践)。凡事要三思而后行,这适用于多个层面。开发团队领导者需要做出项目相关的决定,每一个开发者需要了解最佳实践和新兴技术,这对他们的手头工作可能有用。有了正确的工具和方法,会减少完成初始工作的时间和精力,如果做对了,从长远看会开发出一套简单、易于维护的系统。不可将全部精力和注意力放在最紧急的任务上,而应做出根本性调整以高效地工作。在项目中可以采用敏捷开发中欢迎变化的方式,但基础的技术决策和相关开发流程不应有大的改动。
显而易见,只关注生产效率是不够的。如果绝对孤立地看待生产效率,什么也不做或许是最好的选择!软件质量、可靠性、流畅的交流、功能的正确性、对流程的遵守和约定俗成的规则同样重要。假设其他条件一样,最好能用最少的动作、最少的资源完成一件任务。因此,任何以提高生产效率为名义,对流程的改变,都应该放在一个更广阔的视野下来审视。而且,一个真正高效的流程也同样强调质量、可靠性和其他重要的因素。
没有一个简单的计划或者理论会马上提高生产效率,这多少有点让人失望。真正能提高生产效率的东西都是在实践中,在具体的项目中变干边学,发现和制定的。先把工作完成,日复一日,就看出了流程什么地方需要完善,完善它,这样周而复始。这些积攒下来的知识,会成为在其他项目中提高、优化流程和去除不合理之处的源泉。
理论上说,一个软件项目需要完成若干任务,因此在理想情况下会尽可能以最高效的方式完成。划分成任务后,更容易理解和衡量生产效率。每一项软件开发任务都需要若干人,使用若干台电脑完成。在做项目时,人与计算机,人与人,计算机与计算机之间可进行交互。所有这些,从地理上看可能分布在不同地方,如图7-1所示。
考虑到这一点,就可以在如下交互之间提高生产效率:
人和计算机是完成一项任务所需要的资源,如表7-1所示。为了提高生产效率,需要:
行动 | 人 | 计算机 |
---|---|---|
重新定义任务 | 确定需求、计划、架构和管理 | 编程语言、软件和编程范式 |
提高效率 | 开发技术、最小化干扰 | 自动化、预处理、压缩、优化、调优 |
增加资源 | 开发者、顾问 | 扩展、增加硬件/处理器 |
花费更多精力 | 时间管理、调整工作量 | 并行处理 |
认识到这些地方的重要性的理由很多。在某个地方没有提高生产效率(比如最近一段时期在计算机领域没有重大的技术创新),会影响到人的生产效率(通过加班或增加人手)。想一下这些因素和项目的关系能帮助人们有效地行动起来,可能花费相对较小的代价就能带来显著的提升。
这种全盘考虑的方式,能避免过份强调某一方面,去解决所有问题。一个大家都知道的例子是在项目后期,冀希望于增加工作量和人手来赶一个太具有野心的最后期限。另一个例子是自大的开发者陷阱,他们认为不管任务的性质如何,都可以闭门造车,让其自动化。本书强调的重点放在计算机上。
迭代是一种向着最终目标迈进(最终实现目标)、不断循环的过程。项目的每一部分都被分解成任务,经过多次迭代完成,如图7-2所示。这种方式适用于软件开发的方方面面,包括需求收集、设计、开发、测试和部署。
你需要将如下几条谨记在心。
这些都显而易见,是简单的常识。但正如那些涉猎软件开发的人所看到的,开发者是习惯的生物。很多人都习惯了某种开发流程或某些工具。这会导致大量不必要的工作,让项目复杂化,最多只能得到次优的结果。
Boyd认为赢得空中格斗的决定性因素不是观察、分析、计划、更好的实施,而是观察、分析、计划和更快的实施。换句话说,主要看一个人能迭代多快。Boyd认为迭代速度胜过迭代质量。
我将Boyd的发现称为Boyd迭代定律:在应对复杂分析时,快速迭代几乎总是优于深入分析。
——Roger Sessions, “ A Better Path to Enterprise Architectures ”
我们中的大多数人都无缘发明什么重大创新,给软件开发流程带来革命性的改变。但是跳出自己的编程文化至少可以开开眼界,有很多现成的改进可供使用,深入研究那些成熟技术,可以让个人获益良多,更不用提对项目的帮助了。让我们通过几个例子来看看。
现在需要修改一个JEE Web应用(由一个包含多个WAR的EAR构成),该应用使用Maven构建,部署在JBoss应用服务器上。负责修改的开发者需要对代码做几处改动(在这个过程中或多或少会犯点错误)。解决该问题有一种方法,需要下面几步。首先,修改代码。其次,键入命令启动一个标准的完全构建,然后部署该应用。根据应用的规模、单元测试的数量、构建过程和其他因素,完成第二步也许要花上几分钟。让我们看看怎样做能改善该流程。
这个例子还是围绕上面项目的,但进展顺利。人们一致认为应该加入测试,作为软件开发生命周期的一部分。这可以发生在软件开发的各个阶段和层次。
这种测试场景将关注点放在如何增加对项目状态的反馈,而不是项目的质量上。优化的价值显而易见,缺陷被更快地发现和修复。而且,需求和实现的不一致也能快速被发现。新的开发者能快速融入该项目,因为通过观察和运行测试,他们能相对独立地理解代码。一个单元测试覆盖率高的项目能在大规模重构后很好地活下来。能进行如此重构的信心来源于这些自动化的测试,它们保证了功能的完备。
作为新项目的架构师,你需要负责挑选最适用的工具,建立起系统的初始架构。对于云部署的、高扩展性的Web应用,可采用本书描述的客户端-服务器端架构。团队采用一些新的工具和流程是允许的,而采用Java作为编程语言是必需的,因为这是经过实践的,在内部被证明是能够胜任工作的(这就除去了使用Rails和Grails等依赖其他JVM语言的可能性)。
这些前期工作完成后,在开发者实现具体的业务需求前,很多重大决策就已经定下来了。这些决策允许客户端和服务器端进行相对独立(并行)的开发。通过单元测试和具体的例子,开发者为新功能添加测试时可以直接复制,得到快速反馈;也允许发布自动生成的文档和IDE模板,鼓励大家相对一致地编码和注释。
这些例子都很主观,随着新技术的出现,毫无疑问会改变和提高。它们的意义在于证明了与其盲目地遵循前人的实践和习惯,对流程做持续改进也是可行的。后面的几节旨在帮助大家进行头脑风暴,发现那些项目中有待提高的地方。
需要在软件开发生命周期的各个阶段思考如何提高生产效率。这是因为对于流程一个地方的改进,不一定会带来其他地方生产效率的提升。总的来说,可以将和提高生产效率相关的任务按照收益递减的顺序排出优先级。虽然项目和团队各异,还是可以总结出一些会产生主要影响的因素。总的来说,管理方式和企业文化是最根本的,然后是整体的技术架构、具体的应用设计和底层编码以及平台选择。对任务有了准确的分析和优先级,就能按照影响生产效率的重要程度优化处理。
通常来说,从总体上把握由很多人参与的项目能带来最大的收益。虽然这不是本书的重点,但是管理方式、团队活力和工作文化对所要完成的工作影响深远。这些环境因素设定了工作目标、组织和个人的价值观。它们效果显著,常常是最应该重视的地方。统一目标是最大的挑战,尤其对大型组织来说。 查理·芒格 ,商人和投资家,因与沃伦·巴菲特的关系而被人们所熟知,他描述过联邦快递将员工和组织的目标统一起来,从而消除延迟这一挑战。问题的解决之道在于保证所有参与者得到适当的激励:
在所有的商业案例中,我最喜欢的有关激励措施的案例是联邦快递。联邦快递的系统核心——创建了产品的信誉——是让所有的飞机在午夜到达同一个地方,然后将包裹在飞机之间分发。如果有延误,整个运营团队就不能把包裹按照所承诺的那样按时送达联邦快递的客户手中。
他们总是把事情搞砸,从来不能按时完成任务。他们试遍了各种方法——道德说教、威胁,总之各种你能想得到的方法。但是,都不起作用。
最后,有人想了个办法,降低他们的计时工资,提高计件工资——做完后就能回家。一夜之间,问题解决了。
保持合适的激励是非常重要的一课。这种解决方案对那时的联邦快递来说还不是很明显。但是现在,对大家来说这都是显而易见的了。
——查理·芒格
还有其他一些“大的方面”需要考虑:那些基本的组织和管理原则依然适用。分工明确、文档集中化管理、使用版本控制,这些看起来都很新鲜,但却常常被忽略。
系统的整体架构决定了后续技术的选型和实现。一个在大范围、公开部署的高扩展、基于云的应用和组织内部使用的应用不同,需要更加复杂的配置,后一种场景对错误的容忍度也高。如果成员需要在编码时考虑那些永远也不会发生的场景,或者在项目后期要适应没预料到的架构要求,整个团队的生产效率就会受到拖累。架构选型时应该明确项目的范围。
数据存储方式的选型是另外一个相似的例子。人们对传统的关系型数据库理解相对充分,它提供了引用和事务完整性,开发者认为这是理所当然的。新出现的NoSQL方案提供了优化的写操作,以一种限制更小的方式存储数据。每种方案都有自己的优势,但是没有一种锦囊妙计能覆盖所有的情况。出于对数据扩展性的考虑,可能采用NoSQL方案。但是如果一开始没有考虑到报表功能,数据就不会以一种利于高效生成报表的方式存储。开发者可能技术出众、效率极高,但是在完成具体的报表任务时,也无法逾越因为采用了错误的数据存储方式而导致的鸿沟。
每一门语言都有自己忠诚的信徒,他们乐此不疲地为各自语言的优点辩论。可以确定的一点是,对于一项给定的任务,某门语言的特性能让其在更短的时间内,更简单地完成任务。比如,如果不需要编译(通过使用IDE的自动编译选项或者脚本语言),任务就能用更短的时间完成。像Java这样的语言,有大量现成的函数库可用,而其他一些新语言,比如Scala和Groovy,则夸耀和其他语言有根本性的不同,使用较少的代码就能完成同样的任务。像Ruby和Python这样的脚本语言则有它们自己的工作流,在它们的领域里效率很高,而且也影响到了其他语言所使用的工具和流程。
选择编程语言、开发工具和框架是架构师确立项目方向的主要领域。每个程序员在开发项目过程中所具备的能力、所受到的限制都深刻地受到这些决定的影响。技术和与之相关的工作流程都有各自的考虑,生产效率不可避免地会受到这些选择的影响。
在 Software Tools (Addison-Wesley Professional出版,1976)一书中,Brian Kernighan说过一句名言:“编程的精髓在于控制复杂度。”试图降低复杂度的软件工具遍及软件开发生命周期的方方面面:版本控制系统、文档自动化、覆盖率和质量报告、测试工具、事项管理系统和持续集成服务器,这里只是仅举几例。除过这些,每位开发者所掌握的日常工具也能让他和同僚之间在效率上差好几个数量级。
每门编程语言都有与之相关的构建工具。虽然可以混用语言和构建工具,但是基于Maven的Java、Gradle的Groovy、SBT的Scala、Rake的Ruby都是紧密联系在一起的。每门语言都有开发客户端-服务器端Web应用的相关框架。Java有JEE和Spring(借由一款高度自动化的工具Roo),还有一些较新的框架,比如Play(同时支持Scala)。同样,Ruby有Rails、Groovy有Grails、Python有Django。多数框架包含一个嵌入式服务器,以方便开发流程。他们通常包含起始项目,显著地减少了费时的样板代码。选择合适的框架能减少构建时间、省略不必要的完整构建、享受预处理资产管道所带来的好处、方便地和测试进行集成。
IDE有代码补全、智能搜索、代码导航、重构(将一段代码抽取成一个新方法、在不同种类的文件中重命名变量)、单元测试集成、后台编译等功能。它们是Java社区的中流砥柱。在使用类似Java这样的语言工作时,它们价值连城,以致有些开发者觉得程序员如果不使用IDE完成一项任务简直难以置信。
使用脚本语言(尤其那些不是为JVM而开发的脚本语言)的开发者倾向于使用轻量级的 代码编辑器 ,如果在*nix系统的命令行里工作 1 ,vi(或者vim)和Emacs和它们内置的一些小工具会为软件开发提供类似(或者更好)的编辑功能。即使你不总是在命令行下工作,精通命令行也是很有必要的,因为很多支持的任务(部署服务器、查看日志、检查服务器性能)都得在命令行下完成。
1对门外汉来说,星号在程序员的语言里是通配符的意思。∗nix代表“Unix和类Unix系统,比如Linux”。∗nix还包含苹果公司的OS X,但是不包括Windows。可在Windows上运行类似Cygwin这样的工具,让其看起来像个Unix系统。
性能越好的应用,调试起来越快。让迭代周期(反馈循环的长短)最短,修改和验证项目的机会就更多。优化性能差的代码,能为开发者节省出时间干其他的事,而不是在那儿干等着系统响应。一开始选择的API、算法、数据存储机制和相关的流程会自上而下影响性能。甚至编程语言范型的选择都会影响性能,比如函数式编程(它以避免产生副作用而著称),通常可用来构建高效的缓存机制。它还可以使用非常简洁、易于理解的代码高效遍历数据结构。它简化了流程,使用更少的代码就能完成重构。应用的性能和代码的可读性都会因正确的技术(正确的团队)选择而受益。即使在一个成熟的项目中,也有可优化的地方。比如很多Web应用的网络性能都可通过压缩或减少连接次数而受益。这些细节通常在项目的早期被忽视,但在后期可以通过非破坏性的方式来实现。对性能的提升总能带来其他好处,开发者能用节省下来的时间来编码和测试,而无需等待系统响应。
通过更一般的应用设计,RESTful风格的应用也会提高生产效率。客户端-服务器端的架构允许并行开发,调试和维护也更简单,同时简化了很多在其他时候会很复杂的任务。这些益处对开发系统中独立的模块也适用。正确地将项目模块化,会在前期高效地实现项目,而那些扩展性不好的方案可在后期重写。良好的设计会提高日后的生产效率。
早先的测试很随意,边用边测,现在测试已经成为一种正规化的操作了。自动化测试(包括将测试集成进构建过程)是有信心大规模重构系统所必需的,也是自动化部署的基础。自动化测试早先只是断断续续地在运行,频率也不高。现在处理器能力增强了,每次项目构建(或文件保存)时运行测试也是可行的。测试的范围会因项目的成熟度而变化,但在大多数非平凡的Web开发中都是必需的。测试已经在JavaScript社区树立了牢固的地位,它让人们可以开发出大型的项目,能在各种浏览器和设备上可靠地执行。为了应对基于云的部署,人们开发出新形式的测试,比如Netflix公司的 Chaos Monkey ,它通过主动触发失败,帮助开发出可恢复的服务。
合理地执行测试,能方便程序员和计算机之间的交流,也能方便程序在团队之间进行交流。乍一看还不明显,毕竟测试的目的是为了理解和验证软件的可靠性和功能的。一些类型的测试会显著提升大型项目的生产效率。当测试减轻了集成的痛苦、提升了产品质量时,效果就变得显而易见了。遵循行为驱动开发范型的测试,能在代表各领域的项目成员之间搭建一条一致的沟通渠道,通过一种更精妙的方式来提高生产效率。正如 The Cucumber Book: Behaviour-Driven Development for Testers and Developers 一书所说:
“当开发者和业务干系人能明白无误地沟通时,软件团队才能以最好的方式工作。一种非常好的方式是合作编写自动化的验收测试来描述任务……当团队能合作编写验收测试时,他们能发展出自己的特殊语言来描述领域内的问题,这可以帮助他们避免误解。”
更好的沟通减少了误解浪费的时间,更好的理解会产生更好的产品。
有时候运行测试会成为负担,降低生产效率。编写和维护测试需要时间,运行测试需要时间和资源。和其他开发任务一样,测试的精力和时间本可以用在其他地方。这导致了开发者和其他人在很大程度上减少了测试。
现在将测试集成进Web应用已经变得相对容易,很多启动项目都包含了测试相关的配置。为了最大限度发挥测试的价值,需要鼓励形成尊重测试价值、将维护测试当成真正的工作,并且认为这是物有所值的文化。不能说测试会直接提高生产效率,但对大多数拥有较长生命周期的项目来说,测试的价值是不能被低估的。
操作系统和安装的基础软件组成了底层部署平台。具有一两台强大处理器的工作站(加上一台额外的显示器以增大屏幕的实际使用面积)就抵得上几年前的一台超级计算机。为JVM分配足够的内存、关闭不需要的程序节省资源,这些可能会有一定的价值,但是应用的设计常常才是更重要的因素。在有些情况下,一个更快的文件系统会显著影响构建时间。
是使用集中化的数据库,还是使用开发者各自维护的备份也是一个重要的决定。在开发者维护的场景里,迁移时可使用FlyWayDB( http://flywaydb.org/ )这样的框架。如果需要远程使用资源,网络就成了问题,和分散在各地的团队一起工作时尤其如此。
本章有意不提供示例项目。生产效率,或开发时的效率需要后退一步考虑现有的选项是否和手头的项目匹配。项目的各个阶段都有宏观或微观的任务可被简化、自动化,或者更高效地完成。你最好能够“上来透口气”,它能让你充分思考现有的选项,做出最佳的决策。
书籍简介
JavaScript和Java这两大生态系统之间如何协同,成为所有Web开发人员共同面临的问题。本书应运而生,全面又简练地为读者展示了最新的C/S应用开发范式。本书以Java和JavaScript这两种最流行的服务器与客户端开发环境为例,全面讲解了最新的C/S应用开发范式。作者不仅讲解了很多实用的C/S开发架构,还通过各种实例进一步强化了读者的认知。