728x90
이펙티브 코루틴을 요약한 내용입니다
- 함수가 null을 리턴한다는 것은 함수에 다라 여러 의미를 가질 수 있음
- String.toIntOrNull()은 String을 Int로 적절하게 변환할 수 없는 경우 null 리턴
- Iterable<T>.firstOrNull(() → Boolean) 은 주어진 조건에 맞는 요소가 없을 경우 null 리턴
- null은 최대한 명확한 의미를 갖는 것이 좋음
val printer: Printer? = getPrinter()
printer.print() // 오류
printer?.print() // 안전한 호출
if (printer != null) printer.print() // 스마트 캐스팅
printer!!.print() // not-null assertion
- nullable 타입은 세 가지 방법으로 처리
- ?., 스마트 캐스팅, Elvis 연산자 활용
- throw
- 함수 또는 프로퍼티를 리팩터링해서 nullable 타입이 나오지 않게 변경
null을 안전하게 처리하기
- 안전 호출(safe call), 스마트 캐스팅(smart casting)
- 애플리케이션 사용자 관점(개발자)에서 가장 안전한 방법
- 편리하여 가장 많이 활용
printer?.print() // 안전한 호출 if (printer != null) printer.print() // 스마트 캐스팅
- 대표적으로 인기 있는 다른 방법은 Elvis 연산자 사용
- 연산자 오른쪽에 return 또는 throw를 포함한 모든 표현식이 허용
val printerName1 = printer?.name ?: "Unnamed" val printerName2 = printer?.name ?: return val printerName3 = printer?.name ?: throw Error("Printer must be named")
- 스마트 캐스팅은 코틀린의 규약 기능(Contracts feature)를 지원
println("What is your name?")
val name = readLine()
if (!name.isNullOrBlank()) {
println("Hello ${name.toUpperCase()}")
}
val news: List<News>? = getNews()
if (!news.isNullOrEmpty()) {
news.forEach { notifyUser(it) }
}
방어적 프로그래밍과 공격적 프로그래밍
- 모든 가능성을 올바른 방식으로 처리하는 것을 방어적 프로그래밍(defensive programming)
- 프로덕션 환경에서 발생할 수 있는 것들로부터 프로그램을 방어해서 안정성을 높이는 방법을 나타내는 포괄적인 용어
- 상황을 처리할 수 있는 올바른 방법이 있을 때 좋음
- 모든 상황을 안전하게 처리하는 것은 불가능, 이런 경우 공격적 프로그래밍(offensive programming) 방법 사용
- 예상하지 못한 상황이 발생했을 때 문제를 개발자에게 알려서 수정하게 만듬
- require, check, assert가 공격적 프로그래밍을 위한 도구
- 코드의 안전을 위해서는 두가지 방법 모두 필요
오류 throw하기
- printer가 null일때 개발자에게 알리지 않고 코드가 그대로 진행 됨
- 개발자가 오류를 찾기 어렵게 만듬
- 문제가 발생한 경에는 개발자에게 오류를 강제로 발생시켜 주는 것이 좋음
- throw, !!, requireNotNull, checkNotNull 활용
fun process(user: User) { requireNotNull(user.name) val context = checkNotNull(context) val networkService = getNetworkService(context) ?: throw NoInternetConnection() networkService.getData { data, userData -> show(data!!, userData!!) } }
not-null assertion(!!)과 관련된 문제
- nullability는 어떻게든 적절하게 처리해야 하므로 추가 비용이 발생
- 필요한 경우가 아니라면 nullability 자체를 피하는 것이 좋음
- null은 중요한 메시지를 전달하는 데 사용될 수 있음
- 이유 없이 null을 사용했다면 다른 개발자들이 코드를 작성할 때
- !!연산자를 사용하게 됨
- 의미 없이 더럽히는 예외 처리를 해야함
- nullability를 피할 때 사용할 수 있는 몇가지 방법
- 클래스에서 nullability에 따라 여러 함수를 만들어 제공
- List → get, getOrNull 함수
- 클래스 생성 이후에 확실하게 설정된다는 보장이 있다면 lateinit 프로퍼티와 notNull 델리게이트 사용
- 빈 컬렉션 대신 null을 리턴하지 않기
- List<Int>?와 Set<String?>과 같은 컬렉션을 빈 컬렉션으로 둘 때와 null로 둘 때는 의미가 완전 다름
- 요소가 부족하다는 것을 나타내려면 빈 컬렉션 사용
- nullable enum과 None enum값은 완전 다른 의미
- null enum은 별도 처리 필요
- None enum 정의에 없으므로 필요한 경우에 사용하는 쪽에서 추가해서 활용
- 클래스에서 nullability에 따라 여러 함수를 만들어 제공
lateinit 프로퍼티와 notNull 델리게이트
- 클래스 생성 중에 초기화할 수 없는 프로퍼티를 가지는 것은 드문 일이 아니지만 분명 존재
- 프로퍼티는 사용 전에 반드시 초기화해서 사용해야 함
- ex) JUnit의 @BeforEach
class UserControllerTest { private var dao: UserDao? = null private var controller: UserController? = null @BeforeEach fun init() { dao = mockk() controller = UserController(dao!!) } @Test fun test() { controller!!.doSomething() } }
- 프로퍼티를 사용할 때마다 nullable에서 null이 아닌 것으로 타입 변환하는 것은 바람직하지 않음
- lateinit 한정자를 사용하는 것이 좋음
- lateinit은 처음 사용하기 전에 반드시 초기화가 되어 있을 경우에만 사용
class UserControllerTest { private lateinit var dao: UserDao private lateinit var controller: UserController @BeforeEach fun init() { dao = mockk() controller = UserController(dao) } @Test fun test() { controller.doSomething() } }
- lateinit과 nullable 비교
- !! 연산자로 언팩 하지 않아도 됨
- 어떤 의미를 나타내기 위해서 null 사용하고 싶을 때, nullable로 만들 수 있음</aside>
- <aside> ❓ nullable이 아닌데???
- 프로퍼티가 초기화된 이후에는 초기화되지 않은 상태로 돌아갈 수 없음
- lateinit을 사용할 수 없는 경우
- JVM에서 Int, Long, Double, Boolean과 같은 기본 타입과 연결된 타입으로 프로퍼티 초기화해야 하는 경우
- lateinit 보다는 약간 느린 Delegates.notNull을 사용
class DoctorActivity: Activity() { private var doctorId: Int by Delegates.notNull() private var fromNotification: Boolean by Delegates.notNull() override fun onCreate(saveInstanceState: Bundle?) { super.onCreate(saveInstanceState) doctorId = intent.extras.getInt(DOCTOR_ID_ARG) fromNotification = intent.extras.getBoolean(FROM_NOTIFICATION_ARG) } }
- 초기화하는 프로퍼티는 지연 초기화하는 형태로 프로퍼티 위임을 사용할 수 있음
class DoctorActivity: Activity() {
private var doctorId: Int by arg(DOCTOR_ID_ARG)
private var fromNotification: Boolean by arg(FROM_NOTIFICATION_ARG)
}
728x90
'코틀린 스터디 > 이펙티브 코틀린' 카테고리의 다른 글
아이템10) 단위 테스트를 만들어라 (1) | 2024.02.29 |
---|---|
아이템9) use를 사용하여 리소스를 닫아라 (0) | 2024.02.28 |
아이템7) 결과 부족이 발생한 경우 null과 Failure를 사용하라 (0) | 2024.02.22 |
아이템6) 사용자 정의 오류보다는 표준 오류를 사용하라 (0) | 2024.02.16 |
아이템5) 예외를 활용해 코드에 제한을 걸어라 (0) | 2024.02.14 |