본문 바로가기
Language/Kotlin

[Kotlin IN ACTION] - Kotlin 로컬 함수와 확장

by Jman 2022. 7. 21.

많은 개발자들이 좋은 코드의 중요한 특징 중 하나가 중복이 없는 것이라고 믿는다.

 

그래서 많은 경우 메소드 추출 리팩토링을 적용해서 긴 메소드를 부분부분 나눠서 각 부분을 재활용 한다

 

하지만 그렇게 코드를 리팩토링하면 클래스 안에 작은 메소드가 많아지고, 각 메소드 사이의 관계를 파악하기 힘들어서 코드를 이해하기가 더 어려워질 수 있다.

 

또한 이 부분을 해결하고자, 추출한 메소드를 별도의 내부 클래스 안에 넣으면 코드가 깔끔하게 조직할 수는 있지만,

그에 따른 불필요한 준비 코드가 늘어난다.

 

코틀린에서는 더 깔끔한 해법이 존재한다.

 

코틀린에서는 함수에서 추출한 함수를 원 함수 내부에 중첩 시킬 수 있다.

 

 

코드 중복을 보여주는 예제

class User(val id : Int, val name : String, val address : String)

fun saveUser(user : User) {
	if(user.name.isEmpty()) {
    	throw IllegalArgumentException("Can't save user ${user.id} : empty Name")
    }
    
    if(user.address.isEmpty()) {
    	throw IllegalArgumentException("Can't save user ${user.id} : empty Address")
    }
}

 

 

// 호출
fun main() {
    saveUser(User(1, "", ""))
}

위 코드는 중복이 그리 많지 않다. 하지만 클래스가 사용자의 필드를 검증할 때, 필요한 여러 경우를 하나씩 처리하는 메소드로 넘쳐나기를 바라지는 않을 것이다.

 

로컬 함수를 사용해 코드 중복 줄이기

class User(val id : Int, val name : String, val address : String)

fun saveUser(user : User) {
    fun validate(user : User, value : String, fileName : String) {
        if (value.isEmpty()) {
            throw IllegalArgumentException("Can't save user ${user.id} : empty $fileName")
        }
    }

    // 로컬 함수를 호출해서 각 필드를 검증한다.
    validate(user, user.name, "Name")
    validate(user, user.address, "Address")
}

위 코드로 바꾸면 검증 로직 중복이 사라졌고, User 의 다른 필드에 대한 검증도 쉽게 추가할 수 있다.

하지만, User 객체를 로컬 함수에게 하나하나 전달해야 한다는 점은 아쉽다.

 

다행이지만, 사실은 그럴 필요가 없다.

로컬 함수는 자신이 속한 바깥 함수의 모든 파라미터와 변수를 사용할 수 있다.

아래 코드를 확인해보자.

class User(val id : Int, val name : String, val address : String)

fun saveUser(user : User) {
	// saveUser 함수의 user 파라미터를 중복 사용하지 않는다.
    fun validate(value : String, fileName : String) { 
        if (value.isEmpty()) {
        	// 바깥 함수의 파라미터를 직접 접근이 가능하다.
            throw IllegalArgumentException("Can't save user ${user.id} : empty $fileName")
        }
    }

    // 로컬 함수를 호출해서 각 필드를 검증한다.
    validate(user.name, "Name")
    validate(user.address, "Address")
}

 

검증 로직을 확장 함수로 호출하기

class User(val id : Int, val name : String, val address : String)

fun User.validateBeforeSave() {
    fun validate(value : String, fileName : String) {
        if (value.isEmpty()) {
            // User 의 프로퍼티를 직접 사용이 가능하다.
            throw IllegalArgumentException("Can't save user $id : empty $fileName")
        }
    }

    // 로컬 함수를 호출해서 각 필드를 검증한다.
    validate(name, "Name")
    validate(address, "Address")
}

fun saveUser(user : User) {
    // 확장 함수를 호출한다.
    user.validateBeforeSave()
}

User를 간결하게 유지하면서 생각해야 할 내용이 줄어 들어서 더 쉽게 코드를 파악할 수 있다.