代码不朽:编写可维护软件的10大要则(Java版)(txt+pdf+epub+mobi电子书下载)


发布时间:2020-07-01 10:33:02

点击下载

作者:张若飞

出版社:电子工业出版社

格式: AZW3, DOCX, EPUB, MOBI, PDF, TXT

代码不朽:编写可维护软件的10大要则(Java版)

代码不朽:编写可维护软件的10大要则(Java版)试读:

前言

简单出真知。[1]——歌德

在SIG经历了长达15年有关软件质量的咨询工作后,我们在可维护性方面学习到了不少经验。

首先,在软件开发过程中,对可维护性的忽视是一个确实存在的问题。可维护性低意味着开发人员需要在维护和修复旧代码方面花费更多的时间和精力。相应地,留给软件开发中最有价值的部分——编写新代码的时间就少了。我们的经验和收集到的数据都表明,低于平均值与高于平均值的可维护性相比,在维护源代码方面至少相差两倍的工作量。我们会在附录A中介绍如何来测量可维护性。

第二,很大程度上,可维护性随着一些小问题的不断发生而变得越来越低。因此,提升可维护性的最高效、最有效的方式,就是找出这些小问题。提升可维护性不需要什么魔法或者高科技,一些简单的技能和知识,再加上对它们的遵守和使用环境,就可以让可维护性有一个飞跃的提升。

在SIG,我们见过已完全无法再维护的系统。在这些系统中,bug没有被修复,功能没有被修改或扩展,最终原因都是因为时间太长、风险太大。不幸的是,这种情况在今天的IT行业中非常普遍,但是本不该如此。

这也是为什么我们编写这10条原则的原因。我们希望将每一个一线开发人员都应该掌握的知识和技能分享出来,让每个人都能不断写出高可维护性的代码。我们相信,作为软件开发者的你,在阅读并理解这10条原则后,一定能写出高可维护性的代码。剩下的就是,创造出让这些技能发挥最大效果的开发环境,包括互相共享的开发实践、适合的工具等。我们将在第二本书《构建软件团队》(Building Software Teams)中介绍这些开发环境。

本书的主题:构建可维护软件的十个原则

后续章节中所介绍的原则与系统的类型无关。这些原则都是关于代码单元(Java中的方法)大小及参数数量、代码中决策点的数量,以及源代码等方面的讨论。可能许多开发人员都已经对它们广为熟悉,至少在培训中应该或多或少都听说过一些。这些章节还会提供示例代码(大多数以重构的形式),来帮助读者在实践中掌握这些原则。虽然这些原则都是基于Java语言介绍的,但是它们不受所使用的编程语[2]言的限制。这些原则其中的4/5,大概都来自SIG/TÜViT认证产品可维护性评估标准(Evaluation Criteria for Trusted Product [3]Maintainability),它是一组用于系统化评估源代码可用性的指标集合。

你为什么应该阅读本书

单独来看本书中的每一条原则,可能都已经被开发人员广为熟知。事实上,许多常用的代码分析工具,都会检查其中的一部分原则。例如,Checkstyle (http://checkstyle.sourceforge.net) (用于Java)、Style‐Cop+ (http://stylecopplus.codeplex.com) (用于C#)、Pylint (http://www.pylint.org) (用于Python)、JSHint (http://jshint.com) (用于JavaScript), RuboCop (https://github.com/bbat sov/rubocop) (用于Ruby),以及 PMD (https://pmd.github.io) (可用于多种语言,包括C#和Java),这些工具都会检查代码是否符合第2章中所介绍的原则。但是,以下3个特点是本书区别于其他一些软件开发书籍的地方:

我们根据经验选择了10条最重要的原则

代码风格工具和静态代码分析工具可能会让人望而却步。Checkstyle 6.9包含了近150个规则,每个都代表着一条原则。虽然它们都有意义,但是对提高可维护性的效果却不相同。我们已经从中选出了10个对提升可维护性最有效的原则。下一页中的“为什么选择这十条原则?”中解释了我们是如何来选择这些原则的。

我们会告诉你如何才能符合这10条原则

告诉一个开发人员什么应该做或者什么不应该做是一回事(正如很多工具做的那样),教会他们如何做到是另一回事。在本书中,对于每一个原则,我们都提供了翔实的示例代码,一步一步讲解如何编写符合原则的代码。

我们提供来自于真实系统的统计数据及示例代码

在SIG,我们已经见过了大量由开发人员在各种实际限制条件下编写的源代码,其中包含了各种妥协的处理。因此,我们将自己的基准测试数据分享出来,让读者了解到现实中的代码与理想中的差距。

谁应该阅读本书

本书的目标读者是使用Java语言编程的软件开发人员。在这些读者中,本书又针对两个不同的人群各有侧重点。第一个人群是那些接受过计算机科学或软件工程方面专业教育的开发人员(例如,大学主修这两个专业之一的)。对于这样的开发人员,本书可以巩固他们在专业编程课程上所学的基础知识。

第二种是没有进行过计算机科学或软件工程专业学习的软件开发人员。我们认为这些开发人员主要是一些进行自学或者大学主修完全是其他专业的人员,但是他们后来又从事了软件开发这个行业。我们的经验是,这类人员除了熟悉所用语言的语法和语义之外,很少接受其他的专业培训。这也是我们在编写本书时特别考虑的人群。

为什么选择这10条原则?

本书包含了10条原则。前8条与SIG/TÜViT认证产品的可维护性评估标准(它是SIG评估可维护性的理论基础)中的系统属性一一对应。对于SIG/TÜViT评估标准,我们按照如下原则来选择评估指标:

● 数量尽可能少

● 与技术无关

● 易于测量

● 可以与实际的企业软件系统进行有意义的比较

因此,我们就选出SIG/TÜViT评估标准中的这8个系统属性。而添加另外两个原则(关于整洁代码和自动化测试),是考虑到它们是最关键的,并且可以由开发人员直接控制。

计算机科学和软件工程中的研究人员已经定义了非常多的源代码指标。不管你怎么数,几十个指标总是有的。因此我们这里提炼出的8个系统属性,显然只是所有可维护性指标中的很小一部分。

但是,我们想说的是,这8个SIG/TÜViT指标是完全适合并且足够测量可维护性的,因为它们解决了以下几个问题:

依赖于具体技术实现的指标

有些指标(例如,继承深度)与具体使用的技术(例如,只有在面向对象的语言中才存在继承关系)有很大的关系。但是在现实中,面向对象还远没有达到完全统治的地位,因此我们也需要考虑评估大量非面向对象代码(例如用Cobol、RPG、C和Pascal编写的系统)的可维护性。

与其他指标紧密相关的指标

有些指标与其他指标之间有非常紧密的关系,系统中的决策点总数就是一个例子。实验证明,这个指标与代码量有直接的关系。这意味着一旦你知道系统中代码行的总数(这很容易测量),那么就几乎可以非常准确地预测出决策点的数量。我们没理由去选择那些较难于测量的指标,因为与较容易测量的指标相比,你不得不花费更多的精力来执行并统计结果,但是又得不到的内容和价值。

在实践中没有区别的指标

有些指标从理论角度看很好,但是在软件开发实践中,它在所有系统上的表现都几乎一样。我们没理由将这些指标作为评估标准,因为无法用它们的结果来区分各个系统。

本书不包括哪些内容

本书使用Java语言(本书中的唯一一种语言)来阐述和解释我们的原则。但是我们并不是要教大家如何使用Java。我们会假设读者至少可以阅读Java代码和Java标准库的API,并且尽可能地保证示例代码足够简单,只使用Java语言的基本特性。

这也不是一本介绍Java习惯用法的书,也不是要告诉大家什么才是符合Java习惯的代码。我们不相信熟练使用某种语言就可以达到高可维护性。相反,本书中的原则在很大程度上都与语言无关,因此也与语言的习惯用法无关。

虽然我们在书中会介绍或解释许多重构模式,但我们并不是想写一本关于这些模式的书。市场上已经存在了很多关于模式的优秀书籍和网站。我们这本书只关注为何选择这些重构模式,以及它们如何能提高可维护性。因此,这本书只是学习重构模式的一个起点。

下一本书

我们知道,单个开发人员并不能控制开发流程的方方面面。使用哪些开发工具、质量控制如何管理、如何搭建部署环境等,这些都是影响软件质量的重要因素,但它们同时也是一个“团队”的责任。因此,这些主题已经超出了本书的范围,我们会在下一本书《构建软件团队》中介绍这方面的最佳实践,以及如何测量它们的结果。

关于SIG

虽然本书封面只列出了一个作者名字,但是本书的真正作者不止一人。真正的作者是SIG——一个软件管理咨询公司。可以说,这本书提炼了SIG所有顾问从2000年以来,在测量软件质量和提出建议过[4]程中总结的集体经验和知识。我们运营着唯一一个经过认证的软件分析实验室,可以按照ISO 25010国际标准,对软件产品的质量进行标准化的检测。

SIG提供的服务之一是我们的“软件风险监控”服务。我们的客户通过该服务,定期(通常每周)上传他们的源代码。然后我们的软件实验室会自动对上传源代码进行检测。SIG顾问会评估所有自动分析出的异常情况,并与客户讨论。在本书编写时,SIG已经总共分析了71亿行代码,每周有将近7千多万行代码被上传到SIG。

SIG成立于2000年。它的历史可以追溯到荷兰国家数学和计算机科学研究院。15年之后的现在,我们仍然保持并重视与软件工程学术界的联系。SIG顾问会定期在学术期刊上发表文章,并且许多博士论文都是基于对开发和提高SIG质量模型的研究。

关于此版本

这是本书的Java版本。所有代码示例都用Java编写(并且只用Java编写),文中经常提到的工具和名词都是在Java社区中所广泛使用的。我们假定读者都拥有一定的Java编程经验。如前文所述,本书中用Java展示的各条原则,实际上是与语言无关的。本书的C#版本由O'Reilly出版社负责出版发行。

相关书籍

我们列举了10条实现高可维护性的基本原则。虽然这可能是许多人关于可维护性方面阅读的第一本书籍,但是我们希望它不要成为最后一本。因此我们推荐读者继续阅读以下图书:《构建软件团队》,SIG著

这是同一批作者编写的配套书籍。与本书关注于构建可维护软件的开发原则不同,这本书关注于软件开发流程的最佳实践以及如何使用“目标—问题—指标”的方法来有效地测量它们。《构建软件团队》一书将于2016年出版。《重构:改善现有代码的设计》,Martin Fowler著

这本书关注于提升现有代码的可维护性(以及其他质量特征)。《代码整洁之道》,Robert C.Martin(也称为Bob大叔)著

与本书不同,这本书讲述的是如何编写高质量的软件源代码,但是它介绍了更高抽象层次的原则。《代码质量》,Diomidis Spinellis著

这本书也介绍了关于代码质量的原则,但是同《代码整洁之道》一样,它们都处于更高的抽象层次。《设计模式:可复用面向对象软件的基础》,Erich Gamma、Richard Helm、Ralph Johnson和John Vlissides (也称为四人帮)著

推荐那些想成为软件架构师的开发人员一定要阅读该书。

本书中使用的约定

本书使用以下印刷排版约定:

斜体(Italic)

表示新名词、URL、邮件地址、文件名及文件扩展名。

等宽字体(Constant width)

用于程序列表(包括段落中的),表示程序元素,例如变量或者函数名、数据库、数据类型、环境变量、语句和关键字。

该图标表示一个提示或建议。

该图标表示一个一般说明。

该图标表示一个提醒或警告。

该图标表示一个重要的备注。

源代码中各元素的命名方式

虽然本书中我们使用Java来示范可维护性的原则,但是它们并不是只能用于Java。这些原则来源于SIG的可维护性模型,与具体技术无关,并且已经被应用于大约100种编程语言及相关技术中(例如JSP)。

各种编程语言(理所当然的)在功能和语法上各不相同。例如,Java使用关键字final来表示常量,而C#使用readonly。另一个例子是C#提供了一个称为“分部类”的功能,而Java(当前)不支持该功能。

除了语法方面的不同之处,编程语言在图书、教程以及规范中使用的术语也不一样。例如,几乎每种编程语言都有将一组代码行作为整体执行的概念。在Java和C#中,该概念被称为一个方法(method)。而在Visual Basic中,它被称为一个子程序(subroutine)。在JavaScript和C中,它被称为一个函数(function)。在Pascal中,它被称为一个过程(procedure)。

因此,与技术无关的模型需要通用名来对这些概念进行分类。表P-1展示了我们在本书中使用的通用名。表P-1.对概念的通用分类以及在Java中的表示

以下列表进一步阐释了表P-1中的分类结构与Java编程实践之间的关系:

从最小到最大

表P-1中的分类概念按照从最小到最大的顺序排列。虽然单元本身由多个语句组成,但是语句不是一个可分类的结构。

同许多其他编程语言一样,Java的语句和行数之间也有着复杂的关系。一行代码可能是一条或多条语句,但是一条语句也可能跨越多行代码。为了方便起见,我们只关心代码行(line of code, LOC),即源代码中任何以回车/换行结束的行,不包括空行以及只含有注释的行。

特定语言中没有定义某些概念

如表P-1所示,Java中没有某些通用的概念。例如,在Java中,没有语法能用来描述一个系统的边界。之所以定义这个概念,是因为我们需要它。这并不是Java缺少语法的问题,而是实际中我们会通过其他方式来确定这些边界。

一些众所周知的通用概念无影响

你可能会奇怪,为什么表P-1中没有定义像子组件或者子系统这样众所周知的概念。原因很简单:我们在阐述本书中原则时用不到它们。

并不代表一个语言的所有分类结构

Java中的分类结构远比表P-1中的要多。Java有用来对类分组的包、接口,还有内部类。因为我们不需要在原则中使用它们,所以没有列举在表P-1中。例如,我们不需要用类和内部类的区别,来说明有关耦合的原则。

Java中的包与我们在表P-1中提到的组件不同。在一个非常小的Java系统中,组件和包之间可能是一对一的关系。而在更大的Java系统中,通常包的数量要远比组件要多。

构建工具对通用术语无影响

在Java开发中,使用Apache Ant的团队有另一个分类概念:目标(target)。同样,使用Maven的团队可能会使用一些POM文件,每个文件对应系统中的某一部分。但是,在我们的原则中都不会涉及Ant目标或者Maven POM文件。虽然组件与Ant目标或Maven POM文件之间,可能会存在一对一的关系,但这并不是一定的。

组件由系统的架构来决定

组件并不是一个只有在Java中才有的概念。组件并不是Java中的包,也不是Ant的目标或者Maven POM文件。相反,组件是由系统的软件架构而决定的最高层的构建模块。这些模块只有在系统的架构设计图中才会出现。第7章会进一步解释组件的概念,并提供一些示例程序。

如何使用书中的代码示例

你可以在https://github.com/oreillymedia/building_maintainable_software 下载本书的补充资料(代码示例、练习题等)。

本书的目的是帮助你完成工作。一般来说,如果本书提供了示例代码,你就可以用在自己的程序或者文档中。除非你改写了代码中的很大一部分,否则不需要联系我们申请使用权。例如,编写一段使用了本书中部分代码的程序不需要授权,但是售卖O'Reilly书中的代码或者分发含有代码的存储媒介(例如CD-ROM)需要授权。引用本书及书中示例代码来回答问题不需要授权,但是在你的产品文档中引用大量的本书示例代码需要授权。

我们感谢但不强制你使用署名。通常,署名包括书籍名称、作者、出版商和ISBN号。例如”Building Maintainable Software: Ten Guidelines for Future-Proof Code by Joost Visser.Copyright 2016 Software Improvement Group B.V., 978-1-4919-5352-5“。

如果你觉得使用代码示例的目的,不在我们上面提到的授权范围之内,可以联系permissions@oreilly.com获得特别授权。®

SafariBooks Online

Safari Books Online(www.safaribooksonline.com)是一家应需而变的数字图书馆。它同时以图书和视频的形式出版世界顶级技术和商务作家的专业作品。

Safari Books Online 是技术专家、软件开发人员、Web 设计师、商务人士和创意人士开展调研、解决问题、学习和认证培训的第一手资料。

对于组织团体、政府机构和个人,Safari Books Online 提供各种产品组合和灵活的定价策略。用户可通过一个功能完备的数据库检索系统访问O'Reilly Media、Prentice Hall Professional、Addison-Wesley Professional、Microsoft Press、 Sams、Que、Peachpit Press、Focal Press、Cisco Press、John Wiley & Sons、Syngress、Morgan Kaufmann、IBM Redbooks、Packt、Adobe Press、FT Press、Apress、Manning、New Riders、McGraw-Hill、Jones & Bartlett、Course Technology 以及其他几十家出版社的上千种图书、培训视频和正式出版之前的书稿。要了解Safari Books Online 的更多信息,请访问我们的网站。

联系我们

请将对本书的评价和发现的问题通过如下地址告知出版者。

美国:

O'Reilly Media, Inc.

1005 Gravenstein Highway North

Sebastopol, CA 95472

中国:

北京市西城区西直门南大街2 号成铭大厦C 座807 室(100035)

奥莱利技术咨询(北京)有限公司

我们在http://bit.ly/info_architecture_4e上列出了勘误表、示例和所有额外的信息。

要评论或者询问关于本书的任何技术问题,请发邮件到bookquestions@oreilly.com。

要了解O'Reilly更多的图书、课程、会议和新闻,请访问我们的网站http://www.oreilly.com。

我们的Facebook账号:http://facebook.com/oreilly

我们的Twitter账号:http://twitter.com/oreillymedia

YouTube的观看网址:http://www.youtube.com/oreillymedia

感谢

我们想要感谢参与编写本书的以下人员:

● 感谢Yiannis Kanellopoulos (SIG) ,他是我们的项目经理,负责各个方面。

● 感谢Tobias Kuipers (SIG),他是本项目的发起人。

● 感谢Lodewijk Bergmans (SIG)帮助制定了本书的目录结构。

● 感谢Zeeger Lubsen(SIG)对全书进行了通篇审校。

● 感谢Ed Louwers(SIG)帮助设计了本书的视觉效果。

● 感谢所有优化测量模型、进行基准测试以及分析软件质量的SIG同事们(包括前同事)。

对于我们的出版商O'Reilly,我们想感谢:

● 感谢Nan Barber,他是我们的文字审校。

● 感谢Keith Conant,他是我们的技术审校。

以及感谢Arie van Deursen授权让我们引用JPacman的代码片段。[1]约翰 •沃尔夫冈 •冯 •歌德(1749年8月28日—1832年3月22日),出生于德国法兰克福,戏剧家、诗人、自然科学家、文艺理论家和政治人物。[2]TÜViT是TÜV(德国的一个从事技术质量管理的全球性公司)的一部分。它主要专注于IT和安全方面的认证咨询。[3]请参考可维护性评估标准(http://bit.ly/eval_criteria)。[4]即ISO/IEC 17025认证。第1章简介

谁写的这段代码?我实在受不了了!—— 一个程序员

成为一个软件开发人员是一件非常棒的事。当某人交给你一个问题或者需求时,你可以给出一个解决方案,并将该方案翻译成计算机可以理解的语言。这些都是极具挑战性并极具价值的工作。同时,软件开发也可能是一份辛苦的工作。如果你需要不断修改其他人(甚至是你自己)写的源代码,你知道这可能很容易,也可能很难。有些时候,你可以很快确定需要修改哪些代码,这些代码被很好地与其他代码隔离,并且通过测试可以确定其正确性。但是在另一些时候,唯一的解决方案就是使用一种hack的方式来修改代码,因此而导致的问题可能比解决的问题还要多。

我们将一个软件系统可被修改的难易程度称为它的可维护性。一个软件系统的可维护性由其源代码的多个属性决定。本书会讨论这些属性,并归纳出10条开发原则来帮助你写出更容易被修改的代码。

在本章中,我们会阐释我们所理解的可维护性。然后,我们会讨论为什么可维护性如此重要。这也为引出本书的主题做铺垫——如何从一开始就构建一个可维护的软件。在每章最后,我们会讨论对可维护性的常见误解,并介绍这10条原则背后的思想。1.1 什么是可维护性?

假设两个不同的软件系统提供一模一样的功能,只要给定一样的输入,就能计算出一样的输出。这两个系统其中之一运行较快,对用户较友好,并且它的源代码易于修改。而另一个系统运行较慢,使用不便,并且它的源代码几乎无法理解,更别提修改了。即使两个系统提供相同的功能,但在质量上孰高孰低显而易见。

可维护性(一个系统可被修改的难易程度)是软件质量的一个特征,而性能(一个系统得到输出的速度快慢)是另一个特征。

国际标准ISO/IEC 25010:2011(我们在本书中简称为ISO [1]25010)将软件质量划分为八个特征:可维护性、功能可适性、性能效率、兼容性、可使用性、可靠性、安全性以及可移植性。本书会专门来介绍可维护性。

虽然ISO 25010没有描述如何测量软件质量,但这并不意味着你无法测量它。附录A中介绍了在SIG中,我们是如何按照ISO 25010的标准来测量软件质量的。

软件维护的四种方式

软件维护并不是去修复磨损的器件。软件不是物质的,因此也不会像物质器件那样自己会慢慢磨损。但是大多数软件系统都会在交付后被修改很长一段时间,这就是软件维护。软件维护可以被分为以下四种方式:

● 发现并修复Bug(我们称为纠正性维护)。

● 系统需要去适应操作环境的改变——例如,操作系统或者技术的升级(我们称为适应性维护)。

● 系统用户(或者其他能够影响系统的人,例如股东)有新的需求,或者对之前的需求有变化(我们称为完善性维护)。

● 确定可以改进质量或者预防将来可能产生的Bug的方法(我们称为预防性维护)。1.2 为什么可维护性很重要?

我们之前介绍过,可维护性只是ISO 25010中规定的软件产品质量的8个特征其中之一。那么为什么可维护如此重要,以至于我们需要专门拿出一本书来进行介绍?我们可以从两个角度来回答这个问题:

● 低可维护性会对业务造成严重影响。

● 可维护性是其他质量特征的推动者。

我们接下来会用两节来讲解这两点。

低可维护性会对业务造成严重影响

在软件开发中,软件系统的维护阶段通常要跨越长达10年的时间,甚至更久。在这个阶段的大部分时间中,我们需要解决不断产生的问题(纠正性维护和适应性维护),以及实现各种改善需求(完善性维护)。因此,解决问题和实现需求的效率和效果,对于公司的管理者来说非常重要。

当我们可以很快、很容易地解决问题和实现需求时,在维护上所花的精力就减少了。高效的维护可以减少维护人员(开发人员)的数量,同时降低维护成本。当开发人员数量保持不变时,高效的维护工作可以让他们有更多时间用于其他任务,例如开发新的功能。快速实现需求意味着可以更快地将新产品和服务投放到市场。因此,对于解决问题和实现需求来说,缓慢低效将可能导致交付延期或者系统不可用。

SIG经过实验证明,可维护性处于平均水平之上的系统,在解决问题和实现需求方面的速度,是处于平均水平之下系统的将近两倍。对于企业级系统来说,两倍已经是一个非常可观的数字了。因为解决问题和实现需求的时间通常都按照日或者周来计算,所以这个数字所意味的不是一小时修复5个bug还是10个bug的区别,而是你的新产品第一个投放到市场,还是眼睁睁看着竞争对手比你提前几个月抢占市场的区别。

这还只是可维护性在平均水平之上和之下之间的区别。在SIG,我们见过新搭建的系统可维护性却非常差,甚至在上线前系统就已经变得无法再进行修改了。对于这类系统的修改,引入的bug会比解决掉的bug更多。开发时间过长,导致商业环境(以及因此产生的用户需求)也已经发生了变化,需要的修改越多,引入的bug也会越多。这样的系统,往往还没有发布1.0就已经结束了。

可维护性是其他质量特征的推动者

可维护性在软件质量中占具特殊地位的另一个原因是,它作为其他质量特征的一个推动者而存在。当系统拥有高可维护性时,会更容易提高其他的质量方面,例如修复一个安全bug。更通俗一点讲,不管是提升性能、功能可适性、安全,还是ISO 25010定义的其他方面,对系统的所有优化都需要修改源代码。

有些时候,我们只需要进行较少的、局部的修改。但有些时候,我们需要进行大规模的重构。所有的修改都需要这些步骤:找到相关源代码,进行分析,理解其内在逻辑以及在业务流程中所处的地位,分析不同代码之间的依赖并进行测试,再将修改后的代码推送到开发持续集成环境。不管怎样,在一个可维护性更好的系统中,进行这些修改会更容易,你也可以更快、更高效地实现质量优化。例如,可维护性好的代码比无法维护的代码更加稳定,修改高可维护性的系统可能出现的未预期影响,也会比修改那些代码难以分析和测试的系统更少。1.3 本书的三个基本理论

既然可维护性如此重要,你又如何来提高你要写的代码的可维护性呢?本书提供了10条可以实现高可维护性的指导原则。在后续章节中,我们会对每条原则进行讲解和讨论。本章中,我们先介绍一下这些原则背后的理论。

1.坚持简单的原则最有助于提高可维护性。

2.可维护性不是开发完后才去考虑的,而应该是在项目开发的一开始就加以考虑。每一个人的贡献都应当计算在内。

3.对各原则的违背会带来不同的影响,有些严重程度甚于其他。一个软件系统越遵守原则,可维护性越高。

接下来我们对这三个理论逐一进行解释。

理论1:简单的原则最有助于提高可维护性

人们可能希望有一个可以提高可维护性的“银弹”——有某种技术或者理论可以一劳永逸地解决可维护性的问题。但我们的理论恰恰相反——可维护性需要遵循简单的原则,而非复杂的原则。这些原则需要能够保证足够的可维护性,而不是完美的可维护性(不管可能会是什么样)。即便是遵循了这些原则的源代码,依然可以变得更易于维护。从某个时刻开始,可维护性能带来的收益会变得越来越小,而保持可维护性的成本会变得越来越高。

理论2:可维护性不是开发完才考虑的事情,每一个人的贡献都计算在内

可维护性需要从一开始开发时就加以考虑。我们能够理解,对于读者来说,很难看到对某一条原则的“违例”会对系统的整体可维护性有何影响。这也是为什么所有开发人员必须遵守和执行这些原则,才能实现一个整体上可维护的系统。因此,你的个人贡献会对系统整体有巨大的重要意义。

遵守本书中的原则不仅能让你写出易于维护的代码,还可以为其他开发人员树立正确的代码规范。这样可以避免开发中的“破窗效应”,即开发人员临时放松自我约束而选择一些捷径的编码方式。树立正确的代码规范,不一定能让你成为资深的工程师,但是能让你在开发过程中保持自律。

记住,你不只是在为自己编写代码,还要考虑到后续接手工作却缺乏经验的开发人员。这样想会帮助你简化编程的思路。

理论3: 各原则的影响大小不同

本书中的各项原则将指标的阈值作为一个绝对的标尺。例如,在第2章中,我们会告诉你不要编写超过15行代码的方法。我们也完全清楚,在实践中总会遇到各种特殊情况。如果某段代码违反了一个或多个原则怎么办?许多软件质量工具会假定所有违反原则的代码都是不好的,这背后的假设其实是所有违反原则的代码都应该被更正。在实践中,更正所有这些代码既不是必需的,也不切实际。这种“要么全有要么全无”的方式可能会让开发人员选择忽略所有这些违反原则的提示。

我们采取了一种不同的方式。为了让指标简单实用,我们没有采用违反原则的代码数量来衡量一个代码库的质量,而是使用这个代码库的质量分析。质量分析将各项指标划分成几个不同的类别,从完全遵循原则到严重违反代码。通过使用质量分析,我们可以区分出轻微的违反情况(例如,一个含有20行代码的方法)和严重的违反情况(例如,一个含有200行代码的方法)。在下一节讨论了对可维护性的常见误解之后,我们会介绍如何使用质量分析来测量一个系统的可维护性。1.4 对可维护性的误解

在本节中,我们会讨论实践中遇到的有关可维护性的种种误解。

误解:可维护性与使用的语言有关“我们的系统使用了最前沿的编程语言,因此它至少不会比其他系统的可维护性差。”

我们在SIG的数据并没有表明,一个系统选择的技术(编程语言)是对可维护性产生至关重要的决定因素。我们的数据集既包括可维护性最好的Java系统,同时也包括可维护性最差的Java系统。在我们的基准测试中,所有Java系统的平均可维护性与基准测试的平均值一样,对于C#也一样。这说明用Java(或者用C#)可以写出可维护性很好的系统,但是使用这些语言并不能保证系统的可维护性。显然,决定可维护性的有其他的因素。

为了统一起见,我们在本书中均使用Java代码。但是本书中所介绍的原则并不是只适用于Java语言。事实上,根据本书中的原则和指标,SIG已经对超过上百种编程语言实现的系统进行过基准测试。

误解:可维护性与行业有关“我的团队开发的是汽车行业的嵌入式软件,我们这的可维护性跟其他的不一样。”

我们相信本书中介绍的原则适用于所有形式的软件开发:嵌入式软件、游戏、科学软件、软件组件(例如编译器或者数据库引擎)以及管理软件。当然,这些领域之间有一些区别。例如,科学软件通常会使用特定的编程语言,例如使用R进行统计分析。但是,即便使用R语言,保持代码单元短小简单仍然是一个好主意。嵌入式软件所在的环境通常都有严格的性能要求,并且资源有限。因此当我们需要在性能和可维护性之间做出一个选择时,我们只能选择前者。但是不管领域如何,ISO 25010中定义的特征仍然是适用的。

误解:可维护性等价于Bug的数量多少“你说系统的可维护性在平均值之上,但是它到处都是bug!”

根据ISO 25010的定义,一个系统可以是高度可维护的,但是同时可以不满足其他质量特征。这也就是说,一个系统可以有高于平均值的可维护性,但仍然存在功能可适性、性能、稳定性以及其他问题。高于平均值的可维护性只意味着,我们可以高效、有效地对系统进行修改来降低bug数量。

误解:可维护性是一个“非此即彼”的是非问题“我的团队可以不断地修复系统中的bug,因此证明系统是可维护的。”

这个区别很重要。“Maintain-Ability”字面上表示可以维护的能力。根据在ISO 25010中的定义,源代码的可维护性不是一个是非答案。相反,可维护性表示高效、有效地进行修改的程度。因此回答这个问题的正确答案,不应该是是否进行了修改(例如修复bug),而是修复这个bug到底用了多少时间(高效),以及这个bug是否被正确地修复了(有效)。

根据ISO 25010对可维护性的定义,我们可以说,一个软件系统永远不可能是完全可维护的,也不可能是完全不可维护的。在实践中,我们在SIG遇到过很多被认为是无法维护的系统。这些系统在修改的效率和有效性方面非常差,以至于系统的所有者承受不了维护它的成本。1.5 评价可维护性

现在我们知道了可维护性是其中一个质量特征。它表示能够维护一个系统的不同程度。但是什么是“易于维护”,什么又是“难于维护”呢?显然,对于一个复杂的系统,经验丰富的专家会比缺少经验的开发人员更容易维护它。通过基准测试,我们让SIG在软件行业中的指标来回答这个问题。如果一个系统的软件指标评分低于平均值,那么它就比平均值的系统更难以维护。基准测试每年会重新校准一次。因为整个软件行业如今都在学习如何更有效地编码(例如,借助于一些新技术的帮助),所以指标的平均值也呈递增趋势。前几年还处于平均值水平的系统,现在可能已经低于平均值了。基准测试就是这样来反映软件工程的发展趋势的。

SIG通过星级评分来区分基准测试中的系统,评分从1星(最难维护)到5星(最易维护)。这些星级的系统分布情况为5%-30%-30%-30%-5%。因此,基准测试中排名头5%的系统被评级为5星。在这些系统中,仍有可能存在违反原则的代码,但是比起其他星级的系统来说要少很多。

星级评分可以作为对实际系统可维护性的一个预示。SIG已经收集了大量的实验数据,证明4星系统在解决问题和实现需求方面的速度是2星系统的2倍。

基准测试中的系统均基于其指标的质量分析。图1-1展示了三个代码单元大小的质量分析(印刷版用户可以到这里 (https://github.com/oreillymedia/building_maintainable_software)查看本图及其他质量分析的全彩版本)。图1-1.三个质量分析的示例

图1-1中第一张饼图,是基于Jenkins的(http://jenkins-ci.org,一个流行的开源持续集成服务器)1.625版本源代码的代码单元质量分析。这个质量分析告诉我们Jenkins的代码库中,64%的方法代码不超过15行(符合原则)。它还显示出18%的方法代码在16到30行之间,12%的方法代码在31到60行之间。Jenkins的代码库不是很完美,它严重违背了代码单元大小的原则:6%的代码单元长度过长(超过60行代码)。

图1-1中的第二张饼图展示了一个2星系统的质量分析。注意,该代码库1/3的代码单元超过了60行。该系统的维护工作将会非常艰难。

最后,图1-1中的第三张饼图展示了一个4星项目代码体积的上限标准。与第一个饼图相比,你会发现Jenkins的代码单元体积也符合4星评级(虽然不能达到5星),因为其每个分类的代码百分比都低于4星的上限标准。

在每一章介绍原则的最后部分,我们都会展示对该原则质量分析的分类,它们也是我们在SIG用来对可维护性评分的标准。尤其是对于每个分类,我们会展示出4星或4星以上评分(占基准测试排名头35%)的上限值,以及每类代码的最大百分比。1.6 可维护性原则的概述

在后续章节中,我们会一一介绍所有的十个原则,不过这里先将它们列举出来,以便读者有一个整体印象。我们建议你从第2章开始依次阅读本书所有章节。

编写短小的代码单元(第2章)

短小的代码单元(方法和构造函数)更易于分析、测试和重用。

编写简单的代码单元(第3章)

拥有更少决策点的代码单元更易于分析和测试。

不写重复代码(第4章)

任何时候都应该避免源代码重复使用,因为修改时就需要对每处代码都进行修改。

重复代码也是产生回归bug(regression bug)的一个来源。

保持代码单元的接口简单(第5章)

含有更少参数的代码单元(方法和构造函数)更易于测试和重用。

分离模块之间的关注点(第6章)

松耦合的模块(类)更易于修改,也利于构建更加模块化的系统。

架构组件松耦合(第7章)

系统的顶层组件之间越是松耦合,越易于修改,也利于构建更加模块化的系统。

保持架构组件之间的平衡(第8章)

一个平衡度很好的架构拥有不多不少的组件、统一的代码规模以及最大程度的模块化,并通过隔离关注点使得修改变得很容易。

保持小规模代码库(第9章)

大型系统之所以难以维护,因为需要分析、修改并测试更多的代码。同样,大型系统中维护每一行代码的效率也比小型系统要低。

自动化开发部署和测试(第10章)

自动化测试(即测试不需要人工干预即可执行)可以得到对修改的有效性的即时反馈。手工测试无法形成规模。

编写简洁的代码(第11章)

代码库中存在越多的TODO、无用代码等遗留产物,新的团队成员就越难高效工作,从而影响维护工作的效率。[1]标准全名为《国际标准ISO/IEC 25010.系统和软件工程——系统和软件质量需求和评估(SQuaRE)——系统和软件质量模型》,第1版,2011年3月1日出版。第2章编写短小的代码单元

任何人都能编写计算机能够理解的代码,而好的程序员编写人能理解的代码。—— Martin Fowler

原则:

● 代码单元的长度应该限制在15行代码以内。

● 为此首先不要编写超过15行代码的单元,或者将长的单元分解成多个更短的单元,直到每个单元都不超过15行代码。

● 该原则能提高可维护性的原因在于,短小的代码单元易于理解、测试及重用。

代码单元是可独立维护和执行的最小代码集合。在Java中,代码单元指的就是方法或者构造函数。代码单元总是作为一个整体执行,你无法只执行其中的某几行。因此,代码单元是可以被重用及测试的最小代码片段。

以下面的代码片段为例,它会根据URL中的一个客户标识,生成该客户的所有银行账户及收支明细列表。该列表会以JSON字符串的形式返回,并包含总余额数。这段代码会使用一个校验码来验证银行账号的有效性,并跳过无效的账号。下面的“银行账号校验码11-Check”会解释我们如何使用校验码来验证账号。

银行账号校验码11-Check

11-check是用来验证荷兰9位银行账号的校验码。该校验码是银行账号从右至左9位数字的加权总和。最左侧的数字权重为9,最右侧的数字权重为1。当且仅当银行账号的校验码可以被11整除时,该银行账号才为有效账号。这个校验码可以用来检查银行账号中的数字是否存在错误。

举例说明,假设银行账号是12.34.56.789。我们将号码中的数字加粗显示,并从左至右开始相加:(1 × 9) + (2 × 8) + (3 × 7) + (4 × 6) + (5 × 5) + (6 × 4) + (7 × 3) + (8 × 2) + (9 × 1) = 165。由于165 = 15 × 11,所以这个号码是有效的。

你需要深入很多细节才能理解这段代码单元。首先,我们建立了一个JDBC连接。然后我们通过一个while循环来遍历所有从SQL查询到的结果记录,并在每次循环中通过一个for循环来验证账号。记住,其中还有关于JSON格式化和Java servlets的具体代码。

代码单元中间部分的for循环实现了校验码验证的过程。虽然从概念上讲,对这类代码的测试并不难,但说的容易做起来难,因为你只能通过调用doGet方法来进行测试。这就需要首先创建HttpServletRequest和HttpServletResponse对象。我们还需要搭建一个可用的数据库服务器,并得到一个它的测试账号。当调用doGet方法后,你将获得一个隐藏在HttpServletResponse对象中的JSON格式化字符串。为了测试总余额是否正确,你不得不把这个值从字符串中提取出来。

生成校验码的代码同样难以重用。执行校验码代码的唯一方式就是调用doGet方法。因此,所有其他想要重用校验码的代码,都必须使用一个SQL数据库才能提供进行检测的银行账号。

过长的代码单元难以测试、重用和理解。在刚才这个例子中,根本原因是doGet方法混合了(至少)四个职责:处理一个HTTP GET请求,访问数据库中的数据,执行某些业务逻辑,并将结果转换为所选的数据传输格式——JSON。把这些都放到一起,doGet方法就包含了39行代码(不含有空行或者只有注释的行)。你只需要减少提供的功能,就能拥有一个更短小的代码单元。2.1 动机

短小的代码单元的优势是易于测试、分析和重用。

短小的代码单元易于测试

代码单元封装了系统的应用程序逻辑,并且通常需要进行更多的测试来验证逻辑的正确性。这是因为Java编译器、编辑器或者IDE(集成开发环境,例如Eclipse)都无法自动识别出应用程序逻辑中的错误。拥有单一职责的代码更易于测试。一般来说,短小的代码单元可能只做一件事,而较长的代码单元会尝试做多件事,也因此拥有多项职责。由于单一职责的代码只实现了一个独立的任务,所以它更容易测试。这样以来,代码单元之间的测试可以被隔离起来(对于代码单元来说),并且每个测试都很简单。我们在第10章会更详细地介绍有关测试的内容。

短小的代码单元易于分析

相比起代码行数较多的代码单元,分析短小单元内部原理所花费的时间更少。可能这一点在你编写新代码时并不明显,但是当你修改已有代码时就完全不一样了。由于维护工作从项目启动之日就开始了,所以这不会是个例情况。

短小的代码单元易于重用

一个代码单元至少会被一个方法调用,否则就是一段无用的代码。在一个系统中,你可以在多个方法中重复使用同一个代码单元。相比于较长的单元,短小的代码单元更易于被重用。较长的代码单元试图提供各种具体实现细节,或者几个功能的固定组合,因此提供的功能范围也更加特定。正因为如此,我们很难确定这些功能是否适合于每个场景,所以也很难重用它们。相反,短小的代码单元更加通用,因此它们很可能能够满足你的需求,也因此更容易被重用。代码重用还可以帮助将总代码量维持在一个较低的水平(请参考第9章)。

我们这里讲的重用不是复制并粘贴某个代码单元。这种重用会造成代码重复,应该在任何时候都避免这样做(请参考第4章)。2.2 如何使用本原则

当你掌握正确的技巧后,写出符合该原则的代码并不困难,但是需要坚守纪律。本节会介绍两个我们认为尤其重要的技巧。当编写一个新的代码单元时,绝对不要让它超过15行代码。这意味着当你写到第15行代码时,你需要开始思考如何添加下一步的功能。它真的属于你当前编写的单元,还是应该有自己的代码单元?如果即便这样,单元中的代码仍然超过15行,那么你就需要想办法将它变得更短。

当编写一个新的代码单元时

假设你正在编写一个代表“JPacman(吃豆人游戏)”(本书中大量示例代码都来自这一项目。请参考下面的“关于JPacman”说明)中某一关的类。这个类为游戏界面上的按钮提供了公开的start和stop方法。这一关还维护了一个观察者类列表,当本关卡结束时需要通知这些观察者类。

关于JPacman

在本书的某些章节中,我们使用了一个简单的、类似吃豆人的游戏源代码(如图2-1所示)。这个代码库被它的作者称为“JPacman框架”。JPacman由代尔夫特理工大学(Delft University of Technology )的Arie van Deursen教授及其团队所创建,目的是为了讲授如何进行测试。该源代码已经在Apache 2.0许可下开源,你可以从GitHub上进行下载(http://bit.ly/jpacman)。图2-1.JPacman,一款类似于吃豆人的游戏

start方法的最初版本会检查游戏是否已经开始。如果已经开始,它会直接返回,否则它会更新私有成员变量inProgress,来维护其自身状态:

目前我们这个代码单元只包含了四行代码。现在我们可以为它添加一个单元测试。当你使用TDD(测试驱动开发)时,你应该已经有了个一个单元测试。我们会在第10章来讨论如何进行单元测试。

当向代码单元中添加新功能时

当你向系统添加新功能时,就会发现代码单元开始变得越来越长。这时就需要坚持原则,绝不能超过代码行数的限制。接下来,我们需要在start方法中添加一个功能,告诉本关卡所有观察者当前的游戏状态。以下代码表示,如果玩家死亡我们就告诉所有观察者本关结束,如果所有的豆都被吃光我们则告诉所有观察者游戏胜利:

添加通知观察者的代码之后,我们的代码单元增长到16行(一共18行,但有2行只是注释)。在测试新代码的功能后,你可能已经在想下一个要实现的功能了。别急,现在你首先需要重构代码,使其能够符合本章所规定的原则。

使用重构技巧来应用原则

本节会介绍两个能够应用该原则并缩短代码行数的重构技巧。

重构技巧:提取方法

本例中可以使用“提取方法”这个重构技巧。在如下代码片段中,我们将之前的代码提取成一个方法。

如你所见,之前增长到16行的代码单元(start方法)现在只有7行,低于15行的限制。我们添加了一个新的代码单元(updateObservers方法),它只有12行代码,也低于15行的限制。这样做还带来了另外一个好处,实际上,并不是只有在开始和恢复关卡时才需要更新观察者,而是每一次移动后(不管是玩家还是幽灵)都需要通知它们。现在实现这一点就变得很容易了,只需要在move方法(控制玩家或者幽灵的移动)中调用updateObservers方法即可。

如注释说明,新方法仍然有两个职责。我们可以进一步来重构这段代码,再提取出两个方法:

我们这里不再需要注释了,因为新的方法名足以很好地表达意思。使用短小的代码单元后,方法名取代了注释的作用,使得源代码一目了然。但是,这样做也是有代价的,代码总行数从16行增加到了25行。

编写可维护代码总是要在各个原则之间做出权衡。当我们将一个代码单元拆分成多个时,可能会增加总的代码行数。这看起来违背了保持小规模代码库的原则(参考第9章)。但是,你降低了这个代码单元的长度和复杂度,也降低了它测试和理解的难度,因此提高了可维护性。虽然保持小规模代码库是一个很好的实践,但是短小的代码单元远远超过了代码总行数增加所能带来的优点——尤其是本例中总行数只增加了很少一点。

同样,JPacman作者所做的选择,也恰恰证明了编写可维护代码总是在不断做出权衡。在GitHub的源代码中,作者只进行了一次“提取方法”的重构,使用了12行代码的updateObservers方法,而没有选择再进一步将updateObservers方法分成updateObserversPlayerDied和updateObserversPelletsEaten方法。

重构技巧:将方法替换为方法对象

在本例中,很容易使用“提取方法”的重构技巧。原因在于需要提取的代码没有使用任何局部变量,也没有任何返回值。有些时候,你可能会希望提取一个访问了局部变量的方法。我们虽然可以将局部变量作为参数传递给提取后的方法,但是,这可能会导致参数列表过长的问题(请参考第5章)。返回值的处理有时候会更加麻烦,因为在Java中一个方法只能有一个返回值。在这些情况下,你可以使用第二种重构技巧——“将方法替换为方法对象”。

JPacman包含了一段可以进行此类重构的代码。我们以BoardFactory类中的18行代码为例:

最内层for循环中的四行代码符合使用“提取方法”的要求。但是,这四行代码一共使用了六个局部变量——width、height、x、y、dir、square,以及一个伪变量——参数grid。如果你在这里使用“提取方法”,那么就需要向提取后的方法传递七个参数:

试读结束[说明:试读内容隐藏了图片]

下载完整电子书


相关推荐

最新文章


© 2020 txtepub下载