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>
  • 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 보고서 양이 너무 많아져서 개발자가 지레 겁 먹는 경우가 많아지므로 일정 수준 이상의 품질 목표를 달성한 후에 필요에 따라 추가하자.


 Click here to expand...
<?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>


Design

AvoidReassigningParameters

SwitchDensity


ConstructorCallsOverridableMethod

AvoidThrowingRawExceptionTypes


logging 룰셋

AvoidPrintStackTrace

<rule ref="rulesets/java/logging-java.xml/AvoidPrintStackTrace"/> 



위 룰셋을 적용하면 운영에 올라가면 예외를 먹어버려서 머리에 쥐가 나게 만드는 아래와 같은 코드를 사전에 검출할 수 있다. 


class Foo {
 void bar() {
  try {
   // do something
  } catch (Exception e) {
   e.printStackTrace();
  }
 }
}

SystemPrintln


<rule ref="rulesets/java/logging-java.xml/SystemPrintln"/>

Logger 대신 System.out.print* 나 System.err.print* 로 프린트하는 코드를 검출할 수 있다.


Basic 룰셋

EmptyCatchBlock


<rule ref="rulesets/java/basic.xml/EmptyCatchBlock"/>

catch 구문에서 예외처리를 아무것도 안 하는 문제 많은 코드를 검출할 수 있다.

public void doSomething() {
  try {
    FileInputStream fis = new FileInputStream("/tmp/bugger");
  } catch (IOException ioe) {

  }
}

EmptyIfStmt

<rule ref="rulesets/java/basic.xml/EmptyIfStmt/">


if 문에서 아무 짓도 안 하는 코드를 검출한다.

public class Foo {
 void bar(int x) {
  if (x == 0) {
   // empty!
  }
 }
}

PMD Priority

PMD 의 룰에 우선순위를 지정할 수 있다. 다음은 catch 구문에 처리 로직이 없을 경우 우선 순위 1로 보고하는 예제이다.

<rule ref="rulesets/java/basic.xml/EmptyCatchBlock"  message="Must handle exceptions">
    <priority>1</priority>
</rule>

PMD 의 의 우선순위는 총 5가지가 정의되어 있다.

  1. Change absolutely required. Behavior is critically broken/buggy.
  2. Change highly recommended. Behavior is quite likely to be broken/buggy.
  3. Change recommended. Behavior is confusing, perhaps buggy, and/or against standards/best practices.
  4. Change optional. Behavior is not likely to be buggy, but more just flies in the face of standards/style/good taste.
  5. Change highly optional. Nice to have, such as a consistent naming policy for package/class/fields...


Ref