kotlin - thought-corner/Backend-PlayGround GitHub Wiki

κ²°κ³Όκ°€ 없을 κ°€λŠ₯성이 μžˆλŠ” 경우 널 κ°€λŠ₯ λ˜λŠ” Result λ°˜ν™˜ νƒ€μž…μ„ μ„ ν˜Έν•˜λΌ

  • 정보λ₯Ό μ „λ‹¬ν•˜λŠ” ν‘œμ€€ λ°©μ‹μœΌλ‘œ μ˜ˆμ™Έλ₯Ό μ‚¬μš©ν•΄μ„  μ•ˆ λœλ‹€.
  • μ˜ˆμ™ΈλŠ” 비정상적이고 νŠΉμˆ˜ν•œ 상황을 μ˜λ―Έν•˜λ©°, μ΄λŸ¬ν•œ 상황을 μ²˜λ¦¬ν•˜κΈ° μœ„ν•΄μ„œ μ‚¬μš©λ˜μ–΄μ•Ό ν•œλ‹€.
  • μ˜ˆμ™Έκ°€ μ „νŒŒλ˜λŠ” 과정은 직관적이지 μ•ŠμœΌλ―€λ‘œ μ½”λ“œμ—μ„œ λ†“μΉ˜κΈ° 쉽닀.
  • μžλ°”μ™€ 달리 μ½”ν‹€λ¦°μ—μ„œ λͺ¨λ“  μ˜ˆμ™ΈλŠ” 비검사 μ˜ˆμ™Έμ΄λ‹€. μ‚¬μš©μžλ“€μ—κ²Œ μ˜ˆμ™Έλ₯Ό μ²˜λ¦¬ν•˜λ„λ‘ κ°•μš”ν•˜κ±°λ‚˜ ꢌμž₯ν•˜μ§€ μ•ŠλŠ”λ‹€.
  • μ˜ˆμ™Έλ“€μ€ μ œλŒ€λ‘œ λ¬Έμ„œν™”λ˜μ–΄ μžˆμ§€ μ•Šμ€ κ²½μš°κ°€ λ§Žμ•„ APIλ₯Ό μ‚¬μš©ν•  λ•Œ μ–΄λ–€ μ˜ˆμ™Έκ°€ λ°œμƒν• μ§€ μ•ŒκΈ° μ–΄λ ΅λ‹€.
  • μ˜ˆμ™ΈλŠ” 비정상적인 상황을 μœ„ν•΄ μ„€κ³„λ˜μ—ˆκΈ° λ•Œλ¬Έμ— JVMμ—μ„œ μ˜ˆμ™ΈλŠ” λͺ…μ‹œμ μΈ 검사와 달리 λΉ λ₯΄κ²Œ μ²˜λ¦¬λ˜μ§€ μ•ŠλŠ”λ‹€.
  • try~catch 블둝 내뢀에 κ΅¬ν˜„ν•œ μ½”λ“œλŠ” μ»΄νŒŒμΌλŸ¬κ°€ μ΅œμ ν™”ν•˜κΈ°κ°€ μ–΄λ ΅λ‹€.

nullκ³Ό Result.failure

  • nullκ³Ό Result.failure λ‘˜ λ‹€ μ˜ˆμƒλ˜λŠ” 였λ₯˜λ₯Ό ν‘œν˜„ν•˜λŠ” 데 μ ν•©ν•˜λ‹€.
  • λͺ…μ‹œμ μ΄κ³  효율적이며 μžμ—°μŠ€λŸ¬μš΄ λ°©μ‹μœΌλ‘œ 였λ₯˜λ₯Ό μ²˜λ¦¬ν•œλ‹€.
  • λ”°λΌμ„œ 였λ₯˜κ°€ μ˜ˆμƒλ˜λŠ” κ²½μš°μ—λŠ” null λ˜λŠ” Result.failureλ₯Ό λ°˜ν™˜ν•˜κ³  였λ₯˜κ°€ μ˜ˆμƒλ˜μ§€ μ•ŠλŠ” μƒν™©μ—μ„œλŠ” μ˜ˆμ™Έλ₯Ό λ˜μ Έμ•Ό ν•œλ‹€.

λ°˜ν™˜ νƒ€μž…μœΌλ‘œ Resultλ₯Ό μ‚¬μš©

  • 성곡 λ˜λŠ” μ‹€νŒ¨κ°€ 될 수 μžˆλŠ” κ²°κ³Όλ₯Ό λ°˜ν™˜ν•  λ•ŒλŠ” μ½”ν‹€λ¦° ν‘œμ€€ 라이브러리의 Result 클래슀λ₯Ό μ‚¬μš©ν•œλ‹€.
  • μ‹€νŒ¨μ—λŠ” 였λ₯˜μ— λŒ€ν•œ 정보λ₯Ό κ°€μ§€κ³  μžˆλŠ” μ˜ˆμ™Έκ°€ ν¬ν•¨λœλ‹€.
  • μ‹€νŒ¨ μ‹œ μΆ”κ°€ 정보λ₯Ό 전달해야 ν•˜λŠ” ν•¨μˆ˜μ—μ„œλŠ” 널 κ°€λŠ₯(nullable) νƒ€μž… λŒ€μ‹  Resultλ₯Ό μ‚¬μš©ν•œλ‹€.
userText.readObject<Person>()
    .onSuccess { showPersonAge(it) }
    .onFailure { showError(it) }
  • nullμ΄λ‚˜ Resultλ₯Ό μ‚¬μš©ν•΄ 였λ₯˜λ₯Ό μ²˜λ¦¬ν•˜λŠ” 것이 try~catch 블둝을 μ‚¬μš©ν•˜λŠ” 것보닀 더 κ°„λ‹¨ν•˜λ©° 더 μ•ˆμ „ν•˜λ‹€.
  • μ˜ˆμ™ΈλŠ” λ†“μΉ˜κΈ° μ‰¬μšΈ 뿐만 μ•„λ‹ˆλΌ μ• ν”Œλ¦¬μΌ€μ΄μ…˜ 전체λ₯Ό μ€‘μ§€μ‹œν‚€λŠ” μœ„ν—˜λ„ μžˆμ§€λ§Œ nullμ΄λ‚˜ Result κ°μ²΄λŠ” λͺ…μ‹œμ μœΌλ‘œ μ²˜λ¦¬ν•΄μ•Ό ν•˜λ©° μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ˜ 흐름을 μ€‘λ‹¨μ‹œν‚€μ§€ μ•ŠλŠ”λ‹€.
  • 널 κ°€λŠ₯ νƒ€μž…μ˜ 결과와 Result 객체의 μ°¨μ΄λŠ” μ‹€νŒ¨ν–ˆμ„ λ•Œ 좔가적인 정보λ₯Ό 전달해야 ν•œλ‹€λ©΄ Result 객체λ₯Ό, 그럴 ν•„μš”κ°€ μ—†λ‹€λ©΄ null을 μ‚¬μš©ν•˜λŠ” 것이 μ’‹λ‹€.
/*
 * Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors.
 * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
 */

@file:Suppress("UNCHECKED_CAST", "RedundantVisibilityModifier")

package kotlin

import kotlin.contracts.*
import kotlin.internal.InlineOnly
import kotlin.jvm.JvmField
import kotlin.jvm.JvmInline
import kotlin.jvm.JvmName

/**
 * A discriminated union that encapsulates a successful outcome with a value of type [T]
 * or a failure with an arbitrary [Throwable] exception.
 */
@SinceKotlin("1.3")
@JvmInline
public value class Result<out T> @PublishedApi internal constructor(
    @PublishedApi
    internal val value: Any?
) : Serializable {
    // discovery

    /**
     * Returns `true` if this instance represents a successful outcome.
     * In this case [isFailure] returns `false`.
     */
    public val isSuccess: Boolean get() = value !is Failure

    /**
     * Returns `true` if this instance represents a failed outcome.
     * In this case [isSuccess] returns `false`.
     */
    public val isFailure: Boolean get() = value is Failure

    // value & exception retrieval

    /**
     * Returns the encapsulated value if this instance represents [success][Result.isSuccess] or `null`
     * if it is [failure][Result.isFailure].
     *
     * This function is a shorthand for `getOrElse { null }` (see [getOrElse]) or
     * `fold(onSuccess = { it }, onFailure = { null })` (see [fold]).
     */
    @InlineOnly
    public inline fun getOrNull(): T? =
        when {
            isFailure -> null
            else -> value as T
        }

    /**
     * Returns the encapsulated [Throwable] exception if this instance represents [failure][isFailure] or `null`
     * if it is [success][isSuccess].
     *
     * This function is a shorthand for `fold(onSuccess = { null }, onFailure = { it })` (see [fold]).
     */
    public fun exceptionOrNull(): Throwable? =
        when (value) {
            is Failure -> value.exception
            else -> null
        }

    /**
     * Returns a string `Success(v)` if this instance represents [success][Result.isSuccess]
     * where `v` is a string representation of the value or a string `Failure(x)` if
     * it is [failure][isFailure] where `x` is a string representation of the exception.
     */
    public override fun toString(): String =
        when (value) {
            is Failure -> value.toString() // "Failure($exception)"
            else -> "Success($value)"
        }

    // companion with constructors

    /**
     * Companion object for [Result] class that contains its constructor functions
     * [success] and [failure].
     */
    public companion object {
        /**
         * Returns an instance that encapsulates the given [value] as successful value.
         */
        @Suppress("INAPPLICABLE_JVM_NAME")
        @InlineOnly
        @JvmName("success")
        public inline fun <T> success(value: T): Result<T> =
            Result(value)

        /**
         * Returns an instance that encapsulates the given [Throwable] [exception] as failure.
         */
        @Suppress("INAPPLICABLE_JVM_NAME")
        @InlineOnly
        @JvmName("failure")
        public inline fun <T> failure(exception: Throwable): Result<T> =
            Result(createFailure(exception))
    }

    internal class Failure(
        @JvmField
        val exception: Throwable
    ) : Serializable {
        override fun equals(other: Any?): Boolean = other is Failure && exception == other.exception
        override fun hashCode(): Int = exception.hashCode()
        override fun toString(): String = "Failure($exception)"
    }
}

/**
 * Creates an instance of internal marker [Result.Failure] class to
 * make sure that this class is not exposed in ABI.
 */
@PublishedApi
@SinceKotlin("1.3")
internal fun createFailure(exception: Throwable): Any =
    Result.Failure(exception)

/**
 * Throws exception if the result is failure. This internal function minimizes
 * inlined bytecode for [getOrThrow] and makes sure that in the future we can
 * add some exception-augmenting logic here (if needed).
 */
@PublishedApi
@SinceKotlin("1.3")
internal fun Result<*>.throwOnFailure() {
    if (value is Result.Failure) throw value.exception
}

/**
 * Calls the specified function [block] and returns its encapsulated result if invocation was successful,
 * catching any [Throwable] exception that was thrown from the [block] function execution and encapsulating it as a failure.
 */
@InlineOnly
@SinceKotlin("1.3")
public inline fun <R> runCatching(block: () -> R): Result<R> {
    return try {
        Result.success(block())
    } catch (e: Throwable) {
        Result.failure(e)
    }
}

/**
 * Calls the specified function [block] with `this` value as its receiver and returns its encapsulated result if invocation was successful,
 * catching any [Throwable] exception that was thrown from the [block] function execution and encapsulating it as a failure.
 */
@InlineOnly
@SinceKotlin("1.3")
public inline fun <T, R> T.runCatching(block: T.() -> R): Result<R> {
    return try {
        Result.success(block())
    } catch (e: Throwable) {
        Result.failure(e)
    }
}

// -- extensions ---

/**
 * Returns the encapsulated value if this instance represents [success][Result.isSuccess] or throws the encapsulated [Throwable] exception
 * if it is [failure][Result.isFailure].
 *
 * This function is a shorthand for `getOrElse { throw it }` (see [getOrElse]).
 */
@InlineOnly
@SinceKotlin("1.3")
public inline fun <T> Result<T>.getOrThrow(): T {
    throwOnFailure()
    return value as T
}

/**
 * Returns the encapsulated value if this instance represents [success][Result.isSuccess] or the
 * result of [onFailure] function for the encapsulated [Throwable] exception if it is [failure][Result.isFailure].
 *
 * Note, that this function rethrows any [Throwable] exception thrown by [onFailure] function.
 *
 * This function is a shorthand for `fold(onSuccess = { it }, onFailure = onFailure)` (see [fold]).
 */
@InlineOnly
@SinceKotlin("1.3")
public inline fun <R, T : R> Result<T>.getOrElse(onFailure: (exception: Throwable) -> R): R {
    contract {
        callsInPlace(onFailure, InvocationKind.AT_MOST_ONCE)
    }
    return when (val exception = exceptionOrNull()) {
        null -> value as T
        else -> onFailure(exception)
    }
}

/**
 * Returns the encapsulated value if this instance represents [success][Result.isSuccess] or the
 * [defaultValue] if it is [failure][Result.isFailure].
 *
 * This function is a shorthand for `getOrElse { defaultValue }` (see [getOrElse]).
 */
@InlineOnly
@SinceKotlin("1.3")
public inline fun <R, T : R> Result<T>.getOrDefault(defaultValue: R): R {
    if (isFailure) return defaultValue
    return value as T
}

/**
 * Returns the result of [onSuccess] for the encapsulated value if this instance represents [success][Result.isSuccess]
 * or the result of [onFailure] function for the encapsulated [Throwable] exception if it is [failure][Result.isFailure].
 *
 * Note, that this function rethrows any [Throwable] exception thrown by [onSuccess] or by [onFailure] function.
 */
@InlineOnly
@SinceKotlin("1.3")
public inline fun <R, T> Result<T>.fold(
    onSuccess: (value: T) -> R,
    onFailure: (exception: Throwable) -> R
): R {
    contract {
        callsInPlace(onSuccess, InvocationKind.AT_MOST_ONCE)
        callsInPlace(onFailure, InvocationKind.AT_MOST_ONCE)
    }
    return when (val exception = exceptionOrNull()) {
        null -> onSuccess(value as T)
        else -> onFailure(exception)
    }
}

// transformation

/**
 * Returns the encapsulated result of the given [transform] function applied to the encapsulated value
 * if this instance represents [success][Result.isSuccess] or the
 * original encapsulated [Throwable] exception if it is [failure][Result.isFailure].
 *
 * Note, that this function rethrows any [Throwable] exception thrown by [transform] function.
 * See [mapCatching] for an alternative that encapsulates exceptions.
 */
@InlineOnly
@SinceKotlin("1.3")
public inline fun <R, T> Result<T>.map(transform: (value: T) -> R): Result<R> {
    contract {
        callsInPlace(transform, InvocationKind.AT_MOST_ONCE)
    }
    return when {
        isSuccess -> Result.success(transform(value as T))
        else -> Result(value)
    }
}

/**
 * Returns the encapsulated result of the given [transform] function applied to the encapsulated value
 * if this instance represents [success][Result.isSuccess] or the
 * original encapsulated [Throwable] exception if it is [failure][Result.isFailure].
 *
 * This function catches any [Throwable] exception thrown by [transform] function and encapsulates it as a failure.
 * See [map] for an alternative that rethrows exceptions from `transform` function.
 */
@InlineOnly
@SinceKotlin("1.3")
public inline fun <R, T> Result<T>.mapCatching(transform: (value: T) -> R): Result<R> {
    return when {
        isSuccess -> runCatching { transform(value as T) }
        else -> Result(value)
    }
}

/**
 * Returns the encapsulated result of the given [transform] function applied to the encapsulated [Throwable] exception
 * if this instance represents [failure][Result.isFailure] or the
 * original encapsulated value if it is [success][Result.isSuccess].
 *
 * Note, that this function rethrows any [Throwable] exception thrown by [transform] function.
 * See [recoverCatching] for an alternative that encapsulates exceptions.
 */
@InlineOnly
@SinceKotlin("1.3")
public inline fun <R, T : R> Result<T>.recover(transform: (exception: Throwable) -> R): Result<R> {
    contract {
        callsInPlace(transform, InvocationKind.AT_MOST_ONCE)
    }
    return when (val exception = exceptionOrNull()) {
        null -> this
        else -> Result.success(transform(exception))
    }
}

/**
 * Returns the encapsulated result of the given [transform] function applied to the encapsulated [Throwable] exception
 * if this instance represents [failure][Result.isFailure] or the
 * original encapsulated value if it is [success][Result.isSuccess].
 *
 * This function catches any [Throwable] exception thrown by [transform] function and encapsulates it as a failure.
 * See [recover] for an alternative that rethrows exceptions.
 */
@InlineOnly
@SinceKotlin("1.3")
public inline fun <R, T : R> Result<T>.recoverCatching(transform: (exception: Throwable) -> R): Result<R> {
    return when (val exception = exceptionOrNull()) {
        null -> this
        else -> runCatching { transform(exception) }
    }
}

// "peek" onto value/exception and pipe

/**
 * Performs the given [action] on the encapsulated [Throwable] exception if this instance represents [failure][Result.isFailure].
 * Returns the original `Result` unchanged.
 */
@InlineOnly
@SinceKotlin("1.3")
public inline fun <T> Result<T>.onFailure(action: (exception: Throwable) -> Unit): Result<T> {
    contract {
        callsInPlace(action, InvocationKind.AT_MOST_ONCE)
    }
    exceptionOrNull()?.let { action(it) }
    return this
}

/**
 * Performs the given [action] on the encapsulated value if this instance represents [success][Result.isSuccess].
 * Returns the original `Result` unchanged.
 */
@InlineOnly
@SinceKotlin("1.3")
public inline fun <T> Result<T>.onSuccess(action: (value: T) -> Unit): Result<T> {
    contract {
        callsInPlace(action, InvocationKind.AT_MOST_ONCE)
    }
    if (isSuccess) action(value as T)
    return this
}

// -------------------

λ°˜ν™˜ νƒ€μž…μœΌλ‘œ null을 μ‚¬μš©

  • μ½”ν‹€λ¦°μ—μ„œ null은 값이 μ—†μŒμ„ λ‚˜νƒ€λ‚Έλ‹€.
  • ν•¨μˆ˜κ°€ null을 λ°˜ν™˜ν•œλ‹€λŠ” 것은 값을 λ°˜ν™˜ν•  수 μ—†λ‹€λŠ” 것을 μ˜λ―Έν•œλ‹€.
List<T>.getOrNull(Int) // μ£Όμ–΄μ§„ μΈλ±μŠ€μ— 값이 없을 λ•Œ null을 λ°˜ν™˜ν•œλ‹€.

String.toIntOrNull() // String을 Int둜 μ˜¬λ°”λ₯΄κ²Œ νŒŒμ‹±ν•˜μ§€ λͺ»ν•  λ•Œ null을 λ°˜ν™˜ν•œλ‹€.

Iterable<T>.firstOrNull(() -> Boolean) // 인수둜 μ „λ‹¬λœ 쑰건식에 λ§žλŠ” μš”μ†Œκ°€ 없을 λ•Œ null을 λ°˜ν™˜ν•œλ‹€.
  • ν•¨μˆ˜μ—μ„œ μ‹€νŒ¨κ°€ λ°œμƒν–ˆμ„ λ•Œ μΆ”κ°€ 정보λ₯Ό 전달할 ν•„μš”κ°€ μ—†λ‹€λ©΄, Result λŒ€μ‹  널 κ°€λŠ₯ νƒ€μž…μ„ μ‚¬μš©ν•˜λ©΄ λœλ‹€.
  • 이와 같이 null을 λ°˜ν™˜ν•˜λŠ” λͺ¨λ“  ν•¨μˆ˜μ—μ„œ null의 μ˜λ―ΈλŠ” λͺ…ν™•ν•΄μ•Ό ν•œλ‹€.
  • 널 κ°€λŠ₯ 값은 μ‚¬μš©ν•˜κΈ° 전에 μ–Έλž˜ν•‘μ„ ν•΄μ•Ό ν•œλ‹€. 이λ₯Ό μ²˜λ¦¬ν•˜κΈ° μœ„ν•΄ μ•ˆμ „ν•œ 호좜 μ—°μ‚°μž, μ—˜λΉ„μŠ€ μ—°μ‚°μž, 슀마트 μΊμŠ€νŒ… λ“±κ³Ό 같은 μœ μš©ν•œ κΈ°λŠ₯듀을 μ œκ³΅ν•œλ‹€.

방어적 ν”„λ‘œκ·Έλž˜λ°κ³Ό 곡격적 ν”„λ‘œκ·Έλž˜λ°μ˜ 차이

⚠️ **GitHub.com Fallback** ⚠️