Android 개발/Kotlin

Kotlin 2.4 RC context parameters로 LoggerContext 6인자 날린 후기

stackD 2026. 6. 10. 18:00

 

메서드 시그니처 평균 5.2줄이 1.4줄로, 파일 LOC는 312에서 218로 약 30% 감소했습니다. 코틀린(Kotlin) 2.4 context parameters를 LoggerContext 인자 6개에 적용한 실제 수치예요.

 

5월 13일 풀린 2.4.0-RC를 사이드 프로젝트 안드로이드(Android) 모듈에 올려둔 지 열흘 정도 되었는데요, 그동안 도메인 레이어 한 곳에 부분 적용해본 후기를 정리해봤어요. 빌드 자체는 RC지만 context parameters API는 이번 RC에서 Stable로 확정된 상태라, 문법만 놓고 보면 정식 릴리스 때 그대로 굳혀도 됩니다.

 

Kotlin 2.4 context parameters Stable, 무엇이 풀렸나

2.4.0-RC부터 context parameters가 Stable로 승격됐습니다. -Xcontext-parameters 컴파일러 플래그가 빠졌다는 게 가장 큰 변화예요. 더 이상 Gradle 설정에 플래그를 끼워둘 필요가 없어졌습니다.

 

다만 모든 게 한 번에 풀린 건 아닙니다. 호출부에서 명시적으로 전달하는 context arguments, 그리고 ::process 같은 callable reference는 JetBrains 공식 블로그(Update on Context Parameters) 기준으로 여전히 Experimental로 남아 있어요. 본인 코드에서 이 두 가지를 자주 쓴다면 RC를 그대로 올리기 전 한 번 점검하시는 게 좋겠습니다.

 

옛 문법인 context receivers(context(Logger))는 deprecated 처리됐습니다. 인텔리J(IntelliJ) 인텐션으로 파일 단위 일괄 변경이 가능하더라고요. 저는 이걸로 모듈 4개를 30분 만에 옮겼습니다.

 

 

Hilt·Koin은 그대로 둡니다, 적용 자리는 따로

먼저 짚고 가야 할 게 하나 있는데요, context parameters는 Hilt나 Koin을 대체하는 도구가 아닙니다. 처음 봤을 땐 "이거 DI 프레임워크 안 써도 되는 거 아냐?" 싶었는데 직접 만져보니 역할이 분명히 달랐어요.

 

Hilt와 Koin은 안드로이드 생명주기에 묶인 전역·컴포넌트 스코프 의존성을 관리합니다. @HiltViewModel, 액티비티/프래그먼트 스코프 바인딩 같은 자리예요. 반면 context parameters는 함수 호출 체인 안에서 이미 주입된 객체를 전파하는 지역 스코프 도구입니다. 컴파일 타임에 정적으로 풀린다는 점이 핵심이에요.

 

그래서 진가가 살아나는 자리가 ViewModel → UseCase → Repository처럼 길게 이어지는 호출 사슬, 그리고 Hilt를 쓰기 힘든 KMP(Kotlin Multiplatform) 공유 모듈입니다. 저도 KMP 공유 모듈의 Repository 두 개부터 옮겨봤어요.

 

LoggerContext 인자 6개를 context로 옮긴 실제 diff

가장 효과를 크게 봤던 건 사내 LoggerContext였습니다. 원래는 도메인 함수마다 logger, traceId, userSession, featureFlags, clock, analytics 이렇게 6개가 줄줄이 따라다녔거든요. 이 중 다섯은 그냥 다음 레이어로 흘려보내기만 하는 보일러플레이트였습니다.

// 기존
suspend fun loadFeed(
    userId: String,
    logger: Logger,
    traceId: String,
    session: UserSession,
    flags: FeatureFlags,
    clock: Clock,
    analytics: Analytics,
): Feed { ... }

// 변경
context(logger: Logger, session: UserSession, flags: FeatureFlags, clock: Clock, analytics: Analytics)
suspend fun loadFeed(userId: String, traceId: String): Feed {
    logger.info("loadFeed start $traceId")
    // ...
}

 

호출부 ViewModel에서는 context(loggerCtx, sessionCtx, flagsCtx, clockCtx, analyticsCtx) { useCase.loadFeed(userId, traceId) } 형태로 한 번에 묶어 넘겨줍니다. 보일러플레이트가 진입점 한 곳에만 모이는 구조가 되더라고요.

 

수치로 보면 메서드 시그니처가 평균 5.2줄에서 1.4줄로 줄었고, 도메인 모듈 한 파일 기준 LOC가 312에서 218로 약 30% 감소했어요. PR 리뷰할 때 시그니처가 한눈에 들어와서 검토 시간 자체가 짧아진 게 의외의 수확이었습니다.

 

 

Hilt와 같이 쓸 때 걸리는 3가지

같이 굴려보면서 조심해야겠다 싶었던 자리를 정리해봤습니다.

  1. @HiltViewModel 생성자에는 적용 불가입니다. ViewModelProvider.Factory 경로와 충돌하니까, ViewModel 안에서는 suspend fun 메서드 레벨에만 쓰셔야 해요.
  2. 같은 타입 컨텍스트가 중첩되면 의도치 않은 shadowing이 일어날 수 있습니다. DomainLogger, NetworkLogger처럼 타입을 쪼개 두니 사이드 프로젝트에서는 충돌이 사라졌어요.
  3. 함수 시그니처만 봐서는 어떤 의존성이 흐르는지 한 번에 안 잡혀요. PR 템플릿에 "이 함수가 받는 context 목록"을 적게 해두는 식의 팀 규칙이 같이 가야 리뷰 비용이 안 늘어납니다.

개인적으로는 세 번째 항목이 진짜 변수라고 봅니다. 코드 줄 수가 줄어든 만큼 암시적인 부분이 늘어난다는 맞바꿈이 있거든요. 저는 PR 설명란에 "이 함수의 context 의존성" 한 줄을 박는 규칙을 사이드 프로젝트에서도 적용해뒀어요.

 

 

Kotlin 2.4 Stable 출시 전 선적용 가이드

지금이 RC 단계이니, 전 모듈에 한 번에 바르기보다는 부분 적용으로 감을 잡아두시는 걸 권합니다. 제 경우엔 멀티모듈 프로젝트의 도메인 레이어 한 모듈, 그리고 KMP 공유 모듈의 Repository 1~2개부터 이관해두었어요.

 

코틀린 버전을 2.4.0-RC로 올리면서 KSP, Hilt, Compose 컴파일러와의 호환성 매트릭스는 반드시 확인하셔야 합니다. 저는 KSP 버전이 한 번 어긋나서 빌드가 통째로 깨졌던 적이 있어서, 라이브러리 버전은 Version Catalog로 한꺼번에 묶어두고 올렸어요.

 

마이그레이션 PR은 파일 단위로 쪼개는 게 리뷰하는 사람도 편합니다. 인텔리J 인텐션이 한 방에 다 바꿔주긴 하는데, 그걸 한 PR에 다 담으면 변경 줄 수가 700~800줄씩 나와서 리뷰가 멈춰버리더라고요. 모듈 → 패키지 → 파일 순으로 잘게 나누는 쪽이 통과 속도가 훨씬 빨랐습니다.

 

순수 단일 모듈 안드로이드 앱이라면 RC 단계에서 무리해서 들어갈 필요는 없다고 봅니다. 호출 사슬이 길거나 모듈 경계를 자주 넘는 코드에서 효과가 큰 도구인데, 단일 모듈에서는 체감이 그만큼 크지 않았어요. 저도 메인 앱 모듈은 일단 Stable 정식 릴리스까지 두고 보기로 했습니다.