Skip to content

perf(ci): ktlint 를 PR 당 두 번 검사 — reviewdog 경로 32초·버전 이원화 정리 #1012

Closed

Description

@OnlyReadOneBook

현황

lint.yml 의 ktlint job 은 같은 규칙을 두 번 검사한다.

스텝 소요 검사 범위
Build ScaCap/action-ktlint@master 12초 (Docker 액션이라 매 run 빌드)
Run Ktlint (reviewdog) 20초 PR diff 의 추가된 줄 (filter_mode: added)
Run full Ktlint check (./gradlew ktlintCheck) 72초 저장소 전체

reviewdog 경로가 32초, job 전체의 약 30% 이고 ktlint 실행 자체가 이중이다. 44개 PR 을 한 번에 갱신하면 약 23분이 여기 쓰인다.

왜 지금 다시 보는가

인라인 코멘트의 원래 수요자는 "Actions 로그 수만 줄을 뒤지기 싫은 사람" 이다. 현재 이 저장소는 작업자 전원이 에이전트(Codex·Claude Code)를 쓰고, 에이전트는 gh run view --log 로 로그를 직접 읽는다 — 인라인 코멘트는 오히려 API 를 한 번 더 호출해야 닿는 곳에 있다.

반면 게이트는 그대로 필요하다. 에이전트도 틀린다 (2026-08-24 세션에서 git add 누락으로 컴파일 불가 상태가 push 됐고, pre-commit 훅은 staged 스냅샷만 보므로 통과했다. 막은 건 CI 였다).

"막는 것"(ktlintCheck)과 "알려주는 것"(reviewdog)을 나눠 보면 앞은 필수, 뒤는 비용 대비 가치가 역전됐다.

부수 문제

Android Lint 는 대상이 아니다

같은 job 의 dvdandroid/action-android-lint 는 lintDebug 가 만든 XML 을 재활용한다. 검사를 다시 하지 않으므로 추가 비용이 거의 없고, 모듈별 위반이 수만 줄 로그에 묻히던 문제(#279)를 푼다. 유지한다.

ktlint 만 검사를 두 번 한다.

범위

둘 중 하나를 택해 적용한다.

A. reviewdog 단계 제거 (단순)

  • Run Ktlint(ScaCap) 스텝을 없애고 ktlintCheck 하나만 남긴다.
  • 실패 위치는 ktlintCheck 콘솔 출력(파일:라인)으로 충분한지 확인하고, 부족하면 job summary 에 요약을 붙인다.

B. 검사 1회 + 인라인 유지 (android-lint 패턴)

  • ktlintCheck 가 checkstyle XML 리포트를 내도록 하고, 그 XML 을 reviewdog generic reporter 에 먹인다.
  • 인라인 코멘트를 유지하면서 ktlint 실행은 1회가 되고 버전 이원화도 사라진다.

어느 쪽이든 ScaCap/action-ktlint@master 의존이 없어진다.

완료 조건

  • ktlint 실행이 PR 당 1회다.
  • ktlint 버전 정본이 libs.versions.toml 하나다 — workflow 에 손으로 적은 버전이 없다.
  • ci: 삭제로 발생한 ktlint 위반을 전체 검사에서 감지 #875 회귀 없음: 삭제만으로 발생한 파일 단위 위반이 여전히 CI 를 실패시킨다. 그 성질은 원래 ktlintCheck 가 담당하므로 reviewdog 를 빼도 유지되지만, 회귀 테스트로 확인한다.
  • lint.yml 의 ktlint job 소요가 실측으로 감소한다. 수치를 이 이슈에 남긴다.
  • B 를 택했다면 위반이 있는 PR 에서 인라인 코멘트가 실제로 달린다.

관련

근거

Activity

  1. OnlyReadOneBook commented on Aug 24, 2026

    @OnlyReadOneBook
    CollaboratorAuthor

    PR #1034 로 머지됐다. 완료 조건인 소요 수치를 남긴다.

    ktlint job 소요 (Check Code Quality (Ktlint))

    소요
    기존 (reviewdog + ktlintCheck 이중 검사) 179 · 190 · 193 · 200 · 205 · 206 · 211초 (평균 약 198초)
    변경 후 (ktlintCheck 단독) 169초

    약 29초(15%) 감소. 이슈가 예상한 reviewdog 경로 비용(액션 빌드 12초 + 검사 20초 = 32초)과 부합한다. 표본이 변경 후 1건이라, develop 기준 run 이 더 쌓이면 갱신한다.

    완료 조건 대조

    • ✅ ktlint 실행이 PR 당 1회다 — lint-workflow-policy.test.mjs 가 run: 줄의 ktlintCheck 를 정확히 1건으로 강제한다.
    • ✅ 버전 정본이 libs.versions.toml 하나다 — 워크플로의 ktlint_version: "1.8.0" 이 사라졌고, 정책 테스트가 ktlint_version 재등장을 막는다.
    • ✅ ci: 삭제로 발생한 ktlint 위반을 전체 검사에서 감지 #875 회귀 없음 — 저장소 전체 검사(ktlintCheck --continue)가 그대로이고 filter_mode(diff 필터) 부재를 정책 테스트가 고정한다. 위반을 심으면 EXIT=1 로 막히는 것도 실측했다.
    • ✅ ScaCap/action-ktlint@master 의존 제거 — Actions 참조 SHA 고정 · secret scanning · push protection 적용 #847 의 SHA 고정 대상이 하나 줄었다.
    • ✅ 실패 위치 확인 수단 — A안(제거)을 택했으므로 인라인 코멘트는 없어졌고, 대신 job summary 에 규칙별 집계 + 파일:라인:칼럼 목록을 렌더한다. 검사를 다시 하지 않고 ktlint-gradle 이 이미 만드는 checkstyle XML 을 읽는다.

    부수 발견 — 로컬 전용 함정

    ktlintCheck 는 파일을 지워도 UP-TO-DATE 로 옛 위반을 계속 보고한다 (ktlint-gradle 14.2.0). probe 파일 삭제 후에도 runKtlintCheckOverMainSourceSet UP-TO-DATE 로 넘어가며 리포트가 stale 인 채 EXIT=1 이었고, --no-configuration-cache 로도 안 풀리고 --rerun-tasks 라야 실제 소스셋을 다시 읽었다. CI 는 매 run clean checkout 이라 해당 없다. 로컬에서 «지웠는데 안 사라지는 위반» 을 보면 코드가 아니라 태스크 상태를 먼저 의심할 것.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

enhancementNew feature or request

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions