[EffectiveKotlin] 예외처리 방법

SSY·2024년 5월 11일
0

Algorithm

목록 보기
6/6
post-thumbnail

시작하며

해당 글은 [토스 NEXT] 2022년 코딩 테스트 모범 답안과 [EffectiveKotlin]저서의 7, 8, 9장의 내용을 종합하여 작성하는 글이다. 코딩 테스트 모범 답안답게, 반환값이 이상한 경우는 null로 반환받게 만들었으며, null의 처리로 ValidationError을 던져주고 있다.

[참고]
[토스 NEXT] 2022년 안드로이드 코딩테스트 과제 풀이

Tool.

[EffectiveKotlin]의 7, 8장의 내용을 종합하면 아래와 같다.
연산 도중에 문제가 발생하거나 의도된 결과가 나오지 않는다면 null을 반환한다. 그 후, null을 적절히 처리한다. 그 방법으론,

  1. Evis 연산자를 사용하여 기본값을 매핑해준다.
  2. Elvis 연산자를 사용하여 return호출 및 메서드를 나간다.
  3. Elvis 연산자를 사용하여 예외를 throw한다.
  4. 강제 언랩핑을 사용한다. (절대 쓰지 말 것)

이 있다.

1. Elvis 연산자를 사용하여 기본값을 매핑해준다.

fun loadingUserInfo() {
	val userName = userInfo?.name ?: "Mike"
}

2. Elvis 연산자를 사용하여 return호출 및 메서드를 나간다.

fun loadingUserInfo() {
	val userName = userInfo?.name ?: return
}

3. Elvis 연산자를 사용하여 예외를 throw한다.

토스 과제 모범 답안에서 굉장히 잘 다루어지고 있으며, 코드는 아래와 같다.

fun String.toAssetResult(): Asset =
    when {
        length != 9 -> throw ValidationError
        slice(0..1).toIntOrNull() == null -> throw ValidationError
        get(2) != '-' -> throw ValidationError
        runCatching { Asset.Type.valueOf(slice(3..4)) }.isFailure -> throw ValidationError
        slice(5..6).toIntOrNull() == null -> throw ValidationError
        slice(7..8).toIntOrNull() == null -> throw ValidationError
        else -> Asset(
            text = this,
            yy = slice(0..1).toInt(),
            type = Asset.Type.valueOf(slice(3..4)),
            mm = slice(5..6).toInt(),
            no = slice(7..8).toInt()
        )
    }

우선 위 메서드 설명부터 할까 한다. 위 메서드는 특정 규칙의 문자열 (ex. 13-DE0401)을 Asset이라는 클래스로 파싱해주는 메서드이다. 이때, 수신 객체 문자열이 올바르지 않은 포맷 (ex. 125dksh30)일 수 있는데, 이런 상황일 때 예외를 throw해주고 있다.

[예외 throw 조건]
1. 수신 객체 문자열이 9자리가 맞는지?
2. 수신 객체 문자열 0, 1번 인덱스 자리가 숫자가 맞는지?
3. 수신 객체 문제열 2번 인덱스 자리가 "-"가 맞는지?
4. 수신 객체 문자열 5, 6번 인덱스 자리가 숫자가 맞는지?
5. 수신 객체 문자열 7, 8번 인덱스 자리가 숫자가 맞는지?

또한 위의 처리는 [EffectiveKotlin] 7장, '결과 부족이 발생할 경우 null과 Failure를 사용하라'의 내용을 잘 따르고 있는 코드이다. 뿐만 아니라, 해당 장에서 예상할 수 있는 오류상황 땐, xxxOrNull()메서드 사용을 권장하고 있는데, 위 모범 사례 코드는 이를 사용하고 있다.

toIntOrNull로 파싱하는 과정에서 숫자로 된 문자열이 아닐 경우, ValidationErrorthrow해주고 있다. 또한 이러한 처리는 [EffectiveKotlin] 8장, '적절하게 null을 처리하라'의 내용을 아주 잘 따르고 있는 부분이기도 하다.

즉, 7~8장 내용을 종합해보면, 연산 도중, 의도치 않은 동작이 예상된다면 null 반환 후, 예외를 throw하거나, 아니면 곧바로 예외를 throw하라 라는 것으로 이해할 수 있다.

[의도치 않은 동작이 예상된다?]
1. null반환 후, 예외를 throw한다.
2. 곧바로 예외를 throw한다.

추가로, toAssetResult()메서드는 어떻게 사용되고 있을까? 당연히 runCatching으로 래핑하여 아래와 같이 사용중이다.

fun solution(assets: Array<String>): Array<String> =
        assets.mapNotNull { asset -> runCatching { asset.toAssetResult() }.getOrNull() }
            .sorted()
            .filter(Asset::isValid)
            .map(Asset::text)
            .distinct()
            .toTypedArray()

toAssetResult()메서드를 runCatching메서드로 래핑한 후, getOrNull()메서드를 호출하여 Nullable한 값을 반환받는다. 이러한 일련의 로직은 mappNotNull로 또 다시 래핑됨으로써 밑으로 내보내지지 않는다.

마치며

모범 답안이라는 데에는 이유가 있는법이다. 내가 분석해본 바, 모범 답안은 [EffectiveKotlin]의 7, 8장 준수를 잘 하고 있다. 좀 더 설명해 보자면, 아까 예를 들었던 toAssetResult() 메서드는 관심사(String타입을 Asset타입으로 파싱)를 지키기 위해 예외를 과감히 던지는 부분이 7, 8장 준수를 잘하고 있다. 이때 Asset?한 타입을 반환해줄 수 도 있었을 것이다. 하지만 이는 toAssetResult()메서드 관심사에 맞지 않는다. (관심사에 맞으려면 toAssetResultOrNull()이 되어야 할 것이다.) 따라서 관심사를 지키기 위해 runCatching으로 래핑해서 사용하고 있는 것이다.

하지만 이런 방법을 실무에서 사용할 땐 조심해야할 것이다. 다른 개발자들이 toAssetResult()와 같은 메서드를 예외처리 없이 사용할 땐, 오류 상황이 발생할 수 있다. 따라서 이런 부분은 문서로 남기는 등의 작업이 선행되어야 할 것이다.

profile
불가능보다 가능함에 몰입할 수 있는 개발자가 되기 위해 노력합니다.

0개의 댓글