Skip to content

코딩 컨벤션

Kotlin 스타일

  • kotlin.code.style=official 설정 사용
  • allWarningsAsErrors 옵션 지원 (프로젝트 속성으로 토글 가능)
  • JVM Target: 21 (Java 21)
  • 컴파일러 옵션:
    • -opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi
    • -XXLanguage:+PropertyParamAnnotationDefaultTargetMode

정보

ktlint, detekt 등 별도 정적 분석 도구는 사용하지 않습니다. 코드 품질 분석은 Qodana를 통해 수행합니다.

네이밍 컨벤션

파일 네이밍

유형패턴예시
Composable Screen{Screen}Route.ktHomeRoute.kt, LoopRoute.kt
ViewModel{Screen}ViewModel.ktHomeViewModel.kt
UiState{Screen}UiState.ktHomeUiState.kt
UiEvent{Screen}UiEvent.ktHomeUiEvent.kt
SideEffect{Screen}SideEffect.ktHomeSideEffect.kt
Env + EnvModule{Screen}Env.ktHomeEnv.kt
Navigation{Screen}Navigation.ktHomeNavigation.kt
EventTracker{Screen}EventTracker.ktHomeEventTracker.kt
API Interface{Domain}Api.ktLoopApi.kt, AuthApi.kt
DataSource{Domain}RemoteDataSource.ktLoopRemoteDataSource.kt
Local DataSource{Domain}LocalDataSource.ktLoopLocalDataSource.kt
Repository Interface{Domain}Repository.ktFeedRepository.kt
Repository Impl{Domain}RepositoryImpl.ktFeedRepositoryImpl.kt
Request DTO{Action}RequestDto.ktCreateLoopRequestDto.kt
Response DTO{Action}ResponseDto.ktGetLoopResponseDto.kt
Hilt Module{Domain}Module.ktNetworkModule.kt, ApiModule.kt
UseCase (fun interface){Action}UseCaseObserveUserUseCase, JoinLoopUseCase
UseCase (구현){Action}UseCase.ktObserveUserUseCase.kt
Domain API typealiasApiTypes.ktdomain/loop/ApiTypes.kt
Domain Model{ModelName}.ktCurrentUser.kt, Loop.kt

클래스/인터페이스 네이밍

유형패턴예시
ViewModel{Screen}ViewModelHomeViewModel
Sealed Interface (Event){Screen}UiEventHomeUiEvent
Sealed Interface (Effect){Screen}SideEffectHomeSideEffect
Data Class (State){Screen}UiStateHomeUiState
Hilt Module{Domain}ModuleNetworkModule
Qualifier@{Name}Identifier@RefreshCurrentUserIdentifier, @JoinLoopIdentifier

Compose 컨벤션

Composable 함수

kotlin
// Navigation 3 EntryProviderScope 확장 함수
fun EntryProviderScope<Any>.homeEntries(navigator: AppNavigator) {
    entry<HomeKey> { key ->
        HomeRoute(
            navigator = navigator,
            homeSharedViewModel = hiltViewModel(),
        )
    }
}

Stability 최적화

  • @Immutable: UiState data class에 적용하여 Compose 안정성 보장
  • @Stable: ViewModel, 변경 가능한 객체에 적용
  • stability_config.conf: 프로젝트 루트에 Compose Stability 설정 파일 관리 (현재 co.butbeautiful.truloop.core.data.model.* 등록)
kotlin
@Immutable
internal data class HomeUiState(
    val me: CurrentUser = CurrentUser.EMPTY,
    val selectedTabIndex: Int = 0,
    val error: TruloopError? = null,
)

@HiltViewModel
@Stable
internal class HomeViewModel @Inject constructor(/* ... */) : ViewModel()

접근 제한자

  • Feature 모듈의 UiState, UiEvent, SideEffect, ViewModel, Route는 internal로 선언
  • Navigation Entry 함수 (fun EntryProviderScope<Any>.{name}Entries(...))만 public으로 외부에 노출
  • API Interface, DataSource는 internal로 선언

아키텍처 컨벤션

MVI 패턴 규칙

규칙설명
단방향 데이터 흐름View -> UiEvent -> ViewModel -> UiState -> View
단일 UiEvent 핸들러onUiEvent(event: UiEvent) 함수 하나로 모든 이벤트 처리
Immutable StateUiState는 data class로 정의하고 copy()로만 변경
SideEffect는 Channel일회성 이벤트는 Channel로 전달

Repository 패턴 규칙

  • core:data 모듈에 Repository 인터페이스와 구현체 (RepositoryImpl) 모두 위치
  • DataSource를 통해 API 호출 (Remote) 또는 DB 접근 (Local)
  • DTO -> Domain Model 변환은 Repository에서 수행

Domain 모듈 규칙

  • Domain 모듈은 fun interface로 UseCase를 정의
  • API 타입은 typealias로 정의 (ApiTypes.kt)하고, Queries/Commands data class로 그룹화
  • UseCase DI에는 @Qualifier annotation (@{Name}Identifier)을 사용
  • Domain 모듈 간 의존성은 허용 (예: domain:loop -> domain:place, domain:user)

Env 패턴 규칙

kotlin
// Env: Feature에 필요한 UseCase를 data class로 캡슐화
data class HomeEnv(
    val refreshUser: RefreshCurrentUserUseCase,
    val observeUser: ObserveUserUseCase,
    val joinLoop: JoinLoopUseCase
)

// EnvModule: Hilt Module에서 Env를 생성하여 제공
@Module
@InstallIn(ViewModelComponent::class)
class HomeEnvModule {
    @Provides
    fun providesEnv(
        @RefreshCurrentUserIdentifier refreshCurrentUserUseCase: RefreshCurrentUserUseCase,
        @ObserveUserIdentifier observeCurrentUser: ObserveUserUseCase,
        @JoinLoopIdentifier joinLoop: JoinLoopUseCase,
    ): HomeEnv {
        return HomeEnv(
            refreshUser = refreshCurrentUserUseCase,
            observeUser = observeCurrentUser,
            joinLoop = joinLoop
        )
    }
}
  • Feature별 하나의 Env data class + EnvModule 쌍을 사용
  • Env는 data class로 정의하고, Hilt @Module + @Provides를 통해 DI
  • ViewModel의 생성자 파라미터를 간결하게 유지
  • UseCase 호출을 Env에 위임

패키지 구조

Feature 모듈

feature/{name}/src/main/java/co/butbeautiful/truloop/feature/{name}/
  {Name}Route.kt              -- Composable 진입점 (internal)
  {Name}ViewModel.kt          -- ViewModel (internal)
  {Name}UiState.kt            -- UI State (internal)
  {Name}UiEvent.kt            -- UI Event (internal)
  {Name}SideEffect.kt         -- Side Effect (internal)
  {Name}Env.kt                -- Env data class + EnvModule
  {Name}Navigation.kt         -- Navigation 3 entry 정의 (public)
  {Name}EventTracker.kt       -- Analytics 이벤트 트래커

Core Data 모듈

core/data/src/main/java/co/butbeautiful/truloop/core/data/
  api/               -- Retrofit API 인터페이스
    ApiModule.kt     -- API DI 모듈
    LoopApi.kt
    AuthApi.kt
    ...
  datasource/        -- DataSource 구현
    local/           -- 로컬 DataSource
    LoopRemoteDataSource.kt
    ...
  repository/        -- Repository 인터페이스 + 구현
    RepositoryModule.kt
    FeedRepository.kt
    FeedRepositoryImpl.kt
    ...
  model/
    domain/          -- Domain 매핑 모델
    request/         -- Request DTO
    response/        -- Response DTO
  mapper/            -- DTO <-> Domain 변환 매퍼
  di/                -- 추가 DI 모듈

Domain 모듈

domain/{name}/src/main/kotlin/co/butbeautiful/truloop/domain/{name}/
  UseCases.kt        -- fun interface UseCase 정의
  ApiTypes.kt        -- typealias API 타입 정의
  api/               -- Queries/Commands data class (API 그룹)
    core/
    ...
  usecase/           -- UseCase 구현체
  model/             -- Domain Model
  di/                -- UseCase DI 모듈

에러 처리 컨벤션

  • truloopExceptionHandler를 사용하여 CoroutineScope에서 에러를 UiState에 반영
  • TruloopError 인터페이스로 통일된 에러 표현 (core:ktx 모듈)
  • GeneralTruloopError sealed interface가 구체적인 에러 유형 정의:
    • SnackBar — Snackbar로 표시할 에러
    • Dialog — Dialog로 표시할 에러
    • Specified — 서버의 display_type에 따른 에러
    • InternalError — 네트워크/서비스 이용 불가 등 내부 에러
kotlin
// ViewModel에서의 사용 패턴
private val exceptionHandler = truloopExceptionHandler { error ->
    _uiState.update { it.copy(error = error) }
}

// exceptionHandler를 CoroutineScope에 적용
viewModelScope.launch(exceptionHandler) {
    env.refreshUser()
}

변경 이력

날짜변경 내용
2026-03-10Navigation 3 패턴 반영, Env 패턴 수정 (data class + Module), Qualifier 네이밍 수정 (@{Name}Identifier), Repository/Domain 모듈 구조 정정, TruloopError 상세화, 접근 제한자 규칙 수정