일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- Gradle
- Spring Batch
- scala
- Java
- 인텔리J
- 제주
- SBT
- spark
- Clean Code
- elastic search
- Spring XD
- 스프링 배치
- hdfs
- apache storm
- design pattern
- Storm
- 도메인주도설계
- intellij
- 엘라스틱서치
- Spring
- hibernate
- nginx
- Angular2
- DDD
- elasticsearch
- Linux
- Hbase
- hadoop
- docker
- Spring Boot
- Today
- Total
욱'S 노트
타입 에러/Typed Errors (Kotlin 함수형 프로그래밍 #3) 본문
타입 에러/Typed Errors (Kotlin 함수형 프로그래밍 #3)
devsun 2025. 1. 14. 10:27예외(Exception)의 문제점
예외를 던지는 것은 부수효과이다. 그로 인한 문제점은 제어 상실이다. 제어 상실은 예외가 처리되지 않아 프로그램이 중단되거나, 호출 스택위의 어떤 코드가 예외를 잡아서 예외를 처리하는 상황을 의미한다.
아래의 코드를 보면 치환 모델이 제공하는 단순한 추론을 벗어나서 참조투명성이 깨지는 것을 확인할 수 있다. 예외를 사용하면 프로그램 전체에서 전역적 추론을 해야되는 문제가 발생한다.
fun failingFn(i: Int): Int {
val y: Int = throw Exception("boom") // <1>
return try {
val x = 42 + 5
x + y
} catch (e: Exception) {
43 // <2>
}
}
fun failingFn2(i: Int): Int =
try {
val x = 42 + 5
x + (throw Exception("boom!")) as Int // <1>
} catch (e: Exception) {
43 // <2>
}
그리고 예외는 타입에 안전하지 않다. 고차함수 (Int) -> Int에서는 예외가 발생할 수 있다는 점을 언급할 수 없다. 그러므로 컴파일러도 호출하는 쪽에서 예외를 어떻게 처리할 지 강요받지 못한다.
타입 에러(Typed Errors)
코드가 실행될 때 발생할 수 있는 잠재적인 에러를 명시적인 타입으로 만드는 함수형 프로그래밍 기술을 의미한다. 컴파일러에 에러 처리를 강제하는 방식이다.
Logical Failure vs Real Exceptions
- 논리적 실패란 도메인적으로 성공은 아니며, 도메인의 영역안에 있는 상황을 의미한다. 예를 들어 UserRepository를 구현 했을 때 유저를 찾을수 없다면 특정 쿼리의 논리적 실패로 볼 수 있다.
- 예외란 도메인의 영역이 아닌 기술적인 실패 문제를 의미한다. 예를 들어 데이터베이스 연결이 끊어지거나, 네트워크 타임아웃이 발생하거나, 호스트가 unavailable 할 경우이다. 이런 처리는 Arrow resilience 메커니증에서 이점을 얻을 수 있다.
Sucess vs Failure
- 에러 처리시 success, failure를 구분해야 한다.
- 이러한 접근에 따라 함수의 시그니처는 실패 가능성 및 실패가 발행한 문제의 범위를 묘사할 수 있어야 한다.
Either
성공과 실패를 인코딩하는 대표적인 타입이다. 가장 기본적인 구현은 아래와 같다. Arrow KT에서도 Either를 제공한다.
sealed class Either<out E, out A>
data class Left<out E>(val value: E) : Either<E, Nothing>()
data class Right<out A>(val value: A) : Either<Nothing, A>()
성공/실패 정의
Right, Left로 성공과 실패를 정의할 수 있다.
object UserNotFound
data class User(val name: String, val age: Int)
val user: Either<UserNotFound, User> = Right(User("John", 33))
val error: Either<UserNotFound,User> = Left(UserNotFound)
결과 검사
Either는 대수적인 타입이므로 코틀린의 when 절로 논리적인 실패나 성공 케이스를 검사할 수 있다. fold나 getOrElse등 다양한 연산을 지원한다.
when (error) {
is Left -> error.value shouldBe UserNotFound
is Right -> fail("A logical failure occurred!")
}
error.fold(
ifLeft = { fail("User not found") },
ifRight = { println("User found: ${it.name}") }
)
error.getOrElse { fail("User not found") }
에러 처리
문제는 두가지 케이스로 분류할 수 있다.
- Logical Failure는 일반적인 도메인 로직에서 도메인의 한 부분으로 처리되는 문제점. 예를 들어 유저를 찾았을 때 없다던지, 입력데이터가 유효하지 않다던지 하는 것이다.
- Exceptions는 시스템이 동작을 이어가는데, 영향이 되는 문제. 예를 들어 도메인 로직 외부의 데이터베이스 연결이 끊어졌다는지 라는 것이다.
역사적으로, Exceptions에서 두가지 케이스를 다 처리하기도 했었다.(UserNotFoundException, UserNotValidException) 하지만 FP에서는 이러한 타입을 명확히 구분하는 것을 선호한다.
에러 집계
타입 에러을 사용하면 에러를 집계하는 경우에도 유용하다. 애로우에서는 DSL을 제공하여 아래와 같은 validation을 깔끔하게 처리할 수 있다.
sealed interface UserProblem {
data object EmptyName : UserProblem
data class NegativeAge(val age: Int) : UserProblem
}
data class User private constructor(val name: String, val age: Int) {
companion object {
operator fun invoke(name: String, age: Int): Either<NonEmptyList<UserProblem>, User> = either {
zipOrAccumulate(
{ ensure(name.isNotEmpty()) { UserProblem.EmptyName } },
{ ensure(age >= 0) { UserProblem.NegativeAge(age) }}
) { _, _ -> User(name, age)}
}
}
}
fun example() {
User("", -1) shouldBe Either.Left(nonEmptyListOf(UserProblem.EmptyName, UserProblem.NegativeAge(-1)))
}
정리
타입 에러를 사용하면 다음과 같은 장점이 있다.
- Type Safety: 타입 에러는 컴파일러에게 타입 미스매치를 미리 알려줌으로써, 제품을 만들기 전에 버그를 더 쉽게 개선할 수 있도록 한다. 하지만 예외는 컴파일 타입에 에러를 검출하기 더 어렵다.
- Predictability: 타입 에러를 사용하면, 함수의 타입 명세에 발생가능한 에러가 명시된다. 이는 발생할 에러 조건을 미리 이해하고, 모든 에러 시나리오에 대한 테스트를 작성하기 쉽다.
- Composability: 타입 에러는 연속된 함수 호출에서 쉽게 합성되거나 전파될 수 있다. 이는 모듈화가 쉽고 쉽게 합성할 수 있다는 것을 의미한다. 예외를 사용하면, 복잡한 코드페이스에서 어떻게 전파가 되는지 알아차리기 힘들다. 특히 집계와 같은 작업은 타입 에러에서는 간단한데, 예외에서는 매우 어렵다.
- Performance: 예외 처리는 성능에 영향을 준다. 특히 예외전용처리 스택이 따로 있지 않은 언어에서는 더욱 그러하다. 타입 에러는 가능한 에러 조건을 더 많은 정보를 컴파일단에서 처리할 수 있도록 함으로써 성능에서도 우위가 있다.
참고자료 : https://arrow-kt.io/
'Methdology > Functional Programming' 카테고리의 다른 글
불변 컬렉션/Immutable Collections (Kotlin 함수형 프로그래밍 #5) (4) | 2025.01.16 |
---|---|
Option을 사용해야하는 이유/Nested Nullability (Kotlin 함수형 프로그래밍 #4) (1) | 2025.01.15 |
함수형 데이터구조/대수적 타입/Algebraic Data Type (Kotlin 함수형프로그래밍 #3) (0) | 2025.01.13 |
불변 데이터/Immutable Data/Optics(Kotlin 함수형 프로그래밍 #2) (0) | 2025.01.09 |
함수형 프로그래밍이란 (Kotlin 함수형 프로그래밍 #1) (0) | 2025.01.07 |