참조
Kotlin in Action을 보고 작성하였습니다.
자바에서 반복되는 코드를 줄이기 위해 많은 경우 메서드 추출 리팩토링을 적용해서 긴 메서드를 부분부분 나눠서 각 부분을 재활용 할 수 있습니다. 하지만 그렇게 코드를 리팩토링하면 클래스 안에 작은 메서드가 많아지고 각 메서드 사이의 관계를 파악하기 힘들어서 코드를 이해하기 더 어려워 질 수 있습니다.
코틀린에서는 함수에서 추출한 함수를 원 함수 내부에 중첩시킬 수 있습니다. 그렇게 되면 문법적인 부가 비용을 들이지 않고도 깔끔하게 코드를 조작할 수 있습니다.
예제를 보며 확인하겠습니다.
사용자를 데이터베이스에 저장하는 함수가 있습니다. 이때 데이터베이스에 사용자 객체를 저장하기 전에 각 필드를 검증하는 코드입니다.
data class User(val name: String, val address: String)
fun saveUser(user: User) {
// 필드 검증이 중복
if(user.name.isEmpty()) {
throw IllegalArgumentException(
"${user.id} : empty Name"
)
}
// 필드 검증이 중복
if(user.address.isEmpty()) {
throw IllegalArgumentException(
"${user.id} : empty Address"
)
}
// user를 데이터베이스에 저장하는 코드
}
위 예시에서 만약 사용자의 필드가 늘어난다면 검증하는 코드가 계속해서 증가합니다.
이런 경우 검증 코드를 로컬 함수로 분리하면 중복을 없애는 동시에 코드 구조를 깔끔하게 유지할 수 있습니다.
fun saveUser(user: User) {
// 로컬 함수 정의
fun validate(user: User, value: String, fieldName: String) {
if(value.isEmpty()) {
throw IllegalArgumentException(
"${user.id} : empty $fieldName"
)
}
}
// 로컬 함수를 호출해서 각 필드를 검증
validate(user, user.name, "Name")
validate(user, user.address, "Address")
// user를 데이터베이스에 저장하는 코드
}
검증 로직 중복이 사라졌고, 필요하면 User의 다른 필드에 대한 검증도 쉽게 추가할 수 있습니다. 다만 로컬 함수는 자신이 속한 바깥 함수의 모든 파라미터와 변수를 사용할 수 있습니다.
fun saveUser(user: User) {
// 로컬 함수 정의
// user 파라미터를 중복해서 사용하지 않음
fun validate(value: String, fieldName: String) {
if(value.isEmpty()) {
throw IllegalArgumentException(
// 바깥 함수 파라미터에 직접 접근(saveUser 함수의 user에 직접 접근)
"${user.id} : empty $fieldName"
)
}
}
// 로컬 함수를 호출해서 각 필드를 검증
validate(user.name, "Name")
validate(user.address, "Address")
// user를 데이터베이스에 저장하는 코드
}
validate 메서드 안에서 중복해서 user 파라미터를 사용하지 않고 바깥 메서드인 saveUser의 파라미터에 직접 접근하여 사용합니다.
// 검증 로직을 확장 함수로 추출
fun User.validateBeforeSave() {
fun validate(value: String, fieldName: String) {
if(value.isEmpty()) {
throw IllegalArgumentException(
// User 클래스의 프로퍼티에 직접 접근
"$id : empty $fieldName"
)
}
}
validate(name, "Name")
validate(address, "Address")
}
fun saveUser(user: User) {
user.validateBeforeSave()
// user를 데이터베이스에 저장하는 코드
}
검증 로직은 User를 사용하는 다른 곳에서는 쓰이지 않는 기능이기 때문에 User에 포함시키지 않고 확장 함수로 만든 코드입니다. 이와 같이 작성하면 User를 간결하게 유지하여 더 쉽게 코드를 파악할 수 있습니다.
한 객체만을 다루면서 객체의 비공개 데이터를 다룰 필요는 없는 함수(확장 함수는 비공개 멤버에 접근하지 못함)는 위와 같이 확장 함수로 만들면 객체.멤버처럼 수신 객체를 지정하지 않고도 공개된 멤버 프로퍼티나 메서드에 접근할 수 있습니다.