CodeRabbitCodeRabbitKorea User Group
CodeRabbit Android & Jetpack Compose AI 코드 리뷰 설정 가이드
코드레빗코드래빗안드로이드 코드 리뷰Jetpack ComposeCompose 코드 리뷰AI 코드 리뷰AI 코드 리뷰 도구AI 코드 리뷰 설정coderabbit.yamlKotlin 코드 리뷰베스트 프랙티스

CodeRabbit Android & Jetpack Compose AI 코드 리뷰 설정 가이드

CodeRabbit Korea User Group·

CodeRabbit을 그대로 켜 두기만 해도 코드 리뷰 자체는 잘 굴러갑니다. 다만 안드로이드 프로젝트는 일반 Kotlin 코드와는 다른 문법(Composable 함수, Lifecycle, Hilt, Gradle DSL, AndroidManifest 등)을 동시에 다루기 때문에, Android & Compose 환경에 맞춘 컨텍스트.coderabbit.yaml에 알려주면 리뷰의 깊이가 눈에 띄게 달라집니다.

이 글에서는 Android & Jetpack Compose 프로젝트에 적용 가능한 .coderabbit.yaml을 단계별로 작성해 봅니다. 일반 Kotlin 설정 가이드를 먼저 보시고 싶다면 코드래빗 Kotlin 활용 베스트 프랙티스를, .coderabbit.yaml 옵션 자체가 처음이시라면 코드래빗 설정 완벽 가이드를 함께 보시면 좋습니다.

왜 Android 전용 설정이 필요한가요?

기본 Kotlin 설정은 "Kotlin 코드" 만을 가정합니다. 그래서 다음 같은 Android 특유의 안티 패턴이 잘 잡히지 않습니다.

  • @Composable 함수가 PascalCase가 아니거나, 비즈니스 로직을 직접 들고 있는 경우
  • ViewModel에서 viewModelScope 대신 GlobalScope를 사용하는 경우
  • Activity/Context 참조를 long-lived 객체에 보관해 메모리 누수가 생기는 경우
  • LaunchedEffect/DisposableEffect의 key를 잘못 잡아 무한 재실행되는 경우
  • 안정성(stability) 어노테이션 누락으로 불필요한 재구성(recomposition)이 일어나는 경우
  • AndroidManifest.xml에서 위험한 권한이 추가됐는데 사유가 코드에 보이지 않는 경우

이런 항목들을 path_instructions로 명시하면, AI 코드 리뷰가 사람 시니어 안드로이드 개발자가 PR을 본 듯한 코멘트를 달아 줍니다.

기본 골격 만들기

루트 디렉터리에 .coderabbit.yaml을 만들고, 다음과 같은 헤더로 시작합니다.

# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json
language: ko-KR
early_access: false
 
reviews:
  profile: chill            # 처음에는 chill, 익숙해지면 assertive
  high_level_summary: true
  collapse_walkthrough: true
  poem: false
  • language: ko-KR: 리뷰 코멘트를 한국어로 받습니다. CodeRabbit이 PR description, walkthrough, 인라인 코멘트 모두를 한국어로 생성합니다.
  • profile: chill: 처음 도입하는 팀에 권장합니다. 사소한 스타일 nitpick까지 받고 싶다면 assertive로.

path_filters: 어떤 파일을 보고 어떤 파일을 무시할까

Android 프로젝트는 빌드 산출물, 생성 코드(R.java, BuildConfig, KSP/KAPT 산출물), 리소스 등이 PR에 포함되곤 합니다. 이런 파일까지 리뷰 대상에 들어가면 정작 봐야 할 변경에 코멘트가 묻혀 버리니, 처음부터 제외해 두는 편이 깔끔합니다.

reviews:
  path_filters:
    # Kotlin/Compose 소스
    - "**/*.kt"
    - "**/*.kts"
    # AndroidManifest
    - "**/AndroidManifest.xml"
    # Gradle / 버전 카탈로그
    - "**/*.gradle"
    - "**/*.gradle.kts"
    - "**/libs.versions.toml"
    - "**/gradle.properties"
 
    # 빌드 산출물
    - "!**/build/**"
    - "!**/.gradle/**"
    - "!**/out/**"
    - "!**/generated/**"
    - "!**/buildSrc/build/**"
    # KSP/KAPT 생성 코드
    - "!**/ksp/**"
    - "!**/kapt/**"
    # 리소스(텍스트가 아닌 이진 파일·자동생성 R.java/BuildConfig)
    - "!**/R.java"
    - "!**/BuildConfig.java"
    # 자동 생성 보호
    - "!**/*.pb.kt"
    - "!**/databinding/**"
    # 의존성 잠금/락 파일
    - "!**/gradle-wrapper.properties"

!**/generated/** 같은 패턴 덕분에, KSP/KAPT가 만들어 둔 코드를 두고 "미사용 import 입니다" 같은 잡음 코멘트가 달리지 않습니다.

auto_review: 자동 리뷰 동작

reviews:
  auto_review:
    enabled: true
    auto_incremental_review: true
    drafts: false
    base_branches:
      - main
      - develop
    ignore_usernames:
      - dependabot
      - "dependabot[bot]"
      - renovate
      - "renovate[bot]"
  • auto_incremental_review: true: 새 커밋이 푸시될 때마다 변경분에 대해 추가 리뷰를 받을 수 있습니다.
  • drafts: false: 작성 중인 Draft PR에는 리뷰를 달지 않아 알림 피로를 줄입니다.
  • ignore_usernames: dependabot/renovate가 만든 PR은 리뷰를 건너뜁니다. CI가 이미 이런 의존성 PR을 검증하므로 중복 리뷰가 불필요합니다.

path_instructions: Android & Compose 전용 컨텍스트 주입

여기가 이 가이드의 핵심입니다. 같은 .kt 확장자라도 위치/역할에 따라 리뷰 기준을 다르게 줍니다.

Kotlin 일반 규칙

모든 .kt 파일에 공통으로 적용되는 베이스라인입니다. Compose, ViewModel, DI 등 더 좁은 path 규칙이 뒤에 붙어도, 이 베이스라인은 모든 코틀린 파일에 동시에 적용됩니다. null 안전성, structured concurrency, 시크릿 하드코딩 같은 "안 지키면 사고로 이어지는" 항목 위주로 채웠습니다.

reviews:
  path_instructions:
    - path: "**/*.kt"
      instructions: |
        Kotlin best practices to check:
 
        Language correctness:
        - Null safety: nullable 타입을 적절히 다루고, 불필요한 !! 단언 사용 금지
        - Immutability: var보다 val을 우선, 가능한 경우 immutable collection 사용
        - Data class: DTO와 값 객체에는 data class를 사용
        - Sealed class/sealed interface: 제한된 계층 표현에 적극 활용
 
        Coroutines:
        - Structured concurrency: viewModelScope, lifecycleScope 등 적절한 스코프 사용
        - GlobalScope 사용 금지 (테스트 가능성과 취소 전파 문제)
        - Dispatcher 사용: IO는 blocking I/O에, Default는 CPU 집약 작업에
        - withContext로 디스패처 전환 시 의도가 명확한지 확인
 
        Performance:
        - hot path에서 불필요한 객체 할당 피하기
        - 큰 collection에 여러 연산을 체인할 때는 sequence 활용
 
        Error handling:
        - 예외 삼키기(swallow) 금지
        - 실패 가능한 작업은 Result 또는 sealed class로 표현 고려
 
        Security:
        - 하드코딩된 secret/credential 금지
        - 보안 민감 작업에는 SecureRandom 사용
        - 안전하지 않은 역직렬화 금지

Jetpack Compose 전용 규칙

Compose는 일반 함수와 다른 선언형 UI 모델 위에서 동작하므로, 별도 path로 분리해 더 깊은 컨텍스트를 줍니다.

path glob을 정할 때 한 가지 중요한 점이 있습니다. NowInAndroid 같은 모던 모듈식 프로젝트는 Composable 파일을 feature/foryou/impl/.../ForYouScreen.kt처럼 feature 모듈 루트에 직접 두고, 재사용 컴포넌트만 core/ui, core/designsystem에 모읍니다. 즉, **/ui/** 같은 단일 디렉터리 패턴으로는 화면 단위 Composable이 대부분 빠집니다. 따라서 파일명 컨벤션(*Screen.kt)을 1차 신호로 삼고, 디자인 시스템·단일 모듈 프로젝트용 패턴을 보조로 같이 거는 형태가 가장 안전합니다. 아래처럼 같은 instructions를 path별로 각각 걸어 주시면 됩니다(path_instructions 스키마는 path당 한 개의 instructions를 받습니다).

    # 1) 화면 단위 Composable - 파일명 컨벤션, 모듈 깊이와 무관
    - path: "**/*Screen.kt"
      instructions: |
        Jetpack Compose 화면 best practices:
        - state hoisting: stateful/stateless 분리, ViewModel 비즈니스 상태를 컴포저블이 직접 보관 금지
        - rememberSaveable로 구성 변경에서 살아남아야 하는 상태 보존
        - LaunchedEffect/DisposableEffect의 key가 의도와 일치하는지 검증
        - Modifier 매개변수는 default Modifier로 두고 첫 번째 옵셔널 위치에 배치
        - stringResource/MaterialTheme 사용, 하드코딩 색상/문자열 회피
        - 의미 있는 contentDescription, 최소 48dp 터치 영역(접근성)
 
    # 2) 재사용 UI / 디자인 시스템 - core/ui, core/designsystem
    - path: "**/core/{ui,designsystem}/**/*.kt"
      instructions: |
        디자인 시스템·재사용 컴포넌트 best practices:
        - @Composable 함수명은 PascalCase, public API라면 KDoc 주석 권장
        - Modifier 매개변수 default와 위치(첫 번째 옵셔널)
        - 매개변수 데이터 클래스에 @Stable/@Immutable 검토(재구성 최적화)
        - SideEffect/derivedStateOf로 불필요한 재구성 줄이기
        - 디자인 토큰을 우회하는 하드코딩(0xFFxxxxxx, 임의 dp) 발견 시 토큰 추출 권장
        - @Preview 함수에 라이트/다크 모드, 폰트 크기 변형 제공
 
    # 3) 보조 패턴 - 단일 모듈 프로젝트의 ui/screen/components 디렉터리
    - path: "**/{ui,screen,screens,components,composables}/**/*.kt"
      instructions: |
        Jetpack Compose best practices:
 
        함수 시그니처:
        - @Composable 함수명은 PascalCase (예: `UserCard`, `LoginScreen`)
        - 결과를 반환하지 않는 한 반환 타입을 명시하지 않음 (Unit 명시 X)
        - Modifier 매개변수는 default 값으로 `Modifier`를 두고, 매개변수 목록 가장 앞 또는 첫 번째 옵셔널 위치에 배치
 
        State hoisting:
        - 가능한 경우 stateful 컴포저블 안의 상태를 상위로 끌어올려 stateless로 분리
        - `remember { mutableStateOf(...) }`는 정말 UI-local일 때만 사용
        - 화면 회전 등 구성 변경에서 살아남아야 하는 상태는 `rememberSaveable` 사용
        - ViewModel의 비즈니스 상태를 컴포저블 안에서 직접 보관하지 않기
 
        Recomposition 최적화:
        - 매개변수로 받는 데이터 클래스에는 `@Stable` 또는 `@Immutable` 어노테이션 검토
        - 람다 내부에서 `remember`로 캐싱이 필요한지 확인 (특히 큰 리스트 항목)
        - `derivedStateOf`로 파생 상태를 캐싱해 불필요한 재구성 줄이기
        - `key()`로 리스트 항목의 안정적 ID를 부여 (LazyColumn items)
 
        Side effects:
        - `LaunchedEffect`의 key가 의도와 일치하는지 (key 변경 = 재실행)
        - 컴포저블 외부 자원을 다룰 때는 `DisposableEffect`로 cleanup 보장
        - `SideEffect`는 매 재구성마다 호출됨 (성능 주의)
        - `produceState`/`snapshotFlow` 사용 시 적절한 dispatcher와 스코프 확인
 
        Modifier:
        - Modifier 체인 순서가 의미적으로 맞는지 (padding, clickable, background 순서 영향)
        - 부모 Modifier를 자식에 전달할 때 누락 없음
        - 자체 Modifier를 만들기 전에 표준 Modifier 조합으로 표현 가능한지 확인
 
        리소스:
        - 텍스트는 가능한 한 `stringResource(R.string.x)` 사용 (i18n)
        - 색상/크기는 `MaterialTheme.colorScheme`/`MaterialTheme.typography`/dp·sp 단위 사용
        - 하드코딩된 색상값(0xFFxxxxxx) 발견 시 디자인 토큰으로 추출 권장
 
        Preview:
        - `@Preview` 함수에는 적절한 `name`/`group`/`showBackground` 지정
        - 다양한 폰트 크기·라이트/다크 모드 Preview를 함께 제공하면 더 좋음
        - Preview 함수 내부에서 ViewModel을 직접 생성하지 않고, 더미 상태/콜백만 주입
 
        Accessibility:
        - 의미 있는 contentDescription 제공 (장식 요소는 null로 명시)
        - clickable에는 `Modifier.clickable(onClickLabel = ...)` 또는 `semantics { ... }` 활용
        - 충분한 터치 영역(최소 48dp) 확보

ViewModel 규칙

ViewModel은 안드로이드에서 가장 메모리 누수와 라이프사이클 버그가 자주 발생하는 지점입니다. Activity/Context 참조를 잘못 보관하거나, viewModelScope 대신 GlobalScope를 쓰면 화면이 사라진 뒤에도 코루틴이 살아남아 크래시, 중복 네트워크 호출로 이어집니다. 파일명 컨벤션(*ViewModel.kt)을 활용해 ViewModel만 별도로 더 엄격히 검사합니다.

상태 관리는 NowInAndroid에서 자리 잡은 패턴(sealed UI state + stateIn(WhileSubscribed(5_000)) + SavedStateHandle.getStateFlow + combine/flatMapLatest로 derived state 구성)을 기준선으로 두면 리뷰 기준이 명확해집니다. 아래 instructions에 이 패턴을 그대로 옮겼습니다.

    - path: "**/*ViewModel.kt"
      instructions: |
        ViewModel best practices:
 
        의존성과 스코프:
        - @HiltViewModel + @Inject constructor로 의존성 주입(필드 주입 금지)
        - 코루틴 실행은 viewModelScope 기준, GlobalScope/runBlocking 사용 금지
        - 외부에서 주입받은 CoroutineDispatcher를 사용해 테스트 결정성 확보
        - SavedStateHandle을 생성자로 받아 nav args/process death 상태 복원
          (예: `savedStateHandle.getStateFlow(KEY, default)` 패턴)
 
        UI 상태 모델링 (sealed + StateFlow):
        - UI 상태는 sealed interface(또는 sealed class)로 명시적 상태 표현
          (예: `sealed interface FeedUiState { object Loading; data class Success(...); object Error }`)
        - 단일 source of truth로 `StateFlow<XxxUiState>` 한 개를 외부에 노출
        - 중간 상태(Loading, Empty, Error)를 누락하지 않기. 빈 결과와 미초기화 상태는 구분
          (NowInAndroid의 SearchResultUiState: Loading/EmptyQuery/SearchNotReady/LoadFailed/Success)
        - data class Success는 immutable 필드만, 컬렉션은 List/Set 사용
 
        Cold flow → hot StateFlow 변환:
        - Repository/UseCase의 cold Flow는 viewModelScope 안에서 stateIn으로 hot 변환
        - SharingStarted.WhileSubscribed(5_000) 권장: 화면 회전 같은 짧은 구독 단절은
          업스트림을 유지하고, 진짜로 떠난 경우 5초 후 정리
        - initialValue는 sealed UI state의 Loading 상태로 명시
 
        Derived state 구성:
        - 여러 source 결합은 combine(...)으로 처리하고 그 결과를 stateIn
        - 입력에 따라 업스트림이 바뀌는 경우 flatMapLatest 사용(이전 구독 자동 취소)
        - map(SuccessFactory::Success)처럼 매핑 함수로 sealed 분기 깔끔하게
 
        Mutable 상태는 캡슐화:
        - MutableStateFlow는 private, 외부에는 StateFlow asStateFlow()/직접 val 노출
        - 사용자 액션은 fun으로 노출(예: `fun onTopicSelected(id: String)`)하고
          내부에서 viewModelScope.launch { repository.update(...) }로 처리
 
        일회성 이벤트:
        - 토스트, 네비게이션 같은 단발 이벤트는 StateFlow 부적합
        - SharedFlow(replay=0, extraBufferCapacity>=1) 또는 Channel(BufferOverflow.DROP_OLDEST) 사용
        - 가능하다면 이벤트도 상태로 환원해 SavedStateHandle에 저장 후 소비 시 비우기 검토
 
        리소스 누수:
        - Activity/Fragment/Context 참조를 ViewModel 필드에 보관 금지
          (Application Context가 꼭 필요하면 @ApplicationContext로 주입)
        - 콜백/리스너 등록은 onCleared()에서 해제
        - 직접 만든 CoroutineScope 사용 시 onCleared에서 cancel
 
        테스트:
        - 모든 의존성은 생성자 주입(테스트에서 fake/test double로 교체)
        - 테스트 dispatcher(StandardTestDispatcher) 사용 + Dispatchers.setMain
        - StateFlow 검증은 Turbine으로(`viewModel.state.test { ... }`)
        - SavedStateHandle 직접 생성해 nav args/복원 시나리오 검증

Hilt / DI 모듈

DI 모듈은 잘못 짜면 앱 전체에 잘못된 인스턴스가 흘러 들어가는 영향이 큰 코드입니다. 스코프 누락, @Binds로 표현 가능한 것을 @Provides로 작성, ApplicationContext 한정자 누락처럼 "동작은 하지만 점진적으로 문제를 만들어 내는" 항목을 자동으로 잡도록 합니다.

NowInAndroid를 살펴보면 Hilt 모듈은 **/di/(예: core/network/di/NetworkModule.kt)뿐 아니라 모듈 루트(예: app/.../di/JankStatsModule.kt, sync/.../sync/di/SyncModule.kt)에 직접 두는 경우도 많습니다. 그래서 디렉터리 패턴과 파일명 컨벤션(*Module.kt) 을 함께 걸어 누락을 줄입니다.

    # 1) DI 디렉터리 안의 모든 코드
    - path: "**/{di,inject}/**/*.kt"
      instructions: |
        Dependency Injection (Hilt) best practices:
 
        - @Module 클래스는 적절한 `@InstallIn` 컴포넌트(SingletonComponent, ViewModelComponent 등) 명시
        - `@Provides` 함수는 가능하면 `@Singleton`/`@ViewModelScoped` 등 명시적 스코프
        - 인터페이스 바인딩은 `@Binds`(추상 함수)로, `@Provides`보다 선호
        - Application Context가 필요한 경우 `@ApplicationContext` 한정자 사용
        - Activity Context는 라이프사이클 컴포넌트에서만 주입받기
 
        - 의존성이 너무 많은 모듈은 책임 단위로 분리
        - 테스트 모듈에서 `@TestInstallIn(replaces = ...)`을 사용해 fake로 교체 가능한지 확인
 
    # 2) /di 디렉터리 밖에 있는 모듈도 잡기 위한 파일명 패턴
    - path: "**/*Module.kt"
      instructions: |
        Hilt @Module 파일 검증:
        - @Module + @InstallIn 함께 명시
        - flavor별 모듈(예: demo/, prod/)이 있을 때 동일 키 충돌이 없는지
        - 테스트용 fake 모듈이 운영 모듈과 일관된 인터페이스를 노출하는지

Gradle / build script

Gradle 파일은 단순한 빌드 설정처럼 보이지만 빌드 재현성, 보안, 의존성 관리가 모두 모이는 곳입니다. 평문 keystore 비밀번호, 사라진 jcenter 저장소, 버전 하드코딩, deprecated API 같은 항목은 사람이 매 PR마다 일일이 짚기 어렵습니다. *.gradle.ktslibs.versions.toml을 분리해 각자에게 맞는 컨텍스트를 줍니다.

    - path: "**/*.gradle.kts"
      instructions: |
        Gradle Kotlin DSL review:
 
        - 더 이상 사용하지 않는 jcenter() 저장소 사용 금지 (mavenCentral 권장)
        - 알려진 취약점이 있는 의존성 버전 확인
        - 버전 하드코딩보다 version catalog (libs.versions.toml) 사용 권장
        - deprecated된 Gradle API 사용 시 마이그레이션 코멘트
        - JVM target과 Kotlin compileSdk/targetSdk 일관성 확인
        - applicationId, versionCode, versionName이 `BuildConfig`를 통해서만 노출되는지 확인
        - signing config에 평문 keystore 비밀번호가 들어가 있지 않은지 (환경 변수/secrets로 분리)
        - 디버그 전용 라이브러리(`debugImplementation`)와 릴리즈 라이브러리 분리 확인
 
    - path: "**/libs.versions.toml"
      instructions: |
        Version catalog review:
        - 명확한 versions/libraries/plugins/bundles 분리
        - 의존성 그룹화 누락(예: Compose BOM 의존성을 bundle로 묶기) 확인
        - 동일 라이브러리의 중복 정의/충돌 확인

AndroidManifest

매니페스트는 권한, 노출 표면(exported), 보안 정책이 한 자리에 모이는 파일이라, 잘못 추가된 한 줄이 그대로 보안 사고로 이어질 수 있습니다. 새 위험 권한이 추가됐는데 런타임 권한 요청 코드가 함께 들어왔는지, exported="true"가 의도된 것인지, deeplink scheme이 너무 광범위하지 않은지 같은 "리뷰어가 매번 일일이 머리에 떠올려야 하는 체크리스트" 를 자동화합니다.

    - path: "**/AndroidManifest.xml"
      instructions: |
        AndroidManifest review:
 
        - 새로 추가된 위험 권한(`READ_PHONE_STATE`, `READ_CONTACTS`, `ACCESS_FINE_LOCATION` 등)에 대한 사유와 런타임 권한 요청 코드가 PR에 함께 있는지 확인
        - exported 액티비티/서비스/리시버에 대한 명시적 `android:exported` 속성과 노출 사유
        - intent-filter가 너무 광범위하지 않은지 (deeplink scheme, host)
        - `android:debuggable`, `usesCleartextTraffic`, `allowBackup`이 의도와 일치하는지
        - 백업 정책(`android:fullBackupContent`, `android:dataExtractionRules`)이 비밀 데이터를 제외하는지

테스트 코드

테스트 코드는 "통과만 하면 OK" 로 넘어가기 쉽지만, 안드로이드에서는 flaky 테스트와 디스패처 누수가 CI 비용을 가장 많이 차지하는 부분입니다. 테스트 이름 패턴, AAA 패턴, 코루틴은 runTest+TestDispatcher, StateFlow는 Turbine, Compose UI는 createComposeRule, Hilt는 @HiltAndroidTest로 규약을 명시해 두면 신규 입사자도 동일한 패턴으로 테스트를 작성하게 됩니다.

    - path: "**/{test,androidTest}/**/*.kt"
      instructions: |
        Android 테스트 best practices:
 
        공통:
        - 테스트 이름은 시나리오와 기대 결과가 드러나도록 (예: `loginScreen_emptyEmail_showsError`)
        - Arrange-Act-Assert 패턴
        - 테스트는 한 가지 논리적 개념만 검증
        - 공유 가변 상태로 인한 테스트 간 의존성 금지
 
        ViewModel/Coroutine:
        - `runTest` + 주입된 TestDispatcher로 결정론적 테스트
        - StateFlow 검증은 Turbine 라이브러리 사용 권장
        - 시간 기반 테스트는 `advanceTimeBy`/`advanceUntilIdle` 활용
 
        Compose UI 테스트:
        - `createComposeRule` 또는 `createAndroidComposeRule`
        - 테스트에서 검색은 `onNodeWithText`/`onNodeWithTag`/`onNodeWithContentDescription` 사용
        - flaky 방지를 위해 `composeTestRule.waitForIdle()` 또는 `waitUntil` 활용
 
        스크린샷 테스트:
        - Roborazzi/Paparazzi 사용 시 결과 PNG 차이 검토(예: `*.snap.png`, `screenshots/` 폴더)
        - PreviewParameterProvider 활용해 라이트/다크/폰트 크기 변형을 자동 커버
        - record/verify 모드 분리 운영(record는 의도적 갱신 PR에서만)
 
        Hilt 테스트:
        - `@HiltAndroidTest` + `HiltAndroidRule` 사용
        - 모듈 교체에는 `@TestInstallIn(replaces = ProductionModule::class)` 사용
 
        매크로벤치마크/Baseline Profile:
        - `:benchmarks` 모듈에서 BaselineProfileRule, MacrobenchmarkRule 사용
        - profile 결과(`baseline-prof.txt`)가 PR에 함께 들어왔는지 확인
        - cold/warm/hot startup 시나리오와 critical user journey가 빠지지 않았는지

tools: 정적 분석 통합

CodeRabbit은 자체 AI 리뷰 외에도 외부 정적 분석기를 PR에서 같이 돌리는 기능을 제공합니다. Android 프로젝트라면 다음 조합을 권장합니다.

reviews:
  tools:
    # Kotlin 정적 분석
    detekt:
      enabled: true
      # 프로젝트 루트에 detekt.yml이 있다면 명시
      # config_file: "detekt.yml"
 
    # 시크릿 스캐닝
    gitleaks:
      enabled: true
  • detekt: Kotlin 전용 린터. Android Lint와 별개로 코드 스타일, 복잡도, 잠재 버그를 잡습니다. 팀에서 detekt baseline을 운영 중이라면 config_file로 그대로 연결하시면 됩니다.
  • gitleaks: API 키, AWS 자격 증명, OAuth 토큰 같은 시크릿이 커밋에 섞였는지 확인합니다. Android 프로젝트는 local.propertiesgradle.properties에 자칫 키를 두기 쉬워, 머지 전 단계에서 잡는 것이 중요합니다. 보안 관점에서 더 깊은 맥락은 Vercel 침해 사고가 엔터프라이즈 코드 보안에 던지는 세 가지 교훈에서 이어 보실 수 있습니다.

참고: detekt 외 포매터, Spotless / ktlint

NowInAndroid는 detekt 대신 Spotless(ktlint·license header 통합)를 사용합니다. 팀이 이미 Spotless나 ktlint만 운영하고 있다면, 위 yaml에서 detekt 섹션을 빼고 CI에서 ./gradlew spotlessCheck를 별도로 돌리는 편이 자연스럽습니다. CodeRabbit은 코드 스타일이 아닌 로직·아키텍처 차원에 집중하게 두고, 포매팅은 CI에 맡기는 분담이 효과적입니다.

이외에 OSV-Scanner 연동으로 Maven/Gradle 의존성에 대한 알려진 CVE를 함께 점검할 수도 있습니다.

모듈식 프로젝트(Convention plugin) 추가 팁

NowInAndroid처럼 build-logic/convention 에 컨벤션 플러그인을 모아 두는 패턴은 점점 표준이 되고 있습니다. 이 영역은 한 번 잘못 짜면 모든 모듈에 영향이 가므로, 별도 path로 더 엄격히 검사하면 좋습니다.

    - path: "build-logic/**/*.kt"
      instructions: |
        Convention plugin best practices:
        - 모든 plugin 클래스명은 의도가 드러나도록(`AndroidLibraryComposeConventionPlugin` 등)
        - `pluginManager.apply(...)` 호출 시 적용 순서가 의존성 규칙과 일치하는지
        - configureAndroid()/configureKotlin() 같은 헬퍼는 한 책임만 가지도록 분리
        - 버전은 반드시 `libs.versions.toml`에서 끌어오고 하드코딩 금지
        - 새 컨벤션 추가 시 `settings.gradle.kts` 또는 `pluginManagement.includeBuild`에 등록되어 있는지
 
    - path: "**/feature/**/api/**/*.kt"
      instructions: |
        Feature API 모듈 best practices:
        - public 인터페이스만 노출, 구현 세부는 impl 모듈에 두기
        - data class·sealed interface 등 안정적 타입만 export
        - api 모듈에 Hilt @Inject 직접 사용 금지(impl로 위임)

api/impl 분리, flavor(src/demo/, src/prod/), :benchmarks, core/screenshot-testing 같은 모듈식 구조라면, 각각에 맞는 path 패턴을 위와 같이 한두 개 더 얹는 방식으로 확장하실 수 있습니다.

전체 예시: 안드로이드 팀이 그대로 가져다 쓸 수 있는 yaml

# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json
language: ko-KR
early_access: false
 
reviews:
  profile: chill
  high_level_summary: true
  collapse_walkthrough: true
  poem: false
 
  path_filters:
    - "**/*.kt"
    - "**/*.kts"
    - "**/AndroidManifest.xml"
    - "**/*.gradle"
    - "**/*.gradle.kts"
    - "**/libs.versions.toml"
    - "!**/build/**"
    - "!**/.gradle/**"
    - "!**/out/**"
    - "!**/generated/**"
    - "!**/ksp/**"
    - "!**/kapt/**"
    - "!**/R.java"
    - "!**/BuildConfig.java"
    - "!**/databinding/**"
    - "!**/*.md"
 
  auto_review:
    enabled: true
    auto_incremental_review: true
    drafts: false
    base_branches:
      - main
      - develop
    ignore_usernames:
      - dependabot
      - "dependabot[bot]"
      - renovate
      - "renovate[bot]"
 
  path_instructions:
    - path: "**/*.kt"
      instructions: |
        Kotlin best practices: null safety(!! 금지), val 우선, structured concurrency,
        viewModelScope/lifecycleScope 사용, GlobalScope 금지, Result/sealed로 실패 표현,
        하드코딩 시크릿 금지, SecureRandom 사용.
 
    # Composable 화면 (파일명 기반 - 모듈식 프로젝트 대응)
    - path: "**/*Screen.kt"
      instructions: |
        Compose 화면: state hoisting, rememberSaveable로 구성 변경 대응,
        LaunchedEffect/DisposableEffect의 key 검증, MaterialTheme/stringResource 사용,
        하드코딩 색상·문자열 회피, contentDescription/48dp 터치 영역 접근성 점검.
 
    # 디자인 시스템·재사용 UI (NowInAndroid 식 모듈 분리 대응)
    - path: "**/core/{ui,designsystem}/**/*.kt"
      instructions: |
        디자인 시스템: @Composable PascalCase, Modifier 매개변수 디폴트와 위치,
        @Stable/@Immutable 검토(재구성 최적화), 디자인 토큰 우회 하드코딩 금지,
        @Preview 라이트/다크/폰트 변형 제공.
 
    # 단일 모듈 프로젝트 보조 패턴
    - path: "**/{ui,screen,screens,components,composables}/**/*.kt"
      instructions: |
        Jetpack Compose: @Composable PascalCase, Modifier 매개변수 디폴트와 위치,
        state hoisting, rememberSaveable로 구성 변경 대응, 데이터 클래스에 @Stable/@Immutable 검토,
        LaunchedEffect/DisposableEffect의 key 검증, MaterialTheme 사용,
        하드코딩 색상/문자열 회피, contentDescription/접근성 점검.
 
    - path: "**/*ViewModel.kt"
      instructions: |
        ViewModel: viewModelScope 기준, MutableStateFlow는 private+StateFlow 노출,
        Activity/Context 참조 보관 금지, 의존성 생성자 주입, 일회성 이벤트는 SharedFlow/Channel.
 
    - path: "**/{di,inject}/**/*.kt"
      instructions: |
        Hilt: @InstallIn 명시, 적절한 스코프, @Binds 우선, ApplicationContext 한정자 사용,
        테스트 모듈은 @TestInstallIn으로 교체 가능 구조 확인.
 
    # /di 디렉터리 밖에 있는 모듈도 잡기 위한 파일명 패턴
    - path: "**/*Module.kt"
      instructions: |
        Hilt @Module 파일: @Module + @InstallIn 함께 명시, flavor별 동일 키 충돌 점검,
        테스트용 fake 모듈이 운영 모듈과 일관된 인터페이스를 노출하는지.
 
    # 컨벤션 플러그인(build-logic) - 모듈식 프로젝트
    - path: "build-logic/**/*.kt"
      instructions: |
        Convention plugin: 클래스명에 의도 반영, 한 책임 원칙, libs.versions.toml에서 버전 끌어오기,
        plugin 적용 순서 점검.
 
    - path: "**/*.gradle.kts"
      instructions: |
        Gradle: jcenter() 금지, version catalog 사용, deprecated API 마이그레이션,
        signing config에 평문 비밀번호 금지, debug/release 의존성 분리.
 
    - path: "**/libs.versions.toml"
      instructions: |
        Version catalog: versions/libraries/plugins/bundles 정리, Compose BOM bundle 묶음,
        중복 정의 금지.
 
    - path: "**/AndroidManifest.xml"
      instructions: |
        Manifest: 위험 권한 추가에 사유/런타임 요청 동반, exported 명시,
        intent-filter scope, debuggable/cleartextTraffic/allowBackup 의도 일치 확인.
 
    - path: "**/{test,androidTest}/**/*.kt"
      instructions: |
        Android 테스트: 시나리오/기대 결과가 드러나는 이름, AAA 패턴, runTest+TestDispatcher,
        StateFlow는 Turbine, Compose UI는 createComposeRule, Hilt 테스트는 @HiltAndroidTest.
 
  tools:
    detekt:
      enabled: true
    gitleaks:
      enabled: true
 
chat:
  auto_reply: true

도입 후 점검 포인트

설정이 적용된 PR을 한두 개 받아 본 뒤, 다음을 가볍게 점검해 보시는 것을 추천 드립니다.

  • 자주 쓰는 패키지 경로(features/, core/, presentation/ 등)가 path_instructions의 glob 패턴에 잘 들어오는가? 패키지 구조가 다르다면 path 부분만 팀 컨벤션에 맞게 바꿔 주세요.
  • detekt가 이미 잡는 항목과 CodeRabbit 코멘트가 중복되는 비율이 너무 높지 않은가? 중복이 심하면 instructions에서 "detekt가 잡는 스타일 nitpick은 제외" 같은 한 줄을 추가해 둘 수 있습니다.
  • Compose 관련 코멘트의 정확도가 낮다면 패키지 컨벤션과 glob을 다시 맞추거나, 팀 내부 컴포넌트 컨벤션(예: 자체 Modifier 라이브러리, 디자인 토큰)을 instructions에 추가하면 효과가 큽니다.

조직 단위로 동일한 정책을 강제하고 싶다면, 조직에 coderabbit 레포지터리를 만들고 같은 yaml을 Central Configuration으로 올려두면 모든 안드로이드 레포가 같은 기준으로 리뷰됩니다. 더 큰 그림에서 AI 코드 리뷰 도구를 평가하고 비교하는 관점은 AI 코드 리뷰 도구 완벽 가이드 2026에서, CodeRabbit이 처음이시라면 CodeRabbit으로 깃헙 PR 코드 리뷰 자동화 하기를 함께 보시기를 추천 드립니다.

이 설정을 들고 PR을 한 번 올려 보시고, 실제로 받은 리뷰를 팀에 공유해 보세요. AI 코드 리뷰의 톤과 깊이가 "팀 시니어 안드로이드 개발자가 본 듯" 한 모습으로 바뀌는 게 가장 큰 변화일 겁니다.

CodeRabbit 시작하기