코틀린은 간결성이 아니라 가독성(readability)를 좋게 하는데 목표를 두고 설게된 프로그래밍 언어이다.
개발자는 코드를 작성하는것 보다 읽는 데 많은 시간을 소모한다.
따라서 항상 가독성을 생각하면서 코드를 작성해야 한다.
인식 부하 감소
가독성은 사람에 따라 다르게 느낄 수 있지만 경험과 인식에 대한 과학으로 만들어진 어느정도의 규칙이 존재한다.
- 구현 A
if (person != null && person.isAdult) {
view.shopwPerson(person)
} else {
view.showError()
}
A는 일반적인 관용구(if / else, &&, 메소드 호출)을 사용하고 있어 초보자에게는 구현 A가 더 이해하기 쉽다.
- 구현 B
person?.takeIf { it.isAdult }
?.let(view::showPerson)
?.: view showError()
B는 더 짧지만 B는 코틀린에 대한 숙련도가 있는 사람이 아니라면 읽고 이해하기 어렵다.
이외에도 아래와 같은 차이가 있다.
- A는 수정 및 디버깅이 용이하다. if 혹은 else 블록에 작업을 추가하는 경우 B에서는 함수 참조를 사용할 수 있어 코드를 수정해야 한다.
- 구현 B에서 showPerson의 리턴 타입에 따라(null인 경우) 의도치 않게 showError를 호출할 수도 있다.
우리의 뇌는 짧은 코드를 빠르게 읽을 수 있지만 익숙한 코드는 더 빠르게 읽을 수 있다. 따라서 기본적으로 인지 부하를 줄이는 방향으로 코드를 작성하는것이 좋다.
극단적이 되지 않기
위의 구현 B에서 let으로 인해 예상치 못한 결과가 나오는 경우를 보고 일부는 let을 절대로 쓰면 안된다고 이해하는 경우가 있다.
let은 좋은 코드를 만들기 위해 다양하게 활용할 수 있으므로 극단적으로 받아들이면 안된다.
var person: Person? = null
fun printName() {
person?.let { print(it.name) }
}
위와 같이 가변 프로퍼티를 가진 경우 스마트 캐스팅이 불가능하고 safe call과 let을 활용하면 사람들이 쉽게 인식할 수 있는 코드를 작성할 수 있다.
이외에도 아래와 같은 경우 let을 많이 사용한다.
-
연산을 아규먼트 처리 후로 이동시킬 때
print(students.filter{ }.joinToString{ }) -> students.filter{ }.joinToString{ }.let(::print)
-
데코레이터를 사용해도 객체를 감쌀 때
var obj = FileInputStream("/file.gz") .let(::BufferedInputStream) .let(::ZipInputStream) .let(::ObjectInputStream) .readObject() as SomeObject
위의 경우는 디버그하기 어렵고 경험이 적은 코틀린 개발자는 이해하기 어렵다.
하지만 위의 경우는 비용을 지불할 만한 가치가 있으며 정당성 없이 복잡성을 추가하는 경우가 아니다.
컨벤션
네이밍, 규칙, 관용구 등에 대해 개발자는 항상 토론하며 이를 위해 이해하고 기억해야 하는 규칙이 존재한다.
val abc = "A" { "B" } and "C"
print(abc)
operator fun String.invoke(f: () -> String): String = this + f()
infix fun String.and(s: String) = this + s
위 코드는 이후에 설명하는 수많은 규칙들을 위반한다.
- 연산자는 의미에 맞게 사용해야 한다. invoke는 위와 같은 형태로 사용하면 안된다.
- 람다를 마지막 아규먼트로 사용한다 라는 컨벤션을 여기에 적용하면 코드가 복잡해진다.
- 현재 코드에서 and라는 함수 이름이 실제 함수 내부에서 이루어지는 처리와 맞지 않다.
- 문자열을 결합하는 기능은 이미 언어에 내장되어 있어 이미 있는 것들 다시 만들 필요는 없다.
Reference
- 이펙티브 코틀린 - 프로그래밍 인사이트, 마르친 모스칼라 지음, 윤인성 옮김
개인적인 기록을 위해 작성된 글이라 잘못된 내용이 있을 수 있습니다.
오류가 있다면 댓글을 남겨주세요.