본문 바로가기

Kotlin/이펙티브코틀린

item 6~10

안정성에 관련된 내용 이어서

 

Item 6 : 사용자 정의 오류보다는 표준 오류를 사용하라


직접 오류를 정의하기 보다는 표준 라이브러리 오류를 사용하는 것이 좋다

당연한 이야기지만 문제가 발생했을 때, 그게 잘 알려진 문제라면 해결한 사람도 많을 것이기 때문에 도움받기 쉽다

 

Item 7 : exception의 대체, null과 failure를 사용하라


exception을 남발해선 안된다

단순히 정보 전달의 목적으로 사용해선 안되고 정말 예외적인 상황이 발생했을 때 사용하여 적절한 처리가 이루어지도록 해야 한다

 

예외를 던지지 않고 예측할 수 있는 범위의 오류를 표현하는 방법

1. null 리턴 

getOrNull : 리스트에서 값이 있으면 값을 반환하고 없으면 null을 반환하는 함수. IndexOutOfBoundException을 피할 수 있다

 

2. sealed class Failure 타입 리턴

sealed class Result<out R> {
    data class Success<out T>(val data: T) : Result<T>()
    data class Failure(val throwable: Throwable) : Result<Nothing>()
}

 

전달해야 할 정보가 있다면 Failure를 그렇지 않다면 null을 사용한다

 

 + 지식

kotlin에 chekedException이 없는 이유

코틀린은 자바와 달리 모든 exception이 uncheckedException이기 때문에 사용자가 예외처리를 하지 않을 수도 있다

모든 예외가 uncheckedException인 이유는 checkedException이라 해도 제대로 오류를 처리하지 않는 경우가 많다

이렇게 되면 checkedException이 오히려 불필요한 코드만 증가시킬 뿐이기에 unchecked 하는 방향으로 정한 것 같다

참고

 

Exceptions | Kotlin

 

kotlinlang.org

 

Exception과 Error의 차이

Exception과 Error 모두 Throwable 클래스를 상속받는 클래스다

 

Error는 프로그램이 종료되는 수준의 심각한 문제

: OutOfMemoryError, StackOverflowError

 

Exception은 개발자의 로직에서 발생한 실수나  런타임 시 발생하는 문제로 예외처리를 통해 프로그램 종료를 방지할 수 있다

: NullPointerException, IlleagalArgumentException

 

Item 8 : 적절하게 Null을 처리하라


null의 의미

"어떤 동작이 제대로 이루어지지 않아서", "조건에 맞는 값이 없어서" 등등의 이유로

값이 설정되지 않았다, 값이 제거되었다

 

null 안전하게 처리하는 방법

1. ?, 스마트캐스팅, Elvis 연산자 활용

 

printer?.print() // safe call
if (printer != null) printer.print() // smart casting

 

컬렉션 처리를 할 때 무언가 없다는 의미를 나타낼 때는 null이 아니라 빈 컬렉션을 사용하는 것이 일반적이다

null 은 컬렉션 자체가 없다는 의미이며, null 을 반환하면 컬렉션 자체가 null인지도 체크도 하고 값이 null인지도 체크해야 해서 조건이 여러개 걸리게 된다

그래서 컬렉션의 데이터가 없다는 것을 나타내기 위해선 orEmpty()를 사용하면 편하다

 

2. 오류를 throw

개발자가 당연히 동작할 것이다 라고 생각하게 되는 부분에서 문제가 발생할 경우에는 오류를 강제로 발생시켜 오류를 알리는 것이 문제 파악에 도움된다

이럴 때 throw, !!, requireNotNull, checkNotNull 등을 활용할 수 있다

 

그러나 !!으로 쓰이는 not-null asertion은 사용이 간단하고 가독성 측면에서 좋을 수 있으나,

null 값이 들어올 경우 NPE가 발생하기 때문에 좋은 해결 방법이 아닐 수도 있다

 

프로퍼티 값이  클래스 생성 이후에 채워져서 null로 초기화 시켜두고 이후에 값을 채우는 패턴이 종종있는데 이는 의미있는 null 값이 아니다

이럴때는 lateinit, 원시타입일 경우에는 Delegates.notNull 을 사용하도록 한다

 

3. 함수 또는 프로퍼티를 리팩토링 하여 nullable 타입이 나오지 않게 바꾼다

 

Item 9 : use를 사용하여 리소스를 닫아라


close 메서드를 사용해서 명시적으로 닫아야 하는 리소스들

  • InputStream, OutputStream
  • java.sql.Connection
  • java.io.Reader
  • java.new.Socket, java.util.Scanner

보통 try-catch-finally 블럭을 사용하여 finally에서 close를 해주는데,

문제는 리소스를 닫을 때 예외가 발생할 가능성이 있다

이를 해결하기 위해선 finally에서도 try-catch 블럭으로 한번 더 close 호출부를 감싸주어야 하는데 이러면 코드가 복잡해진다

 

kotlin에서는 use라는 이름의 함수를 제공하여 위와 같은 문제를 편하게 해결할 수 있도록 만들었다

 

@InlineOnly
public inline fun <T : Closeable?, R> T.use(block: (T) -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    var exception: Throwable? = null
    try {
        return block(this)
    } catch (e: Throwable) {
        exception = e
        throw e
    } finally {
        when {
            apiVersionIsAtLeast(1, 1, 0) -> this.closeFinally(exception)
            this == null -> {}
            exception == null -> close()
            else ->
                try {
                    close()
                } catch (closeException: Throwable) {
                    // cause.addSuppressed(closeException) // ignored here
                }
        }
    }
}

 

Item 10 : 단위 테스트를 만들어라


  • 복잡한 부분
  • 계속해서 수정이 일어나고 리팩토링이 일어날 수 있는 부분
  • 비즈니스 로직
  • 공용 API
  • 문제가 자주 발생하는 부분
  • 수정해야 하는 프로덕션 버그 

등에서는 단위테스트를 꼭 실행해야 코드를 안전하게 만들 수 있다

'Kotlin > 이펙티브코틀린' 카테고리의 다른 글

3장 재사용성 : 23 ~ 25  (0) 2023.08.23
2장 가독성 : Item 14 - 16  (0) 2023.08.02
1장 : item 1  (0) 2023.07.12