협업에서 코드 컨벤션은 지나치기 쉽지만 매우 중요한 요소 중 하나입니다.
일관성
일관된 코드 스타일을 유지하는데 도움을 줍니다.
가독성 향상
동일한 스타일이 적용된 코드는 훨씬 더 쉽게 이해할 수 있습니다.
유지보수 용이성
이해하기 쉬운 코드는 오류를 방지하고 코드 안정성을 높힙니다.
개인 프로젝트를 진행할 때는 IntelliJ의 내장 코드 포맷팅 기능을 사용하고 있었습니다.
구글 자바 스타일 규칙 파일을 이용해 포맷팅 규칙을 설정하고 저장할 때마다 코드가 자동으로 해당 규칙에 맞게 수정되도록 구성해 놓았습니다.
하지만 협업 과정에서는 이러한 방식이 비효율적이라고 느꼈습니다.
팀 내 모든 개발자가 해당 규칙 파일을 받아 직접 코드 포맷팅 설정을 해야하고, 만약 새로운 팀원이 들어왔을 때는 코드 포맷팅을 위한 인수인계 과정이 추가돼 커뮤니케이션 비용이 발생하게 됩니다.
좀 더 자동화된 방식을 찾던 중 Spotless라는 플러그인을 발견하게 되었습니다.
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
}
spotless {
java {
// Google Java 포맷 적용
googleJavaFormat().aosp()
// 아래 순서로 import문 정렬
importOrder('java', 'javax', 'jakarta', 'org', 'com')
// 사용하지 않는 import 제거
removeUnusedImports()
// 각 라인 끝에 있는 공백을 제거
trimTrailingWhitespace()
// 파일 끝에 새로운 라인 추가
endWithNewline()
}
}
googleJavaFormat()
googleJavaFormat()
: indent가 2개의 탭googleJavaFormat().aosp()
: indent가 4개의 탭importOrder()
removedUnusedImports()
trimTrailingWhiteSpace()
endWithNewline()
이후 Terminal에서 ./gradlew spotlessApply
를 실행하면 수동으로 코드 포맷팅이 적용됩니다.
하지만 매번 명령어를 칠 수는 없겠죠?
빌드가 수행될 때마다 코드 포맷팅이 되게 설정할 수 있고 코드 포맷팅을 더 강제하기 위해 Git Hooks까지 설정해 완전한 자동화를 구성할 수 있습니다.
빌드가 수행될 때마다 spotlessApply
가 자동으로 수행되게 할 수 있습니다.
이번에도 build.gradle
파일을 수정해야 합니다.
...
tasks.named('compileJava') {
dependsOn 'spotlessApply'
}
...
빌드를 수행하면 compileJava
라는 Task가 실행되게 됩니다. 위의 코드를 추가하면 compileJava
가 실행되기 전에 spotlessApply
가 수행되어 코드 포맷팅을 진행합니다.
결론은 IntelliJ에서 서버를 실행할 때마다 Spotless를 통해 코드 포맷팅이 적용됩니다.
하지만 프로젝트를 진행하면서 아래와 같은 문제점이 발생했습니다.
이 문제를 해결하기 위해 어디선가 보았던 Git Hooks를 떠올리게 되었습니다!
Git Hooks는 git과 관련된 이벤트가 일어날 때 해당 이벤트에 따른 스크립트를 실행하는 기능입니다.
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
해당 스크립트는 커밋되기 전 실행되어 코드 포맷팅을 진행합니다.
chomd +x pre-commit
하지만 .git 디렉토리는 git의 관리 대상이 아니기 때문에 변경 사항이 커밋되지 않습니다.
결국 개발자가 다시 일일히 설정해줘야 하는 불편함이 발생하게 됩니다.
pre-commit
파일을 프로젝트 디렉토리에 추가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을 지원하는 플러그인이나 기능이 있었음 좋겠네요!