티스토리 뷰

특정한 동작을 해야하는 코드가 있다면 예외를 활용해 제한을 거는것이 좋다

 

제한을 걸었을때 장점

1. 문서를 읽지 않은 개발자도 문제를 확인할 수 있다.

- 코드의 최상단 부분에 제한을 걸어두면 문서를 읽지 않은 개발자도 코드의 윗줄만 보고 간단히 어떤 값이 오면 문제가 될수있나 확인 가능

 

2. 문제가 있는 경우 함수가 예상하지 못한 동작을 하지 않음. 

- 문제가 있는 값이 올때 처리를 해주기때문에 예상치못한 동작을 막는다

 

3. 코드가 어느정도 자체적으로 검사된다(?) 단위 테스트를 줄일 수 있다.

- 아마 assert 얘기인듯.. 함수 중간중간 assert를 통해 확인을 해주면 테스트 코드를 하나하나 만들지 않아도 어느정도 테스팅이 된다는 말인듯

 

4. 스마트 캐스트를 활용할 수 있게되어 타입 변환을 적게할 수 있음

- 아래에 작성한 방법대로 타입 체크를 하여 제한을 걸면 스마트캐스팅이 되어 ~as 이런식으로 타입 변환하는것을 줄일수있다


제한을 거는 방법들

1. require 블록

- 매개변수가 참인지, 거짓인지 판별해서 거짓이라면 IllegalArgumentException을 던진다.

require(1 == 2) {
        "거짓!"
}

 

2. check 블록

- 매개변수의 값이 참인지 체크한다. 아니라면 IllegalStateException 을 throw

check(1==2) {
        "check fail...."
}

 

3. assert 블록

- 실제 런타임에서 사용하는것이 아니라 테스트 코드에서만 사용가능

- 특정 조건을 만족하는지 테스팅하기 위함

assert(10 == 3)

테스트 코드 외에 다른곳에서 assert를 사용하려면 -ea JVM 옵션을 활성화해야 한다.

 

4. return 또는 throw와 함께 활용하는 Elvis 연산자


아규먼트에 제한걸기

함수의 매개변수를 받을때 그 변수 값에 제한이 필요한 경우가 있다.

ex) 사용자에게 메뉴 리스트를 입력받아 저장하는 함수 -> 메뉴 리스트가 비어있어서는 안된다.

이런식으로 아규먼트에 제한이 필요할때 일반적으로 require를 사용한다.

fun saveMenus(menu: List<String>) {
    require(menu.isNotEmpty())
    // 메뉴 저장
    print("menu saved!")
}

위와같이 입력된 값의 유효성 검사는 함수의 가장 윗부분에 배치되므로 함수를 읽는 사람도 쉽게 어떤 값이 들어와야 하는지 확인 가능하다.


상태에 제한걸기

특정 조건을 만족하는 경우에만 함수를 쓸수있도록 해야하는 경우가 있다.

ex) 사용자가 로그인을 한 경우에만 게시글을 읽도록하는 경우

fun main() {
	login()
    print(getBoard("12"))
}
var token: String? = null

fun login() {
    token = "token value..."
}

fun getBoard(boardId: String): String {
	val boardContent = "게시글 1 내용..."
    checkNotNull(token)
    return boardContent
}

check 는 require와 비슷하지만 다른 exception을 던진다

이렇게 상태에 대해 확인을 하고 제한을 거는것을 하면..

규약을 어기고 함수를 사용하는 사람이 사용하면 안되는곳에서 함수를 호출하고 있는지 확인을 할 수 있고 이런 문제가 생길것으로 예측될때 쓴다

항상 함수를 쓰는사람이 올바르게 쓸것이라고 생각하고 만들기보단 이렇게 예외를 항상 체크해주는것이 좋다

 

일반적으로 맨 앞에서 함수 전체에 대해 처음부터 이 함수가 실행가능한것인지 확인(ex 매개변수 올바르게 왔는지 등) 할때는 require로 제한을 걸고 그 밑으로 상태에 따라서 제한을 줄땐 check를 쓴다.


내가만든 함수 확인하기

내가 만든 함수를 확인하기 위헤서는 assert를 써서 테스팅 코드를 만든다

 

왜 테스팅이 필요한가?

1. 나의 실수로 잘못된 결과를 내는 코드를 만들 수 있다.

특정한 입력이 들어올때 특정 값을 내야하는데 구현이 잘못되어 다른 값이 리턴될 수 있음

2. 잘못된 리팩터링

내가 아닌(혹은 내가 만든코드를 잊어버렸을때) 다른사람이 리팩터링을 하다 실수해서 틀린 값을 리턴하도록 수정했을수 있다. 이런경우 리팩터링 후에 테스팅을 진행해서 올바른 값이 나오는지 확인 필요하다

 

class StackTest {
    @Test
    fun `Stack pops correct number of elements`() {
        val stack = Stack(20) { it }
        val let = stack.pop(10)
        assertEquals(10, ret.size)
    }
}

위 처럼 테스트 코드에서 쓸수도 있고

fun pop(num: Int = 1): List<T> {
    // ...
    assert(ret.size == num)
    return ret
}

이런식으로 테스트 코드외에 값 제한, 확인이 필요한 함수 안에서 assert를 쓸수도 있다

테스트 코드가 아니라 확인이 필요한 함수 안에서 assert를 쓸때 장점

1. 하나하나 모든 예외를 생각해서 테스트 코드를 만들지 않아도 되므로 효율적

2. 특정 상황이 아닌 모든 경우에 대해 테스트 할수있음

3. 어떻게 실행이 이루어지는지 과정을 확인할 수 있음

4. 어떤 시점에 어떤 문제가 있어 테스트가 실패했는지 알수있음

 

하지만 check 나 require와 달리 assert는 테스팅 단계에서만 예외를 던지고 프로덕션 환경에서는 예외를 던지지 않는다

따라서 내가 만든 함수를 다른 개발자가 쓸때 잘못된 동작을 해서 심각한 문제를 일으키는 경우 check, require를 써서 exception을 던지도록 해서 심각한 문제 발생을 막아야한다


조건 확인 후 스마트 캐스트

타입 비교를 한 후에는 그 밑으로 스마트 캐스팅이 작동한다

타입 비교는 require, check 사용, 혹은 if문 사용, elvis 연산자 사용(null 인지 체크하는 경우) 세가지 방법으로 할수있다.

 

require, check 를 통한 타입 체크, 스마트 캐스팅

require, check 를 통해 어떤 조건을 확인한 후 true가 나오면 이후로도 그 조건은 true라고 가정하게된다

open class OutFit

class Dress: OutFit()
class Shirt: OutFit()


fun main(){
	sendEmail(Person("hi@gmail.com", "id123", Dress()))
}

class Person(val email: String?, val id: String, val outFit: OutFit)
fun sendEmail(person: Person) {
    val dress: Dress = person.outFit
}

위와같이 코드를 만들었다면

타입 오류가 생기게 된다

fun sendEmail(person: Person) {
    val dress: Dress = person.outFit as Dress
}

check, require등을 사용하지 않는 경우 위처럼 캐스팅을 해야하지만

fun sendEmail(person: Person) {
    require(person.outFit is Dress)
    val dress: Dress = person.outFit
}

이렇게 require, check로 타입체크 후엔 스마트 캐스팅된다

 

이런 특성은 특히 어떤값이 null 인지 확인할때 유용하다

위처럼 null 가능한 값을 null 불가한 변수에 넣으려하면 타입 에러 생긴다

그런 경우에 require, check를 사용해서 체크한 다음 사용하게되면 유용하다

 

이럴때 requireNotNull, checkNotNull 이란 특수 함수를 써도 된다

- requireNotNull: 매개변수 값이 null이 아니면 값을 반환, null이면 throw IllegalArgumentException

- checkNotNull: 매개변수 값이 null이 아니면 값을 반환, null이면 throw IllegalStateException

class Person(val email: String?)
fun validateEmail(email: String) { /* ... */ }

fun sendEmail(person: Person, text: String) { // 언팩 하는 경우
    val email = requireNotNull(person.email) // person변수 내의 email값을 꺼내서 언팩
    validateEmail(email)
}

fun sendEmail(person: Person, text: String) { // 언팩 안하는 경우
    requireNotNull(person.email)
    validateEmail(person.email)
    // ...
}

위 처럼 특수 함수를 사용하면 null 이 아닌 조건을 만족하는 경우에 괄호에 넣은 값을 리턴해주기때문에 변수 안에 있는 값을 언팩하는 용도로 활용할수있다.

 

if 로 타입 체크 후 return 하기

위에서 본것처럼 require, check 를 안쓰고도 if 문을 통해 타입을 확인하고 스마트캐스팅하는게 가능하다

fun sendEmail(person: Person) {
    if(person.email == null)
    	return
    val email: String = person.email
}

위와 같이 null 체크 후 return을 해버리면 그 밑에 있는 코드들에 조건 확인한 변수에 대해 스마트 캐스팅이된다

fun sendEmail(person: Person) {
    if(person.outFit !is Dress)
    	return
    val dress: Dress = person.outFit
}

위 처럼 타입 체크시에도 쓸수있다. 이런 경우에 잘못된 값이 와도 exception이 생기지 않으니 exception을 일으키고 싶지 않을때 써도 될듯하다

fun sendEmail(person: Person) {
    if(person.email != null) {
        val email: String = person.email
    }
    
    if(person.outFit is Dress){
        val dress: Dress = person.outFit
    }
}

이렇게 해버려도 된다..

 

엘비스 연산자를 사용하여 null 체크

fun sendEmail(person: Person) {
    val email: String = person.email ?: return 
}

위처럼 엘비스 연산자를 사용해 null확인을 하는경우가 많으며 이렇게 작성하는것이 읽기도 좋다

이런식으로 타입 체크를 하고 타입 체크 결과에 따라 이 함수를 실행해야하는지, 말아야하는지 따져야된다면

타입 체크후 return 해버리는 코드를 가장 윗쪽에 배치하는것이 좋겠다.

 


내생각...

안드로이드 앱에서 exception이 발생하면 앱이 종료된다.

안드로이드 앱에서 앱이 비정상 종료되는것은 심각한 문제다.

그렇다면 위와 같이 잘못된 값이 왔을때 exception이 발생하도록 해도 되는것인가?

난 아니라고 생각한다...

앱이 터지나 안터지나 함 볼라고 (혹시몰라서) 위와같이 간단히 만들어봤는데 역시나 터진다.

 

따라서 위와같은 제한 방법을 사용해서 exception이 나타나도록 하는것은 실제 사용자가 있는 앱을 만들때 쓰기보다는 SDK나 라이브러리를 만드는 개발자들이 사용하는게 좋을듯하다...(아마? 아니면 서버 개발자?)

 

실제 앱을 만들땐 exception을 일으켜 앱을 터뜨리는것보단 아래 블로그 내용처럼

https://medium.com/prnd/%EC%95%84%EB%A6%84%EB%8B%B5%EA%B2%8C-%EC%95%B1-%EC%98%A4%EB%A5%98-%EC%B2%98%EB%A6%AC%ED%95%98%EA%B8%B0-8bf9a46df515

 

아름답게 앱 오류 처리하기

앱에서 오류가 발생해도 사용자가 좋은 경험을 유지할 수 있도록 처리하는 글입니다.

medium.com

사용자에게 일시적 오류가 일어났으니 다른 화면으로 이동해달라는 안내를 보여주거나

이상한 값이 들어왔을때 기능이 다르게 실행되도록 조치를 취하는것이 이로울듯하다..

 

가장 마지막에 작성한 if 문을 활용한 방법이나 엘비스 연산자를 사용하여 null 확인하는 방법은 사용자가 있는 앱을 만들때 얼마든지 사용해도 될것같다 (exception을 던지지 않기때문)

나는 실제로 사용하고있기도하다 예를들면...

https://velog.io/@librarywon/RecyclerView%EA%B0%80-NestedSrolledView-%EC%86%8D%EC%97%90-%EC%A1%B4%EC%9E%AC%ED%95%98%EB%A9%B4-%EB%B0%9C%EC%83%9D%ED%95%98%EB%8A%94-%EB%AC%B8%EC%A0%9C%EC%A0%90

 

RecyclerView가 NestedSrolledView 속에 존재하면 발생하는 문제점

📚공부배경 나는 인생네컷 공유 보관 서비스 포포리의 Android 개발자로 있어서 인스타그램과 비슷한 뷰를 구현하는 개발하는 업무를 맡았다. 💡 해당 화면을 구현하기 위해서 아래의 조건을 만

velog.io

위 블로그 내용처럼 여러 타입이 들어갈 수 있는 하나의 리사이클러뷰를 만들때 사용했다.

여러 타입이 들어갈수있는 리사이클러뷰를 만들게 되면 각 타입에 맞는 뷰홀더를 리턴해야 하는데

이때 각 viewHolder안에서 bind 할때

fun bind(item: ItemTypes) {
	if(item is ItemTypes.AType) {
    	//binding...
    }
}

머 이런식으로 해야 되는디 나같은경우에는 (사실 내가 한게 아니고 예전에 누군가 만든 코드 따라한거긴하지만 ^^)

fun bind(item: ItemTypes) {
	if(item !is ItemTypes.AType)
    	return
    //binding...
}

요런식으로 해가지고 괄호 하나를 없앴다 하하

 

 

- 참고 자료

https://seosh817.tistory.com/155#google_vignette

 

[Kotlin] Kotlin 예외처리 - require(), requireNotNull(), check(), checkNotNull()(feat. IllegalArgumentException, IllegalState

Kotlin 예외처리 함수 require(), check() require(), requireNotNull() 함수 require() : 매개변수의 값이 참인지 체크, 거짓이라면 throw IllegalArgumentException requireNotNull() : 매개변수의 값이 null이 아니면 value를 반환

seosh817.tistory.com

https://medium.com/prnd/%EC%95%84%EB%A6%84%EB%8B%B5%EA%B2%8C-%EC%95%B1-%EC%98%A4%EB%A5%98-%EC%B2%98%EB%A6%AC%ED%95%98%EA%B8%B0-8bf9a46df515

 

아름답게 앱 오류 처리하기

앱에서 오류가 발생해도 사용자가 좋은 경험을 유지할 수 있도록 처리하는 글입니다.

medium.com

https://mangbaam.github.io/book/2022/04/21/effective-kotlin-5.html

 

Effective Kotlin_아이템5. 예외를 활용해 코드에 제한을 걸어라

안드로이드 개발자가 되기 위한 기록들

mangbaam.github.io

https://onlyfor-me-blog.tistory.com/481

 

[이펙티브 코틀린] 아이템 5. 예외를 활용해 코드에 제한을 걸어라

확실하게 어떤 형태로 동작해야 하는 코드가 있다면 예외를 활용해 제한을 걸어주는 게 좋다. 코틀린에선 코드 동작에 제한을 걸 때 아래 방법을 쓸 수 있다. require 블록 : 아규먼트를 제한할 수

onlyfor-me-blog.tistory.com

 

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2024/10   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
글 보관함