maven PMD plugin 사용해서 문제되는 코드 검출하기
개요
PMD 는 정적 소스 코드 분석기로 사용하지 않는 변수나 아무 일도 하지 않는 catch 구문등 문제가 될 만한 부분을 찾아준다.
이제는 Java 코드뿐만 아니라 Java Script 나 PLSQL 도 지원하며 추가로 복사 & 붙여넣기 검출기인 CPD 를 포함하고 있어서 중복 코드를 검출할 수도 있다.
maven 설정
pmd 는 site phase 에서 실행하는 것을 개인적으로 선호하므로 <reporting> 부분에 추가한다.
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>
<reporting>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-pmd-plugin</artifactId>
<version>3.2</version>
<configuration>
<linkXref>true</linkXref>
<rulesets>
<ruleset>file:///${project.basedir}/my-pmd-ruleset.xml</ruleset>
</rulesets>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>cobertura-maven-plugin</artifactId>
<version>2.6</version>
</plugin>
</plugins>
</reporting>
XML
- 2 line
프로젝트 소스의 encoding을 지정하며 pmd는 여기에 지정한 encoding 을 사용하게 되며 설정하지 않았을 경우 UTF-8 을 사용한다. pmd 에만 별도로 지정하려면 <configuration> 항목에 <sourceEncoding> 으로 설정하면 된다. - 3 line
보고서의 출력 인코딩을 지정한다. - 14 line
사용할 커스텀 룰셋의 위치를 설정하며 URL 형식으로 지정하지 않으면 클래스 패스에서 찾게 된다. 커스텀 룰셋을 사용하는 가장 좋은 방법은 메이븐 프로젝트에 룰셋을 넣어두고 file:/// 형식 URL 로 지정하는 것이다. 절대 경로를 사용해야 하므로 maven의 ${project.basedir} 프로퍼티를 활용하자.
Custom rule set
사용하지 않는 변수에 대해서 검사하는 룰인 UnusedLocalVariable 와 UnusedPrivateField 를 포함하면 PMD 보고서 양이 너무 많아져서 개발자가 지레 겁 먹는 경우가 많아지므로 일정 수준 이상의 품질 목표를 달성한 후에 필요에 따라 추가하자.
<?xml version="1.0"?>
<ruleset name="Custom ruleset"
xmlns="http://pmd.sourceforge.netruleset/2.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://pmd.sourceforge.netruleset/2.0.0 http://pmd.sourceforge.netruleset_2_0_0.xsd">
<description>
This ruleset checks my code for bad stuff
</description>
<!-- We'll use the entire 'strings' ruleset -->
<rule ref="rulesets/java/strings.xml"/>
<!-- <rule ref="rulesets/java/strings.xml/AvoidStringBufferField"/> -->
<!-- basic rules -->
<rule ref="rulesets/java/basic.xml/UnnecessaryConversionTemporary"/>
<rule ref="rulesets/java/basic.xml/ReturnFromFinallyBlock"/>
<rule ref="rulesets/java/basic.xml/EmptyCatchBlock" message="Must handle exceptions">
<priority>1</priority>
</rule>
<rule ref="rulesets/java/basic.xml/EmptyIfStmt"/>
<rule ref="rulesets/java/basic.xml/EmptyWhileStmt"/>
<rule ref="rulesets/java/basic.xml/EmptyTryBlock"/>
<rule ref="rulesets/java/basic.xml/EmptyFinallyBlock"/>
<rule ref="rulesets/java/basic.xml/EmptySwitchStatements"/>
<rule ref="rulesets/java/basic.xml/JumbledIncrementer"/>
<rule ref="rulesets/java/basic.xml/ForLoopShouldBeWhileLoop"/>
<rule ref="rulesets/java/basic.xml/UnnecessaryConversionTemporary"/>
<rule ref="rulesets/java/basic.xml/OverrideBothEqualsAndHashcode"/>
<rule ref="rulesets/java/basic.xml/DoubleCheckedLocking"/>
<rule ref="rulesets/java/basic.xml/EmptySynchronizedBlock"/>
<rule ref="rulesets/java/basic.xml/UnnecessaryReturn"/>
<rule ref="rulesets/java/basic.xml/EmptyStaticInitializer"/>
<rule ref="rulesets/java/basic.xml/UnconditionalIfStatement"/>
<rule ref="rulesets/java/basic.xml/EmptyStatementNotInLoop"/>
<rule ref="rulesets/java/basic.xml/DontCallThreadRun"/>
<!-- strict -->
<rule ref="rulesets/java/strictexception.xml/DoNotThrowExceptionInFinally"/>
<rule ref="rulesets/java/logging-java.xml/SystemPrintln"/>
<rule ref="rulesets/java/logging-java.xml/AvoidPrintStackTrace"/>
<!-- unusedcode 는 차후에 검사 -->
<!--
<rule ref="rulesets/java/unusedcode.xml/UnusedLocalVariable"/>
<rule ref="rulesets/java/unusedcode.xml/UnusedPrivateField"/>
-->
<rule ref="rulesets/java/imports.xml/DuplicateImports"/>
<!-- finalizer -->
<rule ref="rulesets/java/finalizers.xml/FinalizeShouldBeProtected"/>
<!-- We want to customize this rule a bit, change the message and raise the priority -->
<!-- Now we'll customize a rule's property value -->
<rule ref="rulesets/java/codesize.xml/CyclomaticComplexity">
<properties>
<property name="reportLevel" value="5"/>
</properties>
</rule>
<!-- We want everything from braces.xml except WhileLoopsMustUseBraces -->
<rule ref="rulesets/java/braces.xml">
<exclude name="WhileLoopsMustUseBraces"/>
</rule>
</ruleset>
XML
Design
AvoidReassigningParameters
SwitchDensity
ConstructorCallsOverridableMethod
AvoidThrowingRawExceptionTypes
logging 룰셋
AvoidPrintStackTrace
<rule ref="rulesets/java/logging-java.xml/AvoidPrintStackTrace"/>
XML
위 룰셋을 적용하면 운영에 올라가면 예외를 먹어버려서 머리에 쥐가 나게 만드는 아래와 같은 코드를 사전에 검출할 수 있다.
class Foo {
void bar() {
try {
// do something
} catch (Exception e) {
e.printStackTrace();
}
}
}
JAVA
SystemPrintln
<rule ref="rulesets/java/logging-java.xml/SystemPrintln"/>
XML
Logger 대신 System.out.print* 나 System.err.print* 로 프린트하는 코드를 검출할 수 있다.
Basic 룰셋
EmptyCatchBlock
<rule ref="rulesets/java/basic.xml/EmptyCatchBlock"/>
XML
catch 구문에서 예외처리를 아무것도 안 하는 문제 많은 코드를 검출할 수 있다.
public void doSomething() {
try {
FileInputStream fis = new FileInputStream("/tmp/bugger");
} catch (IOException ioe) {
}
}
JAVA
EmptyIfStmt
<rule ref="rulesets/java/basic.xml/EmptyIfStmt/">
XML
if 문에서 아무 짓도 안 하는 코드를 검출한다.
public class Foo {
void bar(int x) {
if (x == 0) {
// empty!
}
}
}
JAVA
PMD Priority
PMD 의 룰에 우선순위를 지정할 수 있다. 다음은 catch 구문에 처리 로직이 없을 경우 우선 순위 1로 보고하는 예제이다.
<rule ref="rulesets/java/basic.xml/EmptyCatchBlock" message="Must handle exceptions">
<priority>1</priority>
</rule>
XML
PMD 의 의 우선순위는 총 5가지가 정의되어 있다.
- Change absolutely required. Behavior is critically broken/buggy.
- Change highly recommended. Behavior is quite likely to be broken/buggy.
- Change recommended. Behavior is confusing, perhaps buggy, and/or against standards/best practices.
- Change optional. Behavior is not likely to be buggy, but more just flies in the face of standards/style/good taste.
- Change highly optional. Nice to have, such as a consistent naming policy for package/class/fields...
Ref
- PMD Rulesets index: Current Rulesets
- How to make a new rule set
- Rule guidelines
- How to make a new rule set - http://pmd.sourceforge.net/pmd-5.1.3/howtomakearuleset.html