38장. Java스럽게 작성한 Kotlin 개선하기
자바를 하다 코틀린으로 넘어오면
자기도 모르게 “자바처럼” 코드를 짜게 됩니다.
동작은 하지만
코틀린의 장점을 살리지 못한 코드입니다.
이 장에서는 흔한 “자바스러운 코틀린“을
“코틀린다운 코틀린“으로 바꾸는 법을 정리합니다.
지금까지 배운 내용을
“고치는 관점“에서 복습하는 장이기도 합니다.
38.1 Getter와 Setter를 그대로 옮기지 않기
자바 습관대로
게터/세터를 손으로 만드는 경우가 있습니다.
// 자바스러운 코틀린
class User {
private var name: String = ""
fun getName(): String = name
fun setName(value: String) { name = value }
}
코틀린은 프로퍼티가
게터/세터를 자동으로 만들어 줍니다. (7장)
// 코틀린다운 코드
class User(var name: String)
한 줄이면 충분합니다.
38.2 불필요한 var 줄이기
습관적으로 var를 쓰는 경우가 많습니다.
// 개선 전
var total = 0
for (item in items) {
total += item.price
}
대부분은 val로 바꿀 수 있습니다. (2·13장)
// 개선 후
val total = items.sumOf { it.price }
값이 한 번 정해지면 바뀌지 않으니val이 더 안전합니다.
일단
val, 꼭 필요할 때만var.
38.3 불필요한 !! 없애기
!!는 “null이면 터뜨려라“는 뜻입니다. (5장)
남발하면 위험합니다.
// 위험한 코드
val name = user!!.name!!
대부분 엘비스 연산자나 safe call로
더 안전하게 바꿀 수 있습니다.
// 개선 후
val name = user?.name ?: "이름 없음"
!!가 보이면
“정말 여기서 터져도 되는가“를 되물어야 합니다.
38.4 반복문을 Collection 함수로 바꾸기
for 반복문으로 목록을 가공하는 코드가 많습니다.
// 개선 전
val names = mutableListOf<String>()
for (user in users) {
if (user.age >= 20) {
names.add(user.name)
}
}
컬렉션 함수를 쓰면
의도가 한눈에 드러납니다. (13장)
// 개선 후
val names = users
.filter { it.age >= 20 }
.map { it.name }
“거른다 → 변환한다“는 흐름이
그대로 코드가 됩니다.
38.5 Utility Class를 Extension Function으로 바꾸기
자바에서는 StringUtils 같은
유틸리티 클래스를 만들곤 했습니다.
// 자바스러운 코틀린
object StringUtils {
fun isValidEmail(value: String): Boolean = value.contains("@")
}
StringUtils.isValidEmail(email)
코틀린에서는 확장 함수가 더 자연스럽습니다. (17장)
// 코틀린다운 코드
fun String.isValidEmail(): Boolean = contains("@")
email.isValidEmail()
값이 앞에 오니
문장처럼 읽힙니다.
38.6 과도한 Scope Function 줄이기
스코프 함수(15장)는 편하지만
너무 중첩하면 오히려 읽기 어렵습니다.
// 과한 예
user?.let { u ->
u.address?.let { a ->
a.city?.let { c ->
println(c)
}
}
}
safe call 체인으로 펴는 편이 낫습니다.
// 개선 후
val city = user?.address?.city
if (city != null) println(city)
코틀린다운 코드가
코틀린 문법을 많이 쓴 코드는 아닙니다.
38.7 불필요한 상속 제거하기
상속을 습관적으로 쓰는 경우가 있습니다.
하지만 코틀린 클래스는 기본이 final입니다. (8장)
상속보다
- 인터페이스로 역할을 나누거나 (8장)
- 위임(by)을 쓰는 편이 (19장) 더 유연할 때가 많습니다.
“이건 저것의 한 종류다“가 아니면
상속을 의심해 보세요.
38.8 Null과 Exception을 명확하게 표현하기
실패를 어정쩡하게 표현하지 말고
의도를 분명히 드러냅니다. (24장)
// 모호한 코드
fun find(id: Long): User {
return repository.findById(id)!! // 없으면 그냥 터짐
}
“없을 수 있음“인지 “없으면 오류“인지
명확히 구분합니다.
// 없을 수 있음 → nullable
fun findOrNull(id: Long): User? = repository.findById(id)
// 없으면 오류 → 명확한 예외
fun getById(id: Long): User =
repository.findById(id) ?: throw UserNotFoundException(id)
이름과 타입만 봐도
동작이 예상되게 만드는 것이 좋습니다.
38장을 마치며
이 장에서 우리는 다음을 정리했습니다.
- 손수 만든 게터/세터 대신 프로퍼티 쓰기
- 불필요한
var와!!줄이기 - 반복문을 컬렉션 함수로 바꾸기
- 유틸리티 클래스를 확장 함수로 바꾸기
- 과한 스코프 함수와 상속 줄이기
- null과 예외로 실패를 명확히 표현하기
다음 장은 이 책의 마지막입니다.
백엔드 개발자가 기억해야 할 핵심을 정리합니다.