2025-11-19
1일 1아티클
LY
조기 반환
장점
- 에러 케이스를 우선 배제 → 함수 주요 목적에 초점을 맞춘 코드 작성에 용이
- 에러 조건 및 처리 로직 간 관계 이해에 용이
조기 반환이 능사는 아니다
// Before
fun getUserNames(userIds: List<UserId>): List<String> {
if (userIds.isEmpty()) {
return emptyList()
}
if (userIds.size == 1) {
val userData = repository.getUserData(userIds[0])
return listOf(userData.name)
}
return userIds.asSequence()
.map(repository::getUserData)
.map(UserData::name)
.toList()
}
// After
fun getUserNames(userIds: List<UserId>): List<String> =
userIds.asSequence()
.map(repository::getUserData)
.map(UserData::name)
.toList()
- 조기 반환 적용 여부 : 에러 케이스와 정상 케이스의 처리가 어느 정도 달라야 하는가?
- 두 케이스의 처리가 동일하다 → 에러 케이스로 분리하지 말고, 정상 케이스에서 처리하는 것이 코드 단순화에 유리
- 위 case에서는,
userIds.isEmpty()와userIds.size == 1을 정상 케이스로 간주 시 처리 통합 가능
case 1. 비어있는 컬렉션 순회
-
컬렉션 순회 함수 (
map(),filter(),forEach()) : 많은 언어 및 라이브러리에서 컬렉션이 비어있는 경우에 대한 처리 가능 ```kotlinval empty: List
= emptyListOf() empty.all { false } // => true empty.any { true } // => false empty.sum() // 0
**case 2. `null`**
- `null`(`nil`, `undefined`)을 정상 케이스 취급 시, safe call 연산자 (`?.`) 유용
- `null`을 기본값으로 되돌릴 때, `?:` 또는 `??`(null 결합 연산자) 사용 가능한 언어도 존재
- `?: 0` 또는 `orEmpty()` 등 기본값으로 fall back하는 것도 고려해볼 수 있음
- but 변환한 기본 파라미터로 다시 case 구분하지 않도록 주의
```kotlin
// Before
fun function(foo: Foo?): Bar? {
if (value == null) {
return null
}
return value.toBar()
}
// After
fun function(foo: Foo?): Bar? =
value?.toBar()
case 3. 범위를 벗어난 배열/리스트
- 범위가 벗어난 경우, 조기 반환보다는
getOrNull()또는getOrElse()사용 -
표준 라이브러리에 위의 함수 형태 없을 시, 범용 함수로 정의 추천 ```kotlin
val fooList: List<Foo?> = …
// Before fun function(index: Int): Foo? { if (index < 0 || fooList.size <= index) { return null } return fooList[index] }
// After fun function(index: Int): Foo? = fooList.getOrNull(index)
**case 4. 다른 속성에 의존하는 속성**
- 어떤 속성이 특정 값일 때에만 다른 속성이 의미를 갖는 경우
- ex. UI 요소 `textView`에 표시 여부를 나타내는 `textView.isVisible`, 텍스트 내용을 나타내는 `textView.text` 존재
- 이때, `text`가 의미를 갖기 위해서는 `isVisible`이 **반드시 `true`여야 함**
- 다르게 생각하면, `isVisible`이 `false`일 때 `text`는 어떤 값이어도 상관 없다 → 에러 분기 처리하지 않고 통합 처리
```kotlin
if (someText.isEmpty()) {
textView.isVisible = false
return
}
textView.isVisible = true
textView.text = someText
textView.isVisible = someText.isNotEmpty()
textView.text = someText
case 5. 연속 함수 호출 중 예외
- 함수 중 여러 지점에서 예외 발생 시 : 조기 반환 적용 → 코드 복잡성 증가
- ex.
someData→anotherData→yetAnotherData변환 과정에서 예외 발생 - →
Success에 대해서만 동작하는flatMap활용 ```kotlin
// Before sealed class FooResult { class Success(val fooData: FooData): FooResult() class Error(val errorType: ErrorType): FooResult() } enum class ErrorType { SOME, ANOTHER, YET_ANOTHER }
fun getFooData(): FooResult { val someData = try { apiClient.getSomeData() } catch (: SomeException) { return Error(ErrorType.SOME) } val anotherData = try { unreliableRepository.getAnotherData(someData) } catch (: AnotherException) { return Error(ErrorType.ANOTHER) }
return try { unreliableRepository.getYetAnotherData(anotherData) } catch (_: YetAnotherException) { Error(ErrorType.YET_ANOTHER) } }// After sealed class FooResult
{ class Success (val value: T): FooResult () class Error (val errorType: ErrorType): FooResult () @Suppress(“UNCHECKED_CAST”) // … fun flatMap(action: (T) -> FooResult): FooResult = when (this) { is Success -> action(value) is Error -> this as FooResult } }
private fun getSomeData(): FooResult
= try { FooResult.Success(apiClient.getSomeData()) } catch (_: SomeException) { FooResult.Error(ErrorType.SOME) } fun getFooData(): FooResult = getSomeData() .flatMap(::toAnotherData) .flatMap(::toYetAnotherData)
- ex.
```
조기 반환 사용 전, 에러 케이스와 정상 케이스를 통합 가능한지 생각할 것
오늘 배운 것
- 스프링
- REST API
내일 할 일
- 스프링
- Spring Security