이 책에서 코틀린을 사용하는 첫번째 이유로 안정성(Safety)을 꼽고 있다. 코틀린은 잠재적인 오류와 크래시를 줄이며, 사용자와 개발자 모두에게 좋은 비즈니스 가치를 제공한다라고 설명한다.

안전성은 기본적으로는 언어일뿐이고, 사실은 개발자가 이를 사용하여 어떻게 코드를 작성하느냐에 따라 안정성이 좌지우지 된다. 다음의 항목들을 참고하여 오류가 덜 발생하는 코드를 만들 수 있다고 이 책은 소개 하고 있다.

1. 가변성을 제한하기

가변성이란 상태(state)가 변할 수 있는 점을 말한다.

가변성이 갖는 단점

  • 상태 변경이 잦으면 이를 추적하기 힘들고, 예상치 못한 오류가 발생한다.
  • 멀티스레드에서 동시성 오류가 발생
  • 테스트하기 어려움

가변성을 제한 하기

  • 읽기 전용 프로퍼티 (val) 사용하기
  • 가변 컬렉션과 읽기 전용 컬렉션 구분하기. 예) List vs MuatableList
  • 데이터 클래스의 copy 활용하기
  • 가변적인 객체를 외부에 노출하지 않기

2. 변수의 스코프 최소화하기

상태를 정의할 때는 변수와 프로퍼티의 스코프(범위)를 최소화 하는 것이 좋다.

  • 범위가 좁으면 변경을 추적하기 슆다.
  • 범위가 넓으면 다른 개발자에 의해 변수가 잘 못 사용될 수 있다.
  • 람다에서의 변수 캡쳐를 항상 주의한다.

3. 플랫폼 타입 사용하지 않기

플랫폼 타입

코틀린은 기본적으로 null을 허용하지 않는 null-safey한 언어다. 하지만 자바와 같은 다른 언어와 상호운용을 하는 경우 문제가 생길 수 있다.

// 자바
public class UserRepo{
    public User getUser(){ ... }
}

//코틀린
val repo = UserRepo()
val user1 = repo.user       // user1의 타입은 User!
val user2:User = repo.user  // user2의 타입은 User
val user3:User? = repo.user // user3의 타입은 User?

코틀린에서는 자바 등의 다른 프로그래밍 언어에서 넘어온 타입을 느낌표(!)를 붙여 특수하게 다루는데 이를 플랫폼 타입이라고 부른다. 플랫폼 타입은 nullable인지 아닌지 알 수 없는 타입을 말한다.

플랫폼 타입을 사용하는 코드는 해당 코드 라인을 위협할 뿐만 아니라, 이를 활용하는 곳까지 영향을 줄 수 있는 위험한 코드이다. 이런 코드를 사용하고 있다면 @Nullable, @NonNull 등의 어노테이션을 활용하여 플랫폼타입의 사용을 제한할 수 있다.

4. 추론 된 타입으로 반환하지 않기

코틀린에서는 타입 추론(type inference)을 도입했다. 타입이 명시적이지 않은 경우, 코드 수정이나 프로젝트가 고도화 될 때 예측하지 못한 결과를 낼 수 있다. 따라서 타입을 명시적으로 지정하거나 또는 외부에서 참조가능한 부분의 리턴타입을 명시적으로 지정해주는 것이 좋다.

왜 코틀린에서 제공하는 타입 추론 기능을 안쓰려고 하느냐라고 반문할 수도 있다. 물론 타입추론이 갖는 장점도 분명히 있다. 하지만 타입추론이 끼칠 수 있는 영향을 생각해야하며, 타입을 확실하게 지정해야 하는 경우는 반드시 그렇게 해야한다라는 원칙을 가지고 코드를 작성하시길 바란다.

5. 예외를 활용하기

확실하게 어떤 형태로 동작해야하는 코드가 있다면 다음과 같은 방법을 사용할 수 있다.

  • require 블록 : 인자와 관련된 예측을 정의할 때 사용하는 방법
  • check 블록 : 상태와 관련된 예측을 정의할 때 사용하는 방법
  • assert 블록 : 테스트 시에 사용하는 방법
  • return 또는 throw와 함께 활용하는 엘비스 연산자

6. 표준 오류 사용하기

커스텀 Exception 보다는 미리정의 된 Exception을 활용하자.

예) IllegalArgumentException, IllegalStateException, IndexOutOfBoundsException 등

7. null과 Failure 활용하기

함수가 원하는 결과를 만들어 반환하지 못할 때가 있다. 이때 충분히 예측 가능한 오류 범위내에서 null과 Failure를 활용할 수 있다.

단순히 실패한 결과를 null로 반환할 수 있는데, 이때 단순히 get() 보다는 getOrNull()과 같은 명시적인 이름을 붙이는게 좋다. 실패한 결과에 자세한 설명이 필요하다면 sealed 클래스로 Failure를 반환하여 내부에 오류 정보등을 같이 전달하자.

8. null을 적절하게 처리하기

null을 안전하게 처리하는 방법

  • 물음표를 사용하여 액세스하는 safe call 하기
    예) user?.name
  • 윗 코드라인에서 null이 아님을 확인하는 스마트 캐스팅을 활용하기
    예) if(!name.isNullOrBlank()) { name.toUpperCase() }
  • 엘비스 연산자 사용하기
  • !! 표현식을 사용을 자제한다.
  • lateinit 또는 Delegates.notNull을 사용하기

9. use를 사용하여 GC하기

특정 객체(리소스)가 더이상 필요하지 않을 때, close 메서드를 호출해서 명시적으로 닫고, GC대상으로 만들어야 하는 경우가 있다. 예를 들면 BufferedReader가 있다.

표준 라이브러리에 use라는 함수를 사용하면 close를 명시적으로 호출하지 않아도 코드 블럭이 끝나는 시점에 자동으로 close를 호출할 수 있다.

fun countCharacters(path:String):Int{
    val reader = BufferedReader(FileReader(path))
    reader.use{
        return reader.lineSequence().sumBy{ it.length }
    }
}

10. 유닛테스트 만들기

유닛 테스트는 개발자가 만들고 있는 요소가 제대로 동작하는지를 빠르게 피드백해 주므로 개발하는 동안에 큰 도움이 된다.

효과적인 단위 테스트를 하는 방법을 습득하여, 테스트코드를 작성하면 안정적인 프로그램을 만들 수 있다.

카테고리: Kotlin

0개의 댓글

답글 남기기

Avatar placeholder

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다.