假如你需求一个自动化的东西协助你或许你的团队发现代码中的缺点,在提高代码质量一起减少人工Code Review的本钱,那这篇文章十分的合适你。本文环绕SpotBugs与Gradle集成,将相关装备和运用进行了详细介绍,并供给了各种能够为你的项目定制化装备的手段。来历和出处都已在文中要害处以超链接给出,尽情享受吧。

SpotBugs是什么?

SpotBugs是一个开源的Java静态剖析东西,旨在协助开发人员检测代码中的潜在缺点和漏洞。SpotBugs能够经过扫描Java字节码来发现潜在的问题,例如空指针引证、类型转化错误、未运用的变量等等。它还能够检测代码中的潜在安全漏洞,例如SQL注入、XSS攻击等。SpotBugs供给了一个用户友爱的GUI和命令行接口,能够轻松地与各种构建东西和IDE集成,例如Ant、Maven、Gradle、Eclipse和IntelliJ IDEA。SpotBugs还支持插件和自定义规矩,使得开发人员能够依据项目的特定需求和标准对其进行定制化装备。更多详细信息能够查看SpotBugs官方文档。

SpotBugs是FindBugs的一个分支,它在FindBugs的基础上进行了改善和升级,它运用了更先进的算法和技能来提高剖析的准确性和效率。SpotBugs还添加了对新的Java版其他支持,例如Java 8和Java 11。SpotBugs还供给了更好的用户界面和命令行界面,并支持更多的构建东西和IDE集成。

FindBugs也是一款十分流行的Java静态剖析东西,可是现已中止更新。停更的原因似乎是项目具有者不肯继续在这个项目上花时间,代码贡献者由于没有权限也无法继续迭代和发布新的版别,在FindBugs GitHub主页的README文件中供给了两个文档项目状况 2016-November和项目状况 2017-September,里边叙述了代码贡献者当时的一些担忧和无法。

SpotBugs与Gradle集成

开端之前先简略介绍下本文中将会说到的几个依靠以及它们之间的联系,以便于理解后续内容:

  • com.github.spotbugs.snom:spotbugs-gradle-plugin是一个Gradle插件,它将SpotBugs集成到 Gradle构建中,生成SpotBugsTask并能够经过装备特点来扩展。
  • com.github.spotbugs:spotbugs这个依靠基本包括了一切SpotBugs检测器,这些检测器完结了Bug descriptions中说到的相关Bug检测项的逻辑。
  • com.h3xstream.findsecbugs:findsecbugs-plugincom.github.spotbugs:spotbugs的基础上添加了安全相关的查看器也便是Bug descriptions#Security中描绘的相关查看项。
  • com.github.spotbugs:spotbugs-annotations是Spotbugs的一个扩展/辅助东西,开发者运用这里边注解能够让Spotbugs依照咱们的意图来查看代码。

spotbugs-gradle-plugin

spotbugs-gradle-plugin是一个Gradle插件,它将SpotBugs集成到Gradle构建中,生成SpotBugsTask并供给相关装备来扩展,运转SpotBugsTask就能够履行查看,生成陈述等。

默许情况下,这个插件里边现已包括了一个spotbugs,所以应用了这个插件后一般无需在其他添加com.github.spotbugs:spotbugs。从SpotBugs version mapping中能够知道spotbugs-gradle-plugin不同版别中默许包括的spotbugs版别之间的联系,比方:com.github.spotbugs.snom:spotbugs-gradle-plugin:5.0.13包括了com.github.spotbugs:spotbugs:4.7.3

SpotBugs Gradle插件要求Gradle版别为7.0或许更高,JDK版别为1.8或许更高。

这个插件会为每个sourceSets生成SpotBugsTask。例如项目中有两个sourceSets main和test,插件将生成两个SpotBugsTask(spotbugsMain和spotbugsTest)。

假如不想自动生成SpotBugsTask,能够运用SpotBugs Base插件从头开端装备,参阅com.github.spotbugs-base。

生成的SpotBugsTask履行时需求编译后的.class文件作为输入,因此它们将在Java编译后运转。SpotBugs Gradle添加了check相关的任务依靠,所以简略的运转./gradlew check也会履行生成的SpotBugsTask。

参阅com.github.spotbugs在build.gradle文件中添加以下内容来引进SpotBugs Gradle插件:

buildscript {
 repositories {
  maven {
   url "https://plugins.gradle.org/m2/"
   }
  }
 dependencies {
  classpath "com.github.spotbugs.snom:spotbugs-gradle-plugin:5.0.13"
  }
}
​
apply plugin: "com.github.spotbugs"

插件里边供给了许多可选的特点来装备或许扩展插件的行为。

下面展现了在build.gradle文件添加spotbugs{}相关特点的比方,详细信息能够参阅SpotBugsExtension,这里边有每个特点作用、可指定的值和其他相关信息。spotbugs{}中指定的大部分特点将作为生成的SpotBugsTask装备的默许值,意味着能够经过spotbugs{}给一切SpotBugsTask装备特点。

spotbugs {
  ignoreFailures = false
  showStackTraces = true
  showProgress = false
  reportLevel = 'default'
  effort = 'default'
  visitors = [ 'FindSqlInjection', 'SwitchFallthrough' ]
  omitVisitors = [ 'FindNonShortCircuit' ]
  reportsDir = file("$buildDir/reports/spotbugs")
  includeFilter = file('spotbugs-include.xml')
  excludeFilter = file('spotbugs-exclude.xml')
  onlyAnalyze = ['com.foobar.MyClass', 'com.foobar.mypkg.*']
  projectName = name
  release = version
  extraArgs = [ '-nested:false' ]
  jvmArgs = [ '-Duser.language=ja' ]
  maxHeapSize = '512m'
}

除了上述方法外,还可直接装备SpotBugsTask,以设置某个任务特定的特点,比方下面为名为spotbugsMain的任务独自进行了设置,跟spotbugs{}同名的特点将被掩盖,详细信息参阅SpotBugsTask。

spotbugsMain {
  sourceDirs = sourceSets.main.allSource.srcDirs
  classDirs = sourceSets.main.output
  auxClassPaths = sourceSets.main.compileClasspath
    reports {
    html {
      required = true
      outputLocation = file("$buildDir/reports/spotbugs/main/spotbugs.html")
      stylesheet = 'fancy-hist.xsl'
     }
   }
​
  ignoreFailures = false
  showStackTraces = true
  showProgress = false
  reportLevel = 'default'
  effort = 'default'
  visitors = [ 'FindSqlInjection', 'SwitchFallthrough' ]
  omitVisitors = [ 'FindNonShortCircuit' ]
  reportsDir = file("$buildDir/reports/spotbugs")
  includeFilter = file('spotbugs-include.xml')
  excludeFilter = file('spotbugs-exclude.xml')
  baselineFile = file('spotbugs-baseline.xml')
  onlyAnalyze = ['com.foobar.MyClass', 'com.foobar.mypkg.*']
  projectName = name
  release = version
  extraArgs = [ '-nested:false' ]
  jvmArgs = [ '-Duser.language=ja' ]
  maxHeapSize = '512m'
}

后边将会有一段描绘比较常用或许重要的特点。

com.github.spotbugs:spotbugs

由于spotbugs-gradle-plugin中现已包括了spotbugs,所以一般情况下不需求再独自引进这个依靠。不过或许由于默许带的版别有Bug、需求跟IDEA的SpotBugs插件运用相同的版别,又或许新版其他检测器新加了实用的检测项等原因咱们会需求独自指定版别,下面供给了两种方法完结。

在装备spotbugs-gradle-plugin的时候经过toolVersion指定spotbugs版别。

spotbugs {
 toolVersion = '4.7.3'
}

dependencies添加依靠并指定依靠版别。

dependencies {
  spotbugs 'com.github.spotbugs:spotbugs:4.7.3'
}

findsecbugs-plugin

findsecbugs-plugin是Spotbugs的安全漏洞检测插件。它在spotbugs的基础上添加了自己的规矩集,专注于检测安全相关的问题,例如密码泄漏、SQL 注入、XSS 等,也便是Bug descriptions#Security中描绘的相关查看项。

dependencies {
  spotbugsPlugins 'com.h3xstream.findsecbugs:findsecbugs-plugin:1.12.0'
}

spotbugs-annotations

spotbugs-annotations是Spotbugs的一个扩展/辅助东西,里边供给了许多注解,咱们能够在编码时将它们加在被检测代码的相应方位(如特点、办法、形参、本地变量),让Spotbugs依照咱们的意图来查看代码,以便SpotBugs能够更恰当地宣布正告。在Annotations中列举了一切注解,需求注意里边有许多现已标记为Deprecated标明现已弃用了。

比方下面这段代码,仅仅输出value到终端,即便test()传入null也不会导致空指针。Spotbugs本来不会宣布正告,可是由于咱们在办法上加了注解edu.umd.cs.findbugs.annotations.NonNull,Spotbugs会依照咱们的意图进行入参不允许为null的校验,然后宣布正告。

import edu.umd.cs.findbugs.annotations.NonNull;
​
public class SpotbugsAnnotationsSample {
​
  public static void main(String[] args) {
    test(null);
   }
  
  public static void test(@NonNull Integer value) {
    // 输出到终端
    System.out.println(value);
   }
}

下面是引进这个依靠的比方,参阅Refer the version in the build script,这里边还说到从spotbugs v4版别开端,spotbugs.toolVersionString 变为 Provider<String>,所以请运用 get() 或其他方法引证实际版别。

dependencies {
  compileOnly "com.github.spotbugs:spotbugs-annotations:${spotbugs.toolVersion.get()}"
}

SpotBugs Gradle扩展特点

includeFilter和excludeFilter

过滤器(Filter file)文件能够定义匹配检测项、被查看的类和被查看办法的一套匹配规矩,让咱们能够为项目做定制化。文件装备好后,运用SpotBugsTask供给的特点指定过滤器文件的途径,完结包括(includeFilter)或许扫除(excludeFilter)匹配的内容。

文件是xml格局的,每个匹配规矩独自写在<Match></Match>中,运用Types of Match clauses描绘的各种标签组合成匹配规矩的内容,在Examples中给出了许多比方作为参阅。

咱们要点重视下这个标签,它能够通category、code、pattern来指定Bug descriptions中列出来的查看项,多个参数用逗号分隔,也能够运用正则表达式,以~字符开头。

每个查看项有仅有的pattern,比方下图中的XSS_REQUEST_PARAMETER_TO_SEND_ERRORXSS_REQUEST_PARAMETER_TO_SERVLET_WRITER;多个查看项或许归于同一个code,比方下图中的XSS;多个不同的code或许归于同一个category。这三者之间是一个清晰的层级联系,这样就能够按不同的粒度来装备。

代码Bug太多?给新人Code Review头都大了?快来试试SpotBugs

下面给出了一个运用<Bug>简略的比方和描绘,更多比方请参阅Examples。

<!--匹配指定的这两个查看项-->
<Match>
  <Bug pattern="XSS_REQUEST_PARAMETER_TO_SEND_ERROR,XSS_REQUEST_PARAMETER_TO_SERVLET_WRITER" />
</Match>
<!--匹配一切code为XSS的查看项-->
<Match>
  <Bug code="XSS" />
</Match>
<!--匹配SECURITY目录下一切查看项-->
<Match>
  <Bug category="SECURITY" />
</Match>

visitors和omitVisitors

这两个特点能够分别指定运用(visitors)和禁用(omitVisitors)查看器,一个查看器会包括一个或许多个Bug descriptions中说到的查看项,所以这两个特点装备的目的和includeFilterexcludeFilter是相同的,让咱们能够为项目做定制化,仅仅从查看器的维度进行装备。

在Detectors中将Spotbugs一切的查看器都列出来了,在不装备的情况下,Standard detectors中列出来的默许运用,Disabled detectors中列出来的默许禁用。

以查看器SynchronizationOnSharedBuiltinConstant为例,从下图咱们能够看到,查看器称号下面有段简短的描绘,再往下将查看器包括的查看项都列出来了,能够看到上文说到的patterncode,点击能够跳转到Filter file文档相应的方位。咱们装备visitorsomitVisitors的时候填查看器的姓名SynchronizationOnSharedBuiltinConstant即可。

代码Bug太多?给新人Code Review头都大了?快来试试SpotBugs

effort

effort是装备代码检测预期的等级,等级由低到高分别为min、less、more、max,等级越高计算本钱越高,花费的时间也就越长,在这个文档Effort里边有表格清晰的列出了这几个等级分别包括了哪些查看内容。effort的默许值是default,等同于more

jvmArgs

jvmArgs用于设置JVM参数,由于SpotBugs是Java写的,自然要在JVM上运转。咱们在比方里边看到了-Duser.language=ja,这个意思是设置语言为日语,也便是代码剖析的结果展现的语音(输出到终端或许陈述中)。总共有英语(en 默许),日语(ja),法语(fr)三种,在GitHub中能够看到相关展现文本的装备文件。

代码Bug太多?给新人Code Review头都大了?快来试试SpotBugs

maxHeapSize

maxHeapSize是设置JVM最大堆内存大小,在spotbugsextension#maxHeapSizea中说了默许值为空,因此会运用Gradle的默许装备,所以一般不必管。

onlyAnalyze

onlyAnalyze指定哪些代码要被SpotBugs剖析,在大型项目里边用这个特点防止没必要的剖析,或许会大大减少运转剖析所需的时间。能够指定类名,或许包名,指定包名的时候用.*.-的作用相同,意思是剖析这个包下以及子包中的文件。

onlyAnalyze = ['com.foobar.MyClass', 'com.foobar.mypkg.*']

reportLevel

SpotBugs按严重程度高到低将Bug分为了三个等级P1、P2和P3,reportLevel特点指明到达哪个等级的Bug才需求展现在陈述里边,它可装备值对应有HIGH、MEDIUM、LOWDEFAULT,它们定义在Confidence.groovy中。默许值是DEFAULT等同于MEDIUM,意思是要到达P2等级,那就意味着低等级的P3 Bug将被疏忽,

reports

reports装备陈述的类型,有html、xml、sarif和text四种,装备在SpotBugsTask中(比方spotbugsMain {}),再往里一层可装备的特点不是许多,参阅SpotBugsReport。下面是html类型陈述的装备示例:

spotbugsMain {
    reports {
        html {
            required = true
            outputLocation = file("$buildDir/reports/spotbugs/main/spotbugs.html")
            stylesheet = 'fancy-hist.xsl'
        }
    }
}

最佳实践

装备

在根目录gradle.properties中装备版别。

spotbugsGradlePluginVersion=5.0.13
findsecbugsPluginVersion=1.12.0

在根目录build.gradle中添加以下装备:

buildscript {
    repositories {
        mavenLocal()
        maven {
            url "https://plugins.gradle.org/m2/"
        }
        mavenCentral()
    }
    dependencies {
        classpath "com.github.spotbugs.snom:spotbugs-gradle-plugin:${spotbugsGradlePlugin}"
    }
}
apply plugin: "com.github.spotbugs"
repositories {
    mavenLocal()
    maven {
        url "https://plugins.gradle.org/m2/"
    }
    mavenCentral()
}
dependencies {
    compileOnly "com.github.spotbugs:spotbugs-annotations:${spotbugs.toolVersion.get()}"
    spotbugsPlugins "com.h3xstream.findsecbugs:findsecbugs-plugin:${findsecbugsPluginVersion}"
}
spotbugs {
    ignoreFailures = false
    showStackTraces = false
    showProgress = false
    excludeFilter = file("${project.rootDir}/code-analysis/spotbugs/exclude-filter.xml")
    extraArgs = [ '-nested:false' ]
}
spotbugsMain {
    reports {
        html {
            required = true
            stylesheet = 'fancy-hist.xsl'
        }
    }
}

假如是多模块项目按这种方法装备:

buildscript {
    repositories {
        mavenLocal()
        maven {
            url "https://plugins.gradle.org/m2/"
        }
        mavenCentral()
    }
    dependencies {
        classpath "com.github.spotbugs.snom:spotbugs-gradle-plugin:${spotbugsGradlePlugin}"
    }
}
allprojects {
		apply plugin: "com.github.spotbugs"
    repositories {
        mavenLocal()
        maven {
            url "https://plugins.gradle.org/m2/"
        }
        mavenCentral()
    }
    dependencies {
        compileOnly "com.github.spotbugs:spotbugs-annotations:${spotbugs.toolVersion.get()}"
        spotbugsPlugins "com.h3xstream.findsecbugs:findsecbugs-plugin:${findsecbugsPluginVersion}"
    }
    spotbugs {
        ignoreFailures = false
        showStackTraces = false
        showProgress = false
        excludeFilter = file("${project.rootDir}/code-analysis/spotbugs/exclude-filter.xml")
        extraArgs = ['-nested:false']
    }
    spotbugsMain {
        reports {
            html {
                required = true
                stylesheet = 'fancy-hist.xsl'
            }
        }
    }
}

在项目根目录下创立扫除查看项的文件/code-analysis/spotbugs/exclude-filter.xml,后期再依据需求装备。

<?xml version="1.0" encoding="UTF-8"?>
<FindBugsFilter
        xmlns="https://github.com/spotbugs/filter/3.0.0"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="https://github.com/spotbugs/filter/3.0.0 https://raw.githubusercontent.com/spotbugs/spotbugs/3.1.0/spotbugs/etc/findbugsfilter.xsd">
</FindBugsFilter>

运用

在Gradle窗口中双击运转spotbugsMain任务即可,检测完结会在Run窗口中打印出陈述地址。

也能够在Terminal窗口中履行./gradlew spotbugsMain,或许需求先履行chmod +x gradlew给gradlew文件履行权限。

代码Bug太多?给新人Code Review头都大了?快来试试SpotBugs

假如履行进程中产生类似下面这种反常能够不必管,仅仅有部分相关的剖析会无法履行,并不会中断整个进程,其他不相关的部分都会正常查看完结。在GitHub上issues#527许多人反映了这个问题,可是暂时没有完美的解决。

The following classes needed for analysis were missing: apply test accept

解读陈述

陈述概要(Summary)按包和BUG等级计算了数量。

代码Bug太多?给新人Code Review头都大了?快来试试SpotBugs

按目录阅读(Browse by Categories)将发现的缺点代码按查看项中的category、code、pattern分层展现。

代码Bug太多?给新人Code Review头都大了?快来试试SpotBugs

按包名阅读(Browse by Packages)便是换了个视点,依照包名 > 类名 > 查看项pattern分层展现。

代码Bug太多?给新人Code Review头都大了?快来试试SpotBugs

最后一个窗口Info展现剖析的代码文件(Analyzed Files)、剖析的源文件夹(Source Files)运用的依靠(Used Libraries)、运用到的SpotBugs插件(Plugins)和剖析中产生的反常(Analysis Errors)。

过滤装备

经过includeFilterexcludeFilter指定过滤器(Filter file)文件是最灵敏的,基本什么维度都能够操控。可是主张这里边只装备要包括或许扫除哪些查看项,运用category、code、pattern装备不同的粒度。

从整体缩小被查看的代码范围运用onlyAnalyze,假如有必要的话。假如要疏忽不查看详细的类或许办法能够运用注解@SuppressFBWarnings来标记,它来自spotbugs-annotations

其他技巧

SpotBugs Links里边列出了跟SpotBugs集成或许相似的东西,有需求能够了解下。

参阅

[SpotBugs]官网

[SpotBugs]官方文档

[SpotBugs]SpotBugsTask

[SpotBugs]SpotBugsExtension

[Gradle]spotbugs

[GitHub]SpotBugs

[GitHub]FindBugs

[GitHub]spotbugs-gradle-plugin

[FindBugs]官网