아이템 1: 가변성을 제한하라
코틀린은 모듈로 프로그램을 설계한다. 모듈은 클래스, 객체, 함수, 타입 별칭(type alias), 톱레벨(top-level) 프로퍼티 등 다양한 요소로 구성된다. 이러한 요소중 일부는 상태(가변성)를 가질 수 있다. mutable 객체를 사용하거나, var 프로퍼티를 사용하거나....
요소가 상태를 갖게 되면 시간(이력)에 따라 값이 변하기 때문에 바람직하지 않다.
그래서 코틀린은 가변성을 제한할 수 있게 설계되어 있다. 대표적인 방법은 아래와 같다.
- val / var
- Immutable / Mutable
- data class copy() / data class
한눈에 볼 수 있게 Immutable / Mutable 형식으로 정리했다.
정리하자면
- 가능하다면 Mutable보다는 Immutable을 사용하는 것이 좋다.
- Mutable객체는 외부에 노출하지 않는 것이 좋다.
아이템 2: 변수의 스코프를 최소화하라
아이템 3: 최대한 플랫폼 타입을 사용하지 말라
아이템 4: inferred 타입으로 리턴하지 말라
아이템 5: 예외를 활용해 코드에 제한을 걸어라
확실하게 어떤 형태로 동작해야 하는 코드가 있다면, 예외를 활용하는 것이 좋다. 코틀린에서는 코드의 동작에 제한을 걸 때 다음 방법을 쓸 수 있다.
- require 블록 : 값을 제한
- check 블록 : 상태와 관련된 동작을 제한
블록을 사용해 예외를 처리하면 다음과 같은 장점이 있다.
- 리팩토링에 용이하다.
- 예상하지 못한 동작을 실행하지 않는다.
- 단위 테스트를 줄일 수 있다.
- 타입 변환을 줄일 수 있다.
require
fun factorial(n: Int){
// require example
require(n>=0)
require(n>=0){ "Cannot calculate factorial of $n. Because it's smaller than 0."}
// require not null example
requireNotNull(n)
requireNotNull(n){ "Cannot calculate factorial of $n. Because it's null"}
}
require/requireNotNull은 위 코드처럼 사용한다.
require는 제한을 확인하고, 제한을 만족하지 못할 경우 예외를 throw한다.
함수로만 사용하면 IllegalArgumentException을 throw하고, 람다를 활용해서 지연메세지를 정의할 수도 있다.
check
// check example
fun speak(text: String){
check(isInitialized)
check(isInitialized){ "Cannot work. Because it's not initialized"}
}
// check not null example
fun getUserInfo(): UserInfo{
checkNotNull(token)
checkNotNull(token){ "Cannot work. Because it's null"}
}
check/checkNotNull은 위 코드처럼 사용한다.
check는 require와 비슷하지만, 지정된 예측을 만족하지 못할 때 예외를 throw한다.
함수로만 사용하면 IllegalStateException을 throw하고, 람다를 활용해서 지연메세지를 정의할 수도 있다.
nullability와 스마트 캐스팅
코틀린에서는 require(), check() 블록으로 true가 나왔다면 블록 이후도 true라고 가정한다. 따라서 이를 잘 활용하면 스마트 캐스트가 작동한다.
class Person(val email: String?)
fun sendEmail(person: Person, message: String){
// String? -> String smart-casting
requireNotNull(person.email)
val email = person.email
// ...
}
또한 nullability를 목적으로, Elvis연산자를 활용하는 경우가 많다. 오른쪽에 throw 또는 return을 두고 사용한다.
fun sendEmail(person: Person, message: String){
val email = person.email ?: return
// ...
}
프로퍼티에 문제가 있어서 null일 때 여러 처리를 해야 할 때에, return/throw와 run 함수를 조합할 수도 있다.
fun sendEmail(person: Person, message: String){
val email = person.email ?: run{
Log.e("Email not sent, no email address")
return
}
// ...
}
아이템 6: 사용자 정의 오류보다는 표준 오류를 사용하라
위의 require, check를 사용하면 대부분의 코틀린 오류를 처리할 수 있지만, 예측하지 못한 상황에 Exception이 발생할 수도 있다. 일반적으로 사용되는 예외는 다음과 같다.
- IndexOutOfBoundsException : 인덱스 파라미터 값이 범위를 벗어났을 때 나타남
- ConcurrentModificationException : 동시 수정(concurrent midification)을 금지했는데, 발생했을 때 나타남
- UnsupportedOperationException : 사용자가 사용하려고 했던 메서드가 현재 객체에서는 사용할 수 없을 때 나타남
- NoSuchElementException : 사용자가 사용하려고 했던 요소가 존재하지 않을 때 나타남 (ex) 내부 요소가 없는 Iterable에 대한 next 호출)
아이템 7: 결과 부족이 발생할 경우 null과 Failure를 사용하라
아이템 8: 적절하게 null을 처리하라
아이템 9: use를 사용하여 리소스를 닫아라
아이템 10: 단위 테스트를 만들어라
'Books > Effective Kotlin' 카테고리의 다른 글
[Effective Kotlin] 6장 클래스 설계 (0) | 2022.07.25 |
---|---|
[Effective Kotlin] 5장 객체 생성 (0) | 2022.07.18 |
[Effective Kotlin] 4장 추상화 설계 (0) | 2022.07.17 |
[Effective Kotlin] 3장 재사용성 (0) | 2022.07.17 |
[Effective Kotlin] 2장 가독성 (0) | 2022.06.26 |