스팀 앱 개발기 #144 - 개발 완료: 리팩토링: 타입 안전성과 일관성 개선

개발 완료: 리팩토링: 타입 안전성과 일관성 개선

No. 144
2026. 04. 16 (목) | Written by @dorian-mobileapp

이제부터는 클로드를 활용하여 앱을 개발하고 있습니다. 작업 내용도 AI로 요약이 가능하네요. 코드 수정한 내용을 일일이 복기하여 설명 또는 요약하기가 현실적으로 너무 어려웠습니다. 앞으로는 클로드의 도움을 많이 받게 될 것입니다.


Steem 앱 리팩토링: 타입 안전성과 일관성 개선

이번 작업 개요

이번 작업에서는 Navigation Compose 구현 후 코드 리뷰를 통해 발견된 개선점들을 반영했습니다.
주요 목표는 타입 안전성 강화ViewModel 패턴의 일관성 확보였습니다.

주요 변경사항

  1. SavedStateHandle 패턴 적용: 모든 ViewModel에서 일관되게 Navigation 파라미터를 처리하도록 개선
  2. Type-safe Enum 도입: 하드코딩된 문자열을 Enum으로 교체하여 컴파일 타임 안전성 확보
  3. 코드 정리: 레거시 코드 제거 및 명확한 문서화

이러한 변경을 통해 실수를 줄이고, 유지보수가 쉬운 코드베이스를 만들 수 있었습니다.


중요한 변경 코드 소개

1. SavedStateHandle을 활용한 Navigation 파라미터 처리

기존 방식의 문제점: ViewModel이 Navigation 파라미터를 받는 방식이 불일치했고, 타입 안전성이 부족했습니다.

개선된 코드 (PostListViewModel 예시):

@HiltViewModel
class PostListViewModel @Inject constructor(
    val savedStateHandle: SavedStateHandle,
    private val readPostsUseCase: ReadPostsUseCase
) : BaseViewModel() {

    // Navigation 파라미터를 타입 안전하게 추출
    private val postListRoute: PostListRoute = savedStateHandle.toRoute()

    fun readPosts() = viewModelScope.launch {
        val apiResult = readPostsUseCase(
            postListRoute.account,
            postListRoute.sort
        )
        // ...
    }
}

장점:

  • 컴파일 타임에 파라미터 타입 체크
  • IDE 자동완성 지원
  • 모든 ViewModel에서 동일한 패턴 사용

AccountDetailsViewModel, AccountHistoryViewModel, PostContentViewModel도 동일한 패턴으로 통일했습니다.


2. PostSortType Enum으로 타입 안전성 확보

기존 코드 (하드코딩된 문자열):

// ❌ 오타 위험, IDE 지원 없음
onPostListMenuClicked(profile.account, "blog")
onPostListMenuClicked(profile.account, "posts")

개선된 코드:

// 1. Enum 정의
enum class PostSortType(val value: String) {
    BLOG("blog"),
    POSTS("posts"),
    COMMENTS("comments"),
    REPLIES("replies")
}

// 2. 사용
onPostListMenuClicked(profile.account, PostSortType.BLOG.value)
onPostListMenuClicked(profile.account, PostSortType.POSTS.value)

장점:

  • 오타 방지 (컴파일러가 체크)
  • 사용 가능한 값들을 한눈에 파악
  • 리팩토링 시 안전하게 변경 가능

3. ProfileMenuItemID로 메뉴 아이템 식별 개선

프로필 화면의 메뉴 아이템(Details, Blog, Posts 등)을 배열 인덱스로 관리하던 방식을 ID 기반으로 변경했습니다.

개선된 구조:

// 1. 메뉴 ID 정의
enum class ProfileMenuItemID(val value: String) {
    DETAILS("details"),
    BLOG("blog"),
    POST("post"),
    COMMENTS("comments"),
    REPLIES("replies"),
    HISTORY("history")
}

// 2. ProfileMenuItem에 ID 추가
data class ProfileMenuItem(
    val id: ProfileMenuItemID,
    val name: String,
    val textColor: Color,
    val fontSize: Int,
    val backgroundColor: Color,
)

// 3. 메뉴 아이템 정의
val profileMenuItems = listOf(
    ProfileMenuItem(ProfileMenuItemID.DETAILS, "Details", ...),
    ProfileMenuItem(ProfileMenuItemID.BLOG, "Blog", ...),
    // ...
)

장점:

  • 메뉴 순서가 바뀌어도 로직에 영향 없음
  • 메뉴 아이템 추가/제거가 쉬움
  • 의도가 명확한 코드

4. 버그 수정: AccountHistoryRoute 타입 오류

TopBar 제목을 설정하는 코드에서 잘못된 Route 타입을 사용하는 버그를 발견하고 수정했습니다.

// ❌ 버그
destination?.hasRoute<AccountHistoryRoute>() == true -> {
    val route = navBackStackEntry?.toRoute<AccountDetailsRoute>()  // 잘못된 타입!
    "Account History - @${route?.account}"
}

// ✅ 수정
destination?.hasRoute<AccountHistoryRoute>() == true -> {
    val route = navBackStackEntry?.toRoute<AccountHistoryRoute>()  // 올바른 타입
    "Account History - @${route?.account}"
}

타입 안전 Navigation 덕분에 발견할 수 있었습니다.


마무리

이번 리팩토링을 통해:

  • 타입 안전성이 크게 향상되었습니다 (Enum, SavedStateHandle)
  • 코드 일관성이 확보되었습니다 (모든 ViewModel의 동일한 패턴)
  • 유지보수성이 개선되었습니다 (명확한 구조, 컴파일 타임 체크)

작은 변경들이 모여 더 견고한 코드베이스를 만들 수 있었습니다. 다음 단계에서는 메뉴 클릭 핸들러를 ID 기반으로 통합하는 작업을 진행할 예정입니다.


다음 예정 작업
  • Claude가 제안한 Jetpack Compose 마이그레이션 1단계

GitHub Commit

보다 자세한 코드는 아래 commit을 참고하세요.


지난 스팀 앱 개발기


Layout provided by Steemit Enhancer hommage by ayogom

Posted using SteemX

Sort:  

Upvoted! Thank you for supporting witness @jswit.