From e171882f737852b4966f4fd14c8894c87de9bb36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=BC=ED=98=81?= Date: Mon, 5 Oct 2026 07:29:59 +0000 Subject: [PATCH] =?UTF-8?q?refactor(core):=20runCatchingCancellable=20?= =?UTF-8?q?=EC=9D=84=20core:domain=20=EC=9C=BC=EB=A1=9C=20=EC=98=AE?= =?UTF-8?q?=EA=B8=B0=EA=B3=A0=20core:common=20=EC=9D=80=20=EC=9C=84?= =?UTF-8?q?=EC=9E=84=EB=A7=8C=20=EB=82=A8=EA=B8=B4=EB=8B=A4=20(#2260)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/result/RunCatchingCancellable.kt | 39 ++++++------------- .../domain/result/RunCatchingCancellable.kt | 35 +++++++++++++++++ .../result/RunCatchingCancellableTest.kt | 2 +- 3 files changed, 48 insertions(+), 28 deletions(-) create mode 100644 core/domain/src/main/java/com/afternote/core/domain/result/RunCatchingCancellable.kt rename core/{common/src/test/java/com/afternote/core/common => domain/src/test/java/com/afternote/core/domain}/result/RunCatchingCancellableTest.kt (98%) diff --git a/core/common/src/main/kotlin/com/afternote/core/common/result/RunCatchingCancellable.kt b/core/common/src/main/kotlin/com/afternote/core/common/result/RunCatchingCancellable.kt index 78cb4ec12c..94347f6983 100644 --- a/core/common/src/main/kotlin/com/afternote/core/common/result/RunCatchingCancellable.kt +++ b/core/common/src/main/kotlin/com/afternote/core/common/result/RunCatchingCancellable.kt @@ -1,32 +1,17 @@ package com.afternote.core.common.result -import kotlin.coroutines.cancellation.CancellationException +import com.afternote.core.domain.result.runCatchingCancellable as domainRunCatchingCancellable /** - * suspend 호출을 감싸 [Result] 로 돌려주되, [CancellationException] 만은 삼키지 않고 다시 던진다. - * - * suspend 경계에서 stdlib `runCatching` 을 쓰면 안 되는 이유 — 그 함수는 "catching any Throwable" - * 이라(https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/run-catching.html) 코루틴 취소까지 - * `Result.failure` 로 바꾼다. 그러면 취소가 상위로 전파되지 못하고, 이미 취소된 코루틴에서 호출부의 - * `onFailure` 갈래(UI 상태 갱신·스낵바 안내 등)가 실행된다. - * - * Android 코루틴 모범 사례가 이 소비를 직접 금지한다 — "To enable coroutine cancellation, don't - * consume exceptions of type CancellationException (don't catch them, or always rethrow them if - * caught)" (https://developer.android.com/kotlin/coroutines/coroutines-best-practices). - * - * 같은 역할을 stdlib 이 제공하지 않아 여기 둔다: cancellation-aware `runCatching` 요청은 - * kotlinx.coroutines #1814 로 2020-02-18 에 올라와 아직 열려 있다 - * (https://github.com/Kotlin/kotlinx.coroutines/issues/1814). - * - * 쓰는 자리는 suspend 를 감싸는 경계다. 취소가 없는 동기 코드에는 stdlib `runCatching` 을 그대로 - * 쓴다 — `inline` 이라 동기 코드에서도 호출은 되지만(그 경우 stdlib 과 동작이 같다) 이름값을 하지 - * 못한다. + * 정본은 [com.afternote.core.domain.result.runCatchingCancellable] 이다. feature domain UseCase 도 쓸 수 + * 있도록 JVM 모듈로 옮겼고, 기존 호출처가 바뀌지 않도록 이 자리는 위임만 남긴다. */ -inline fun runCatchingCancellable(block: () -> T): Result = - try { - Result.success(block()) - } catch (e: CancellationException) { - throw e - } catch (e: Throwable) { - Result.failure(e) - } +@Deprecated( + message = "core:domain 으로 옮겼다. com.afternote.core.domain.result.runCatchingCancellable 을 쓴다.", + replaceWith = + ReplaceWith( + expression = "runCatchingCancellable(block)", + imports = ["com.afternote.core.domain.result.runCatchingCancellable"], + ), +) +inline fun runCatchingCancellable(block: () -> T): Result = domainRunCatchingCancellable(block) diff --git a/core/domain/src/main/java/com/afternote/core/domain/result/RunCatchingCancellable.kt b/core/domain/src/main/java/com/afternote/core/domain/result/RunCatchingCancellable.kt new file mode 100644 index 0000000000..1fa0f2044c --- /dev/null +++ b/core/domain/src/main/java/com/afternote/core/domain/result/RunCatchingCancellable.kt @@ -0,0 +1,35 @@ +package com.afternote.core.domain.result + +import kotlin.coroutines.cancellation.CancellationException + +/** + * suspend 호출을 감싸 [Result] 로 돌려주되, [CancellationException] 만은 삼키지 않고 다시 던진다. + * + * suspend 경계에서 stdlib `runCatching` 을 쓰면 안 되는 이유 — 그 함수는 "catching any Throwable" + * 이라(https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/run-catching.html) 코루틴 취소까지 + * `Result.failure` 로 바꾼다. 그러면 취소가 상위로 전파되지 못하고, 이미 취소된 코루틴에서 호출부의 + * `onFailure` 갈래(UI 상태 갱신·스낵바 안내 등)가 실행된다. + * + * Android 코루틴 모범 사례가 이 소비를 직접 금지한다 — "To enable coroutine cancellation, don't + * consume exceptions of type CancellationException (don't catch them, or always rethrow them if + * caught)" (https://developer.android.com/kotlin/coroutines/coroutines-best-practices). + * + * 같은 역할을 stdlib 이 제공하지 않아 여기 둔다: cancellation-aware `runCatching` 요청은 + * kotlinx.coroutines #1814 로 2020-02-18 에 올라와 아직 열려 있다 + * (https://github.com/Kotlin/kotlinx.coroutines/issues/1814). + * + * Android 라이브러리인 core:common 이 아니라 JVM 모듈인 core:domain 에 두는 것은 feature domain + * UseCase 도 이 함수를 쓰게 하기 위해서다. domain 모듈은 core:common 에 의존할 수 없다. + * + * 쓰는 자리는 suspend 를 감싸는 경계다. 취소가 없는 동기 코드에는 stdlib `runCatching` 을 그대로 + * 쓴다 — `inline` 이라 동기 코드에서도 호출은 되지만(그 경우 stdlib 과 동작이 같다) 이름값을 하지 + * 못한다. + */ +inline fun runCatchingCancellable(block: () -> T): Result = + try { + Result.success(block()) + } catch (e: CancellationException) { + throw e + } catch (e: Throwable) { + Result.failure(e) + } diff --git a/core/common/src/test/java/com/afternote/core/common/result/RunCatchingCancellableTest.kt b/core/domain/src/test/java/com/afternote/core/domain/result/RunCatchingCancellableTest.kt similarity index 98% rename from core/common/src/test/java/com/afternote/core/common/result/RunCatchingCancellableTest.kt rename to core/domain/src/test/java/com/afternote/core/domain/result/RunCatchingCancellableTest.kt index cbfd64d9ba..eb8ee8c393 100644 --- a/core/common/src/test/java/com/afternote/core/common/result/RunCatchingCancellableTest.kt +++ b/core/domain/src/test/java/com/afternote/core/domain/result/RunCatchingCancellableTest.kt @@ -1,4 +1,4 @@ -package com.afternote.core.common.result +package com.afternote.core.domain.result import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job