使用 IBM Rational Software Analyzer 进行静态分析,第 2 部分: 创建规则和规则过滤器扩展 Java 代码评审

本文是关于 IBM Rational Software Analyzer 及其在 IBM Rational Application Developer 和 IBM Rational Software Architect 中相关性能的系列文章中的第 2 部分。它将介绍如何将 Java 代码评审功能用于分析您的代码,并且带领您逐步创建规则和过滤器。

IBM® Rational® Software Analyzer 促进并且简化了自动代码质量提高的过程。它被设计为一个 API 和用户接口,用来向其他的 Rational 产品(例如:Rational Application Developer 和 Rational Software Architect)创建和集成静态分析工具。它已经发展为一个日常软件开发流程中完善的自动代码评审和结构化分析工具。

Steve Gutz, 软件经理, IBM

Steve Gutz 是负责 IBM Rational 代码分析工具的开发经理。除了构架并管理 IBM 的商业产品以外,Steve 过去还致力于 Eclipse Test and Performance Tools project(TPTP),专注于基本的代码审查工具的实现和集成中的改进。在 2002 年加入 IBM 渥太华实验室之前,他在许多公共和私有的公司中担任高级管理和执行职位,包括两个他自己的成功创业。他还写过两本书和许多文章,并且经常在会议上演讲。



2008 年 10 月 31 日

在本系列文章的第 1 部分中,我们从高层分析了静态分析,并且介绍了在 IBM® Rational® Software Analyzer (同样,在 IBM Rational Software Architect 和 IBM Rational Application Developer)中用于分析的公用用户接口,以及通过选择提供者、类别和规则创建一个分析配置的过程。我们还讨论了显示静态分析结果的视图,以及内嵌在软件中的基本报告功能。

在本部分和后面的第三部分中,您将学习如何编写规则来扩展当前的 Java 代码评审规则集。Rational Software Analyzer 分析架构(rsar)包括若干个扩展点以及一个完整的 API,用于添加新的分析格式和新的规则。但是这样做更主要的目的是,它还提供一个完整的功能分析提供者,用来对 Java™ 源代码执行自动地代码评审。合在一起,本文以及下一篇文章对架构的充分描述使您能够理解并且编写您所需要的任何规则。

本文首先描述了构成一个规则的组成部分。然后我们介绍了类别和规则扩展点,并且非常快速地浏览了 Java IDE。最后,我们带领您通过执行简单的规则创建了您自己的规则。

Java 工具概述

正如您有可能已经知道的,Eclipse 开发平台为 Java 源代码提供了一个完整的剖析器,它被内部地用于公用函数,例如:显示 Java 源代码概要视图,找到依赖关系,以及句法突出等。幸运的是,Eclipse 还将这一用于平台开发的 IDE 暴露给我们使用。JDT 剖析了一个 Java 源文件,并且生成了一个可以被 API (应用程序编程接口)的其他区域查询的树状结构表示。用于 Java 文件的 Abstract Syntax Tree(AST)树的最顶层部分,大致是以下述方式被组织的:

CompilationUnit:
    [ PackageDeclaration ]
        { ImportDeclaration }
        { TypeDeclaration | EnumDeclaration | AnnotationTypeDeclaration | ; }

CompilationUnit 是根类型,理论上说,它本身就是 Java 文件,并且是一个用于 TypeDeclarations(可以是类也可以是接口)的容器。在编写规则时,您还会经常用到 MethodInvocationsMethodDeclarations 和许多其他的节点类型。所有节点的完整描述在 Eclipse 帮助系统中都有完整的描述。在文档中,有超过 100 个 Application Server Toolkit(AST)节点类型,但是不用害怕,因为 Javadoc 信息是很好编写和完成的。

要在 AST 剖析树上执行查询,Java IDE 使用了一个访问者模式。这提供了很好的性能,但是它也可能会对编写一个访问者类以获得有效地和有用的结果造成威胁。分析 API 通过提供一个公用的访问者类和抽象查询机制,消除了和访问 AST 节点相关联的大部分挑战。这一 API 将在本文的剩余部分中被描述。现在,您所需要的就是对 AST 树结构有一个基本的理解,Eclipse Java IDE Help 就可以提供这一点。请花一些时间研读这一文档,因为您最终将会为您的高级规则编写 AST 代码。

Java 代码评审扩展点

我们不会在此处太过深入的描述 Analyzer 分析架构中所提供的所有扩展点,这是因为它们实在是太多了。相反,在本系列的下一篇文章中,我们将只关注您在创建新的类别和规则时所需要用到的那一部分,例如:提供者和结果扩展点。

Code Review for Java 提供者在 Rational Software Analyzer、Rational Software Architect、Rational Application Developer 以及您将在这些工具中所部署的任何规则中都是可用的。如果您打开分析配置对话框并且展开这个提供者,那么您将看到它拥有若干个类别。其中每一个都同 plugin.xml 文件中的一个扩展相关,例如您将在 Analyzer 中的 com.ibm.rsaz.analysis.codereview.java.rules 插件程序中所找到的那个。举例来说,考虑来自于 plugin.xml 源文件中的这个类别:

<analysisCategory            
	class="com.ibm.rsaz.analysis.core.category.DefaultAnalysisCategory"
id="codereview.java.category.awt"
label="%label.category.j2sebestpractices.awt" 
category="codereview.java.j2sebestpractices"
	help="com.ibm.rsaz.analysis.codereview.java.rules.awt"/>

class 属性定义了一个管理类别的类。即使一个自定义类别类能够被编写,但是这个代码仍然使用由 API 所提供的默认类别,它在大多数情况下是有效的。自定义类别必须处理一个类别的共有特性,例如管理一组子类、标签、图标名称等。这已经超出了本文所涉及的范围,现在,您只需要接受这一事实:默认类别能够为任何一个类别提供它们所需要的全部服务。在大多数情况下,您将为一个类别使用被提供的类。

一个类别还拥有一个唯一的 ID,它被其他类别和规则用来决定控制。自然地,一个类别也必须拥有一个标签和一个描述,当类别被显示时它们也将被呈献给用户。

在这个例子中,扩展还包括一个 category 属性,它指出这个类别被嵌入到另一个其唯一 ID 为 codereview.java.j2sebestpractives 的类别中。如果您希望创建一个顶层类别,那么请将 category 属性置换为 provider=provider.idcodereview.java.analysisProvider 是 Java 代码评审提供者的唯一标识符:

<analysisCategory
class="com.ibm.rsaz.analysis.core.category.DefaultAnalysisCategory"
	id="codereview.java.j2sebestpractices"
	label="%label.category.j2sebestpractices"
	provider="codereview.java.analysisProvider"
	help="com.ibm.rsaz.analysis.codereview.java.rules.j2sebestpractices"
</analysisCategory>

规则扩展工作所采用的方式是相似的,但是它还包括更多的一些细节。下面是一个规则的实例扩展:

<analysisRule
category="codereview.java.category.awt"
class="com.ibm.rsaz.analysis.codereview.java.internal.rules.awt.RuleAwtPeer"
id="codereview.java.rules.awt.RuleAwtPeer"
label="%label.relrule.j2sebestpractices.awt.awtpeer"
severity="2"
help="com.ibm.rsaz.analysis.codereview.java.rules.awtpeer">
</analysisRule>

您应当识别出规则扩展的第一个部分,这是由于它几乎等同于前面所显示的类别扩展。在这个例子中,class 属性包含一个用于规则的完整的类路径。

该规则还包含额外的信息。Help 标签提供了一个 ID 用于一个相关的 help.xml 文件中的帮助上下文。这允许一个规则来向用户提供良好格式化的实例和解决方案信息。我们将在下一篇文章中讨论 Help 和其他高级规则概念(请参见顶部的链接)。

一个代码评审规则的剖析

正如我们在前一小节中所讨论的那样,一个规则定义的前一半是一个 Eclipse 扩展。后一半是一个 Java 类,它使用规则 API 或者部分 JDT 来查询一个类的内容,并且为每一个符合规则标准的条目生成结果。规则类是十分简单的,典型情况下只包含一个方法。鉴于规则通常扩展一个由 Rational Software Analyzer 分析架构所提供的默认规则类,所以大多数必须的功能已经被实现了。列表 1 中所示的代码是一个完整的规则,它使用 Java "==" 操作符比较两个对象,为任何一行代码创建结果:

列表 1. 比较两个对象的规则
public class RuleComparisonReferenceEquality
	extends AbstractAnalysisRule
{
	private static final String[] OPERATORS = { "==", "!=" };
	private static final int[] OPERANDS = { ASTModifier.TYPE_PRIMITIVE,
ASTModifier.TYPE_NULLTYPE }; 

	// Rule filters
	private static IRuleFilter[] EXPFILTERS = {
		new OperatorRuleFilter( OPERATORS, true ),
		new LeftOperandRuleFilter( OPERANDS, false ),
		new RightOperandRuleFilter( OPERANDS, false )
	};

	/**
	 * Analyze this rule
	 * 
	 * @param history	A reference to the history record for this analysis
	 * 
	 * @throws CoreException
	 */
	public void analyze(final AnalysisHistory history, 
					final CodeReviewResource resource )
	{
List list = resource.getTypedNodeList( resource.getResourceCompUnit(),
ASTNode.INFIX_EXPRESSION );
      ASTHelper.satisfy( list, EXPFILTERS );

   for(Iterator it=list.iterator(); it.hasNext(); ) {
      InfixExpression tempInf = (InfixExpression)it.next();
      ITypeBinding leftBinding = tempInf.getLeftOperand().resolveTypeBinding();
      ITypeBinding rightBinding = tempInf.getRightOperand().resolveTypeBinding();
      if( (leftBinding != null && leftBinding.isPrimitive()) || 
      (rightBinding != null && rightBinding.isPrimitive()) ) {
       it.remove();
      }
      }
		
	resource.generateResultsForASTNodes( this, history.getHistoryId(), list );
	}
}

规则类从分析架构中扩展 com.ibm.rsaz.analysis.codereview.java.AbstractCodeReviewRule,并且由于这个类是抽象的,所以派生的规则必须实现 ##analyze() 方法。这个方法拥有一个参数(history),它为即将被执行的规则指出分析会话。每次用户执行一个分析时,一个新的 history 就被注册来收集结果。除了结果之外,history 元素还保持同被选中的提供者、类别和规则的联系。

传递到 analyze() 方法中的参数包含了关于扫描历史的信息,在那里任何结果都将被保存。AbstractCodeReviewRule 类,它是 Java 代码评审提供者的一部分,保持同编辑单元的联系,并且管理查询 AST 树。

由于这个例子中的规则是关于 infix 表达式的(表达式拥有左右两边,例如一个比较),所以它使用代码评审资源来获得收集单元中的一组这样的表达式:

List list = resource.getTypedNodeList( resource.getResourceCompUnit(), 
					ASTNode.INFIX_EXPRESSION );

接下来,该规则使用 ASTHelper.satisfy() 方法从满足给定标准的列表中筛选表达式。标准被定义在类的顶部的静态程序块中:

	// Rule filters
	private static IRuleFilter[] EXPFILTERS = {
		new OperatorRuleFilter( OPERATORS, true ),
		new LeftOperandRuleFilter( OPERANDS, false ),
		new RightOperandRuleFilter( OPERANDS, false )
	};

EXPFILTERS 数组包含三个规则过滤器类型。第一个过滤器负责移除任何操作符既不是 "==" 也不是 "!=" 的表达。第二个过滤器负责移除任何表达式左侧为一个基本类型或 null 型的列表条目。最后一个过滤器对表达式右侧重复这一操作。satisfy() 方法接收输入列表和过滤器列表,并且在一步中执行所有的筛选操作。在这行代码被执行之后,列表将只包含那些左右两侧都是对象,并且操作符或者为 "==" 或者为 "!=" 的表达式。因此,列表中所剩余的每一个条目都应当被报告到结果列表之中。通过在规则的最后一行中添加下列语句完成这一操作:

resource.generateResultsForASTNodes( this, history.getHistoryId(), list );

这很简单,不是么?用于编写一个 Java 规则的 API 虽然只包含一些方法,但是它的功能却十分强大。

从头开始编写一个规则

下面,我们制作一个更富挑战性的代码评审规则。我们前面所介绍的规则是最简单的一种了,它只查询 AST 树中一种类型的节点,并且盲目地报告所有结果。我们来考虑一下编写一个具有新类别和新规则的规则插件程序所必需的所有步骤。

创建一个插件程序

要创建一个规则,首先您需要创建一个插件程序:

  1. 使用 Eclipse 的 New Project 向导在您的工作区中创建一个新的插件程序工程,命名为 #com.ibm.rsar.codereview.java.examples。
图 1. New Plug-in Project 向导
对话框
  1. 在向导的第二幅屏幕上(请参见图 2 中所示),去掉“This plug-in makes contributions to the UI.”前面的对勾。
  2. 点击 Finish 按钮,创建插件程序。
图 2. 插件程序内容视图
对话框

为了成功地建造规则,您需要向插件程序中添加依赖关系:

  1. 打开插件程序清单文件,选择 Dependencies 标签。
  2. 添加插件程序:使用 Add 按钮,选择 org.eclipse.core.runtime 添加以下三个插件程序,如图 3 中所示:
    • com.ibm.rsaz.analysis.core,其中包含基础的静态分析架构 API 和扩展点。
    • com.ibm.rsaz.analysis.codereview.java,其中包含用于编写 Java 代码评审规则的 API。
    • org.eclipse.jdt.core,其中包含 JDT API,其中又包含 AST 分析和查询架构。
图 3. 添加插件程序
工作空间

典型情况下,当您创建新的规则时,您需要全部这三个插件程序依赖关系。

创建一个规则类

第一个规则就是实用性。在这一小节中,您将创建一个检测 Java 代码中 finalize() 方法的规则,它只负责调用 super.finalize()。如果一个 finalize() 方法只调用它的父类,那么该方法就应当被移除。例如:

public class Example 
{
    private void method() {
        // Do something
    }

    // Don't do this
    protected  void finalize()  throws Throwable {
    	super.finalize();
    }
}

那么,要检测这类问题,您需要做哪些工作呢?我们来考虑以下两个步骤:

  • 第一步。您首先需要建造一组方法声明,将它们命名为 finalize。进一步地,那些方法声明应当没有参数。
  • 第二步。在您拥有一组方法声明之后,您需要查看它们内部是否只有一条调用父类方法的语句 super.finalize()

为方法声明创建一个规则

您可以通过下述步骤建造一个规则:

  1. 首先,在示例插件程序中的 com.ibm.rsar.codereview.java.examples 包中创建一个新的类。调用这个类 RuleFinalizerSuper
  2. 确保这个规则类扩展 com.ibm.rsaz.analysis.codereview.java.AbstractCodeReviewRule。
import com.ibm.rsaz.analysis.codereview.java.AbstractCodeReviewRule;
import com.ibm.rsaz.analysis.codereview.java.CodeReviewResource;
import com.ibm.rsaz.analysis.core.history.AnalysisHistory;

public class RuleFinalizerSuper extends AbstractCodeReviewRule
{	
}
  1. 您知道您将需要一个 #analyze 方法,所以将下列代码添加到类中:
	/**
	 * Analyze this rule
	 * 
	 * @param history	A reference to the history record
	 */
	public void analyze(AnalysisHistory history,  CodeReviewResource resource ) {
	}

按照这一算法中的步骤一,您需要从被分析的类中收集一组方法声明。

  1. 将下列代码添加到 analyze() 方法中,完成这一操作:
List list = resource.getTypedNodeList( resource.getResourceCompUnit(), 
					ASTNode.INFIX_EXPRESSION );

这一行访问 AST 树,起步于编辑单元(最顶层的节点),并且查询所有的方法声明。请注意:getTypedNodeList() 方法拥有若干个信号。默认情况下,它将进行递归调用。因此,您也可以在任何一个内部类中发现方法声明。

要完成这一算法中的需求,您需要将方法生命中您不需要的部分筛选掉。正如我们在第一个规则中所介绍过的那样,您可以创建一组规则过滤器来完成这一操作。

  1. 向规则类的顶部添加下列代码:
	// Rule filters
	private static final IRuleFilter[] MIFILTERS =  {
	};
  1. 首先,您需要筛选掉那些不是 finalize() 声明的方法。在空白处添加如下代码:
new MethodNameRuleFilter( "finalize", true ),

#MethodNameRuleFilter 类告诉筛选机制您对通过方法名称进行筛选感兴趣。#true 布尔型指出 finalize() 方法应当被保留在列表中。如果这个域是 #false 的话,那么筛选将移除 finalize() 方法并且保留其他的内容。

您还需要确保您只管理值为真的 finalize() 方法。假定开发人员有可能创建一个具有返回值的 finalize() 方法,那么您就需要让您的规则忽略掉它们。

  1. 您可以使用 ReturnTypeRuleFilter 类来添加筛选以消除那些不是 void 返回类型的 finalize() 方法:
new ReturnTypeRuleFilter( "void", true ),

为了进一步减小筛选的列表大小,您只需要让 finalize() 方法不拥有参数。拥有参数的方法不是类终结者(实际上,一个规则应当将这些报告为非法的)。

  1. 将下列代码添加到静态块中:
new ParameterCountRuleFilter( 0, true )

在这种情况下,您告诉过滤器代码要注意参数的数量,并且它应当为 0

最终的过滤器数组如列表 2 中所示。

列表 2. 最终的过滤器数组
	// Rule filters
	private static final IRuleFilter[] MIFILTERS =  {
		new MethodNameRuleFilter( "finalize", true ), 
		new ReturnTypeRuleFilter( "void", true ),
		new ParameterCountRuleFilter( 0, true )
	};

在这一算法中完成步骤一所需要的最后一步操作,就是执行实际的筛选。

  1. analyze() 方法的最后,添加下列代码:
ASTHelper.satisfy( list, MIFILTERS );

确保只有一行是父类方法调用

我们已经完成了规则中的简单部分;现在,我们开始更为复杂的部分。

您需要检查方法内部的代码行,并且确保其中只有一行代码。请记住:您拥有一组方法声明。您需要核查他们之中的每一个方法。在列表中应当只有一个条目,这是因为 Java 不能够以同一个信号支持一个以上的方法声明。

  1. 为了安全起见,通过将下列代码添加到 analyze() 方法的最后,创建一组迭代器:
	for( Iterator it = list.iterator(); it.hasNext(); ) {
		MethodDeclaration md = (MethodDeclaration)it.next();
	}

每一个方法声明都包含一个程序体。当您拥有一个方法声明时,您可以使用 getBody() 方法来获得一个 Block (这是一个 AST 节点)。该程序体中包含了一组语句。此处,您只关心那些只有一条语句的方法声明。

  1. 将下列代码添加到迭代器循环的底部:
Block block = md.getBody(); if( md.getBody().statements().size() == 1 ) { }

所有保留部分用来检查语句,看它是否调用了 super.finalize()。这并不是很明显。

  1. if 语句中,添加下列代码:
List superList = 
        resource.getTypedNodeList( block, ASTNode.SUPER_METHOD_INVOCATION );
	if( superList.size() > 0 ) {
	}

这里需要做些解释。一个方法声明的程序体是由语句组成的,语句可以是任何类型的行为(例如:for 语句、if 语句、or 表达式)。您需要查看程序体中是否有父类方法调用,它是一个特定类型的语句。您通过在具有一个 SUPER_METHOD_INVOCATION 类型的程序块中获得一组 AST 节点完成这一操作。您已经知道程序体中只有一条语句,所以这个类表将拥有 0 个或者 1 个元素。如果列表拥有 1 个或多个元素的话,那么您就可以假定该程序体只有一个到 super.finalize() 的调用。

最后一步操作就是在分析结果视图中生成一个结果。代码评审引擎为这一操作提供了一个简单的 API。

  1. 在空白的 if 语句中,添加下列代码:
resource.generateResultsForASTNode( this, history.getHistoryId(), md.getName() );

这一方法调用的前两个参数总是相同的。它们被用于识别生成结果的规则,以及这些结果将被放置在的历史收集。第三个参数指出 AST 节点将会被突出。在这种情况下,对于每一个匹配的方法声明来说(finalize() 方法),被创建的结果的名称都将被突出。

完整的规则如列表 3 中所示。

列表 3. 完整的规则
package com.ibm.rsar.codereview.java.examples;

import java.util.Iterator;
import java.util.List;

import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.Block;
import org.eclipse.jdt.core.dom.MethodDeclaration;

import com.ibm.rsaz.analysis.codereview.java.AbstractCodeReviewRule;
import com.ibm.rsaz.analysis.codereview.java.CodeReviewResource;
import com.ibm.rsaz.analysis.codereview.java.IRuleFilter;
import com.ibm.rsaz.analysis.codereview.java.ast.ASTHelper;
import com.ibm.rsaz.analysis.codereview.java.rulefilter.MethodNameRuleFilter;
import com.ibm.rsaz.analysis.codereview.java.rulefilter.ParameterCountRuleFilter;
import com.ibm.rsaz.analysis.codereview.java.rulefilter.ReturnTypeRuleFilter;
import com.ibm.rsaz.analysis.core.history.AnalysisHistory;

public class RuleFinalizerSuper extends AbstractCodeReviewRule
{
	// Rule filters
	private static final IRuleFilter[] MIFILTERS =  {
		new MethodNameRuleFilter( "finalize", true ), 
		new ReturnTypeRuleFilter( "void", true ),
		new ParameterCountRuleFilter( 0, true )
	};

	/**
	 * Analyze this rule
	 * 
	 * @param history	A reference to the history record for this analysis
	 */
	public void analyze
        ( final AnalysisHistory history, final CodeReviewResource resource ) {
	  List list = 
          resource.getTypedNodeList( resource.getResourceCompUnit(), 
	   ASTNode.METHOD_DECLARATION );
	   ASTHelper.satisfy( list, MIFILTERS );
	
	for( Iterator it = list.iterator(); it.hasNext(); ) {
	    MethodDeclaration md = (MethodDeclaration)it.next();
	    Block block = md.getBody();

	if( md.getBody().statements().size() == 1 ) {
        List superList = 
        resource.getTypedNodeList( block, ASTNode.SUPER_METHOD_INVOCATION );

     if( superList.size() > 0 ) {
     resource.generateResultsForASTNode( this, history.getHistoryId(), md.getName() );
		}
	}
	}
	}
}

创建扩展点

在规则类被完成之后,它需要通过在分析架构中的所提供的扩展点被描述出来,从而使得代码评审引擎能够将其发现。举例来说,您将首先创建您自己的类别,然后将规则同这个类别相关联。

创建一个新的类别是一项相对简单的任务:

  1. 打开在本小节的第 1 部分中被创建的示例插件程序。
  2. 选择编辑器中的 Extensions 标签,并且点击 Add 按钮。
  3. 从列表中,选择 com.ibm.rsaz.analysis.core.analysisCategory 扩展点,如图 4 中所示。
图 4. Extension Point Selection 视图
对话框
  1. 在 Extension 标签下面,您现在应当能够看到一个新的扩展点入口。右键单击它,并且添加一个新的 analysisCategory。请将图 5 中所描述的详细信息填入到这些扩展区域中:
    • id*: analysis.codereview.java.examples
    • label*: Examples
    • category: codereview.java.j2sebestpractices
    • class: com.ibm.rsaz.analysis.core.categoryDefaultAnalysisCategory (使用 Browse 按钮进行选择)
图 5. Extension Element Details 视图
对话框

这一类别拥有一个父类别(codereview.java.j2sebestpractices),它将 Java™ 2 Platform, Special Edition Best Practices 类别放在用户接口之中。这个类 DefaultAnalysisCategory 可以被用来提供一个预先定义的类别,它为您将创建的大部分类别提供所有必需的功能。

将下列详细结果插入到 plugin.xml 文件之中:

<extension
         point="com.ibm.rsaz.analysis.core.analysisCategory">
      <analysisCategory
            category="codereview.java.j2sebestpractices"
            class="com.ibm.rsaz.analysis.core.category.DefaultAnalysisCategory"
            id="analysis.codereview.java.examples"
            label="Examples">
      </analysisCategory>
   </extension>

接下来,您需要将您的规则添加到新类别之中。

  1. 向插件程序中添加一个新的扩展,就如同我们在类别时所做的那样。这次,请选择 com.ibm.rsaz.analysis.core.analysisRule 扩展点。
  2. 在 Extensions 标签中,右键单击规则扩展,并且添加一个新的 analysisRule
  3. 如图 6 中所示填入规则详细信息:
    • id*: codereview.java.example.finalSuper
    • label*: Avoid finalize() methods that call only super
    • category: analysis.codereview.java.examples
    • class: com.ibm.rsar.codereview.java.examples.RuleFinalizerSuper (使用 Browse 按钮进行选择)
    • severity: 2 (使用下拉菜单进行选择)
图 6. analysisRule 的特性
对话框
  1. 输入一个标签和描述(当用户看到您的规则时,它将出现在用户接口中)。

注释:
这些字符串应当为任何一个生产规则进行局部化,但是现在您可以忽略这一要求。

类域中包含您先前所创建的规则类的名称。类别域中包含用于您的 Examples 类列的标识符字符串。在这些细节被创建之后,plugin.xml 文件将包含下列内容:

<extension
         point="com.ibm.rsaz.analysis.core.analysisRule">
      <analysisRule
            category="com.ibm.rca.codereview.java.examples.RuleFinalizeSuper"
            id="codereview.java.example.finalSuper"
            label="Avoid finalize() methods that call only super"
            severity="2">
      </analysisRule>
   </extension>

运行该规则

  1. 从 Eclipse 视图中,选择 Run 选项,并且创建一个新的 Eclipse Application。
  2. 在配置面板中,点击 Run 按钮。它调用一个新的运行时间工作台来测试您的规则。
  3. 当工作台出现时,将 Java 源代码导入其中,从而对代码进行评审。尝试通过一个只调用 super.finalize()finalize()方法,以便您可以测试该规则。
  4. 通过工作台中源代码,从 Eclipse Run 菜单中选择 Analysis 选项。
  5. 在出现的对话框中,创建一个新的分析配置,并且切换到 Domains 标签。
  6. 展开 Java Code Review 分支,直到看到 Example 子类别(请参见图 7 中所示)。
图 7. 展开 Java Code Review 以显示 Example
工作空间
  1. 通过选中的新规则,点击 Analyze 开启代码评审进程。

如果工作台中的源代码拥有一个满足您的规则的 finalize() 方法,那么分析结果视图将包含一个如图 8 中所示的结果。

图 8. Analysis 结果视图
对话框

规则过滤器评审

在本小节中,我们检查了某些规则,以及它们如何使用规则过滤器。API 提供规则过滤器来支持您希望编写的大部分规则。下面是一组由 Rational Software Analyzer 所提供的规则过滤器(针对 Analysis API 的 Javadoc 将提供关于每一个规则过滤器使用的更多信息):

  • ArgumentTypeRuleFilter
  • ConstructorRuleFilter
  • DeclaringClassRuleFilter
  • EnclosingNodeRuleFilter
  • ExceptionCountRuleFilter
  • ExpressionRuleFilter
  • ForInitializerCountRuleFilter
  • ForUpdateCountRuleFilter
  • FragmentCountRuleFilter
  • IfElseStatementCountRuleFilter
  • IfElseStatementRuleFilter
  • IfThenStatementCountRuleFilter
  • IfThenStatementRuleFilter
  • ImplementedInterfaceRuleFilter
  • LeftOperandRuleFilter
  • MethodNameRuleFilter
  • ModifierRuleFilter
  • OperatorRuleFilter
  • ParameterCountRuleFilter
  • ParameterTypeRuleFilter
  • ReturnTypeRuleFilter
  • RightOperandRuleFilter
  • SuperClassRuleFilter
  • TypeRuleFilter

还有用于逻辑操作(LogicalOrFilterLogicalAndFilter)的两个规则过滤器。您可以使用这些来避免规则中的重复(必须以一种独特的方式比较一组相似的过滤器),例如:

	private static final IRuleFilter[] MIFILTERS =  {
		new MethodNameRuleFilter( "testMethod", true ), 
		new ReturnTypeRuleFilter( "void", true ),
		new LogicalOrFilter( 
		new ParameterCountRuleFilter( 0, true ),
		new ParameterCountRuleFilter( 1, true )
	};

这个代码过滤器方法被命名为 testMethod(),它返回一个 void,并且包含 0 或者 1 作为参数。

也有可能出现这种情况:您所需要的规则过滤器不可用。但是,您也可以毫不费力地编写您自己的规则过滤器。API 提供一个接口(IRuleFilter),您可以将它用于您的过滤器系统之中。列表 4 是一个在 Rational Software Analyzer 中由 Java 代码评审提供者所提供的规则过滤器的例子:

列表 4. Rational Software Analyzer 中包含的一个规则过滤器的实例
public class SuperClassRuleFilter extends AbstractRuleFilter {
	private static final String SATISFIES_SUPER_CLASS = "satisfiesSuperClass";
	private String superclassName;
	
	/**
	 * Constructor
	 * 
	 * @param superclassName
	 *            The super class name by which to filter
	 * @param inclusive
	 *            True if filtering will include only nodes that match the
	 *            filter criteria, false to exclude matching nodes
	 */
	public SuperClassRuleFilter( String superclassName, boolean inclusive ) {
		super( inclusive );
		
		this.superclassName = superclassName;
	}
	
	/**
	 * Determine if the node is satisfied by the specified filter rule
	 * 
	 * @param node	The ASTNode to test
	 * @return	true if the node satisifes the filtering rule
	 */
	public boolean satisfies( ASTNode node ) {
		try {			
			if (node.getNodeType() == ASTNode.TYPE_DECLARATION) {
				// Get the super class type. It is null if this type
				// declaration does not extend any type
				Type superClassType = 
                              ((TypeDeclaration)node).getSuperclassType();
			if( superClassType != null ) {
			      return superClassType.resolveBinding().getQualifiedName()
							.equals( superclassName );
				}
			} else {
				Log.severe (Messages.bind(Messages.RULE_FILTER_ERROR_,
						new Object[]{ SATISFIES_SUPER_CLASS,
						node.getClass().getName()}));
			}
		} catch (NullPointerException e) {
			// Do nothing
		}

		return false;		
	}
}

正如您所看到的:

  • 您需要创建一个构造器来接收生成节点所需要的值。在我们的例子中,构造器接收了一个表示超类名称的字符串。

    提示:
    总是接收布尔型参数,用以支持内含和排除筛选。
  • 您还必须执行 satisfy() 方法,它将接收测试下面的 AST 节点。
  • 最后,由于规则过滤器必须是可靠的,所以您需要确保所有的异常都在 satisfy() 中被捕获。在我们的例子中,代码捕获 NullPointerException

当您的规则过滤器被创建之后,您就可以通过将它放置到一个 IRuleFilter 数组中,并且在您的规则中调用 ASTHelper.satisfy(),从而像使用其他任何一个预定义的过滤器一样使用它。

图 9. Rules 标签显示规则集
工作空间

总结和展望

本指南介绍了分析 API 的内部工作原理,它适用于为 Java 编写新的代码评审规则。本文讨论了在 plugin.xml 文件中创建种类和规则规范的核心概念,然后描述了如何编写一个类来创建一个基础规则。下一篇文章将介绍规则严肃性、自定义规则参数、以及创建规则模板的相关概念。

参考资料

学习

获得产品和技术

讨论

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Rational
ArticleID=349394
ArticleTitle=使用 IBM Rational Software Analyzer 进行静态分析,第 2 部分: 创建规则和规则过滤器扩展 Java 代码评审
publish-date=10312008