space냐 tab이냐, 괄호를 오른쪽에 붙이냐 줄바꿈을 해서 붙이냐 같은 주제로 개발자들은 심심찮게 토론을 벌이곤 한다.

if (condition) {

}

if (condition)
{

}

이런 코드를 보면... 뭔가 엄청 거슬리지 않나? 나는 키워드 사이 공백 일관되지 않으면 무진장 신경쓰인다.

for (int i=0; i< 10; i++) {
    int value1=10;
     int value2 = 20;
}

보통 이런 정답이 없는 것(=취향)들은 모두가 공통으로 지켜야 하는 가이드라인, coding convention으로 정의해둔다. coding convention은 프로그래밍 언어에 자체적으로 만들어져 있기도 하고, 협업하는 팀에서 직접 정하기도 한다. 어떤 컨벤션이 대세인지 이렇게 분석한 사이트도 존재한다.

 

컴파일러는 syntax error는 잡아주지만, convention error는 감지하지 못한다. 갓텔리제이나 VSCode 같은 IDE의 환상적인 지원을 받더라도 소스코드는 결국 인간이 만드는 것이다. 그래서 자기도 모르게 띄어쓰기를 두칸 쓴다던가.. 하는 일이 종종 발생한다.

 

linter는 프로그램에서 코딩컨벤션이 잘 지켜지고 있는지 체크해주는 프로그램이다. linter를 사용하면 컨벤션을 지키지 않은 코드가 있는지 분석하고, 좀 더 나아가서 규칙을 지키지 않은 코드가 있으면 아예 commit을 못하게 만들수도 있다. 나도 사용하기 시작한진 얼마 안됐는데, 내가 놓친 부분들을 은근히 계속 잡아줘서 무척 편리하다고 느끼고 있다.

 

오늘은 Kotlin 프로젝트에서 사용할 수 있는 linter인 ktlint의 사용 방법을 소개한다. standard rule은 번역해볼까? 했으나 네개쯤 하고 나니 급 귀찮아져서 원문을 긁어왔다.. 이런거 저런거 하지 말란 말이 대부분이다.

ktlint standard rules

ktlint는 코틀린 공식 가이드 문서에 있는 Coding conventions을 따른다.

  • Indentation formatting - respects .editorconfig indent_size with no continuation indent (see EditorConfig section for more)
  • No semicolons (unless used to separate multiple statements on the same line)
  • No unused imports
  • No consecutive blank lines
  • No blank lines before }
  • No trailing whitespaces
  • No Unit returns (fun fn {} instead of fun fn: Unit {})
  • No empty ({}) class bodies
  • No spaces around range (..) operator
  • No newline before (binary) + & -, *, /, %, &&, ||
  • No wildcard imports
  • When wrapping chained calls ., ?. and ?: should be placed on the next line
  • When a line is broken at an assignment (=) operator the break comes after the symbol
  • When class/function signature doesn't fit on a single line, each parameter must be on a separate line
  • Consistent string templates ($v instead of ${v}, ${p.v} instead of ${p.v.toString()})
  • Consistent order of modifiers
  • Consistent spacing after keywords, commas; around colons, curly braces, parens, infix operators, comments, etc
  • Newline at the end of each file (enabled by default)
    (set insert_final_newline=false in .editorconfig to disable (see EditorConfig section for more)).
  • Imports ordered consistently (see Custom ktlint EditorConfig properties for more)

직접 설치해서 사용하기

Installation

나는 brew를 사용하고 있어서 brew로 설치했다.

brew install ktlint

Usage

linter를 돌려볼 프로젝트 root 경로에 가서, ktlint를 쳐보았다. 프로젝트에 있는 모든 kt 파일에 대해 정적 검사가 시작된다. (그리고 뿜어져나오는 convention error들.. 🙂)

과거의 내가 생산한 과오를 확인해보자

필요에 따라 명령어에 옵션을 설정할 수 있다. 이 외에도 다양한 옵션을 제공하는데, 궁금하면 ktlint --help를 쳐보면 된다.

# ktlint를 돌릴 경로를 특정한다
# !를 붙인 경로는 검사 대상에서 제외
ktlint "src/**/*.kt" "!src/**/*Test.kt"

# -F를 붙이면 code style이 틀린 부분을 자동으로 수정한다
ktlint -F "src/**/*.kt"

Gradle Plugin으로 설정하기

아래 링크를 보고 pom.xml이나 build.gradle에 의존성을 추가하면 된다.

# 코드 스타일 체크
./gradlew ktlintCheck

# 코드 스타일 체크 후 틀린 부분을 자동으로 수정
./gradlew ktlintFormat

만약 IntelliJ를 사용하고 있다면

IntelliJ를 사용하고 있다면 똑똑한 IDE가 알아서 ktlint 설정을 뚝딱 해준다.

# 모든 IntellJ 프로젝트에 적용하고 싶다면
ktlint applyToIDEA

# 현재 프로젝트에만 적용하고 싶다면, 프로젝트 root 경로에서 아래의 명령어를 실행한다
ktlint applyToIDEAProject

# Android Kotlin Style Guide를 사용하고 싶다면
ktlint --android applyToIDEAProject

안드로이드 프로젝트에 적용해보기

IDE 우측에 Gradle 탭을 켜보면 실행할 수 있는 ktlint 명령어들이 보인다. 터미널에서 타이핑하는 대신 얘네들을 더블클릭해서 실행해도 된다.

Git hook 설정

ktlintCheck와 ktlintFormat를 git의 hook으로 등록해서 commit할 때마다 자동으로 linter가 실행되게 할 수 있다.

# style이 안맞는 부분이 있다면 commit이 안된다.
./gradlew addKtlintCheckGitPreCommitHook

# commit할 때 style이 안맞는 부분을 자동으로 수정한다.
# 의도치 않게 파일이 수정되는 경우가 생길수 있으므로 추천하지 않는 옵션이다.
./gradlew addKtlintFormatGitPreCommitHook

addKtlintCheckGitPreCommitHook을 적용해본 프로젝트에서 테스트를 해보자. 일부러 컨벤션에 어긋나도록 코드를 작성해봤다.

git commit을 실행한 순간 ktlint task가 돌아가고, commit이 실패한다. 어떤 파일에서 convention error가 난건지 line number까지 친절하게 알려준다.

linter를 hook과 조합해서 활용하면 컨벤션이 안맞는 코드가 github 같은 원격 저장소에 올라가는 일을 원천봉쇄할 수 있다. 그러면 코드리뷰를 할 때 convention 같은걸로 코멘트를 달 일이 없어진다. ㅎㅎ

References