在 Java 中使用 ASTParser 实现 Metric Suite

Implementing a Metric Suite using ASTParser in Java

背景 - 下面的问题

我正开始在 Java 中为 Java 实施一个指标套件,但我担心我的方法不合适。

目前我正在为目录中的每个文件使用 JDT ASTParser。这开始很好,我能够为每个 class 收集关于每个方法的行数和平均行数的信息。这是通过 MethodVisitor class 完成的,它扩展了 ASTVisitor 并包含一个方法 visit(MethodDeclaration node)。

我现在正在尝试计算每种方法的圈复杂度。我已经拆分出方法主体并有一个包含访问(IfStatement 节点)和访问(ReturnStatement 节点)的 ComplexityVisitor。

使用这个结构我知道代码中有一个 if 语句,但我不确定如何知道 "if else" 有多少层。我能找到的唯一有用的方法是 node.getElseStatement() 但是这个 returns 基本上(或者在我看来)是一个字符串,因此必须使用正则表达式来知道路径的数量声明可以采取。

所以我的问题是:

在使用 eclipses ASTParser 时,有没有办法推断出 "if - else if - else" 语句中有多少个级别?

我是应该寻找更简洁的解决方案,例如 IJavaElement 还是自己解析代码,将关键字放入列表中,然后循环遍历它们。

一些示例代码 - 大部分处于测试阶段

public class Test {

    private static List<ClassInfo> klasses = new ArrayList<ClassInfo>();

    // Called for every file where str is what the file contains
    public static void parse(String str) {
        ASTParser parser = ASTParser.newParser(AST.JLS3);
        parser.setSource(str.toCharArray());
        parser.setKind(ASTParser.K_COMPILATION_UNIT);

        final CompilationUnit cu = (CompilationUnit) parser.createAST(null);     

        ClassVisitor cv = new ClassVisitor();
        cu.accept(cv);

        MethodVisitor methodsVisitor = new MethodVisitor(cu);
        cu.accept(methodsVisitor);


        ClassInfo klass = new ClassInfo(cv.getClassName(),
                cu.getLineNumber(cu.getLength() - 1),
                methodsVisitor.getNumberOfMethods(),
                methodsVisitor.getAverageLinesPerMethod(),
                methodsVisitor.getMethods());

        for(int i = 0; i < klass.methods.size(); i++){
            parser.setSource(klass.methods.get(i).body.toCharArray());
            CyclomaticComplexityVisitor ccv = new CyclomaticComplexityVisitor();

            cu.accept(ccv);
        }
        klasses.add(klass);
    }

-

public class MethodVisitor extends ASTVisitor {

    private CompilationUnit cu;
    private int numberOfMethods;
    private int lineCount;

    private List<MethodInfo> methods = new ArrayList<MethodInfo>();


    public MethodVisitor(CompilationUnit cu){
        this.cu = cu;
    }

    public boolean visit(MethodDeclaration node){
        int startPos = cu.getLineNumber(node.getStartPosition());
        int endPos = cu.getLineNumber(node.getStartPosition() + node.getLength());

        lineCount += (endPos - startPos);
        numberOfMethods++;

        String methodBody = node.getBody().toString();
        MethodInfo m = new MethodInfo(node.getName().getIdentifier(),
                                    (endPos - startPos),
                                    node.getReturnType2());
        m.body = methodBody;
        methods.add(m); 

        return true;
    }

-

public class CyclomaticComplexityVisitor extends ASTVisitor {

    private int complexityScore = 0;
    private int edges = 0;
    private int nodes = 0;
    private int exitPoints = 1;
    private boolean firstReturn = true;

    public boolean visit(IfStatement node){
        System.out.println("THERE WAS AN IF");
        String statement = node.toString();
        System.out.println(statement);

        return true;
    }

    public boolean visit(ReturnStatement node){
        if (firstReturn) {
            firstReturn = false;
        } else {
            exitPoints++;
        }
        return true;
    }

干杯

我不确定这是否会回答您的问题,但是对于计算 McCabe 的圈复杂度 (McCC) 指标,您不需要关心 if-else-if 嵌套级别。您只需要计算 "branching" 条指令的数量并在末尾加 1 即可。参见User's Guide of our SourceMeter工具中的定义:

McCabe 的圈复杂度 (McCC) 方法:方法的复杂度,表示为其中独立控制流路径的数量。它表示源代码中可能的执行路径数量的下限,同时它是实现完整分支测试覆盖所需的最小测试用例数量的上限。 metric 的值计算为以下指令的数量加 1:if、for、foreach、while、do-while、case 标签(属于 switch 指令)、catch、条件语句 (?:)。而且, 逻辑“与”(&&) 和逻辑“或”(||) 表达式也会将值加 1,因为它们的短路求值会根据第一个操作数导致分支。以下指令不包括:else、switch、default label(属于switch指令)、try、finally。