Kotlin 프로젝트를 진행하던 중, Klint
라는 걸 적용한 내용을 간단하게 정리하고자 합니다.
KtLint
는 Kotlin 프로그래밍 언어의 코드 스타일 규칙을 검사하고 적용하는 Linter 도구인데요.
Linter는, 소스 코드를 분석하여 프로그램 오류, 버그, 스타일 오류, 의심스러운 구조체에 표시(flag)를 달아놓기 위한 도구들을 가르킨다
출처: wikipedia
JavaScript를 위한 ESLinter, Python환경에서의 Pylint 등 다양한 환경에서 linter는 존재 하고, kotlin에서는 ktLint
라는 Linter를 사용할 수 있습니다.
Lint
는 스웨터의 보푸라기 것을 부를 때 사용 한다고 합니다. 기능에 영향을 주요하게 주진 않지만, 지켜야 할 컨벤션이 잘 지켜지지 않는 프로그램을 마치 손질이 필요한 보푸라기가 많은 스웨터로 비유한 것이 아닐까 싶습니다. Linter
가 그런 보푸라기를 제거해주는 거죠.
프로젝트를 시작 하면 Convention
이 정말 중요하다고 느껴질 때가 많은데요. 초기 단계에서 사소한 공백이나 줄바꿈으로 많은 의사소통 비용이 들더라고요. 이후에 기능을 개발 하기에도 급급하다 보니 컨벤션을 꼼꼼하게 지키기도 어려웠고, 공동 작업자가 놓친 컨벤션에 일일이 리뷰를 달기도 주저하게 되더라고요.
Linter
를 사용 해서 Convetion의 기준을 명확하게 정하고, build
나 CI
단계에서 항상 보푸라기를 정리해준다면 매우 유용할겁니다.
저는 kotlin 진영에서 자주 사용 되는 pinterest에서 만든 ktlint를 사용 하였습니다.
repo: https://github.com/pinterest/ktlint
docs: https://pinterest.github.io/ktlint/latest/
Kotlin 공식 컨벤션을 지키고 있다는 점과 .editorconfig
를 지원하여 rule를 custom 할 수 있고, 필요에 의해 몇몇 Rule을 비활성화 할 수 있습니다. 기본적으로 컨벤션을 지원하고 프로젝트에 맞게 설정 할 수 있다는 점이 큰 장점인 것 같습니다.
Quick Start에서는 ktlint를 설치하여 CLI
를 통해 formatting을 수행하는 방법이 소개되어 있지만, gradle 빌드 도구를 사용 하는 프로젝트에 적용하기 위해 wrapping 프로젝트를 사용 하였습니다.
Provides a convenient wrapper plugin over the ktlint project.
JLLeitschuh/ktlint-gradle
star수도 많고 최신 release가 두 달도 안된 살아있는 프로젝트라 사용 하였습니다.
해당 plugin을 사용하면, ktlint를 직접 설치할 필요 없이 gradle
을 통해서 ktlint를 간편하게 사용할 수 있습니다.
간단하게 플러그인을 추가할 수 있습니다.
build.gradle.kts
plugins {
id("org.jlleitschuh.gradle.ktlint").version("12.1.1")
}
repositories {
mavenCentral()
}
이후 새로 고침을 하면 formatting/ktlintFormat
, verification/ktlintCheck
등 여러 태스트가 추가된 걸 확인할 수 있습니다.
이후, build
를 하거나
plugin을 추가하면 자동으로 build task에 포함됩니다.
$ ./gradlew check --dry-run
ref: https://github.com/JLLeitschuh/ktlint-gradle/tree/main?tab=readme-ov-file#tasks-added
ktlintCheck
를 수행하면 코드에 대한 rule
을 체크하고 이에 대한 report(보고서)도 만들어줍니다.
$ ./gradlew ktlintCheck
결과
이렇게 FAILED
와 함께 어느 파일이 어떤 rule을 어겼는지 나옵니다. (좀 많네요...)
그리고 ./build/report/ktlint
경로에 세부적인 보고서가 나옵니다.
저는 보고서 형식을 json
으로 변경하였습니다.
build.gradle.kts
ktlint {
reporters {
reporter(ReporterType.JSON)
}
}
리포트를 보고 해당 파일로 찾아가서 직접 수정 할 수 있습니다.
다른 방법으로는 formatting/ktlintFormat
을 통해 자동 수정도 가능합니다.
$ ./gradlew ktlintFormat
사소하지만 잘 지켜지지 않았던 부분들은 자동으로 수정 되었습니다.
하지만, ktlintFormat
을 사용할 때는 모든 컨벤션이 자동 수정 되지 않는다는 점과 의도하지 않은 방향으로 수정 될 수도 있는다는 점을 인지하고 있어야 합니다.
Ktlint를 적용하여 놓치고 있던 여러 부분을 검증 받았는데요. 하지만, 몇가지 예외사항이 존재하였습니다.
현재 프로젝트에서는 port-adapter
패턴을 사용중이라 in
이라는 이름의 패키지를 사용 하고 있었습니다.
kotlin에서는 in 이라는 예약어가 존재하여 백틱(`)을 사용하여 아래 처럼 선언 되는데,
import com.celuveat.member.application.port.`in`.command.SocialLoginCommand
이 부분이 패키지 rule에 어긋나서 빌드가 실패 되는 문제가 있었습니다.
패키지 이름도 중요한 컨벤션이지만, 코드에 비해 상대적으로 양이 적고 쉽게 발견 할 수 있기 때문에 rule 검사에선 제외하기로 하였습니다.
이외에도 Ko-test 환경에서의 indent 스타일, ktlint에서 정의한 multiline-expression-wrapping을 제거하였습니다.
특정 rule 검사를 무시하려면 .editorconfig
파일에 정의하여야 합니다.
kotlin dsl에서 정의하는 방법은 0.48 버전 이후로 deprecated 되었습니다.
disabledRules.set(setOf("final-newline"))
// not supported with ktlint 0.48+
Ktlint
에서는 Intellij와의 충돌을 최대한 방지하고자 기본적인 intellij idea configuration을 제공합니다.
https://pinterest.github.io/ktlint/latest/rules/configuration-intellij-idea/
root = true
[*]
insert_final_newline = true
[{*.kt,*.kts}]
ij_kotlin_code_style_defaults = KOTLIN_OFFICIAL
# Disable wildcard imports entirely
ij_kotlin_name_count_to_use_star_import = 2147483647
ij_kotlin_name_count_to_use_star_import_for_members = 2147483647
ij_kotlin_packages_to_use_import_on_demand = unset
이후 비활성화 하려는 rule을 추가하였습니다.
root = true
[*]
insert_final_newline = true
[{*.kt,*.kts}]
ij_kotlin_code_style_defaults = KOTLIN_OFFICIAL
# Disable wildcard imports entirely
ij_kotlin_name_count_to_use_star_import = 2147483647
ij_kotlin_name_count_to_use_star_import_for_members = 2147483647
ij_kotlin_packages_to_use_import_on_demand = unset
# 패키지 네이밍 비활성화
ktlint_standard_package-name = disabled
# mutliline-expresion-wrapping 비활성화
ktlint_standard_string-template-indent = disabled
ktlint_standard_multiline-expression-wrapping = disabled
# Ko-test 에서 indent 스타일 체크 비활성화
[src/test/kotlin/**.{kt,kts}]
ktlint_standard_indent = disabled
ktlint_standard_string-template-indent = disabled
editorconfig가 아닌, 코드내에서 비활성화를 결정 시키고 싶은 경우 아래 어노테이션을 추가하여 구성할 수도 있습니다.
@Suppress("ktlint:standard:multiline-expression-wrapping")
공식문서에서 각각 rule에 대한 구성 방법에 대해 자세하게 나와있으니 참고하면 좋을 것 같습니다.
이를 적용하고 다시 수행해보면
$ ./gradlew ktlintCheck
잘 통과 하는 것을 확인할 수 있습니다!
매번 수행하는 것이 귀찮거나 까먹을 것 같다면,
help/addKtlintCheckGitPreCommitHook
help/addKtlintFormatGitPreCommitHook
$ ./gradlew addKtlintCheckGitPreCommitHook
$ ./gradlew addKtlintFormatGitPreCommitHook
이후, git 루트 경로에서 ./.git/hooks
에 pre-commit 파일이 추가되는데요.
아래와 같은 script가 생성 됩니다.
plugin에서는 precommit에 등록되는 task만 제공하는 데요. 위에서 생성된 스크립트를 pre-push
파일에 적용(hook)하면 git push
단위로 ktlint를 실행 시킬 수 있습니다.
kotlin official 컨벤션을 따르는 것도 중요하지만, 팀내에서 잘 공유 되고 합의된 규칙을 지치는 것이 더 중요하다고 생각한다. official convention을 기반으로 적절히 설정하여 팀내 컨벤션이 잘 지켜지도록 사용 한다면,생산성 있게 프로젝트가 진행되는데 도움이 될 것이다!!
출처