본문 바로가기

Language/Kotlin

[Kotlin] Sealed Class와 Result 패턴 복습과제

 

Sealed Class · Result 패턴 — 개념 빈칸 문제
안내: ( )에 알맞은 말을 적으세요. 영어 키워드도 OK입니다.
정답은 맨 아래에 있습니다.

 

1. Sealed Class

1-1. sealed class는 가능한 ( )의 개수가 타입 정의에 고정되어 있다.

 

1-2. sealed class는 보통 ( )처럼 직접 인스턴스를 만들 수 없다.

 

1-3. 추가 데이터가 없는 상태 하나만 표현할 때는 data ( )를 쓴다.

 

1-4. 필드가 있는 상태는 data ( )로 만든다.

 

1-5. sealed의 하위 타입은 보통 ( ) 파일에 선언하며, 바깥에서 임의로 상속할 수 없다.

 

1-6. when에서 is OrderStatus.Paid처럼 분기하면, 해당 블록 안에서 ( ) cast로 status.paidAt 같은 필드를 쓸 수 있다.

 

1-7. sealed 타입을 when의 대상으로 쓰고 모든 하위 타입을 is로 처리하면, ( ) 없이 when을 끝낼 수 있다. (exhaustive)

 

1-8. 나중에 Shipped 같은 하위 타입을 추가하면, 관련 when이 ( ) 에러가 나 빠진 분기를 찾기 쉽다.

 

1-1. 하위 타입(변형/케이스)
1-2. abstract class
1-3. object
1-4. class
1-5. 같은
1-6. smart
1-7. else
1-8. 컴파일

2. enum · open class와 비교

2-1. enum은 상수 개수가 고정이고, sealed class는 하위 타입마다 ( ) 데이터를 다르게 가질 수 있다.

 

2-2. open class는 어디서든 상속 가능하지만, sealed class는 ( )된 곳에서만 하위 타입을 둘 수 있다.

 

2-3. String이나 Int 코드만으로 상태를 두면 ( )나 잘못된 조합이 생기기 쉽다.

2-1. 서로 다른(다른)
2-2. 제한
2-3. 오타

 

 

3. Sealed Interface

3-1. sealed class 대신 sealed ( )로도 같은 “닫힌 타입” 패턴을 만들 수 있다.

 

3-2. UiState<out T>에서 Loading·Error가 UiState<Nothing>인 이유는, 그 상태에는 성공 ( ) T가 없기 때문이다.

 

3-3. 화면의 로딩/성공/실패를 하나의 타입으로 묶을 때 UiState 같은 ( ) interface를 자주 쓴다.

 

3-1. interface
3-2. 데이터(값)
3-3. sealed

4. Result 패턴

4-1. Result 패턴은 작업 결과를 ( )과 ( )으로 나누어 반환 타입에 담는 설계 방식이다.

 

4-2. 실패를 매번 ( )만 쓰지 않고, 호출하는 쪽이 when 등으로 반드시 분기하게 만드는 것이 목적이다.

 

4-3. AppResult의 두 갈래는 ( )와 ( )이다.

 

4-4. AppError도 sealed로 두면, 실패 ( )를 when으로 안전하게 나눌 수 있다.

 

4-5. Failure가 AppResult<Nothing>인 것은, 실패 브랜치에는 성공 ( )이 없다는 뜻이다.

 

4-6. findUser가 User? 대신 AppResult<User>를 반환하면, “없음”을 null이 아니라 AppError.( ) 같은 타입으로 구분할 수 있다.

 

4-1. 성공 / 실패 (Success / Failure도 정답)
4-2. throw(예외)
4-3. Success / Failure
4-4. 종류(이유)
4-5. 값(value)
4-6. NotFound

 

5. kotlin.Result

5-1. Kotlin 표준 라이브러리의 Result<T>는 주로 ( )로 블록을 감싸 예외를 Failure로 만든다.

 

5-2. parseId("abc").getOrElse { -1L }에서 getOrElse는 ( )일 때 대체 값을 준다.

 

5-3. 도메인/API에서 에러 종류를 명확히 쓰려면 kotlin.Result보다 sealed( ) + AppError를 쓰는 경우가 많다.

 

5-4. onSuccess / onFailure는 값을 꺼내 ( ) 효과(출력·로깅 등)를 낼 때 쓴다.

5-1. runCatching
5-2. 실패
5-3. AppResult
5-4. 부수

6. 체이닝 · 예외 (개념)

6-1. map은 Success일 때만 값을 ( )하고, 이미 Failure면 그대로 돌려준다.

 

6-2. 다음 단계도 AppResult를 반환할 때는 map이 아니라 ( )을 쓴다.

 

6-3. 연속 작업 중 한 단계라도 Failure이면, 뒤 단계는 실행하지 않고 ( )가 전파된다.

 

6-4. “사용자 없음”, “입력 오류”처럼 예상 가능한 실패는 AppResult로, 라이브러리가 던지는 예외는 often ( )에서 AppResult로 바꾼다.

 

6-5. HTTP 응답은 HttpResponse 같은 sealed로, 비즈니스 결과는 AppResult로 나누는 것은 ( )마다 역할을 분리하기 위함이다.

6-1. 변환
6-2. flatMap
6-3. Failure
6-4. 경계(레이어)
6-5. 레이어

7. 한 줄 연결 (짝 맞추기)

왼쪽 번호와 오른쪽 설명을 연결하세요. (답: 1-( ), 2-( ) …)

 

① sealed class

A. runCatching에 편함

② AppResult

B. 가능한 경우 N개 고정

③ UiState

C. Success / Failure

④ kotlin.Result

D. Loading / Content / Error

 

 

①-B, ②-C, ③-D, ④-A

8. O / X (개념)

8-1. ( ) sealed class는 어디서나 자유롭게 상속할 수 있다.

 

8-2. ( ) enum은 Paid(시각)처럼 하위마다 다른 필드를 두기 어렵다. (일반적인 학습 수준 기준)

 

8-3. ( ) AppResult.Failure인데도 result.value를 항상 쓸 수 있다.

 

8-4. ( ) when에서 모든 sealed 하위 타입을 처리하면 else가 필요 없을 수 있다.

 

8-5. ( ) Result 패턴은 Kotlin 언어에만 있는 공식 문법 키워드이다.

 

8-6. ( ) kotlin.Result의 실패는 대부분 Throwable(예외)이다.

8-1. X
8-2. O
8-3. X
8-4. O
8-5. X (관용구/설계 패턴)
8-6. O

연습·암기용으로 짧게 묶은 예제

한 파일에 붙여 넣고 main만 실행해 보면 됩니다.

// ========== 1. Sealed Class — 가능한 상태를 타입으로 고정 ==========

sealed class OrderStatus {
    data object Created : OrderStatus()                          // 데이터 없는 상태
    data class Paid(val paidAt: Long) : OrderStatus()            // 결제 시각
    data class Cancelled(val reason: String) : OrderStatus()     // 취소 사유
}

// when + is : 모든 하위 타입을 처리하면 else 없이 OK (exhaustive)
fun orderMessage(status: OrderStatus): String = when (status) {
    is OrderStatus.Created -> "주문 생성됨"
    is OrderStatus.Paid -> "결제 완료: ${status.paidAt}"           // smart cast로 paidAt 사용
    is OrderStatus.Cancelled -> "취소: ${status.reason}"
}


// ========== 2. Sealed Interface — 화면 상태 묶기 (class와 사용법 동일) ==========

sealed interface UiState<out T> {
    data object Loading : UiState<Nothing>                       // 아직 데이터 없음
    data class Content<T>(val data: T) : UiState<T>              // 성공, 데이터 있음
    data class Error(val message: String) : UiState<Nothing>     // 실패, 데이터 없음
}

fun uiMessage(state: UiState<String>): String = when (state) {
    is UiState.Loading -> "로딩 중..."
    is UiState.Content -> "내용: ${state.data}"
    is UiState.Error -> "에러: ${state.message}"
}


// ========== 3. Result 패턴 — 성공 / 실패를 반환 타입으로 표현 ==========

sealed class AppResult<out T> {
    data class Success<T>(val value: T) : AppResult<T>()         // 성공 + 값
    data class Failure(val error: AppError) : AppResult<Nothing>() // 실패 (성공 값 없음)
}

sealed class AppError {
    data class NotFound(val id: String) : AppError()
    data class InvalidInput(val field: String, val message: String) : AppError()
}

data class User(val id: Long, val name: String)

// Repository 역할: null 대신 AppResult 반환
fun findUser(id: Long): AppResult<User> {
    val user = if (id == 1L) User(1, "김코틀린") else null
    return if (user != null) {
        AppResult.Success(user)
    } else {
        AppResult.Failure(AppError.NotFound("user:$id"))
    }
}

// 호출부: 반드시 Success / Failure 분기
fun printUser(result: AppResult<User>) {
    when (result) {
        is AppResult.Success -> println("이름: ${result.value.name}")
        is AppResult.Failure -> when (val e = result.error) {    // 실패 종류도 sealed
            is AppError.NotFound -> println("없음: ${e.id}")
            is AppError.InvalidInput -> println("${e.field}: ${e.message}")
        }
    }
}


// ========== 4. kotlin.Result — 짧은 작업·예외 감싸기 (표준 라이브러리) ==========

fun parseId(text: String): Result<Long> = runCatching {
    text.toLong()   // 실패 시 예외 → Result.failure
}

fun printId(text: String) {
    parseId(text)
        .onSuccess { println("id = $it") }
        .onFailure { println("파싱 실패: ${it.message}") }
}


// ========== 5. (선택) flatMap — 연속 작업, 중간에 실패하면 멈춤 ==========

fun <T, R> AppResult<T>.map(transform: (T) -> R): AppResult<R> =
    when (this) {
        is AppResult.Success -> AppResult.Success(transform(value))
        is AppResult.Failure -> this
    }

fun getUserLabel(id: Long): AppResult<String> =
    findUser(id).map { user -> "${user.name}(id=${user.id})" }


// ========== 실행 ==========

fun main() {
    println(orderMessage(OrderStatus.Paid(1710000000L)))
    println(uiMessage(UiState.Content("안녕")))

    printUser(findUser(1L))   // 성공
    printUser(findUser(99L))  // NotFound

    printId("42")
    printId("abc")

    println(getUserLabel(1L))
}
기억할 것
Sealed
sealed class + data object / data class 하위 타입
when
is로 분기, 전부 처리하면 else 불필요
Result
Success(값) / Failure(에러)
실패 상세
AppError도 sealed → when 한 번 더

'Language > Kotlin' 카테고리의 다른 글

[Kotlin] Coroutine 기초 과제  (0) 2026.06.02
[복습] 1~3강까지 미션  (0) 2026.05.27
[Kotlin] Coroutine 기초  (0) 2026.05.20
[Kotlin] Collection API & 함수형 프로그래밍  (0) 2026.05.20
[Kotlin] Scope Function 완전 정리  (0) 2026.05.20