- DRY 원칙(Don't Repeat Yourself)을 피하기는 쉽지 않다.
- 메서드 추출 리팩토링을 통해서 긴 메서드를 부분부분 나눠서 재활용 하는 것은 가능하다.
- 해당 해결방법의 경우 더 복잡해지기에, 메서드를 별도의 내부 클래스에 넣게 된다면 불필요한 준비코드가 늘어난다.
- Kotlin에서는 추출한 함수를 원 함수에 중첩시킬 수 있다.
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를 데이터베이스에 저장하는 부분
}
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의 다른 필드에 대한 검증을 추가하는 것도 간단하다.
class User(val id: Int, val name: String, val address: String)
fun saveUser(user: User) {
fun validate(value: String, fieldName: String) {
if(value.isEmpty()) {
throw IllegalArgumentException(
"Can't save user ${user.id}: " + "empty ${fieldName}"
)
}
}
}
- 이제 saveUser의 user 파라미터를 중복으로 사용하지 않고, 바깥 함수의 파라미터에 직접 접근한다.
class User(val id: Int, val name: String, val address: String)
fun User.validateBeforeSave()(user: User) {
fun validate(value: String, fieldName: String) {
if(value.isEmpty()) {
throw IllegalArgumentException(
"Can't save user ${user.id}: empty ${fieldName}"
)
}
}
validate(name, "Name")
validate(address, "Address")
}
fun saveUser(user: User) {
user.validateBeforeSave() // user를 DB에 저장
}
- 검증 로직을 User 클래스를 확장한 함수로 만드는 것도 가능하다.
- User는 내가 만든 코드 기반에 존재하는 클래스이지만, 해당 경우 검증 로직은 User 이외에서는 쓰이지 않는다.
- User를 간결하게 유지하면 생각해야 할 내용이 줄어들어서 더 쉽게 코드를 파악할 수 있다.
- 확장함수를 로컬함수로 지정할 수 있지만, 깊이가 깊어지면 코드를 읽기 어려워지기 때문에, 일반적으로 한 단계만 함수를 중첩시킨다. [User.validateBeforeSave를 saveUser 내부에]