스팀 앱 개발기 #144 - 개발 완료: 리팩토링: 타입 안전성과 일관성 개선
개발 완료: 리팩토링: 타입 안전성과 일관성 개선
이제부터는 클로드를 활용하여 앱을 개발하고 있습니다. 작업 내용도 AI로 요약이 가능하네요. 코드 수정한 내용을 일일이 복기하여 설명 또는 요약하기가 현실적으로 너무 어려웠습니다. 앞으로는 클로드의 도움을 많이 받게 될 것입니다.
Steem 앱 리팩토링: 타입 안전성과 일관성 개선
이번 작업 개요
이번 작업에서는 Navigation Compose 구현 후 코드 리뷰를 통해 발견된 개선점들을 반영했습니다.
주요 목표는 타입 안전성 강화와 ViewModel 패턴의 일관성 확보였습니다.
주요 변경사항
- SavedStateHandle 패턴 적용: 모든 ViewModel에서 일관되게 Navigation 파라미터를 처리하도록 개선
- Type-safe Enum 도입: 하드코딩된 문자열을 Enum으로 교체하여 컴파일 타임 안전성 확보
- 코드 정리: 레거시 코드 제거 및 명확한 문서화
이러한 변경을 통해 실수를 줄이고, 유지보수가 쉬운 코드베이스를 만들 수 있었습니다.
중요한 변경 코드 소개
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을 참고하세요.
지난 스팀 앱 개발기
- #143 - 개발 완료: 내비게이션 컴포즈 활용하여 프로필 화면으로부터 다른 화면 이동
- #142 - 개발 완료: API 연동 코드 자동 생성 방안
- #141 - 개발 완료: Main2Activity 관련 리팩토링
- #140 - 개발 완료: Navigation Compose 활용하여 태그 화면에서 포스트 화면 이동
- #139 - 개발 완료: Main2Activity 클래스를 추가하고 내비게이션 라이브러리 변경
- #138 - 개발 완료: Hilt 라이브러리 활용한 의존성 주입 구현
- #137 - 개발 완료: libs.versions.toml 파일 생성
- #136 - 개발 완료: 사용자 히스토리 화면에서 스팀파워 보상을 SP로 보여주기
- #135 - 개발 완료: 태그 및 포스트 리스트 화면에서 텍스트의 HTML 태그 제거
- #134 - 개발 완료: (1) 태그/포스트 리스트 화면의 포스트 항목에 댓글 개수 보여주기 (2) 댓글 리스트를 Jetpack Compose로 리뉴얼
- #133 - 개발 완료: (1) 검색 버튼 클릭시 키보드 숨기기 (2) 버그 수정: 팔로잉 계정 수 오류
- #132 - 개발 완료: 포스트 이미지 페이저 화면에 XML 대신 Jetpack Compose 적용
- #131 - 개발 완료: 프로필 이미지 화면에 XML 대신 Jetpack Compose 적용
- #130 - 개발 완료: 태그 화면에 XML 대신 Jetpack Compose 적용
- #129 - 개발 완료: 지갑 화면에 XML 대신 Jetpack Compose 적용
- #128 - 개발 완료: 포스트 리스트 화면에 XML 대신 Jetpack Compose 적용
- #127 - 개발 완료: 프로필 화면에 XML 대신 Jetpack Compose 적용
- #126 - 개발 완료: 계정 히스토리 화면에 XML 대신 Jetpack Compose 적용
- #125 - 개발 완료: 댓글 리스트 바텀시트에 XML 대신 Jetpack Compose 적용
- #124 - 개발 완료: 포스트 화면에 XML 대신 Jetpack Compose 적용
- #123 - 개발 완료: 계정 상세 화면
- #122 - 개발 완료: 포스트 화면에서 댓글 리스트 보여주기
- #121 - 개발 완료: 사용자 히스토리 화면에서 항목 클릭시 링크 팝업 띄우기
- #120 - 개발 완료: 사용자 히스토리 화면
- #119 - 개발 완료: Comments, Replies 화면
- #118 - 개발 완료: 포스트(Posts) 화면
- #117 - 개발 완료: 블로그 화면
- #116 - 개발 완료: 프로필 화면
- #115 - 개발: 프로필 화면 구현에 필요한 ProfileViewModel 클래스 개발
- #114 - 수정: 태그 화면의 포스트 리스트 항목 레이아웃 변경
- #113 - 오류 수정: 태그 리스트 로딩 중 다른 화면 이동하면 앱 비정상 종료
- #112 - 포스트 화면에서 보팅 리스트 화면으로 연결
- #111 - 시작 화면 추가
- #110 - 오류 수정: 지갑 화면 시작시 오류 토스트 뜸
- #109 - 개선: 태그 입력하고 검색 버튼 클릭 또는 엔터 키 누르면 키보드 닫기
- #108 - 개선: 태그 검색창에 엔터 키 적용
- #107 - 버그 수정: 태그 화면 복귀시 포스트 리스트 다시 로딩됨
- #106 - 포스트 화면 개발
- #105 - 포스트 화면의 로직 처리를 담당할 PostViewModel 클래스 개발
- #104 - 포스트 내용 읽기 기능을 Repository, Use Case 패턴에 적용
- #103 - 포스트 내용을 읽기 위한 bridge.get_discussion API 연동 코드 작성
- #102 - 포스트 내용을 읽기 위한 bridge.get_discussion API
- #101 - build.gradle에서 라이브러리 이름과 버전 통합
- #1 ~ #100
Upvoted! Thank you for supporting witness @jswit.