안드로이드 개발자 노트
[이펙티브 코틀린] item11. 가독성을 목표로 설계하라 본문
"개발자가 코드를 작성하는 데는 1분 걸리지만, 이를 읽는 데는 10분이 걸린다" <클린 코드(Clean Code) : 로버트 마틴>
프로그래밍은 쓰기보다 읽기가 중요하다는 의미입니다.
항상 가독성을 생각하면서 코드를 작성해야 합니다.
인식 부하 감소
// 구현 A
if (person != null && person.isAdult) {
view.showPerson(person)
} else {
view.showError()
}
// 구현 B
person?.takeIf { it.isAdult }
?.let(view::showPerson)
?: view.showError()
구현 A와 구현 B는 비교조차 할 수 없을 정도로 A가 훨씬 가독성이 좋은 코드입니다.
가독성이란 코드를 읽고 얼마나 빠르게 이해할 수 있는지를 의미하며, 이는 얼마나 많은 관용구(구조, 함수, 패턴)에 익숙해져 있는지에 따라 다릅니다.
코틀린 초보자에게는 구현 A가 일반적으로 사용되는 관용구를 사용하고 있어서 더 읽고 이해하기 쉽습니다.
코틀린 숙련자면 구현 A는 물론, 구현 B도 쉽게 읽을 수 있습니다.
숙련된 개발자만을 위한 코드는 좋은 코드가 아니며, 또한 구현 A는 수정하기도 쉽습니다.
구현 A가 디버깅도 더 간단합니다. 일반적으로 디버깅 도구조차 이러한 기본 구조를 더 잘 분석해 주기 때문입니다.
// 구현 A
if (person != null && person.isAdult) {
view.showPerson(person)
view.hideProgressWithSuccess()
} else {
view.showError()
view.hideProgress()
}
// 구현 B
person?.takeIf { it.isAdult }
?.let {
view.showPerson(it)
view.hideProgressWithSuccess()
} ?: run {
view.showError()
view.hideProgress()
}
사실 구현 A와 구현 B는 실행 결과조차 다릅니다.
구현 B에서는 showPerson이 null을 리턴하면 showError도 호출합니다.
이처럼 잘못된 동작을 코드를 보면서 확인하기 어렵습니다.
정리하자면, 기본적으로 '인지 부하'를 줄여야 합니다.
뇌는 짧은 코드를 빠르게 읽을 수 있겠지만, 익숙한 코드는 더 빠르게 읽을 수 있습니다.
극단적이 되지 않기
방금 let으로 인해서 예상하지 못한 결과가 나올 수 있다고해서 let을 잘 모르는 분들은 'let'을 쓰면 안된다'로 이해하는 사람들이 있을 수 있습니다.
이런 관용구는 널리 사용되며, 많은 사람이 쉽게 인식합니다.
다음과 같은 경우에 let을 많이 사용합니다.
- 연산을 아규먼트 처리 후로 이동시킬 때
- 데코레이터를 사용해서 객체를 랩할때
// 연산을 아규먼트 처리 후로 이동시킬 때
students
.filter { it.result >= 50 }
.joinToString(separator = "\n") {
"${it.name} ${it.surname}, ${it.result}"
}
.let(::print)
// 데코레이터를 사용해서 객체를 랩할때
var obj = FileInputStream("/file.gz")
.let(::BufferedInputStream)
.let(::ZipInputStream)
.let(::ObjectInputStream)
.readObject() as SomeObject
이 코드들은 경험이 적은 코틀린 개발자는 이해하기 어려울 수 있습니다.
따라서 이해를 해야하는 비용이 발생합니다.
비용을 지불할 만한 가치가 없는 코드(이유 없이 복잡성을 추가할 때)에 이러한 비용을 지불해야하는 경우, 문제가 될 수 있습니다.
하지만 관용구를 이해하는데 비용이 발생하더라도, 그만한 가치가 있다면 사용해도 좋습니다.
컨벤션
사람에 따라 가독성에 대한 관점이 다르다는 것을 알아보았습니다.
함수 이름을 어떻게 지어야 하는지, 어떤 것이 명시적이고, 어떤 것이 암묵적이어야 하는지, 어떤 관용구를 사용해야 하는지 등으로 토론합니다.
프로그래밍은 표현력의 예술이며, 무엇이 최고인지는 상황에 따라 다르겠지만, 적어도 최악의 코드가 무엇인지는 쉽게 찾을 수 있을 겁니다.
val abc = "A" { "B" } and "C"
print(abc) // ABC
이 코드를 가능하게 하려면, 다음과 같은 코드가 있어야 합니다.
operator fun String.invoke(f: ()->String): String = this + f()
infix fun String.and(s: String) = this + s
이 코드는 수많은 규칠들을 위반합니다.
- 연산자는 의미에 맞게 사용해야한다. invoke는 이렇게 사용하면 안된다.
- 람다를 마지막 인자로 사용한다 라는 컨벤션을 적용하면 코드가 복잡해진다
- 함수 이름을 보고 동작을 유추할 수 있어야한다. and를 다른 의미로 사용하고있다.
- 이미 있는 것을 다시 만들 필요는 없다 문자열 결합은 이미 코틀린에 내장된 함수가 있다.
규칙들에 대한 내용은 item12 ~ item18에 걸쳐 알아보겠습니다.
'Kotlin > 이펙티브 코틀린' 카테고리의 다른 글
[이펙티브 코틀린] Item12. 연산자 오버로드를 할 때는 의미에 맞게 사용하라 (0) | 2023.09.24 |
---|---|
[이펙티브 코틀린] item10. 단위 테스트를 만들어라 (0) | 2023.09.16 |
[이펙티브 코틀린] Item9. use를 사용하여 리소스를 닫아라 (0) | 2023.09.16 |