🛠️ require에 대한 정보
Elvis 연산자에 대한 정의와 예시
ex)
// Stack<T>의 일부 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 }
함수를 정의할 때 타입 시스템을 활용해서 아규먼트(argument)에 제한을 거는 코드를 많이 사용
ex)
1. 숫자를 아규먼트로 받아서 팩토리얼을 계삲나다면 숫자는 양의 정수여야 합니다.
2. 좌표들을 아규먼트로 받아서 클러스터를 찾을 때는 비어 있지 않은 좌표 목록이 필요합니다.
3. 사용자로부터 이메일 주소를 입력받을 때는 값이 입력되어 있는지, 그리고 이메일 형식이 올바른지 확인해야 합니다.
일반적으로 제한을 걸 대는 require 함수를 사용
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<Clusters> { require(points.isNotEmpty()) //... } //... fun sendEmail(user: User, message: String) { requireNotNull(user.email) require(isValidEmail(user.email)) }
↪ 입력 유효성 검사 코드는 함수의 가장 앞부분에 배치되므로,
읽는 사람도 쉽게 확인할 수 있음
fun factorial(n: Int): Long { require(n >= 0) { "Cannot calculate factorial of $n " + "because it is smaller 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) //... }
구현 문제로 발생할 수 있는 추가적인 문제를 예방하려면
-> 단위 테스트를 사용하는 것이 좋음👍
class StackTest { @Test fun 'Stack pops correct number of elements'() { val stack = Stack(20) {it} val ret = stack.pop(10) asserEquals(10, ret.size) } //... }
ex) 현재 코드에서 스택이 10개의 요소를 pop하면, 10개의 요소가 나온다는 보편적인 사실을 테스트하고 있다.
모든 pop 호출 위치에서 제대로 동작하는지 확인하기 위해
pop 함수 내부에서 Assert 계열의 함수를 사용
fun pop(num: Int = 1): List<T> { //... assert(ret.size == num) return ret }
↪ 이러한 조건은 코틀린/JVM에서만 활성화
➕이를 활용해도 단위 테스트는 따로 작성해야 함
표준 애플리케이션 실행에서는 assert가 예외를 throw하지 않는다는 것
코틀린에서 require와 check 블록으로 어떤 조건을 확인해서 true가 나왔다면,
해당 조건은 이후로도 true일 거라고 가정
public inline fun require(value: Boolean): Unit {
contract {
returns() implies value
}
require(value) {" Failed requirement."}
}
↪ 이를 활용해서 타입 비교를 했다면, 스마트 캐스트가 작동
ex) 어떤 사람(person)의 복장(person.outfit)이 드레스(Dress)여야 코드가 정상적으로 진행된다. 따라서 만약 이러한 outfit 프로퍼티가 final이라면, outfit 프로퍼티가 Dress로 스마트 캐스트된다.
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 = person.email
//...
}
이러한 경우 requireNotNull, checkNotNull이라는 특수한 함수를 사용해도 괜찮음
둘 다 스마트 캐스트를 지원하므로, 변수를 '언팩(unpack)'하는 용도로 활용할 수 있음
class Person(val email: String?)
fun validateEmail(email: String) {/*..*/}
fun sendEmail(person: Person, text: String) {
val email = requireNotNull(person.email)
validateEmail(email)
//...
}
fun sendEmail(person: Person, text: String) {
requireNotNull(person.email)
validateEmail(person.email)
//...
}
nullability를 목적으로, 오른쪽에 throw 또는 return을 두고 Elvis 연산자를 활용하는 경우가 많음
첫 번째로 오른쪽에 return을 넣으면, 오류를 발생시키지 않고 단순하게 함수를 중지할 수도 있음
fun sendEmail(person: Person, text: String) {
val email: String = person.email ?: return
//...
}
프로퍼티에 문제가 있어서 null일 때 여러 처리를 해야 할 때도,
reutrn/throw와 run 함수를 조합해서 활용하면 됨
이는 함수가 중지된 이유를 로그에 출력해야 할 때 사용 할 수 있음
fun sendEmail(person: Person, text: String) {
val email: String = person.email ?: run {
log("Email not sent, no email address")
return
}
//...
}
return과 throw를 활용한 Elvis 연산자는 nullable을 확인할 때 굉장이 많이 사용되는 관용적인 방법
이러한 코드는 함수의 앞부분에 넣어서 잘 보이게 만드는 것이 좋음
https://wayhome25.github.io/etc/2017/12/31/parameter-argument/