728x90
반응형
- 많은 개발자들이 좋은 코드의 중요한 특징 중 하나가 중복이 없는 것이라 믿음
- 그래서 그 원칙에는 반복하지 말라라는 이름도 붙어있음(DRY : Dont' Repeat Yourself)
- 하지만 자바 코드를 작성할 때 DRY 원칙을 피하기는 쉽지 않음
- 많은 경우 메서드 추출 리팩토링을 적용해서 긴 메서드를 부분부분 나눠 각 부분을 재활용할 수 있음
- 하지만 그렇게 코드를 리팩토링하게 되면 클래스 안에 작은 메서드가 많아지고, 각 메서드 사이의 관계를 파악하기 힘들어 코드를 이해하기 더 어려워질 수 있음
- 리팩토링을 진행하여 추출한 메서드를 별도의 내부 클래스(Inner Class) 안에 넣으면 코드를 깔끔하게 조직할 수는 있지만, 그에 따른 불필요한 준비 코드가 늘어나게 됨
코틀린에는 더 깔끔한 해법이 있다?
- 코틀린에서는 함수에서 추출한 함수를 원 함수 내부에 중첩시킬 수 있음
- 그렇게 하게 되면 문법적인 부가 비용을 들이지 않고도 깔끔하게 코드를 조직할 수 있음
// 코드 중복을 보여주는 예제
class User(val id: Int, val name: String, val address: String)
fun saveUser(user: User) {
if(user.name.isEmpty()) { // 필드 검증이 중복됨
throw IllegalArgumentException(
"Cant' save user ${user.id}: empty Name")
}
if(user.address.isEmpty()) { // 필드 검증이 중복됨
throw IllegalArgumentExcpetion(
"Can't save user ${user.id}: empty Address")
}
// user를 데이터베이스에 저장
}
saveUser(User(1, "", "")) // IllegalArgumentException
- 클래스가 사용자의 필드를 검증할 때 피요한 여러 경우를 하나씩 처리하는 메소드가 종복된 것을 볼 수 있음
- 이런 경우 검증 코드를 로컬 함수로 분리하면 중복을 없애는 동시에 코드 구조를 깔끔하게 유지할 수 있음
// 로컬 함수를 사용해 코드 중복 줄이기
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 파라미터를 없앨 수 있음
// 로컬 함수에서 바깥 함수의 파라미터 접근하기
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 클래스를 확장한 함수로 만들어 사용하면 됨
// 검증 로직을 확장 함수로 추출하기
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() // 확장 함수를 호출
}
728x90
반응형
'Studying > Kotlin' 카테고리의 다른 글
[Kotlin In Action] 4장. 클래스, 객체, 인터페이스(2) - 뻔하지 않은 생성자와 프로퍼티를 갖는 클래스 선언 (2) | 2023.05.11 |
---|---|
[Kotlin In Action] 4장. 클래스, 객체, 인터페이스(1) - 클래스 계층 정의 (0) | 2023.05.09 |
[Kotlin In Action] 3장. 함수 정의와 호출(4) - 문자열과 정규식 다루기 (0) | 2023.05.08 |
[Kotlin In Action] 3장. 함수 정의와 호출(3) - 컬렉션 처리 (0) | 2023.05.08 |
[Kotlin In Action] 3장. 함수 정의와 호출(2) - 확장 함수와 확장 프로퍼티 (0) | 2023.05.02 |