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()) : 많은 언어 및 라이브러리에서 컬렉션이 비어있는 경우에 대한 처리 가능 ```kotlin

    val 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. someDataanotherDatayetAnotherData 변환 과정에서 예외 발생
    • 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)

```

조기 반환 사용 전, 에러 케이스와 정상 케이스를 통합 가능한지 생각할 것

오늘 배운 것

  1. 스프링
    • REST API

내일 할 일

  1. 스프링
    • Spring Security

참고자료

results matching ""

    No results matching ""