필자는 현재 프로젝트에 KtLint를 도입하고 있다. 이번 포스팅에서 KtLint 소개, KtLint 사용하고 있는 방법에 대해 소개하고자 한다.
포스팅하는 이유
프로젝트 초기 설정을 하면서 자연스럽게 린트 검사에 대한 이야기가 나왔고, KtLint를 잘 알지 못한 채 무작정 도입했다. 그 결과 이슈가 발생했다. 이전에도 크게 제대로 적용하지 못한 경험이 있는데, 이러한 경험도 돌이켜보면, 제대로 알고 쓰지 못해서 발생했던 것 같다. 그래서 이번에 KtLint에 대해서 공부해보고, 포스팅하면서 정리하고자 한다. 또한, 가만히 프로젝트 하다가 PR Failed를 당한.. 팀원에게 소개하고자 글을 쓰는 것도 있다.
린트의 장점
사실 이전의 프로젝트는 하나 빼고 KtLint를 적용하지 않았다. 왜냐하면, 계속해서 혼자서 안드로이드 개발을 맡아, 코드 스타일이나 포맷팅을 맞추는 것에 있어서 크게 이점을 못 느꼈기 때문이다. (아무도 내 코드를 보지 않아서, 나만 알아보면 된다고 생각했다.)
하지만 이번 프로젝트는 다른 안드로이드 개발자분과 함께하는 프로젝트이다. 그래서 서로간 코드를 알아보기 위한 장치가 필요하다고 생각했고, 그 결과 코딩 컨벤션을 정의했다. 코딩 컨벤션이란게 정의한 대로 작성해야 하지만, 계속해서 쓰던 습관 때문에 무의식으로 작성한 경우가 많다.
하지만 린트 검사를 활용한다면, 모든 컨벤션을 준수하지는 못해도, 어느정도 컨벤션을 지킬 수 있도록 유도한다. 또한 컨벤션을 지키기 때문에, 그에 따른 유지보수 및 가독성을 높여주는 부수효과도 가져온다.
서론이 너무 길었는데, 본격적으로 KtLint에 대해서 알아보자!
KtLint 알아보자!
KtLint가 코딩 컨벤션을 지키기 위한 린트 검사 도구로 설명했다. 그럼 정확하게 어떤 것을 검사하는지, 어떻게 사용하는지 알아보자!
공식 KtLint에서 소개하는 KtLint의 기능은 다음과 같다.
- 부가적인 요소를 추가할 필요가 없다.
- ktLint는 Kotlin Coding Covnetion, Android Kotlin Convention을 준수하여, 컨벤션과 충돌이 발생하지 않는다.
- 규칙 설정
- ktLint는 자동적으로 스탠다드 규칙이 설정되어 있으며, 추가로 커스텀 규칙을 설정할 수 있다.
- .editorConfig
- 대부분의 모든 경우에서 정해진 규칙을 수정할 수 없다. 하지만 .editorConfig 파일을 생성하면 해당 파일에 있는 규칙을 포함하여 검사하게 할 수 있다.
- 규칙 비활성화
- 정의된 규칙을 쉽게 비활성화 할 수 있다.
- 내장된 Formatter
- 린트 검사 오류가 발생한 경우, 내장된 Formatter를 통해 수정할 수 있다. 하지만 몇몇의 경우는 수동으로 수정해야 한다.
- 결과 값 커스텀
- 수행 결과를 커스텀할 수 있다. plain-summary, json, html 다양한 형식으로 가능하다.
- 실행가능한 jar
- 모든 종속성을 포함한 jar 형태로도 사용할 수 있다.
그럼 KtLint는 코드의 어떤 부분을 검사할까?
공식 KtLint 페이지를 보면 자세하게 나와있다. 몇 가지만 소개하자면,
- No Wild Card Import && Import Ordering
- Wild (*) Import 금지, Import는 정렬되어야 한다.
- Final New Line
- 각 파일은 마지막 빈 라인을 가지고 있어야 한다.
- No empty class bodies
- 클래스 바디가 비어있으면 ({ }) 안된다.
- When class/function signature doesn't fit on a single line, each parameter must be on a separate line
- 함수 시그니처(함수의 구성요소)가 한 줄로 끊어지지 않으면, 매개 변수는 개행을 통해 분리해야 한다.
이외에도 많은 규칙이 있으며, 읽어보고 수정하거나, 비활성화 하고 싶은 규칙은 .editorConfig을 사용하면 된다 !
이제 KtLint 사용법에 대해 알아보자 !
1. 플러그인 추가하기
프로젝트 단위의 build.gradle에 다음과 같은 플러그인을 버전과 함께 명시한다.
plugins {
id("org.jlleitschuh.gradle.ktlint") version "<current_version>"
}
다음으로 사용할 모듈의 build.gradle이나 모든 모듈에서 사용할거라면, allProjects에 플러그인을 추가한다.
allprojects {
...
apply {
plugin("org.jlleitschuh.gradle.ktlint")
}
}
2. KtLint 수행하기
플러그인을 추가하고, 동기화하면 터미널에서 KtLint 명령어를 수행할 수 있다. 명령어는 두 가지로 구분되는데, 린트 체크(코드 스타일 검사)와 린트 포맷팅(스타일에 맞게 코드 자동 수정)이다.
린트 체크는 다음과 같은 명령어를 통해 수행할 수 있다.
./gradlew ktlintCheck
만약 린트 체크에서 스타일 에러가 발생한다면,
위와 같이, 자동으로 reports/ktlint 패키지에 무엇이 문제인지 알려주는 스크립트가 자동으로 생성된다.
이런 상황에서 린트 포맷팅을 수행하면, 해결할 수 있다. 포맷팅은 다음과 같은 명령어를 통해 수행한다.
./gradlew ktlintFormat
해당 명령어를 수행하고, 다시 린트 체크를 하면, 정상적으로 통과하는 것을 확인할 수 있다.
3. EditorConfig를 통해 규칙 커스텀하기
위에서 설명한 KtLint의 기능 중 하나인 .editorConfig 파일을 통해 규칙을 커스텀할 수 있다.
해당 기능은 내가 직면했던 문제 해결방법 중 하나인데, KtLint를 프로젝트 build.gradle에 추가하면, 안드로이드 프로젝트가 아니라도 KtLint의 영향을 받는다. 그러나 해당 파일을 사용한다면, 코틀린이나 자바로 된 파일만 영향을 받게 만들 수 있다.
또 다른 장점으로는 다음과 같다.
- 확장자마다 규칙을 다르게 설정하는 것도 가능하다.
- 원하지 않는 기능을 비활성화 할 수 있다.
- 코딩 컨벤션에 정의되지 않는 다른 기능도 추가 할 수 있다.
다음과 같이 .editorConfig 파일을 구성할 수 있다.
root = true
[*]
charset = utf-8
indent_size = 4
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
[*.{kt,kts}]
ij_kotlin_allow_trailing_comma=true
ij_kotlin_allow_trailing_comma_on_call_site=true
Bonus : KtLint를 Githook에 등록해 자동으로 수행하기
현재 프로젝트에서 PR Check Github Action으로 ktLint Check를 추가하였다(관련 자료). 따라서 PR을 성공적으로 수행하기 위해서는, 린트 체크후 수정하고 PR 해야 성공한다. 하지만, 매번 커밋할 때마다 린트 체크를 해야 하기 때문에, 귀찮고 잊어버리기 쉬운 반복작업이 될 수 있다. 이럴 때 GitHook을 사용해서 해당 작업을 자동화 할 수 있다.
GitHook에는 pre-commit 스크립트를 통해, 자동으로 커밋 전 작업을 수행할 수 있게 한다. 필자는 이러한 기능을 사용해, 자동으로 커밋을 하기 전, 린트 포맷팅을 수행하고 커밋되도록 구성했다.
하지만, .github의 hooks는 일반적으로 개인 작업으로 간주되기 때문에, 몇가지 작업이 수반된다.
- 공유될 pre-commit script 파일 만들기
- script를 hooks 패키지로 옮기는 Task 구현 및 등록하기
구현 방법을 통해서 확인해보자 !
1. pre-commit script 구현하기
가장 먼저 팀원과 공유할 GitHook 파일을 생성한다. 필자는 root에 scripts/pre-commit을 새로 생성했다.
#!/bin/sh
echo "Running git pre-commit hook"
./gradlew ktlintFormat --daemon
# $? = ".gradlew ktlintFormat --daemon"에 대한 return 값
STATUS=$?
# 문제없이 끝났다면 exit 0, 아니면 1
# -ne: Not equals
[ $STATUS -ne 0 ] && exit 1
exit 0
코드는 커밋 전, 코드 포맷팅 과정을 수행하는 작업밖에 없다. 만약 자동으로 수정되지 않는 경우라면, 커밋도 되지 않게 구성했다.
2. Task 추가하기
몇 가지 Task를 build.gradle에 추가해야 한다.
tasks.getByPath(":app:preBuild").dependsOn("makeFileExecutable")
tasks.register<Exec>("makeFileExecutable") {
commandLine("chmod", "+x", "${rootProject.rootDir}/.git/hooks/pre-commit")
dependsOn("installGitHook")
}
tasks.register<Copy>("installGitHook") {
dependsOn("deletePreviousGitHook")
from("${rootProject.rootDir}/script/pre-commit")
into("${rootProject.rootDir}/.git/hooks")
}
tasks.register<Delete>("deletePreviousGitHook") {
val prePush = "${rootProject.rootDir}/.git/hooks/pre-commit"
if (file(prePush).exists()) {
delete(prePush)
}
}
코드의 Task를 살펴보자.
- 이미 .git/hooks에 파일이 있으면 제거하는 Task
- 위에 구성한 파일을 자동으로 .git/hooks.pre-commit 경로로 옮기는 Task
- 실행을 위해 권한을 추가하는 Task
마지막으로, app:prebuild가 수행할 때 위의 Task가 수행되도록 한다.
이러한 방식으로, GitHooks를 통해 자동으로 커밋 전 KtLint가 수행되도록 할 수 있다!
마치며
이번 포스팅에서 KtLint 소개 및 사용방법에 대해서 소개하였다. 린트 검사는 협업을 위해 필요하다고 생각하고, 협업 뿐만 아니라 더 좋은 코드를 구현하기 위해서도 필요하다고 생각한다.
해당 코드는 다음에서 볼 수 있습니다
https://github.com/jeongjaino/GitExample
GitHub - jeongjaino/GitExample
Contribute to jeongjaino/GitExample development by creating an account on GitHub.
github.com
참고 자료
https://pinterest.github.io/ktlint/1.0.0/
https://github.com/pinterest/ktlint
https://www.youtube.com/watch?v=hSgPNbEcX98