![](http://www-128.ibm.com/developerworks/i/c.gif) | 손쉬운 정적 분석 툴로 버그 잡기
Elliotte Rusty Harold 조교수, Polytechnic University 2005년 1 월 07 일 소스, 정적 분석 툴인 PMD는 버그를 잡기위한 툴로 손색이 없다. PMD의 사용법을 설명한다. Tom Copeland의 PMD는 오픈 소스(BSD 라이센스) 툴로서 자바 소스 코드를 분석하여 잠재적인 버그를 찾아낸다. 일반적인 부분에서는 FindBugs와 Lint4j(참고자료)같은 툴과 비슷하다. 하지만 이 모든 툴들은 다른 버그들을 찾아내기 때문에 주어진 코드 기반에서 이들 각자를 실행하는 것이 적합하다. 이 글에서 PMD를 사용하는 방법과 활용법을 설명하겠다. PMD의 명령행 인터페이스도 연구할 예정이다. PMD를 Ant와 통합하여 자동 소스-코드 체크를 수행할 수 있고 주요 IDE와 프로그래머의 에디터를 위한 플러그인도 있다. PMD의 설치와 실행 PMD는 자바로 작성되고 JDK 1.3 또는 이후 버전이 필요하다. 명령행을 사용하는 것이 익숙하다면 PMD의 설치와 실행은 단순하다. zip 파일을 다운로드하고(참고자료), /usr 또는 홈 디렉토리에 저장한다. 이 글에서는 /usr에 저장하는 것으로 간주하겠다. PMD 를 실행하는 가장 쉬운 방법은 pmd.sh 스크립트(Unix/Linux) 또는 pmd.bat 스크립트(Windows)를 호출하는 것이다. 이 스크립트들은 bin 디렉토리 보다는 pmd-2.1/etc에 있다. 스크립트에는 세 개의 명령행 인자들이 있다: - 체크 할 .java 파일로 가는 경로
- 아웃풋 포맷을 가르키는
html 또는 xml 키워드 - 실행 할 규칙 세트 이름들
예를 들어, 다음 명령어는 네이밍 규칙 세트를 사용하여 ImageGrabber.java 파일을 검사하여 XML 아웃풋을 만든다: $ /usr/pmd-2.1/etc/pmd.sh ImageGrabber.java xml rulesets/naming.xml
|
결과 분석 위 명령어에서 나온 아웃풋은 기본적으로 System.out 으로 보내지며 리포트 형식을 취한다(Listing 1): Listing 1. PMD XML 리포트
<?xml version="1.0"?><pmd> <file name="/Users/elharo/src/ImageGrabber.java"> <violation line="32" rule="ShortVariable" ruleset="Naming Rules" priority="3"> Avoid variables with short names like j </violation> <violation line="105" rule="VariableNamingConventionsRule" ruleset="Naming Rules" priority="1"> Variables that are not final should not contain underscores (except for underscores in standard prefix/suffix). </violation> </file> <error filename="/Users/elharo/src/ImageGrabber.java" msg="Error while processing /Users/elharo/ImageGrabber.java"/> </pmd>
|
Listing 1에서 PMD 가 두 개의 문제들을 발견했음을 알 수 있다: ImageGrabber.java의 32번째 줄에서 짧은 변수 이름과 105번째 줄에서 밑줄을 포함하는 이름이 그것이다. 작은 문제인 것 처럼 보이지만 결과는 엄청날 수 있다. 이 경우, 105번째 줄의 밑줄은 10년 묵은 코드의 그저 픽스하기 쉬운 코드의 단편이였다. 하지만 첫 번째 문제를 검사해보면 j 변수를 완전히 제거할 수 있었다는 것을 깨닫게 된다. 왜냐하면 각각 증가되고 있었던 또 다른 변수의 기능들을 중복시키기 때문이다. 프로그램은 작동했지만 앞으로의 변화에 대비해야 하는 것보다는 훨씬 더 위험한 것이었다. 여러분이 제거한 모든 코드 라인들은 버그가 들어올 수 있는 하나의 작은 장소이다. PMD 아웃풋을 파일로 리다이렉트 하거나 일반적인 방식으로 에디터로 전달(pipe)할 수 있다. 나는 HTML로 아웃풋을 만들어서 이것을 웹 브라우저에 로딩해 보곤 한다. (그림 1) 그림 1. PMD 아웃풋(HTML)
아웃풋을 파일로 보내는 것은 소스 트리를 검사할 때 특히 유용하다. 디렉토리 이름, zip 파일, JAR 아카이브 파일을 첫 번째 인자로서 전달한다면 PMD 는 그 디렉토리 또는 아카이브에 있는 모든 .java 파일을 반복적으로 검사한다. 소량의 아웃풋이 위협적이다. 특히 PMD가 많은 오류 가능성을 만들어 낼 때 그렇다. 예를 들어 XOM 코드 베이스(참고자료)에서 PMD를 실행할 때, "in과 같은 짧은 이름을 가진 변수를 피하라(Avoid variables with short names like in" 는 보고를 지속적으로 보낸다. "in"은 InputStream 을 나타내는 변수 이름으로서 완벽하다고 생각한다. 그럼에도 불구하고 괜찮은 텍스트 에디터에서 아웃풋을 검사한다면 빈번한 오류를 인식하고 지우는 것이 쉽다는 것을 알게 된다. 이들은 매우 비슷하기 때문이다. 그때 다른 재명명 문제를 픽스 할 수 있다. PMD에서 유일하게 부족한 한 가지 기능은 "lint comment"를 소스 코드에 추가하여 명백히 위험한 연산을 수행한다는 것을 나타내는 기능이다. 이것은 기능이지 버그는 아니다. 이것 외에는 PMD는 대체적으로 괜찮다. 예를 들어, 오랜 시간동안 try -catch 블록은 XOM의 다양한 장소에서 발생했다: try { this.data = data.getBytes("UTF8"); } catch (UnsupportedEncodingException ex) { // All VMs support UTF-8 }
|
PMD는 이것을 빈 catch 블록으로 플래그를 단다. VM이 UTF-8 인코딩을 인식하지 못한다는 것을 발견하게 될 때 까지는 문제가 없을 것처럼 보였다. 그래서 이 블록을 다음과 같이 바꾸고 PMD는 괜찮아졌다: try { this.data = data.getBytes("UTF8"); } catch (UnsupportedEncodingException ex) { throw new RuntimeException("Broken VM: Does not support UTF-8"); }
|
규칙 PMD는 16 가지 규칙 세트가 있고 자바 코드의 다양한 일반 문제들을 다룬다. 이중 어떤 것은 문제가 있는 것도 있다: 규칙 이름 명령행에서 전달되는 규칙들의 이름은 문서화가 잘 되어있지 않다. 이들을 파악하려면 여러 시도와 오류 과정을 거쳐야 한다. 괄호 안에 있는 이름들을 사용할 수 있다. |
- Basic (rulesets/basic.xml) -- 대부분의 개발자들이 동의하는 규칙:
catch 블록들은 비어있어서는 안되고, equals() 를 오버라이딩 할 때 마다 hashCode() 를 오버라이드한다.
- Naming (rulesets/naming.xml) -- 표준 자바 네이밍 규약을 위한 테스트: 변수 이름들은 너무 짧아서는 안된다; 메소드 이름은 너무 길어서는 안된다; 클래스 이름은 대문자로 시작해야 하고, 메소드와 필드 이름들은 소문자로 시작해야 한다.
- Unused code (rulesets/unusedcode.xml) -- 결코 읽히지 않은 프라이빗 필드와 로컬 변수, 접근할 수 없는 문장, 결코 호출되지 않는 프라이빗 메소드 등을 찾기.
- Design (rulesets/design.xml) -- 다양한 좋은 디자인 원리 체크, 이를 테면:
switch 문장은 default 블록을 갖고 있어야 하고, 심하게 중첩된 if 블록은 피해야 하고, 매개변수들은 재할당되어서는 안되며, 더블(double)이 동일함(equality)과 비교되어서도 안된다.
- Import statements (rulesets/imports.xml) -- 임포트 문장에 대한 작은 문제들 점검. 같은 클래스를 두 번 반입하는 것이나
java.lang 에서 클래스를 임포팅하는 것 등.
- JUnit tests (rulesets/junit.xml) -- 테스트 케이스와 테스트 메소드 관련 특정 문제 검색. 메소드 이름의 정확한 스펠링과
suite() 메소드가 정적이고 퍼블릭인지 여부.
- Strings (rulesets/string.xml) -- 스트링 관련 작업을 할 때 발생하는 일반적인 문제들 규명. 스트링 리터럴 중복,
String 구조체 호출, String 객체에 toString() 호출하기 등.
- Braces (rulesets/braces.xml) --
for , if , while , else 문장이 괄호를 사용하는지 여부 검사.
- Code size (rulesets/codesize.xml) -- 과도하게 긴 메소드, 너무 많은 메소드를 가진 클래스, 리팩토링에 대한 유사한 후보들을 위한 테스트.
- Javabeans (rulesets/javabeans.xml) -- 직렬화 될 수 없는 bean 클래스 같이 JavaBeans 코딩 규약을 위배하는 JavaBeans 컴포넌트 검사.
- Finalizers --
finalize() 메소드는 자바에서 일반적인 것은 아니기 때문에 사용법에 대한 규칙이 비교적 익숙하지 않다. 이 그룹의 검사는 finalize() 메소드 관련한 다양한 문제들을 찾는다. 이를 테면, 비어있는 finalizer, 다른 메소드를 호출하는 finalize() 메소드 finalize() 로의 호출 등이 그것이다.
- Clone (rulesets/clone.xml) --
clone() 메소드에 대한 규칙: clone() 을 오버라이드하는 클래스는 Cloneable 을 구현해야 하고, clone() 메소드는 super.clone() 을 호출해야 하며, clone() 메소드는 실제로 던지지 않더라도 CloneNotSupportedException 을 던지도록 선언되어야 한다.
- Coupling (rulesets/coupling.xml) -- 클래스들간 과도한 커플링 표시 검색. 지나치게 많은 임포트, supertype 또는 인터페이스가 충분한 곳에서 subclass 유형 사용하기, 너무 적은 필드, 변수, 클래스 내의 리턴 유형 등.
- Strict exceptions (rulesets/strictexception.xml) -- 예외 테스트: 메소드는
java.lang.Exception 을 던지도록 선언되어서는 안되고, 예외는 플로우 제어에 사용되어서는 안되며, Throwable 은 잡혀서는 안된다.
- Controversial (rulesets/controversial.xml) -- 일부 PMD 규칙들은 유능한 자바 프로그래머가 받아들일 수 있는 것들이다. 하지만 어떤 것은 논쟁의 여지가 충분하다. 이 규칙 세트에는 좀더 의심스러운 검사들이 포함되어 있다. 변수에 null 할당하기, 메소드에서 온 다중의 리턴 포인트
sun 패키지에서 임포팅 등이 포함된다.
- Logging (rulesets/logging-java.xml) --
java.util.logging.Logger 를 위험하게 사용하는 경우 검색: 끝나지 않고 정적이지 않은 logger와 한 클래스에 한 개 이상의 logger 등.
명령행에서 이름과 콤마를 분리하여 여러 규칙 세트들을 한번에 검사할 수 있다: $ /usr/pmd-2.1/etc/pmd.sh ~/Projects/XOM/src html rulesets/design.xml,rulesets/naming.xml,rulesets/basic.xml
|
자신의 규칙 세트 구현하기 특정 규칙 세트로 종종 검사하고 있다면 이들을 자신만의 규칙 세트 파일로 결합할 수도 있다.(Listing 2) 이 규칙 세트는 기본 규칙, 네이밍 규칙, 디자인 규칙들을 반입한다: Listing 2. 기본 규칙, 네이밍 규칙, 디자인 규칙들을 반입하는 규칙 세트
<?xml version="1.0"?> <ruleset name="customruleset"> <description> Sample ruleset for developerWorks article </description> <rule ref="rulesets/design.xml"/> <rule ref="rulesets/naming.xml"/> <rule ref="rulesets/basic.xml"/> </ruleset>
|
좀더 세분화 된 것을 원한다면 각 세트에서 원하는 개별 규칙들을 선택할 수 있다. 예를 들어 Listing 3은 세 개의 빌트인 세트에서 11 개의 특정 규칙들을 선택한 커스텀 규칙 세트를 보여주고 있다. 큰 코드 기반을 검사하는 것은 많은 시간이 걸리기 때문에 찾고자 하는 특정 문제를 보다 빨리 찾을 수 있다. Listing 3. 11 가지 특정 규칙을 반입한 규칙 세트
<?xml version="1.0"?> <ruleset name="specific rules"> <description> Sample ruleset for developerWorks article </description> <rule ref="rulesets/design.xml/AvoidReassigningParametersRule"/> <rule ref= "rulesets/design.xml/ConstructorCallsOverridableMethod"/> <rule ref="rulesets/design.xml/FinalFieldCouldBeStatic"/> <rule ref="rulesets/design.xml/DefaultLabelNotLastInSwitchStmt"/> <rule ref="rulesets/naming.xml/LongVariable"/> <rule ref="rulesets/naming.xml/ShortMethodName"/> <rule ref="rulesets/naming.xml/VariableNamingConventions"/> <rule ref="rulesets/naming.xml/MethodNamingConventions"/> <rule ref="rulesets/naming.xml/ClassNamingConventions"/> <rule ref="rulesets/basic.xml/EmptyCatchBlock"/> <rule ref="rulesets/basic.xml/EmptyFinallyBlock"/> </ruleset>
|
세트 안에 대부분의 규칙들을 포함시킬 수 있지만 동의하지 않는 것이나 또는 오류 가능성들이 있는 것들은 배제할 수 있다. 예를 들어, XOM은 테이블 검색을 수행할 때 종종 switch 문장을 디폴트 블록 없이 사용한다. 나는 대부분의 디자인 규칙들을 지킬 수 있지만 <exclude name="SwitchStmtsShouldHaveDefault"/> 자식 요소를 디자인 규칙을 반입하는 규칙 요소에 추가하여 default 블록을 놓치는 지에 대한 검사를 하지 않는다: Listing 4. switch 문장이 디폴트를 가져야 한다는 디자인 규칙을 배제한 규칙 세트
<?xml version="1.0"?> <ruleset name="dW rules"> <description> Sample ruleset for developerWorks article </description> <rule ref="rulesets/design.xml"> <exclude name="SwitchStmtsShouldHaveDefault"/> </rule> </ruleset>
|
(PMD가 옳다면 대신 디폴트 블록을 추가해야 한다.) 빌트인 규칙에는 제한이 없다. 자바 코드를 작성하고 PMD를 재컴파일 하거나 XPath 식을 작성하여 새로운 규칙을 추가할 수 있다. 결론 (매우 비싼) 빌트인 규칙들을 사용해서 PMD는 코드의 실제 문제들을 반드시 찾아낸다. 이들 중 어떤 것은 미미하지만 어떤 것은 그렇지 않다. 모든 버그를 찾는 것은 아니다. 단위 테스트와 수락 테스트를 수행해야 한다. 또한 PMD는 알려진 버그를 잡을 때 훌륭한 디버거용 대체도 아니다. 하지만 알지 못했던 버그를 찾을 때 빛을 발한다. PMD가 문제를 찾을 수 없었던 코드 베이스를 보지 못했다. PMD는 싸고 쉬우며 재미있는 방식으로 프로그램을 향상시킨다. 전에 PMD를 사용하지 않았다면 시도해보라. |