728x90
이펙티브 코루틴을 요약한 내용입니다.
- 예외를 활용해 제한을 걸어주는 것이 좋음
- 코틀린에서 코드의 동작에 제한을 걸 때 다음과 같은 방법을 사용할 수 있음
- require 블록: 아규먼트를 제한할 수 있음
- check 블록: 상태와 관련된 동작을 제한할 수 있음
- assert 블록: 어떤 것이 true인지 확인 가능
- assert 블록은 테스트 모드에서만 동작
- return 또는 throw와 함께 활용하는 Elvis 연산자
- 사용 예제
- 제한을 걸면 문서를 읽지 않은 개발자도 문제를 확인할 수 있음
- 문제가 있을 경우에 함수가 예상하지 못한 동작을 하지 않고 예외를 발생
- 코드가 어느 정도 자체적으로 검사를 함
- 스마트 캐스트 기능을 활용할 수 있음
fun pop(num: Int = 1): List<T> {
require(num <= size) {
"Cannot remove more elements than current size"
}
check(isOpen) {
"Cannot pop from closed stack"
}
val ret = collection.take(num)
collection = collection.drop(num)
assert(ret.size == num)
return ret
}
아규먼트
- 아규먼트에 제한을 거는 예
- 숫자를 아규먼트로 받아서 팩토리얼을 계산한다면 숫자는 양의 정수여야 함
- 좌표들을 아규먼트로 받아서 클러스터를 찾을 때는 비어 있지 않은 좌표 목록이 필요
- 사용자로부터 이메일 주소를 입력받을 때는 값이 입력되어 있는지, 이메일 현식이 올바른지 확인
- 제한을 걸 때는 require 함수를 사용
- 제한을 만족하지 못할 경우 throw 발생
fun factorial(n: Int): Long { require(n >= 0) return if (n <= 1) 1 else factorial(n - 1) * n } fun findClusters(points: List<Point>): List<Cluster> { require(points.isNotEmpty()) } fun sendEmail(user: User, message: String) { requireNotNull(user.email) require(isValidEmail(user.email)) }
- require 함수는 조건을 만족하지 못할 때 무조건적으로 IllegalArgumentException을 발생
- 람다를 활용해 메시지를 정의할 수 있음
fun factorial(n: Int): Long {
require(n >= 0) {
"Cannot calculate factorial of $n because it is samller than 0"
}
return if (n <= 1) 1 else factorial(n - 1) * n
}
상태
- 구체적인 조건을 만족할 때만 함수를 사용할 수 있게 해야할 때가 있음
- 어떤 객체가 미리 초기화되어 있어야만 처리를 하게 하고 싶은 함수
- 사용자가 로그인 했을을 때만 처리를 하게 하고 싶은 함수
- 객체를 사용할 수 있는 시점에 사용하고 싶은 함수
- 상태와 관련된 제한을 걸 때는 일반적으로 check 함수를 사용
fun speak(text: String) {
check(isInitialized)
}
fun getUserInfo(): UserInfo {
checkNotNull(token)
}
fun next(): T {
check(isOpen)
}
- check 함수는 require와 비슷하지만, 지정된 예측을 만족하지 못할 때 IllegalArgumentException 발생
Assert 계열 함수 사용
- 함수가 올바르게 구현되었다면 확실하게 참을 낼 수 있는 코드
- 구현 문제로 발생할 수 있는 문제를 예방하려면, 단위 테스트를 사용하는 것이 좋음
class StackTest {
@Test
fun `Stack pops correct number of elements`() {
val stack = Stack(20) { it }
val ret = stack.pop(10)
assertEquals(10, ret.size)
}
}
- 단위 테스트는 구현의 정확성을 확인하는 가장 기본적인 방법
- pop 함수 내부에서 Assert 계열의 함수를 사용
- 코틀린/JVM에서만 활성화 됨
- -ea JVM 옵션 활성화
fun pop(num: Int = 1): List<T> { assert(ret.size == num) return ret }
- 코틀린/JVM에서만 활성화 됨
- 프로덕션 환경에서는 오류가 발생하지 않음
- 코드가 정말 심각한 오휴고, 심각한 결과를 초래할 수 있는 경우에는 check를 사용하는 것이 좋음
- assert 장점
- Assert 계열의 함수는 코드를 자체 점검하며, 더 효율적으로 테스트할 수 있음
- 특정 상황이 아닌 모든 상황에 대한 테스트를 할 수 있음
- 실행 시점에 정확하게 어떻게 되는지 확인
- 실제 코드가 더 빠른 시점에 실패하게 만듬
- 예상하지 못한 동작이 언제 어디서 실행되었는지 쉽게 찾을 수 있음
- assert를 활용해도 여전히 단위 테스트는 따로 작성
nullability와 스마트 캐스팅
- 코틀린에서 require와 check 블록으로 어떤 조건을 확인해서 true가 나왔다면, 해당 조건은 이후로도 true일거라고 가정
public inline fun require(value: Boolean): Unit {
contract {
returns() implies value
}
require(value) {
"Failed requirement."
}
}
- require와 check를 활용해서 타입 비교를 했다면 스마트 캐스트가 동작
fun changeDress(person: Person) {
require(person.outfit is Dress)
val dress: Dress = person.outfit
}
- null을 확인할 때 유용
class Person(val email: String?)
fun sendEmail(person: Person, message: String) {
require(person.email != null)
val email: String = preson.email
}
- requireNotNull, checkNotNull이라는 특수한 함수를 사용해도 괜찮음
class Person(val email: String?)
fun validateEmail(email: String) { }
fun sendEmail(person: Person, message: String) {
val email = requireNotNull(person.email)
validateEmail(email)
}
fun sendEmail(person: Person, text: String) {
requireNotNull(person.email)
validateEmail(person.email)
}
- nullability를 목적으로 오른쪽에 throw 또는 return을 두고 Elvis 연산자를 확용하는 경우가 많음
- 오류를 발생하지 않고 단순하게 함수를 중지할 수도 있음
- 함수가 중지된 이유를 로그에 출력해야 할 때 사용할 수 있음
fun sendEmail(person: Person, text: String) {
val email: String = person.email ?: run {
log("Email not sent, no email address")
return
}
}
정리
- 제한을 훨씬 더 쉽게 확인할 수 있다
- 애플리케이션을 더 안정적으로 지킬 수 있다
- 코드를 잘못 쓰는 상황을 막을 수 있다
- 스마트 캐스팅을 활용할 수 있다
728x90
'코틀린 스터디 > 이펙티브 코틀린' 카테고리의 다른 글
아이템7) 결과 부족이 발생한 경우 null과 Failure를 사용하라 (0) | 2024.02.22 |
---|---|
아이템6) 사용자 정의 오류보다는 표준 오류를 사용하라 (0) | 2024.02.16 |
아이템4) inferred 타입으로 리턴하지 말라 (0) | 2024.02.08 |
아이템3) 최대한 플랫폼 타입을 사용하지 말라 (0) | 2024.02.07 |
아이템2) 변수의 스코프를 최소화하라 (0) | 2024.02.06 |