[AvAb] Spotless와 Git Hooks로 코드 컨벤션 적용하기

엄기훈·2024년 1월 21일
0

AvAb

목록 보기
1/4
post-thumbnail

🔥 코드 컨벤션의 필요성

협업에서 코드 컨벤션은 지나치기 쉽지만 매우 중요한 요소 중 하나입니다.

  1. 일관성
    일관된 코드 스타일을 유지하는데 도움을 줍니다.

  2. 가독성 향상
    동일한 스타일이 적용된 코드는 훨씬 더 쉽게 이해할 수 있습니다.

  3. 유지보수 용이성
    이해하기 쉬운 코드는 오류를 방지하고 코드 안정성을 높힙니다.

✍️ Spotless

개인 프로젝트를 진행할 때는 IntelliJ의 내장 코드 포맷팅 기능을 사용하고 있었습니다.
구글 자바 스타일 규칙 파일을 이용해 포맷팅 규칙을 설정하고 저장할 때마다 코드가 자동으로 해당 규칙에 맞게 수정되도록 구성해 놓았습니다.

하지만 협업 과정에서는 이러한 방식이 비효율적이라고 느꼈습니다.
팀 내 모든 개발자가 해당 규칙 파일을 받아 직접 코드 포맷팅 설정을 해야하고, 만약 새로운 팀원이 들어왔을 때는 코드 포맷팅을 위한 인수인계 과정이 추가돼 커뮤니케이션 비용이 발생하게 됩니다.
좀 더 자동화된 방식을 찾던 중 Spotless라는 플러그인을 발견하게 되었습니다.

  1. Java, Kotlin 외에도 C++, Javascript, Python 등 다양한 언어를 지원합니다.
  2. Gradle과 Maven과 같은 빌드 도구와 통합됩니다.
  3. 빌드 과정에 Spotless가 제공하는 코드 스타일 검증, 적용의 기능을 추가하면 코드 포맷팅을 자동화할 수 있습니다.
  4. Google Java Format, Eclipse Formatter, ktlint 등 다양한 포맷 규칙과 통합됩니다.

📥 프로젝트에 적용하기

  1. build.gradle에 플러그인 추가
plugins {
    id 'java'
    id 'org.springframework.boot' version '3.2.1'
    id 'io.spring.dependency-management' version '1.1.4'
    id 'com.diffplug.spotless' version '6.23.3' // spotless plugin
}
  1. Spotless 포맷팅 설정 구성
spotless {
    java {
        // Google Java 포맷 적용
        googleJavaFormat().aosp()
        // 아래 순서로 import문 정렬
        importOrder('java', 'javax', 'jakarta', 'org', 'com')
        // 사용하지 않는 import 제거
        removeUnusedImports()
        // 각 라인 끝에 있는 공백을 제거
        trimTrailingWhitespace()
        // 파일 끝에 새로운 라인 추가
        endWithNewline()
    }
}
  • googleJavaFormat()
    Google Java Format 적용
    googleJavaFormat(): indent가 2개의 탭
    googleJavaFormat().aosp(): indent가 4개의 탭
  • importOrder()
    파라미터 순서대로 import문의 순서를 변경
  • removedUnusedImports()
    사용되지 않는 import문 제거
  • trimTrailingWhiteSpace()
    라인 끝에 있는 공백을 제거
  • endWithNewline()
    파일 끝에 개행 추가
    POSIX 명세에는 파일 끝에 항상 개행을 추가해야한다고 명시하고 있습니다.

이후 Terminal에서 ./gradlew spotlessApply를 실행하면 수동으로 코드 포맷팅이 적용됩니다.

🤖 자동화 하기

하지만 매번 명령어를 칠 수는 없겠죠?
빌드가 수행될 때마다 코드 포맷팅이 되게 설정할 수 있고 코드 포맷팅을 더 강제하기 위해 Git Hooks까지 설정해 완전한 자동화를 구성할 수 있습니다.

빌드마다 코드 포맷팅

빌드가 수행될 때마다 spotlessApply가 자동으로 수행되게 할 수 있습니다.

이번에도 build.gradle 파일을 수정해야 합니다.

...
tasks.named('compileJava') {
    dependsOn 'spotlessApply'
}
...

빌드를 수행하면 compileJava라는 Task가 실행되게 됩니다. 위의 코드를 추가하면 compileJava가 실행되기 전에 spotlessApply가 수행되어 코드 포맷팅을 진행합니다.

결론은 IntelliJ에서 서버를 실행할 때마다 Spotless를 통해 코드 포맷팅이 적용됩니다.

Git Hooks로 완전 자동화하기

하지만 프로젝트를 진행하면서 아래와 같은 문제점이 발생했습니다.

  1. 사소한 수정(오타, 단어 수정)을 가했을 때 서버 실행을 잊어 코드 스타일이 망가짐
  2. 코드 포맷팅을 위해 결국 서버를 실행해야 한다는 문제

이 문제를 해결하기 위해 어디선가 보았던 Git Hooks를 떠올리게 되었습니다!

Git Hooks는 git과 관련된 이벤트가 일어날 때 해당 이벤트에 따른 스크립트를 실행하는 기능입니다.

  1. .git/hooks 디렉토리에 pre-commit 스크립트 작성
# 변경된 파일들 이름만 추출하여 저장
stagedFiles=$(git diff --staged --name-only)

# SpotlessApply 실행
echo "Running spotlessApply. Formatting code..."
./gradlew spotlessApply

# 변경사항이 발생한 파일들 다시 git add
for file in $stagedFiles; do
  if test -f "$file"; then
    git add "$file"
  fi
done

해당 스크립트는 커밋되기 전 실행되어 코드 포맷팅을 진행합니다.

  1. 실행 권한 부여
chomd +x pre-commit

하지만 .git 디렉토리는 git의 관리 대상이 아니기 때문에 변경 사항이 커밋되지 않습니다.
결국 개발자가 다시 일일히 설정해줘야 하는 불편함이 발생하게 됩니다.

  1. pre-commit 파일을 프로젝트 디렉토리에 추가

    저는 관리하기 편하게 .script 디렉토리 밑에 두었습니다.
  2. build.gralde 코드 추가
tasks.register('addGitHooks', Copy) { // addGitHooks라는 새 Task 추가
    from new File(rootProject.rootDir, '.script/pre-commit') // .script/pre-commit 파일을 
    into { new File(rootProject.rootDir, '.git/hooks') } // .git/hooks 디렉토리로 복사
    Runtime.getRuntime().exec("chmod -R +x .git/hooks/"); // 실행 권한 부여
}

tasks.named('compileJava') {
    dependsOn 'spotlessApply'
     dependsOn 'addGitHooks' // NEW! 
}

이전과 마찬가지로 compileJava가 실행될 때 addGitHooks가 실행돼 자동으로 Git Hooks가 설정되게 됩니다.
물론 한 번 서버를 실행해야 적용되겠지만, 개발하면서 서버를 실행하지 않을 순 없기 때문에 Git Hooks는 어찌됐건 적용됩니다.
대신 이전처럼 매번 서버를 실행 할 필요 없이 커밋될 때 자동으로 실행되기 때문에 완전 자동화가 되었습니다! (만세!)

😎 결론

Spotless만 써도 코드 컨벤션이 적용되어 정말 편리한데, Git Hooks까지 사용하면 코드 포맷팅이 완전히 강제되어 일관적인 코드 스타일을 유지할 수 있게 됩니다.
Git Hooks는 이외에도 커밋 메시지 검증이나 자동으로 이슈 번호 붙여주기 등 다양한 방법으로 활용할 수 있으니 알아두면 편리할 듯 합니다.
자바스크립트 진영에서는 husky나 pre commit이라는 라이브러리로 더 간편하게 Git Hooks를 관리할 수 있는 모양입니다.
IntelliJ에서도 pre commit을 지원하는 플러그인이나 기능이 있었음 좋겠네요!

👀 참고 자료

https://poisson-it.tistory.com/77

profile
한 번 더 고민해보는 개발자

0개의 댓글