IBM®
跳转到主要内容
    中国 [选择]    使用条款
 
 
Select a scope: Search for:    
    首页    产品    服务与解决方案     支持与下载    个性化服务    
跳转到主要内容

developerWorks 中国  >  Java technology  >

诊断 Java 代码: 在规范钢丝上行走

为什么良好定义的规范对于软件系统是关键性的

developerWorks
文档选项

未显示需要 JavaScript 的文档选项


级别: 初级

Eric E. Allen (eallen@cs.rice.edu), 博士研究生, Java 编程语言小组,Rice 大学

2002 年 2 月 04 日

程序规范是任何软件项目中既关键又费时的一个部分。在 诊断 Java 代码的这一部分中,作者 Eric Allen将讨论为什么良好定义的规范对您的代码是必需的,并研究了传统软件工程方法以及极端编程方法,还比较了两者的优缺点。Eric还讨论了您 希望定义程序规范的方法,并解释了原因。读完本文之后,您将理解如何衡量维护精确的设计规范的成本和收益。请通过单击本文顶部或底部的 讨论来参与本文的 论坛,与作者和其他读者分享您的想法。

要构建可靠的软件,程序规范很关键。没有良好定义的规范,很难诊断软件系统的异常行为。但是很多软件系统的程序规范定义得很差劲。而且更糟的,是许多软件系统根本就没有规范。

直观的看,程序规范是对程序行为的一种描述。它可以采取许多形式,但无论采取何种形式,都有一条主线贯穿所有实例:必须有某种类型的系统规范,因为您得依靠它来判断系统是否运转正常。

规范可以形式化也可以松散地定义,这取决于开发中系统的稳定性和危险程度,还与开发完毕后修改系统的容易程度有关。

我们将通过讨论规范为什么重要、为什么会经常被忽略以及如何改善这种情况来开始我们的这次“旅程”。

平衡精度的成本和收益

在微处理器设计世界中,系统部署在各种规模的应用上,从个人计算机到担负重任的医疗和军事系统。在这个领域中有一个普遍的、牢不可破的规则:在部署之后修改芯片设计的成本是极其昂贵的。

因此,通常会将微处理器的规范形式化,这也就不足为奇了。一个正式的规范有巨大的好处,因为能够自动地解释和分析它。就微处理器而言,设计的许多方面可以自动地被验证无误。

软件类比:编程语言

在软件世界中,在部署和危险程度方面,最类似于微处理器的事物是编程语言。一种普及的编程语言被用来编写无数的程序,它们被用于各种危险程度级别不同的系统中。

和芯片一样,在投入使用之后修改语言设计的成本是相当昂贵的,因为所有现有的程序都必须修改和重新编译。因此,与其它软件系统相比,编程语言的规范通常是相当正式的。

对于语法,这种形式化尤其重要。事实上,所有现代编程语言都拥有正式定义的语法。大多数解析器是通过使用自动的解析器生成器构造的,生成器读入这些文法并产生完整的解析器作为输出。

不幸的是,语言语义并不倾向于作这样严格的规定。这并非因为不能做到这样严格。

象 ML 这样语言拥有形式化的语义,并因此证明了许多关于它们的法则,用于验证其正确性的某些方面(譬如,它们的类型系统的完善)。但是象 ML 这样的语言是特例。我们可以确定造成这种情况的两个原因。

首先,因为证明关于编程语言规范的特性实际上比硬件设计更难以驾驭,所以不太需要正式的规范。相反,许多语言是用叙述性文字规定的。这些文字规范对于大多数实际使用语言的人(譬如编译器作者)来说已经足够了。实际上,编译器作者通常着迷于不太正式的规范,因为它给了他们更多优化程序的空间。另外,有些时候语言的使用者是程序员,他们中的大多数很欣赏非正式规范,因为这样他们可以轻松地理解规范。

第二个理由是,许多语言是由单独的开发人员作为“业余爱好”开发的,而他们往往并不专长于编程语言领域。遗憾的是,这些开发人员常常并不了解为规定编程语言语义而开发的形式体系。

模棱两可的成本的示例

然而,语言规范中的模棱两可或不一致造成的成本可能是相当昂贵的,会导致可移植性、可靠性的降低,甚至会造成安全性漏洞。通过研究一些当前广泛使用的语言,可以发现它们的规范中相对的精确程度是如何影响它们的。

C++ 语言的规范有许多模棱两可的地方,甚至在语法级别也有。此外,规范的许多部分是变成依赖于实现的。结果是:C++ 程序通常很难在多种平台上按预期的那样运行。

Python 语言规范遗留了许多依赖于实现或未定义的细节。结果,诸如 Jython 和 CPython 之类的实现,在提供与另一方相同的行为这个方面,面临着巨大难题。如果不是因为 Python 语言相对比较简单(并非贬义),这一问题还会更糟。

尽管 Java 语言没有正式的规范(类似于 ML 那样的),但是在精确的非正式规范的开发上投入了很多努力。该语言通常编译成由 JVM 解释的字节码,而 JVM 本身具有良好的规范(尽管通过正式分析在该规范中发现了一些模棱两可的地方)。此外,Java API 都作为 JVM 的一部分规定。这使得 Java 代码具有空前程度的可移植性。

我们从中可以得出结论,一份尽可能精确的语言规范会非常有用。但即使在编程语言世界中,规范中的问题也是最昂贵的,精确的规范很少,部分原因在于预先制作一份精确规范很昂贵。

许多公司发现成本更低的做法是先交付产品,以后(或者很可能永远也不会)再充实规范细节。诚然,对于生命周期较短以及部署范围较窄的应用程序,预先定义精确规范确实是太昂贵了。有时可能竞争对手早已交付了系统,而开发团队还未正式确定其系统的规范。

此外,大型规范很少在用户需求更改时更新,并且因此被忽略了。但是如果预先定义规范太昂贵,开发团队应该采取什么方法来规定他们的软件呢?

在回答这个问题之前,让我们考虑一下一个常用的,但也确实是最糟糕的方法吧。





回页首


为什么实现不是规范

与上面的方法相反,许多软件直接实现,而没有可用的规范。如果(或当)软件完成,实现便作为规范提供。

换言之,他们将软件展示的任何行为都说成是规定的行为。

有人可能会争辩说这是一件好事,因为这样防止了开发人员浪费时间去做出某种必定会更改的正式计划。但是,尽管项目规范的确会经常更改,而实现在许多方面会导致一个糟糕的规范。下列是一些原因:

  • 实现包含任意选项。
  • 本质上,每个行为都是有意的,所以不会有错误。

实现中的许多选项是任意的。因而,任何将来在其它平台上实现它们的尝试都只能从现有实现入手。开发人员将不得不花费很大精力,从众多实现细节中确定实现需要的行为。如果在更高级的抽象中规定了这些行为,确定起来就容易得多了。

还有,如果将一个实现完全照搬为它自己的规范,那么就不可能确定该实现的中的任何行为是错误。有多少次我们看到软件公司将其软件中有害的或讨厌的行为标识为“正常”,而只是因为这是由于未开发规范所带来的一个无法预料的结果?





回页首


划算的规范

对于初始开发人员,实现无法有效充当规范的第三个显而易见的原因是这样的实现还不存在。这些开发人员必须依赖于他们正在创建的系统的某种行为模型,因此该模型的原始资料将充当软件的规范。

关于开发人员应该使用哪种具有合理成本的规范,这一点给了我们一些启示。但是实际上,为了确定如何实现一个功能部件,一个开发人员必须有一个关于该功能部件是什么的思想模型,而不必拥有关于整个应用程序的思想模型。

换言之,规范可以分段开发。这不仅使它们更容易驾驭,还允许在客户需求更改时更有效地修改它们。

在极端编程(XP)中,系统所需要的功能是在使用 素材的过程中逐步确定的。每个素材简要描述系统行为的一个方面。例如,这里有素材,可将它包含进一个 Java IDE 的规范中:

当用户将文字输入编辑器时,出现的 Java 关键字将自动显示为蓝色。字符串和字符文字显示为绿色,注解显示为红色。

信不信由您,对于 Java 语言,这个素材实际上很难正确实现,部分原因在于块注释的某些特殊特性。根据开发团队的速度,将该素材划分成两个或更多小素材或许是可取的。

但请注意,这个素材很小,而且是用简单、清晰的语言写出的。当需要时,这样很容易拆分,并可以防止规范各部分之间的耦合。

此外,因为素材很小,所以它们可以更新,添加新素材而不必对规范作大的修改。出于这个原因,素材特别适合于那些在实现时经常要更改最终产品需求的业界设置。





回页首


不,单元测试也不是规范

在结束关于规范的这一主题之前,我们最好讨论一下最后一个关于单元测试的问题。单元测试对于 XP 非常重要。程序员在编写实现的任何部分之前就开始编写它们,并继续为功能的每个新的方面编写更多的单元测试。

覆盖软件项目的一套严格的单元测试提供两个巨大的好处:

  • 他们可以是文档化的。
  • 它们加速了重构过程。

单元测试与静态类型类似,是文档的 可执行形式。因为单元测试理想地覆盖了实现的所有方面,也因为它们用简单的方式调用了功能以确定其是否正常运行,正在加入项目的或开始维护某些代码的程序员只要通读单元测试,就能够容易地确定各种功能组件的用途。

许多人在第一次听说单元测试可以是文档这一概念时,会表示怀疑:“怎么能用编写程序的同一语言来编写它的文档呢?”但是这个问题忽略了代码文档。

不应该用代码来说明代码的行为。代码本身已经说明了这个问题。相反,文档应该说明代码块 为什么要做它所做的事。任何读代码的人应该早已熟悉用来编写这些代码的语言 ― 如果他们不熟悉,则任何语言的文档也未必能帮得上忙。

但代码块是如何与程序其它部分交互的,这一点并不总是很清晰,这就是文档所要说明的。因为代码的读者熟悉(或者应该熟悉)编写它的语言,所以用与代码相同的语言来说明代码背后的意图就很有效了。

还有,单元测试加速了重构的过程。当一套单元测试可以在任何时间在代码上运行以确定是否有功能损坏时,程序员可以比在其它任何情况下更有信心重构代码。所引入的绝大多数错误可以被立即检测出来。

由于这些原因,想要编写健壮的软件,单元测试是一个有力的手段。实际上,因为单元测试充当了一种形式的文档,而且可以自动强制执行(通过将它们包含在构建过程中),所以将测试本身当作系统规范使用的建议听上去很有诱惑力。

用测试形成规范的 一部分是合理的,因为我们要让所有有效的实现通过全部系统级别的测试。但是用单元测试形成整个规范有一些严重的弊端。

首先,系统的测试集必然是不完整的。无论我们为一个系统指定多少测试,总会有一些系统输入和状态是我们所没有提供的。我们可以将测试解释为指定它们“最合理”的范围,但这种范围是模棱两可的。

此外,单元测试本质上是强制执行特定实现的特性。系统的实现方法有多种而不仅是一种。因此,将单元测试用作规范和将特定实现用作规范具有许多相同的缺点。

因此,我们最好将单元测试看作是规范的补充,而不是整个规范。





回页首


收益可以超过成本

我希望能够帮您形成这样的结论,在设计软件系统时需要拥有尽可能精确的规范,并能够认识到在形成这个定义时,极端编程模型规范的模块化构造能够平衡成本和收益。

还有,我希望自己已经充分阐述了将实现误作为规范的缺陷,并指出了过分依赖单元测试,希望它完成整个规范定义的侥幸想法所存在的问题。



参考资料

  • 您可以参阅本文在 developerWorks 全球站点上的 英文原文.

  • 请单击本文顶部或底部的 讨论参与本文的 论坛

  • 请访问极端编程( Extreme Programming)网站,获得对 XP 幕后思想的总体认识。


  • Roy Miller 和 Chris Collins 在他们的文章“ XP distilled”(developerWorks,2001 年 3 月)中给出了关于极端编程的令人信服的解释。


  • 软件迁移是一种将用某种语言或平台开发的应用程序转移到其它语言或平台上的过程。“Migrating to IBM WebSphere Application Server”是一篇关于解决更改的设计的文章,它由几个部分组成。第一部分,“ Designing Software for Change”(WebSphere 开发者园地,2001 年 7 月),讨论了用来减少代码迁移成本的一些软件开发的最佳实践。第二部分,“ Stages of Migration”(WebSphere 开发者园地,2001 年 10 月),提供了着手进行迁移的总体计划。


  • The Go-ForIt Chronicles: Memoirs of eXtreme DragonSlayers”讲述了关于 DragonSlayers 的故事 ― 一组 IBM 顶级技术顾问开始屠杀强壮的巨龙 ― 一个成熟并具备许多花哨功能的 Web 应用程序。他们设计、开发和测试了一个合并了最新技术并使用了 IBM 工具和产品的应用程序。


  • 请下载 Jython 的副本,它是一种动态、面向对象的 Python 实现,用 Java 语言编写,并无缝地集成到 Java 语言和平台中的。


  • The Definition of Standard ML ― Revised (MIT Press,1997 年 5 月)提供了 Standard ML 的正式定义,这是一种设计用于大型项目的多用途的编程语言。


  • 请在此得到 Extended ML 规范的版本,PDF 格式;Extended ML 是 Standard ML 程序的规范和正式开发的框架。


  • JUnit官方网站为用 Java 语言实现单元测试的开发人员提供了大量资料。


  • 请阅读 Eric Allen 诊断 Java 代码 专栏中的所有文章。


  • 请在 developerWorks Java 技术专区查找更多的 Java 参考资料,其中包括许多关于极端编程的文章。


关于作者

Eric Allen 从 Cornell 大学获得计算机科学和数学的学士学位,并且是 Rice 大学 Java 编程语言小组的博士研究生。在返回 Rice 完成学位之前,Eric 担任 Cycorp,Inc. 的首席 Java 软件开发人员。他还在 JavaWorld 主持 Java 初学者讨论论坛。他的研究包括 Java 语言源代码和字节码级别上的语义模型和静态分析工具的开发。Eric 还帮助开发 Rice 的 NextGen 编程语言编译器,这是一种支持泛运行时类型的 Java 语言扩展。可通过 eallen@cs.rice.edu与 Eric 联系。




对本文的评价










回页首


IBM 公司保留在 developerWorks 网站上发表的内容的著作权。未经IBM公司或原始作者的书面明确许可,请勿转载。如果您希望转载,请通过 提交转载请求表单 联系我们的编辑团队。
    关于 IBM 隐私条约 联系 IBM 使用条款