Using Google CheckStyle in Maven Lifecycle

5 분 소요


RECOMMEND POSTS BEFORE THIS

0. 들어가면서

여러 명이 함께 일할 때 코드 스타일은 중요합니다. 중간에 엉뚱한 스타일로 작성된 코드는 개발자의 가독성을 떨어뜨립니다. 이번엔 프로젝트 내 같은 코드 컨벤션(convention)을 가지도록 돕는 도구와 메이븐(maven) 플러그인을 통해 코드 확인을 자동화하는 방법에 대해 정리하였습니다.

1. Google CheckStyle

구글은 코드 스타일을 통일할 수 있도록 컨벤션에 대한 내용을 담은 XML 파일을 제공합니다. XML 파일에 정의된 규칙대로 코드 스타일을 강제할 수 있습니다. 아래 링크에서 해당 파일을 다운로드 받습니다.

다운로드 받은 google_checks.xml 파일은 프로젝트 루트(root) 경로에 위치시킵니다. 파일 경로는 상관 없고, 프로젝트 코드들과 함께 관리되도록 위치시킵니다.

2. Customize Code Style

코드 스타일 일부를 변경합니다.

google_checkstyle.xml
  • JavaDoc 관련된 컨벤션은 주석 처리하여 제거합니다.
  • 들여쓰기를 4칸으로 지정합니다.
<!--    <module name="NonEmptyAtclauseDescription"/>-->
<!--    <module name="InvalidJavadocPosition"/>-->
<!--    <module name="JavadocTagContinuationIndentation"/>-->
<!--    <module name="SummaryJavadoc">-->
<!--        <property name="forbiddenSummaryFragments"-->
<!--              value="^@return the *|^This method returns |^A [{]@code [a-zA-Z0-9]+[}]( is a )"/>-->
<!--    </module>-->
<!--    <module name="JavadocParagraph"/>-->
<!--    <module name="RequireEmptyLineBeforeBlockTagGroup"/>-->
<!--    <module name="AtclauseOrder">-->
<!--        <property name="tagOrder" value="@param, @return, @throws, @deprecated"/>-->
<!--        <property name="target"-->
<!--              value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, METHOD_DEF, CTOR_DEF, VARIABLE_DEF"/>-->
<!--    </module>-->
<!--    <module name="JavadocMethod">-->
<!--        <property name="accessModifiers" value="public"/>-->
<!--        <property name="allowMissingParamTags" value="true"/>-->
<!--        <property name="allowMissingReturnTag" value="true"/>-->
<!--        <property name="allowedAnnotations" value="Override, Test"/>-->
<!--        <property name="tokens" value="METHOD_DEF, CTOR_DEF, ANNOTATION_FIELD_DEF, COMPACT_CTOR_DEF"/>-->
<!--    </module>-->
<!--    <module name="MissingJavadocMethod">-->
<!--        <property name="scope" value="public"/>-->
<!--        <property name="minLineCount" value="2"/>-->
<!--        <property name="allowedAnnotations" value="Override, Test"/>-->
<!--        <property name="tokens" value="METHOD_DEF, CTOR_DEF, ANNOTATION_FIELD_DEF,-->
<!--                       COMPACT_CTOR_DEF"/>-->
<!--    </module>-->
<!--    <module name="MissingJavadocType">-->
<!--        <property name="scope" value="protected"/>-->
<!--        <property name="tokens"-->
<!--              value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF,-->
<!--              RECORD_DEF, ANNOTATION_DEF"/>-->
<!--        <property name="excludeScope" value="nothing"/>-->
<!--    </module>-->

    <module name="MissingJavadocType">
        <property name="scope" value="protected"/>
        <property name="tokens"
                  value=""/>
        <property name="excludeScope" value="nothing"/>
    </module>

3. Maven Plugin

메이븐 플러그인을 통해 메이븐 골(goal)을 만들고, 실행할 수 있습니다.

3.1. Dependency for plugin

maven-checkstyle-plugin 플러그인을 추가합니다. 플러그인에 적합한 버전의 checkstyle 의존성을 함께 정의합니다. LITERAL_SWITCH 토큰을 지원하지 않는다는 에러 메시지를 만나면 버전 10.9.0 이상을 사용합니다.

Token "LITERAL_SWITCH" was not found in Acceptable tokens list in check com.puppycrawl.tools.checkstyle.checks.blocks.RightCurlyCheck

3.2. pom.xml

  • 골 이름을 check로 지정합니다.
  • violationSeverity
    • 컨벤션 위반 심각성 레벨을 warning으로 지정합니다.
    • warning이 발생하는 경우 빌드가 실패합니다.
  • failsOnError
    • 에러가 있는 경우 빌드를 실패시킵니다.

    <properties>
        <java.version>17</java.version>
        <project.build.sourceDirectories>${basedir}/src</project.build.sourceDirectories>
        <checkstyle.config.location>${basedir}/google_checkstyle.xml</checkstyle.config.location>
    </properties>

    ...

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-checkstyle-plugin</artifactId>
                <version>3.2.1</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>check</goal>
                        </goals>
                    </execution>
                </executions>
                <dependencies>
                    <dependency>
                        <groupId>com.puppycrawl.tools</groupId>
                        <artifactId>checkstyle</artifactId>
                        <version>10.9.1</version>
                    </dependency>
                </dependencies>
                <configuration>
                    <violationSeverity>warning</violationSeverity>
                    <failsOnError>true</failsOnError>
                    <consoleOutput>true</consoleOutput>
                    <configLocation>${checkstyle.config.location}</configLocation>
                    <sourceDirectories>${project.build.sourceDirectories}</sourceDirectories>
                    <propertyExpansion>suppressionFile=${basedir}/google_checkstyle.xml</propertyExpansion>
                </configuration>
            </plugin>
        </plugins>
    </build>
Run Package Phase with Checkstyle Goal
$ mvn checkstyle:check package 
[INFO] Scanning for projects...
[INFO] 
[INFO] -------------------< action.in.blog:action-in-blog >--------------------
[INFO] Building action-in-blog 0.0.1-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] 
[INFO] --- maven-checkstyle-plugin:3.2.1:checkstyle (default-cli) @ action-in-blog ---
[INFO] Rendering content with org.apache.maven.skins:maven-default-skin:jar:1.3 skin.
[INFO] Starting audit...
[WARN] /Users/junhyunk/Desktop/action-in-blog/src/main/java/action/in/blog/ActionInBlogApplication.java:6:1: Missing a Javadoc comment. [MissingJavadocType]
Audit done.
[WARNING] Unable to locate Source XRef to link to - DISABLED
[WARNING] Unable to locate Test Source XRef to link to - DISABLED
[INFO] 
[INFO] --- maven-resources-plugin:3.3.0:resources (default-resources) @ action-in-blog ---
[INFO] Copying 1 resource
[INFO] Copying 0 resource
[INFO] 
[INFO] --- maven-compiler-plugin:3.10.1:compile (default-compile) @ action-in-blog ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to /Users/junhyunk/Desktop/action-in-blog/target/classes
[INFO] 
[INFO] --- maven-resources-plugin:3.3.0:testResources (default-testResources) @ action-in-blog ---
[INFO] skip non existing resourceDirectory /Users/junhyunk/Desktop/action-in-blog/src/test/resources
[INFO] 
[INFO] --- maven-compiler-plugin:3.10.1:testCompile (default-testCompile) @ action-in-blog ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to /Users/junhyunk/Desktop/action-in-blog/target/test-classes
[INFO] 
[INFO] --- maven-surefire-plugin:2.22.2:test (default-test) @ action-in-blog ---
[INFO] 
[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
[INFO] Running action.in.blog.ActionInBlogApplicationTests
16:41:15.855 [main] DEBUG org.springframework.boot.test.context.SpringBootTestContextBootstrapper - Neither @ContextConfiguration nor @ContextHierarchy found for test class [ActionInBlogApplicationTests]: using SpringBootContextLoader
16:41:15.859 [main] DEBUG org.springframework.test.context.support.AbstractContextLoader - Could not detect default resource locations for test class [action.in.blog.ActionInBlogApplicationTests]: no resource found for suffixes {-context.xml, Context.groovy}.
16:41:15.859 [main] INFO org.springframework.test.context.support.AnnotationConfigContextLoaderUtils - Could not detect default configuration classes for test class [action.in.blog.ActionInBlogApplicationTests]: ActionInBlogApplicationTests does not declare any static, non-private, non-final, nested classes annotated with @Configuration.
16:41:15.878 [main] DEBUG org.springframework.boot.test.context.SpringBootTestContextBootstrapper - Using ContextCustomizers for test class [ActionInBlogApplicationTests]: [ExcludeFilterContextCustomizer, DuplicateJsonObjectContextCustomizer, MockitoContextCustomizer, TestRestTemplateContextCustomizer, DisableObservabilityContextCustomizer, PropertyMappingContextCustomizer, Customizer]
16:41:15.929 [main] DEBUG org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider - Identified candidate component class: file [/Users/junhyunk/Desktop/action-in-blog/target/classes/action/in/blog/ActionInBlogApplication.class]
16:41:15.930 [main] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper - Found @SpringBootConfiguration action.in.blog.ActionInBlogApplication for test class action.in.blog.ActionInBlogApplicationTests
16:41:15.992 [main] DEBUG org.springframework.test.context.util.TestContextSpringFactoriesUtils - Skipping candidate TestExecutionListener [org.springframework.test.context.transaction.TransactionalTestExecutionListener] due to a missing dependency. Specify custom TestExecutionListener classes or make the default TestExecutionListener classes and their required dependencies available. Offending class: [org/springframework/transaction/interceptor/TransactionAttributeSource]
16:41:15.993 [main] DEBUG org.springframework.test.context.util.TestContextSpringFactoriesUtils - Skipping candidate TestExecutionListener [org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener] due to a missing dependency. Specify custom TestExecutionListener classes or make the default TestExecutionListener classes and their required dependencies available. Offending class: [org/springframework/transaction/interceptor/TransactionAttribute]
16:41:15.995 [main] DEBUG org.springframework.boot.test.context.SpringBootTestContextBootstrapper - Using TestExecutionListeners for test class [ActionInBlogApplicationTests]: [ServletTestExecutionListener, DirtiesContextBeforeModesTestExecutionListener, ApplicationEventsTestExecutionListener, MockitoTestExecutionListener, DependencyInjectionTestExecutionListener, DirtiesContextTestExecutionListener, EventPublishingTestExecutionListener, ResetMocksTestExecutionListener, RestDocsTestExecutionListener, MockRestServiceServerResetTestExecutionListener, MockMvcPrintOnlyOnFailureTestExecutionListener, WebDriverTestExecutionListener, MockWebServiceServerTestExecutionListener]
16:41:15.996 [main] DEBUG org.springframework.test.context.support.AbstractDirtiesContextTestExecutionListener - Before test class: class [ActionInBlogApplicationTests], class annotated with @DirtiesContext [false] with mode [null]

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v3.0.4)

2023-03-18T16:41:16.191+09:00  INFO 48332 --- [           main] a.in.blog.ActionInBlogApplicationTests   : Starting ActionInBlogApplicationTests using Java 17.0.1 with PID 48332 (started by junhyunk in /Users/junhyunk/Desktop/action-in-blog)
2023-03-18T16:41:16.192+09:00  INFO 48332 --- [           main] a.in.blog.ActionInBlogApplicationTests   : No active profile set, falling back to 1 default profile: "default"
2023-03-18T16:41:16.911+09:00  INFO 48332 --- [           main] a.in.blog.ActionInBlogApplicationTests   : Started ActionInBlogApplicationTests in 0.887 seconds (process running for 1.518)
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.517 s - in action.in.blog.ActionInBlogApplicationTests
[INFO] 
[INFO] Results:
[INFO] 
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO] 
[INFO] 
[INFO] --- maven-jar-plugin:3.3.0:jar (default-jar) @ action-in-blog ---
[INFO] Building jar: /Users/junhyunk/Desktop/action-in-blog/target/action-in-blog-0.0.1-SNAPSHOT.jar
[INFO] 
[INFO] --- spring-boot-maven-plugin:3.0.4:repackage (repackage) @ action-in-blog ---
[INFO] Replacing main artifact with repackaged archive
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  5.877 s
[INFO] Finished at: 2023-03-18T16:41:18+09:00
[INFO] ------------------------------------------------------------------------

4. Build Fail when Violation Conventions

코드 컨벤션을 고의로 망가트린 후 빌드가 실패하는지 확인합니다.

ActionInBlogApplication Class
  • 코드 블록에 줄내림을 만들어 컨벤션을 위반합니다.
package action.in.blog;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class ActionInBlogApplication {

    public static void main(String[] args)
    {
        SpringApplication.run(ActionInBlogApplication.class, args);
    }

}
Run Package Phase with Checkstyle Goal
  • 코드 블록에 관련된 경고 메시지와 함께 골 실행이 실패하는 것을 볼 수 있습니다.
$ mvn checkstyle:check package
[INFO] Scanning for projects...
[INFO] 
[INFO] -------------------< action.in.blog:action-in-blog >--------------------
[INFO] Building action-in-blog 0.0.1-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] 
[INFO] --- maven-checkstyle-plugin:3.2.1:check (default-cli) @ action-in-blog ---
[INFO] Starting audit...
[WARN] /Users/junhyunk/Desktop/action-in-blog/src/main/java/action/in/blog/ActionInBlogApplication.java:10:5: '{' at column 5 should be on the previous line. [LeftCurly]
Audit done.
[WARNING] src/main/java/action/in/blog/ActionInBlogApplication.java:[10,5] (blocks) LeftCurly: '{' at column 5 should be on the previous line.
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  1.668 s
[INFO] Finished at: 2023-03-18T17:02:05+09:00
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-checkstyle-plugin:3.2.1:check (default-cli) on project action-in-blog: You have 1 Checkstyle violation. -> [Help 1]
[ERROR] 
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR] 
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoFailureException

TEST CODE REPOSITORY

RECOMMEND NEXT POSTS

REFERENCE

댓글남기기