본문 바로가기

코틀린

코틀린(Kotlin) - 리팩토링 : 로컬 함수와 확장 함수로 코드 중복 없애기

우리가 생각하는 좋은 코드의 중요한 특징 중 하나가 "중복이 없는 것" 이라고 생각합니다.

흔히 "두번 이상 반복적으로 사용되는 로직은 따로 빼서 재활용할 수 있도록 해라."라는 말도 있죠 ㅎㅎ

그래서 리팩토링을 통해, 긴 메소드를 부분 부분 기능을 나눠서 따로 떼어내는 작업을 하게 되는데,

하지만 그렇게 코드를 리팩토링 하게 되면, 클래스 안에 작은 메소드들이 너무 많아지게 되고,

각 메소드 사이의 관계를 파악하기가 쉽지가 않아서, 오히려 코드를 이해하기 더 어려워지는 상황이 발생하게 됩니다...

그렇다고, 따로 떼어낸 메소드들을 모아서 내부 클래스(inner class) 안에 넣자니, 코드는 깔끔해지긴 하는데 그에 따른 불필요한 작업들이 늘어나게 됩니다.

 

하지만 우리에겐 코틀린이 있고, 코틀린에는 더 깔끔한 해결방법이 있습니다!ㅎㅎ

코틀린에서는 함수에서 분리한 작은 함수를 원래 함수 내부에 중첩시킬 수가 있습니다.

그렇게 하면, 문법적인 부가 작업을 하지 않고도 깔끔하게 코드 리팩토링을 할 수 있게 됩니다.

 

흔히 발생하는 코드 중복을 로컬(local)함수를 통해 어떻게 제거할 수 있는지 살펴보도록 하겠습니다.

 

먼저, 코드 중복을 보여주는 예제를 보도록 하겠습니다.

 

/** 코드 중복 예제 */

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

fun saveUser(user: User) {
    if (user.name.isNullOrEmpty()) {
        println("Name 항목을 입력해주세요.")
    }

    if (user.address.isNullOrEmpty()) {
        println("Address 항목을 입력해주세요.")
    }
}

 

코드 중복을 발견하셨나요?

네, 맞습니다! 사용자의 필드를(name, address) 검증하는 코드가 중복되는 것을 발견하셨을 겁니다.

여기서는 비록 코드 중복이 그리 많진 않지만, 검증 필드가 늘어난다면, 검증 코드 또한 계속 늘어날 것이고, 우리는 이런 상황을 원치 않습니다...ㅠㅠ

 

그래서, 이런 검증 코드를 로컬 함수로 분리하여 코드 중복을 없애는 예제를 만들어 보도록 하겠습니다.

 

/** 로컬 함수로 코드 중복 없애기 예제 */

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

fun saveUser(user: User) {

    //필드를 검증하는 로컬 함수 정의
    fun validate(value: String, fieldName: String) {
        if (value.isNullOrEmpty()) {
            println("${fieldName} 항목을 입력해주세요.")
        }
    }

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

 

어떠신가요? 훨씬 더 코드 구조가 깔끔해진것 같지 않으신가요?

 

로컬 함수(예제에서 validate 메소드) 로 검증 로직 중복이 사라졌습니다!

 

만약, 우리가 로컬 함수에서 User의 다른 프로퍼티를 사용하고 싶다면 어떻게 해야 할까요?

문득 드는 생각은, validate() 메소드에 User를 파라미터로 넘기면 되겠다! 라고 생각하실 텐데,

그럴 필요 없이, 로컬 함수는 자신이 속한 바깥 함수의 모든 파라미터와 변수를 사용할 수가 있습니다!

 

이러한 특징을 이용해서, 바깥 함수 saveUser(user: User)의 User객체를 로컬 함수에서 사용하는 예제를 만들어 보도록 하겠습니다.

 

/** 로컬 함수에서 바깥 함수의 파라미터 사용하기 예제 */

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

fun saveUser(user: User) {

    //파라미터로 User를 넘겨주지 않음
    fun validate(value: String, fieldName: String) {
        if (value.isNullOrEmpty()) {
            //바깥 함수의 user에 접근하여, user.id를 사용
            println("${user.id}의 ${fieldName} 항목을 입력해주세요.")
        }
    }

    validate(user.name, "Name")
    validate(user.address, "Address")
}

 

만약, 확장 함수 기능을 알고 계신다면 아마도 이 검증 로직을 확장 함수로 만들어서, 더 개선할 수도 있습니다.

 

/** 검증 로직을 확장 함수로 만들기 예제 */
class User(val id: Int, val name: String, val address: String)

//User의 확장함수
fun User.validateCheck() {

    //확장 함수에 로컬 함수도 정의할 수 있다
    fun validate(value: String, fieldName: String) {
        if (value.isNullOrEmpty()) {
            //User를 확장한 함수이기 때문에, User.id 없이, 바로 id 프로퍼티 접근 가능
            println("${id}의 ${fieldName} 항목을 입력해주세요.")
        }
    }

    //User를 확장한 함수이기 때문에, User 프로퍼티에 바로 접근 가능
    validate(name, "Name")
    validate(address, "Address")
}

fun saveUser(user: User) {
    //확장함수 호출
    user.validateCheck()
}

 

이와 같이, 로컬 함수와 확장 함수를 이용하여 코드 중복을 없애는 멋진 리팩토링 기법을 살펴보았습니다. ^^

 

※ 중첩된 함수의 Depth가 많아질수록 코드 읽기가 상당히 어려워지기 때문에, 일반적으로는 한 단계(1 depth)만 중첩시키는 것을 권장합니다!