UML软件工程组织

让软件改进过程实现自动化
作者:Joe Walker,顾恺翻译 来 源: 赛迪网

目录

概要
你想在提高代码质量方面走多远?
你想在哪里止住?
FindBugs
PMD/CPD
Checkstyle
Jalopy/Jacobe
JDepend
JUnit
Eclipse
项目集成
故障解决

概要

几年前每个人都被Quality 软件的问题所困扰。Quality 中的Q 之所以大写就是为了强调单词的重要性。引入了消耗几个小时的程序和检查过程、以及大量的文书工作,就是为了设法减少软件的故障率。就是前不久,这种趋势已经演变为更为轻便的处理过程,该过程使用了轻便的软件和极端编程。这仍然是一个积极的转变因为保证软件品质仍然是重要的。有些分析家估计:有60%的软件故障可自动检测出来。那么我们能做的就是在昨天的质量装置恐龙和今天的困惑之间建一座桥梁,以便让问题解决的又快又代价低?作者Joe Walker 测试了不同类型的故障和七种工具以帮助你揭示他们的真面目。

有许多免费工具可以轻而易举的报告你的代码有什么故障,然后帮助你改进代码的质量。而且因为故障常常是主观方面的,大部分工具可以配置以适合你的本地代码类型。

但是一开始你就有减少故障的义务。这听起来好像是一个简单的问题——肯定对于每个人来说软件质量必须摆在第一位。但是,花费最少时间来修改代码往往看起来更加合理一些。任何保存寿命短的软件应该编辑的尽可能少,而且编写没有人阅读的单行文档也是毫无意义的。我在想有多少块文档花费在编写和检查方面的时间比某人阅读他们节省的时间多呢?因此,我们的目的就是把值得我们提高作品质量的工作和浪费资源的工作区别开来。

你想在提高代码质量方面走多远?

存在以下几种故障种类。我给出的工具在它们攻击的故障类型方面有区别。我根据他们的严重性确定了四种不同的故障类,从困扰用户的普通故障到阻止代码重新使用的有组织的故障都有:

·Current bugs: 当前故障阻止软件的正确运行。它们困扰用户并且可(或者应该)通过你的软件测试显示出来。当前故障与潜在故障截然不同。

·Latent bugs: 这些故障当前不产生问题。但是它们潜伏着,在以后才出现。阿里安5号火箭的失败就是由于一个浮点到整数转换错误引起的,当前面的火箭速度较慢时处于睡眠期;但是速度较快的阿里安 5却触发了这个问题。千年病毒也是一个潜伏故障的例子,只有当环境改变时它才浮出水面。使用传统的测试方法测试潜伏病毒要困难得多,并且找到他们就需要具有远见的人询问:“什么改变才能让故障从黑暗中走出来?

·Accident-waiting-to-happen bugs: 代码可以既简单又安全,或者他也可以是一团糟,等着绊倒那些对他们的编辑结果想得不多的可怜的程序员。一般而言,代码越老,就越有可能出现明显生锈的刀口;并且某些工业证据也表明:长时间没有重构的代码一般七年之后也就不再需要重新编写了。

·Poorly organized bugs: 组织不好的代码不容易重新使用并且常常包含对代码的过多依赖,其实有些是不必要的。因此它更有可能受其他代码位中的故障影响。编码标准致力于解决这种类型的问题,但是编码标准不能完全解决问题。他们不能停止问题如循环的依赖性,它使得你毫无机会来维持可重新使用的代码。

这些问题根据他们的严重性组织的比较粗糙。当前故障一般比潜伏故障更难以解决,而潜伏故障依次的比等待发生的故障或者组织故障更加令人担心。

下列代码包含了上述四种故障案例。认出它们后奖励一下你自己:

1: public static void main(String[] args)
2: {
3:     System.out.println(toUpperCase('q'));
4: }
5:
6: private static char toUpperCase(char main)
7: {
8:     return (char) (main - 31);
9: }


这里有一个当前故障因为行8应该读取:

8: return (char) (main - 32);


这种简单问题不解决,程序也不会运转。与其辛苦计算出q的大写字母为Q,还不如设法劝说我们相信答案就是R。

这里有一个潜伏故障因为迟早有人会设法得到ß 或者某数的大写版本,而不是任何明智的东西。

一个小小的等待发生事故的故障是由于行6中的某些傻变量名引起的。main 并没有真正的描述什么正在进行,而且还用方法名来迷惑我们。更好的代码如下:

6: private static char toUpperCase(char lower)


如果该代码描述了为什么她要从小写字母盘上的字符减去32,那么它会安全的多。

最后如果你将toUpperCase()方法变为公用的,并为方便其他类的使用将它变为实际类——甚至你可以使用下面的标准Sun微系统提供的方法,那么代码就组织得更好一些:

3: System.out.println(Character.toUpperCase('q'));


你想在哪里止住?

你什么时候停止调整和提高你的代码呢?这个问题没有一个真正的答案;这取决于你和你的项目。商业项目常常只关注于ROI (投资回报),因此它主要解决当前故障,并且通过实现编码标准解决这个问题很容易。

我们常常看到商业项目无意中采取了某些步骤来阻止潜伏故障的解决,它们实现了一个严格的变化控制程序,该程序将代码修改与产生的故障联系在一起来帮助测试。要开发队伍足够忠诚于软件品质,以致于关心某人的软件而且不设置障碍,这实在是太难了。

在更具有启迪性的开发环境里,大家都承认重构是创造高品质软件的至关重要的组成部分。重构就是逐步稳固你代码的组织以避免等待发生事故的过程。

但是即使是最关注品质的开发者也有他们的限制。你真的关心你注释中的标点符号吗?或者你真的关心方法是否是以最合理的顺序声明的吗?

Reasoning Inc.最近研究了不同商业和开源项目的品质,并据此发表大字标题。该公司针对不同的商业选择分析了Linux TCP/IP堆栈的源代码、Apache Web服务器、和Tomcat 。有趣的是, Reasoning公司还运行了下面描述的免费工具版本,然后手工分析结果以删除错误证据。他的结果出现了一些异常,但是它表明:开源关注于品质由此产生的回报比品质软件本身产生的回报明显要大得多。

我鉴别的七种工具可帮助显示故障,他们与Reasoning 公司的方法类似,并且在某些情况下也能帮助解决这些故障。我还将这些工具用于我最近的项目之一:DocTree。 DocTree 产生一个动态的装载Webpage,它包含到一万个类的Java文档的深度链接,这些类来自于许多重要的Java软件项目。DocTree不是一个很大的项目,但是它足够成为这七种工具的有用的测试案例。该资源可从java.net (见 Resources)处下载,所以你可以利用Ant看到这些工具是如何在同一个基础上运行。 有几个工具有绘图用户界面(GUIs)。但是,把它们当作你的构造过程(如,从一个Ant构造脚本)的一部分来运行可能更有意义。运行这些工具花的时间越少,继续提高你的软件就越有可能。

使用工具如CruiseControl、 Anthill是相当容易的,或者甚至是使用简单的cron 每晚寄出当作电子邮件给予你结果的构造,也是相当容易的。在建立了这样一个程序后,它就比GUI容易使用。

让我们看看这七种工具:

·FindBugs

·PMD/CPD

·Checkstyle

·Jalopy/Jacobe

·JDepend

·JUnit

·Eclipse

FindBugs

FindBugs主要解决当前和潜伏故障。它寻找普通问题和常常显示某处出错的小处代码。

FindBugs显示的一些故障案例包括:

·返回不稳定静态数据的方法

·Get方法不同步的地方显示Get方法;set方法同步的地方显示set方法

·Iterator类定义过程中出现的问题

·无意义的控制流程语句

·存在NullPointerException的地方;引用值与Null的多余比较的地方

FindBugs 处于非常活跃的开发中,并且你可以扩展它,通过插入机制将自定义检查包括进来。

FindBugs在DocTree找到了一个相当严重的潜伏故障:一个类定义了equals(),但没有定义hashcode()。这在当前配置中不会引起任何问题。但是,如果以后在DocTree迭代中,使用了Hashtable 或者HashMap中的类,就会出现假的结果。FindBugs也找到了一些次要的潜伏故障如异常上的信息流一直没有关闭直到出现碎片整理和其他次要问题。随着显示的问题的增长,你软件上的故障数就会减少。

FindBugs使用字节码分析来生成他的故障报告。

下面是创建FindBugs 报告的Ant 目标的DocTree项目片段:

<target name="web.findbugs" depends="jar">
<mkdir dir="${target.web}/findbugs"/>
<taskdef name="findbugs"
    classname="edu.umd.cs.findbugs.anttask.FindBugsTask">
  <classpath>
    <fileset dir="${support.tools}/findbugs066"
        includes="**/*.jar"/>
  </classpath>
</taskdef>
<findbugs home="${support.tools}/findbugs066"
    output="text"
    outputFile="${target.web}/findbugs/report.txt"
    reportLevel="low" sort="text">
  <class location="${target.jar}/${ant.project.name}.jar"/>
  <sourcePath path="${source.java}/main"/>
</findbugs>
</target>


PMD/CPD

PMD是非常可自定义的,并能找出这四种故障类的问题。但是,他最擅长于寻找组织故障和事故等待发生的故障。与FindBugs不同,PMD使用源代码分析,与编译器工作方式类似,除了它生成故障报告而不是字节码之外。PMD 是我为这篇文章测试的最可自定义的工具之一,主要是因为她有一个很酷的特征:允许你使用XPath引擎访问源代码解析器的输出。这个解析器产生一棵树,树中的节点属于你程序的一部分。所以一个类节点将包含用于每个变量和方法的字节点。方法节点也会包含用于该方法内的语句的字节点。如果他是一篇使用XPath的XML文档,PMD允许你访问这棵树。

所以一旦你理解了XPath和关于解析器如何从你的源代码中生成节点树的一些基本概念,你就能轻而易举的添加新的法则。例如:来自PMD文档的下列表达式演示了如何检查当语句是否使用了波形括号:

//WhileStatement[not(Statement/Block)]


它将寻找树(//)中任何地方没有包含Block(用于某些波形括号的解析器术语)的当语句。

PMD检测的问题案例有:

·空块

·为方法计算的过多的参数。

·过长的类或者过长的输入清单

·不能使用的域,方法和变量

·坏掉的双检查锁定

这些检查中有些是很主观的。个人最好封装的组件就是它过长的类。所以,感谢你能配置PMD来检查你认为重要的问题。当你第一次使用PMD来检查更大的项目时跳跃式检查尤为重要。如果你一开始运行的检查为空,PMD将生成七年的解决价值,所以从更加重要的检查开始很有好处。

CPD (复制/粘贴 检测器)是检测项目内的复制和粘贴代码的相关设备。大量的复制代码显示出:开发者不使用继承或者创建库就可以找出类似的代码和复制的任何东西、故障等等。

来自DocTree 项目的PMD Ant 目标看起来如下:

<target name="web.pmd">
  <taskdef name="pmd" classname="net.sourceforge.pmd.ant.PMDTask">
    <classpath>
      <fileset dir="${support.tools}/pmd121" includes="**/*.jar"/>
    </classpath>
  </taskdef>
  <mkdir dir="${target.web}/pmd"/>
  <pmd rulesetfiles="${basedir}/${support.tools}/pmd121/ruleset.xml"
      shortFilenames="true">
    <formatter type="html" toFile="${target.web}/pmd/index.html"/>
    <fileset dir="${source.java}/main" includes="**/*.java"/>
  </pmd>
</target>


Checkstyle

Checkstyle 与PMD相当相似,尽管它在寻找组织故障方面更为出色。与 PMD一样,它使用编译器前端来生成他的故障报告。PMD提供更广泛的范围内的更多检查。但是,Checkstyle是更可配置的。虽然某些检查会交迭,但Checkstyle的检查中有许多不在PMD内,反之亦然。对大部分来说只要拥有他们中的一个便以足够。

Checkstyle 可寻找:

·不能使用的或者多余的输入

·空格更好的地方不使用跳格符,反之亦然

·不遵循命名标准的变量、方法或者类

·过分复杂的分配或者返回语句

来自DocTree的Checkstyle Ant目标如下:

<target name="web.checkstyle">
  <mkdir dir="${target.temp}/checkstyle"/>
  <mkdir dir="${target.web}/checkstyle"/>
  <taskdef resource="checkstyletask.properties">
    <classpath>
      <fileset dir="${support.tools}/checkstyle31"
          includes="**/*.jar"/>
    </classpath>
  </taskdef>
  <copy file="${support.tools}/checkstyle31/custom.xml"
      overwrite="true"
      tofile="${target.temp}/checkstyle/custom.xml">
    <filterset>
      <filter token="source.java"
          value="${basedir}/${source.java}"/>
      <filter token="target.checkstyle"
          value="${basedir}/${target.temp}/checkstyle"/>
    </filterset>
  </copy>
  <checkstyle config="${target.temp}/checkstyle/custom.xml"
      failOnViolation="false">
    <fileset dir="${source.java}/main"
        includes="**/*.java"/>
    <formatter type="plain"/>
    <formatter type="xml"
        toFile="${target.temp}/checkstyle/checkstyle_errors.xml"/>
  </checkstyle>
  <style
      basedir="${target.temp}/checkstyle"
      destdir="${target.web}/checkstyle"
      includes="checkstyle_errors.xml"
      style="${support.tools}/checkstyle31/checkstyle-noframes.xsl"/>
</target>


Jalopy/Jacobe

Jalopy和Jacobe是帮助解决事故等待发生故障和组织故障的代码格式程序。Jalopy是开源,但是他没有出现在活跃的开发中。Jacobe 可免费使用,但不是开源。

记住使用任何代码格式程序可严重的毁坏你使用源代码控制系统揭示某些代码后的历史纪录的功能。当引入故障时使用源代码控制系统分析发生的变化是很普通的。但是,源代码格式程序的普遍使用不必作任何实际修改就能够改变你项目中的源代码行的重要率。如果你需要重新格式化你项目的源代码,你应该尽可能早的完成,尽管接受它你不能够追踪重新格式化之前的修改,或者当该代码由于某些其他原因需要编辑的时候,逐步重新格式化。

JDepend

JDepend在你的源代码周围生成了无数个度量,包括来自于主序列的传入和传出的耦合和分配。不幸的是,据我的经验,这些度量常常是毫无意义的,除非你已经理解了你正在测试的代码;并且如果你理解了,他也只告诉你你已经知道的东西,但是,JDepend对于识别循环依赖性特别有用。循环依赖性是组织故障,它可引起维护问题和产生不能重新使用的代码。

例如:如果你有三个软件包A, B和C,并且 A依赖于B, B依赖于C, 而且C 依赖于 A,那么如果不都得到你就不可能重新使用他们。如果这些软件包中任何一个使用了GUI代码,那么当你不注意的下载了GUI代码到你的企业JavaBeans (EJB)或者 servlet中,即使你感兴趣的代码不是GUI代码,那么你就会想为什么你的服务器代码快要死了。

来自于DocTree 的JDepend Ant 目标看起来如下:

<target name="web.jdepend" depends="jar">
  <mkdir dir="${target.temp}/jdepend"/>
  <mkdir dir="${target.web}/jdepend"/>
  <jdepend format="xml" fork="yes"
      outputfile="${target.temp}/jdepend/jdepend-report.xml">
    <sourcespath>
      <pathelement path="${source.java}/main"/>
    </sourcespath>
    <classpath>
      <dirset dir="${target.classes}"/>
      <fileset dir="${source.jar}" includes="**/*.jar"/>
      <fileset dir="${support.tools}/jdepend24" includes="**/*.jar"/>
    </classpath>
  </jdepend>
  <style
      basedir="${target.temp}/jdepend"
      destdir="${target.web}/jdepend"
      includes="jdepend-report.xml"
      style="${support.tools}/jdepend26/jdepend.xsl"/>
</target>


JUnit

JUnit是大家熟知的工具,它可帮助你为你的软件编写自动的单元测试;我这里不想详细讨论它为什么那么有名。想了解更多信息请看Resources 。我想说在本文提到的所有工具中,JUnit 最有可能帮助你找出你代码中的故障,尽管要付出代价。JUnit需要你花费相当多的时间为你的代码编写测试案例,但是很有可能这个时间花得很值。

Eclipse

IDEs 总是一个使人激动的话题。当你考虑免费工具时,Eclipse有几种可集成到编译器上的检测工具如 FindBugs ,并且正在开发中的第3 版本包含了更多这种检测。Eclipse 海配送一个代码格式程序,他虽然不如Jalopy 或者Jacobe高级,但是更加可行因为它可以装在编辑器内。

Eclipse 编译器在当前和即将上市的版本中执行的高级检测包括:

·意外的boolean赋值 (e.g., if (a = b))

·不能到达的捕捉块

·隐藏字段或者变量的局部变量声明

·不能使用的变量、输入、字段和给出的声明

项目集成

将你的项目与jar和配置文件集成而且不使你的项目过大或者迷惑 jar文件,这是一个挑战。如果你的代码仓库包含许多项目,你恰好想将这些工具用在这些项目上,那么你将面临某些仓库严重膨胀的危险。

我建议你建立一个包含你想使用的工具的独立项目,然后从你的Ant 构造文件中引用他们。这样就不出现在想要使用工具的队组成员中分配工具的问题。对于那些不需要使用工具的成员来说他们是可选的,并且他们不会占据仓库太多的空间。

故障解决

自动品质检测可减少你软件中故障数,从而增强它的品质和可持续性。在你安装了工具之后,你可以用非常小的代价——时间和金钱都是——来维护质量控制。在实现本文列出的任何策略之前,明白的定义你对于不同等级的品质有多看重以及消除某种故障有多值得,这是很重要的。这种分析对于不同的项目将产生不同的结果。好消息是调试工具将你从沉重的基础工作中解救出来,并且允许你自主地安排他们的使用和你管理软件品质的方法。【original text
 

版权所有:UML软件工程组织