피엠디
피엠디(PMD, Programming Mistake Detector)는 자바 프로그램의 소스코드를 분석하여 프로그램의 부적절한 부분을 찾아내고 성능을 높이도록 도와주는 공개 소프트웨어 점검 도구이다.
개요[편집]
피엠디는 자바 프로그램의 소스코드를 분석하여 프로그램의 부적절한 부분을 찾아내고 성능을 높이도록 도와주는 공개 소프트웨어 점검 도구로, 사용하지 않는 변수, 아무런 처리도 하지 않은 캐치 블록(catch-block), 불필요한 오브젝터 생성 등을 찾아내며, 시스템 개발 공정의 구현 및 테스트 단계에서 정적 분석에 활용할 수 있다.[1]
특징[편집]
피엠디는 프로그래머가 실수 혹은 무지각으로 인한 잠재되어 있는 에러 혹은 위험요소를 사전에 탐지하여 사후 생길 위험을 제거해 주는 강력한 소스 코드 분석기라고 할 수 있다. 단독 형태로도 사용할 수 있으며, 이클립스나 인텔리J와 같은 유명 자바 IDE에 플러그인 형태로 배포되어 사용할 수도 있다. 피엠디는 작성한 코드에 대한 위반사항을 찾고, 위반 사항을 명시한 리포트 파일(pmd.xml, cpd.xml file)에 대한 수정이 쉽다. 그리고 한 번 클릭으로 수많은 규칙에 대한 수정이 가능하다. 관련 도구로는 이클립스 매트릭스/체크클립스/체크스타일/파인드벅스/Jamit/Simain이 있으며, 제작사는 메이븐이다.[2]
특징[편집]
점검 기준[편집]
피엠디는 자바 코드를 점검하여 다음 세 가지 기준에서 위반 여부를 확인한다.
- 표준 코드 기준(compliance with coding standards) : 클래스, 메소드, 파라미터, 변수 이름과 클래스 및 메소드 길이, 주석 및 JAVADocs의 존재 및 서식을 점검한다.
- 코드 안티패턴(coding antipatterns) : 비어 있는 try/catch/finally/switch 블럭, 사용되지 않은 지역 변수, 파라미터, Private 메소드, 비어 있는 if/while 구문, 너무 복잡한 표현-불필요한 if 구문, 무한 루프에 빠질 수 있는 for 루프, 높은 복잡성 지표를 가진 클래스를 확인한다.
- CPD(Cut and Paste Detector) : 의심스러운 코드 복사를 찾는 도구로, 최소 크기의 코드 블락에 의해 매개변수화될 수 있다. [3]
설치 및 실행[편집]
피엠디는 자바로 작성되고 JDK 1.3 또는 이후 버전이 필요하다. 명령행을 사용하는 것이 익숙하다면 피엠디의 설치와 실행은 단순하다. zip 파일을 다운로드하고, /usr 또는 홈 디렉토리에 저장한다. /usr에 저장하는 것으로 간주한다면, 가장 쉬운 방법은 pmd.sh 스크립트(Unix/Linux) 또는 pmd.bat 스크립트(Windows)를 호출하는 것이다. 이 스크립트들은 bin 디렉토리보다는 pmd-2.1/etc에 있다. 스크립트에는 세 개의 명령행 인자들이 있다.[4]
1. 체크할 .java 파일로 가는 경로 2. 아웃풋 포맷을 가리키는 html 또는 xml 키워드 3. 실행할 규칙 세트 이름들
예를 들어, 다음 명령어는 네이밍 규칙 세트를 사용하여 ImageGrabber.java 파일을 검사하여 XML 아웃풋을 만든다.[4]
$ /usr /pmd-2.1/etc/pmd.sh ImageGrabber.java xml rulesets/naming.xml
기본 규칙[편집]
- Empty Finalizer : 만약 finalize 메소드가 비어 있다면, 이것은 존재할 필요가 없다는 의미이다.
- Empty Finally Block : 비어 있는 마지막 블락을 피하라는 의미이다.
- Unnecessary Return : 불필요한 return 구문을 피하라는 의미이다.
- Only One Return : 메소드는 오직 하나의 Return을 가져야 한다. 그리고 Return은 메소드의 마지막 구문이어야 한다.
- Cyclomatic Complexity : 복잡성은 메소드 내에서 결정 포인트 수와 메소드 엔트리에 대한 결정 포인트 수에 의해 결정되어야 한다. 결정 포인트란 if, while, for, case labels 이다. 일반적으로 1 - 4개면 낮은 복잡성이며 5 - 7개이면 중간 복잡도이며, 8 - 10이면 높은 복잡도에 해당되며 11개 이상이면 아주 높은 복잡도를 가지는 것이다.
- Too Many Fields : 너무 많은 필드(멤버)를 가진 클래스는 더 적은 필드(멤버)를 가진 클래스로 재설계 되어야 한다.
- Long Variable : 너무 긴 이름으로 선언되어진 필드, 지역 변수를 탐지하라는 의미이다.
- No Package : 패키지 정의를 가지지 않은 클래스나 인터페이스를 탐지하라는 의미이다.[3]
- 자바 규칙 집합
- 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 또는 인터페이스가 충분한 곳에서 서브클래스 유형 사용하기, 너무 적은 필드, 변수, 클래스 내의 리턴 유형 등을 확인한다.
- Strict exceptions(rulesets/strictexception.xml) : 예외 테스트이다. 메소드는 java.lang.Exception을 던지도록 선언된어서는 안 되고, 예외는 플로우 제어에 사용되어서는 안 되며, Throwable은 잡혀서는 안 된다.
- Controversial(rulesets/controversial.xml) : 일부 피엠디 규칙들은 유능한 자바 프로그래머가 받아들일 수 있는 것들이다. 하지만 어떤 것은 논쟁의 여지가 충분하다. 이 규칙 세트에는 좀 더 의심스러운 검사들이 포함되어 있다. 변수에 null 할당하기, 메소드에서 온 다중의 리턴 포인트 sun 패키지에서 임포팅 등이 포함된다.
- Logging(rulesets/logging-java.xml) : java.util.logging.Logger를 위험하게 사용하는 경우 검색한다. 끝나지 않고 정적이지 않은 logger와 한 클래스에 한 개 이상의 logger 등을 확인한다. [4]
- 규칙 집합 구현
특정 규칙 세트로 종종 검사하고 있다면 이들을 자신만의 규칙 세트 파일로 결합할 수도 있다. 이 규칙 세트는 기본 규칙, 네이밍 규칙, 디자인 규칙들을 반입한다.
<?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>
좀더 세분화 된 것을 원한다면 각 세트에서 원하는 개별 규칙들을 선택할 수 있다. 예를 들어 아래 11가지 특정 규칙을 반입한 규칙 세트의 코드는 세 개의 빌트인 세트에서 11 개의 특정 규칙들을 선택한 커스텀 규칙 세트를 보여주고 있다. 큰 코드 기반을 검사하는 것은 많은 시간이 걸리기 때문에 찾고자 하는 특정 문제를 보다 빨리 찾을 수 있다.[4]
- 11가지 특정 규칙을 반입한 규칙 세트 : 세트 안에 대부분의 규칙들을 포함시킬 수 있지만 동의하지 않는 것이나 또는 오류 가능성들이 있는 것들은 배제할 수 있다. 예를 들어, XOM은 테이블 검색을 수행할 때 종종 switch 문장을 디폴트 블록 없이 사용한다. 나는 대부분의 디자인 규칙들을 지킬 수 있지만 <exclude name="SwitchStmtsShouldHaveDefault"/> 자식 요소를 디자인 규칙을 반입하는 규칙 요소에 추가하여 default 블록을 놓치는지에 대한 검사를 하지 않는다.
<?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>
- 디자인 규칙을 배제한 규칙 세트 : 피엠디가 옳다면 대신 디폴트 블록을 추가해야 한다. 또한, switch 문장이 디폴트를 가져야하며, 빌트인 규칙에는 제한이 없다. 자바 코드를 작성하고 피엠디를 재컴파일하거나 XPath 식을 작성하여 새로운 규칙을 추가할 수 있다.[4]
<?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>
결과 분석[편집]
- 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>
피엠디가 두 개의 문제들을 발견했음을 알 수 있다. ImageGrabber.java의 32번째 줄에서 짧은 변수 이름과 105번째 줄에서 밑줄을 포함하는 이름이 그것이다. 작은 문제인 것처럼 보이지만 결과는 엄청날 수 있다. 이 경우에는, 105번째 줄의 밑줄은 10년 묵은 코드의 그저 픽스하기 쉬운 코드의 단편이었다. 하지만 첫 번째 문제를 검사해보면 j 변수를 완전히 제거할 수 있었다는 것을 알 수 있다. 왜냐하면 각각 증가되고 있었던 또 다른 변수의 기능들을 중복시키기 때문이다. 프로그램은 작동했지만 앞으로의 변화에 대비해야 하는 것보다는 훨씬 더 위험한 것이었다.[4]
각주[편집]
- ↑ 전자정부보호팀 박지연, 〈공개SW를 활용한 소프트웨어 개발보안 점검가이드〉, 《한국인터넷진흥원》, 2019-06-28
- ↑ Freesia, 〈PMD 와 Xpath〉, 《티스토리》, 2016-12-02
- ↑ 3.0 3.1 Lty0x00, 〈1.PMD-자바 정적 분석 도구-1〉, 《네이버 블로그》, 2015-12-10
- ↑ 4.0 4.1 4.2 4.3 4.4 4.5 Soulflower, 〈PMD 자바 프로그램 분석기〉, 《개인 블로그》, 2006-10-25
참고자료[편집]
- 전자정부보호팀 박지연, 〈공개SW를 활용한 소프트웨어 개발보안 점검가이드〉, 《한국인터넷진흥원》, 2019-06-28
- Freesia, 〈PMD 와 Xpath〉, 《티스토리》, 2016-12-02
- Lty0x00, 〈1.PMD-자바 정적 분석 도구-1〉, 《네이버 블로그》, 2015-12-10
- Soulflower, 〈PMD 자바 프로그램 분석기〉, 《개인 블로그》, 2006-10-25
같이 보기[편집]