전자정부 표준프레임워크에서 제공하는 Code Inspection 도구인 PMD의 기본 사용법에 대하여 설명한다.
일괄적인 Inspection 작업 수행과 작업의 편이성을 위하여 Eclipse IDE의 PMD Perpective에서 Code Inspection 기능을 수행한다.
다음과 같은 과정으로 PMD Perpective로 전환할 수 있다.
Inspection을 수행하고자 하는 프로젝트를 선택하여 Inspection을 수행한다.
기본적으로 java 소스 코드를 대상으로 Inspection을 수행하며, 다음 조건의 경우는 Inspection을 수행하지 않는다.
Inspection을 수행한 후에는 Inspection의 결과를 확인하고 룰에 위배되는 코드를 수정할 수 있다.
PMD Perspective에서는 다음 그림과 같이 다양한 뷰에서 위배 결과를 확인할 수 있다.
Inspection 결과 해당 프로젝트 내에서 위배사항이 발생하였을 경우, 일반적인 오류가 있을때와 마찬가지로 Package Explorer 뷰의 프로젝트 아이콘과 하위 패키지 아이콘들이 또는 와 같이 붉은색 X자 모양의 상자가 추가된 아이콘으로 변경된다.
이 아이콘은 일반 오류 상황과 마찬가지로 해당 위배 내역이 수정될 때까지 정상상태의 아이콘으로 변경되지 않는다.
Problem 뷰에서도 Inspection의 위배 결과를 확인할 수 있다. PMD Perspective에서 다음과 같은 순서로 Problem 뷰를 열 수 있다.
위배결과 내용 중, Problem 뷰를 통하여 다음의 주요 항목을 확인하고 위배된 코드에 접근하여 수정할 수 있다.
항목 | 설명 |
---|---|
Description | 소소코드의 위배된 룰항목에 대한 상세 설명을 표시한다. |
Resource | 위배된 소스코드 이름을 표시한다. |
Path | 위배된 소스코드의 Path를 표시한다. |
Location | 소스코드에서 위배된 해당 Line을 표시한다. |
위 위배결과에서 해당 항목을 더블 클릭하면 Editor 영역에서 위배된 코드를 볼 수 있어, 바로 코드를 수정할 수 있다.
Violations Ovewview 뷰를 이용한 위배사항 확인은 Inspection 결과 리포팅에서 상세히 설명한다.
Inspection을 수행하면 위배된 결과는 수정하기 전까지 계속해서 소스 상에 남아있다.
소스 상에 Inspection의 위배사항이 남아 있으면 코드 상의 실제 오류(컴파일 오류 등)와 구분하기 힘들기 때문에 Inspection결과를 초기화 할 필요가 있다. 또는 Inspection을 재수행하기 위해 기존의 Inspection 결과를 초기화할 수 있다.
Inspection 결과를 초기화하기 위해서는 다음과 같이 프로젝트를 선택하고 초기화를 수행한다.
결과를 초기화하면 소스의 위배결과가 초기화되며, Problem 뷰, Violations Overview 뷰와 Package Explore 뷰에서도 사라진다.
전자정부 표준프레임워크에서는 Code Inpsection을 위한 룰셋으로 논리오류/구문오류/참조오류 영역을 대상으로 하는 총 39개의 룰을 표준으로 선정하였다.
전자정부 표준 Inspection 룰셋은 전자정부 표준 Inspection 룰셋 설치 지침을 참조하여 소스코드 Inspection 대상자의 PC에 설치하며, 개별 룰에 대한 설명과 오류코드(룰 위배 코드) 예제, 권장방안(위배 대응방안 또는 소스코드) 예제는 다음과 같다.
public void doSomething() { try { FileInputStream fis = new FileInputStream("/tmp/bugger"); } catch (IOException ioe) { // } }
public class Foo { void bar(int x) { if (x == 1) { // } } }
public class Foo { void bar(int a, int b) { while (a == b) { // } } }
public class Foo { public void bar() { try { } catch (Exception e) { e.printStackTrace(); } } }
public class Foo { public void bar() { try { int x=3; } finally { // } } }
public class MyClass { public void doit() { ; System.out.println("look at the extra semicolon");; } }
public void doSomething() { while (true) x++; }
public class Foo { public void bar() { int x = 2; if ( (x = getX() ) == 3) { System.out.println("3!"); } } private int getX() { return 3; } }
public class Foo { boolean bar() { return (true); } }
public class Bar { private boolean bar = (isFoo() == true); public isFoo() { return false; } }
public class Foo { public void bar() { int x = 2; switch (x) { case 2; int j = 8; } } }
public class Foo { private void foo(String bar) { bar = "something else"; } }
public class Foo { public final int BAR = 42; }
class Bar { void foo() { String x = "foo"; if (x.equals(null)) { doSomething(); } } }
public class Foo { private SimpleDateFormat sdf = new SimpleDateFormat("pattern"); }
public class Foo { private int x; public Foo() { x = 7; } public void foo() { int a = x + 2; } }
public class StaticField { static int x; public FinalFields(int y) { x = y; } }
public class Foo { synchronized void foo() { } }
public abstract class Foo { void int method1() { // ... } void int method2() { // ... } }
public void doSomething() { }
public interface ConstantsInterface { public static final int CONSTANT1 = 0; public static final String CONSTANT2 = "1"; }
import java.lang.String; import java.lang.*; public class Foo { }
package foo; import foo.Buz; import foo.*; public class Bar { }
class Foo{ public void testA () { System.out.println("Entering test"); } }
public class Foo { public static final int MY_NUM = 0; public String myTest = ""; DataModule dmTest = new DataModule(); }
public class Foo { public void bar(String m_baz) { int m_boz = 42; } }
public class Test { public void bar() { int[] a = new int[10]; int[] b = new int[10]; for (int i=0;i<10;i++) { b[i]=a[i]; } } }
public class Foo { void bar() { throw new NullPointerException(); } }
StringBuffer sb = new StringBuffer( "tmp =" + System.getProperty("java.io.tmpdir") );
public class Foo { void bar(String string) { if (string != null && string.trim().size() > 0) { doSomething(); } } }
public class Something { private static int FOO = 2; // Unused private int i = 5; // Unused private int j = 6; public int addOne() { return j++; } }
public class Something { private void foo() {} // unused }
public class Foo { private void bar(String howdy) { // howdy is not used } }
Inspection을 수행한 후 수행결과를 종합하여 리포팅할 수 있다.
Inspection을 수행한 후, 개발자 환경에서 바로 확인할 수 있는 통계 정보로써, 다음과 같은 화면을 가지고 있다.
Violations Overview 뷰의 화면 구성은 위배 사항의 통계 정보를 조회할 수 있는 그리드와 상단의 그리드의 내용을 제어할 수 있는 우측상단의 기능 버튼들로 구성된다.
개발자는 Violations Overview 뷰로 조회할 수 있는 이러한 유형별 통계 정보를 이용하여, 소스 코드의 품질 개선활동을 개발도구 상에서 바로 수행할 수 있다.
Inspection의 결과를 별도 파일로 리포팅하기 위해서는 프로젝트를 선택하고 리포팅 생성 작업을 수행한다.
Inspection의 별도 리포팅을 수행하게되면 다양한 형태의 리포팅 파일 들이 생성되는 것을 확인할 수 있다.
다음의 그림에서와 같이, 별도 리포팅을 수행한 프로젝트 루트 아래에 있는 reports 폴더에 CSV, HTML, TXT, XML 형태의 리포팅 파일이 생성된다.
Inspection 수행을 통해 위배된 결과를 생성된 리포팅 파일로 한 눈에 확인할 수 있다.
다음은 리포팅 파일 중, HTML 형태로 생성된 리포팅 파일을 웹 브라우저로 확인한 결과이다.
PMD의 리포팅 파일들은 기본적으로 UTF-8으로 인코딩되어 생성되므로, 다음과 같은 상황에서 한글이 깨질 수 있다.
Hudson PMD Plugin을 이용하면, Inspection 수행 결과 리포팅에 대한 통계 정보를 활용할 수 있다. Hudson에서 위배사항에 대한 통계 자료를 조회하기 위해서는 다음과 같은 순서로 확인할 수 있다.
Hudson PMD Plugin이 제공하는 다음과 같은 다양한 통계 정보를 토대로, 개인별/팀별 성과 측정 및 품질보증활동의 결과로써 활용할 수 있다.