Android Studio 는 코드의 구조적 품질 문제를 식별하고 수정하는 데 도움이 되는
코드 스캔 도구를 제공한다.
위 사진처럼 Toast 사용 시 show()
를 실행하지 않으면 기본 알림창이 뜨는 것을 볼 수 있다.
검사 대상은 Java, Kotlin 및 XML 파일, 아이콘 및 ProGuard 구성 파일을 포함해서
Android 프로젝트를 구성하는 파일이다.
코드 상에 Lint가 나타나면 바로 수정할 수도 있지만, 나중에 하겠다고 쌓아놓으면 찾기 힘들다
그래서 수동으로 실행하여 한번에 볼 수 있다.
1. Android Studio로 실행
Android Studio 메뉴 중 code -> Inpect Code 를 실행하면 아래처럼 결과를 볼 수 있다
2. 명령어로 실행
터미널에서 아래 명령어를 실행하면 된다
# Windows
./gradlew lint
#Linux 또는 macOS
./gradlew lint
buildType 중 특정 build variant 의 lint 를 실행하려면
lint
접두사 뒤로 카멜 케이스의 해당 type 을 넣어준다.
./gradlew lintRelease
그러면 html의 보고서가 아래 위치에 저장된다.
root/app/build/reports/lint-results-debug.html
안드로이드 자체에서 제공하고 있는 기본 lint가 강력하므로 Custom Lint의 필요성은 크게 없을 수 있다.
하지만 다수의 인원이 작업할 때 공통으로 지켜야 하는 룰 중에서 강제화할 필요가 있는 것들에 대해서는 Custom Lint를 사용하면 좋은 경우도 있다.
1. Lint 라이브러리 모듈 생성
build.gradle에는 아래의 lint 종속성을 추가한다.
dependencies {
compileOnly "com.android.tools.lint:lint-api:$lint_version"
compileOnly "com.android.tools.lint:lint-checks:$lint_version"
}
Lint Version은 Gradle Plugin Version에 23.0.0을 더해준 것으로 사용하면 된다.
2. IssueRegistry 구현
class LintIssueRegistry : IssueRegistry() {
// lint api 버전 설정
override val api = CURRENT_API
// 검사를 수행 할 이슈 등록
override val issues = listOf()
}
3. META-INF 등록
service registration file을 등록해줘야 한다.
IssueRegistry 클래스에 대한 이름으로 생성해주고, 프로젝트에서 이 class 파일을 생성할 위치를 명시해주면 된다.
resources/META-INF/services
아래에 com.android.tools.lint.client.api.IssueRegistry
파일 생성 후 앞서 구현한 클래스의 위치 기재
4. Detector 구현
수행 할 검사의 이슈를 만드는 클래스로 Detector
를 상속받아 구현한다
XmlScanner
: xml 파일을 검사SourceCodeScanner
: 소스코드 파일을 검사getApplicableMethodNames
: 반환된 리스트의 메소드명을 소스코드에서 검출visitMethodCall
: getApplicableMethodNames
에서 반환된 메서드의 이름이 검출되면 호출됨5. 이슈 등록
이전에 만들어 놨던 IssueRegistry
구현체에 Detector에서 생성하는 이슈를 등록한다.
6. lint 모듈 적용
dependencies {
lintChecks project(':lint')
}
// settings.gradle
include ':lint'
Ktlint는 Kotlin 코딩 규칙 및 Android Kotlin 스타일 가이드를 체크해주는,
코틀린 프로젝트에서 가장 흔히 쓰이는 린터이다.
해당 포스팅에서는 Gradle plugin
방식으로 진행을 하고,
ktlint를 래핑한 Gradle plugin 중 가장 대중적으로 쓰이는 jlleitschuh/ktlint-gradle를 사용한다.
먼저 프로젝트 및 모듈 단위의 build.gradle
에 종속성을 추가한다
plugins {
id("org.jlleitschuh.gradle.ktlint") version "10.2.1"
}
ktlint는 이후 별다른 설정이 없어도 디폴트 규칙으로 실행될 수 있지만,
.editorconfig
파일을 설정해 명시적으로 선언하는 것을 다음의 이유로 권장한다.
.editorconfig
가 참조되는 경우를 막음.editorconfig
파일을 찾기 전까지는 모든 상위 디렉토리를 탐색한다..editorconfig
파일 설정
.editorconfig IntelliJ IDEA 외에, VS Code 등의 다른 코드 편집기 및 IDE에서도 동작하는 설정 파일이다.
파일 경로 및 패턴 설정은 다음과 같다
설정 명 | 설명 |
---|---|
* | 경로 구분 기호 / 를 제외한 모든 문자열과 일치 |
** | 모든 문자열과 일치 |
? | 단일 문자와 일치 |
[name] | 이름 문자열과 일치 |
[!name] | 이름에 없는 문자열과 일치 |
{s1,s2,s3} | 주어진 문자열과 일치 (쉼표로 구분) (EditorConfig Core 0.11.0부터 사용 가능) |
{num1..num2} | num1 과 num2 사이의 모든 정수와 일치 (num1과 num2는 양수 또는 음수) |
섹션 안에 들어갈 수 있는 규칙은 다음과 같다
설정 명 | 설명 |
---|---|
ktlint_code_style | "ktlint_official" or "android" 로 코드 스타일 설정 |
insert_final_newline | "true" or "false"로 줄바꿈으로 끝나도록 하는지 설정 |
indent_style | "space" or "tab"으로 들여쓰기 스타일 설정 |
indent_size | 각 들여쓰기 수준에 사용되는 자리 수 |
end_of_line | "lf" or "cr" or "crlf" 로 설정하여 줄바꿈 표시 방법 제어 |
charset | "latin1" or "utf-8" or "utf-8-bom" or "utf-16be" or "utf-16le"로 설정하여 문자 집합 제어 |
trim_trailing_whitespace | "true" or "false"로 줄 바꿈 문자 앞에 있는 공백 문자를 제거 여부 |
max_line_length | "off" or 양수로 설정하여 최대 줄 길이 설정 |
구성 예시
root = true
[*]
indent_size = 2
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.{kt, kts}]
insert_final_newline = false
./gradlew ktlintCheck
해당 태스크는 스타일 검사를 수행한다.
이 태스크는 ./gradlew build
를 실행했을 때 연결되는 전체 프로젝트 빌드 싸이클에 포함되어서 정의한 코드 스타일을 지키지 않으면 빌드가 실패한다.
./gradlew ktlintFormat
해당 태스크는 스타일에 맞지 않는 코드를 일괄적으로 바꿔준다
전체 프로젝트의 소스 코드를 한꺼번에 고칠 때 사용할 수 있습니다.
해당 기능을 사용하다가 의도하지 않게 파일이 삭제되는 경우도 있었다고 하니 조심해서 사용해야한다.
ktlintCheck
태스크를 pre-commit hook으로 등록
./gradlew addKtlintCheckGitPreCommitHook
ktlintFormat
태스크를 pre-commit hook으로 등록
./gradlew addKtlintFormatGitPreCommitHook
detekt
는 코드 포멧팅 보다는 코드 복잡성, code smell 탐색과 같은 코드 분석에 초점을 둔 도구
Ktlint와 마찬가지로 프로젝트 및 모듈 단위의 build.gradle
에 종속성을 추가한다
버전 호환은 다음을 참고
plugins {
id("io.gitlab.arturbosch.detekt") version 1.23.1
}
프로젝트 레벨의 gradle에 다음의 예시처럼 detekt 옵션 설정
detekt {
config.setFrom("${projectDir}/detekt-config.yml")
buildUponDefaultConfig = true
debug = true
}
detekt config로 사용할 파일 구성 예시
앞선 예시에서 설정한 파일이름 detekt-config.yml
로 생성
build:
maxIssues: 0
config:
warningsAsErrors: true
complexity:
active: true
TooManyFunctions:
thresholdInFiles: 30
thresholdInClasses: 20
ignorePrivate: true
output-reports:
exclude:
- 'TxtOutputReport'
- 'XmlOutputReport'
- 'MdOutputReport'
default로 정의된 설정 파일과 설명한 옵션 이외의 내용은 공식 페이지 참조
build/maxIssue
: 설정된 개수 이상의 문제가 발생되면 더 이상 분석을 하지 않고 빌드를 캔슬한다.config/warningsAsErrors
: 경고도 전부 에러로 간주하여 빌드를 실패시킬지 여부- complexity/TooManyFunctions : 함수 개수에 대한 코드 복잡도 분석 룰 설정
thresholdInFiles
: 한 파일에 몇 개까지의 함수를 허용할지 결정thresholdInClasses
: 클래스에 몇 개까지의 함수를 허용할지 결정ignorePrivate
: Priavte 접근 제어자가 설정된 함수를 카운트에서 제외할지 결정output-reports/exclude
: 생성하지 않을 리포트 파일의 포맷 설정
Report 파일 하나로 합치기
XML과 SARIF 포맷에 한해 Report 파일을 합치는 기능이 있다. 공식 문서
gradle 파일에 설정
val reportMerge by tasks.registering(io.gitlab.arturbosch.detekt.report.ReportMergeTask::class) {
output.set(rootProject.layout.buildDirectory.file("reports/detekt/merge.xml")) // or "reports/detekt/merge.sarif"
}
subprojects {
detekt {
// reports.xml.required.set(true)
reports.sarif.required.set(true)
}
tasks.withType<io.gitlab.arturbosch.detekt.Detekt>().configureEach {
finalizedBy(reportMerge)
}
reportMerge {
input.from(tasks.withType<io.gitlab.arturbosch.detekt.Detekt>().map { it.xmlReportFile }) // or .sarifReportFile
}
}
git hook 설정 스크립트
#!/usr/bin/env bash
echo "Running detekt check..."
OUTPUT="/tmp/detekt-$(date +%s)"
./gradlew detekt > $OUTPUT
EXIT_CODE=$?
if [ $EXIT_CODE -ne 0 ]; then
cat $OUTPUT
rm $OUTPUT
echo "***********************************************"
echo " detekt failed "
echo " Please fix the above issues before committing "
echo "***********************************************"
exit $EXIT_CODE
fi
rm $OUTPUT
git action 설정
jobs:
without-type-resolution:
runs-on: ubuntu-latest
env:
GRADLE_OPTS: -Dorg.gradle.daemon=false
steps:
- name: Checkout Repo
uses: actions/checkout@v3
- name: Setup Java
uses: actions/setup-java@v3
with:
java-version: 11
- name: Run detekt
run: ./gradlew detekt
# Make sure we always run this upload task,
# because the previous step may fail if there are findings.
- name: Upload SARIF to GitHub using the upload-sarif action
uses: github/codeql-action/upload-sarif@v2
if: success() || failure()
with:
sarif_file: build/reports/detekt/detekt.sarif
각 Detekt Gradle task에서
Detekt.basePath
를 설정해야 GitHub가 저장소가 주석을 올바르게 배치할 위치를 알 수 있다.
basePath = rootProject.projectDir.absolutePath
Klint
코드 스타일 검사에 초점
공식 가이드에 기반하여 간단하게 구성
detekt
코드 분석에 초점
code small도 확인하고 많은 구성과 설정을 가지고 있다
복잡할 수 있지만 사용자 지정 검사가 필요한 경우 좋은 옵션
spotless
코드 스타일 검사에 초점
ktlint와 통합하여 사용하기 좋고, 간단한 설정
Android Lint 공식 문서
Android Custom Lint
Ktlint configuration
Detekt 공식 문서