코틀린에서는 함수에서 추출한 함수를 원 함수 내부에 중첩시킬 수 있다.
그렇게 하면 문법적인 부가 비용을 들이지 않고도 깔끔하게 코드를 조직할 수 있다.
이것은 흔히 발생하는 코드 중복을 로컬 함수를 통해 제거하는 방법을 익히기 위해 필요하다.
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") // 필드 검증 ( 혹시 유저 주소가 비어있는지)
}
// user를 데이터베이스에 저장한다.
}
-> saveUser(User(1, "", ""))
java.lang.IllegalArgumentException: Can't save user 1: empty Name
위 코드는 코드 중복이 그리 많진 않지만, 유저 필드를 검증할 때 필요한 여러 경우를 하나씩 처리하는 메서드로 넘쳐나면 그리 생산적인 코드라고 볼 수 없다.
이런 경우 검증 코드를 로컬 함수로 분리하면 중복을 없애는 동시에 코드 구조를 깔끔하게 유지할 수 있다.
class User(val id: Int, val name: String, val address: String)
fun saveUser(user: User) {
fun validate(user: User, value: String, fieldName: String) {
if(value.isEmpty()) {
throw IllegalArgumentException (
"Can't save user ${user.id}: empty $fieldName")
}
}
validate(user, user.name, "Name") // 로컬 함수를 호출해서 각 필드를 검증
validate(user, user.address, "Address") // 로컬 함수를 호출해서 각 필드를 검증
// user를 데이터베이스에 저장한다.
}
검증 로직 중복은 사라졌고, 필요하면 User의 다른 필드에 대한 검증도 쉽게 추가할 수 있다. (User에 파라미터만 더 추가하면 되니까)
로컬 함수는 자신이 속한 바깥 함수의 모든 파라미터와 변수를 사용할 수 있다.
이런 성질을 이용해 불필요한 User 파라미터를 없애보자.
class User(val id:Int, val name: String, val address: String)
fun saveUser(user: User) {
fun validate(value: String, fieldName: String) { // 이제 saveUser 함수의 user 파라미터를 중복 사용하지 않는다.
if(value.isEmpty()) {
throw IllegalArgumentException(
"Can't save user ${user.id} : " + // 바깥 함수의 파라미터에 직접 접근 가능
+ "empty $fieldName")
}
}
validate(user.name, "Name")
validate(user.address, "Address")
// user를 데이터베이스에 저장한다.
}
아래는 User 클래스를 확장 함수로 만든 코드다.
class User(val id: Int, val name: String, val address: String)
fun User.validateBeforeSave() {
fun validate(value: String, fieldName: String) {
if (value.isEmpty()) {
throw IllegalArgumentException(
"Can't save user $id: empty $fieldName") // User의 프로퍼티 직접 사용 가능
}
}
validate(name, "Name")
validate(address, "Address")
}
fun saveuser(user: User) { // 확장 함수 호출
user.validateBeforeSave()
// user를 데이터베이스에 저장한다.
}
확장 함수를 로컬 함수로 정의할 수도 있다. 즉 User.validateBeforeSave를 saveUser 내부에 로컬 함수로 넣을 수 있다.
하지만 중첩된 함수의 깊이가 깊어지면 코드 가독성이 상당히 떨어진다.
일반적으로는 한 단계만 함수를 중첩시키는 방법을 권장한다고 한다.