[Effective Kotlin] 2장 가독성

우발자·2025년 12월 20일

Item 11 ~ Item 18 까지 읽고 정리한 글 이다.
주로 가독성을 다룬 내용이고 코더 보다는 읽는 사람을 기준으로 좋은 코드를 설명하고 있다.


1️⃣ 가독성을 목표로 설계하라

개발자가 코드를 작성하는 데는 1분 걸리지만, 이를 읽는 데는 10분이 걸린다

로버트 마틴의 클린 코드라는 책에서 나오는 유명한 이야기이다.

// 구현 A
if (person != null && person.isAdult) {
	view.showPerson(person)
} else {
	view.showError()
}

// 구현 B
person?.takeIf { it.isAdult }
	?.let(view::showPerson)
    ? : view.showError()

두 코드를 살펴보면 극과 극이다. 난 솔직히 B를 보면 깔끔하군.. 이런 생각을 했었다.
하지만 A코드는 투박하지만 이해하기는 정말 쉬웠다.
짧다고 B를 좀 더 선호했다면 좋은 대답이 아니다.

참고로 두 코드 같은 결과가 나올 것 같지만 아니다.

B의 코드에서는 showPerson()이 null을 반환한다면 showError()가 호출될 것이기 때문이다.

B의 코드를 보면 직접 코드를 작성한 사람이 아니라면 이러한 오류는 찾기 힘들 것 이다. 그렇기 때문에 구현 A처럼 가독성을 중요시 하는 게 좋을 것 같다.
(나부터 노력해야겠다..)


2️⃣ Unit?을 리턴하지 말라

마치 Boolean이 true 또는 false를 갖는 것처럼 Unit?도 Unit? 또는 null이라는 값을 가질 수 있다. 따라서 Boolean과 Unit?은 서로 바꿔서 사용할 수 있다.

fun keyIsCorrect(key : String) : Boolean = //...

if(!keyIsCorrect(key)) return


----

fun verifyKey(key : String) : Unit? = //...
verifyKey(key) ?: return

이러한 코드는 멋있게 보일 수도 있겠지만, 읽을 때는 전혀 그렇지 않다. Unit?으로 불을 표현하는 것은 오해의 소지가 있으며 예측하기 어렵다.

getData()?.let{ view.showData(it) } ?: view.showError()

getData()가 null이 아닌 값일 때 showData()가 null이면 showError()도 같이 호출되기 때문에 이런 코드보단 if-else 조건문을 사용하는 것이 훨씬 이해하기 쉽고 깔끔하다.


3️⃣변수 타입 & 리시버를 명시적으로 지정하라

이 부분은 실무에서 정말 공감되는 이야기이다.
대다수의 개발자들이 처음부터 설계하고 작성한 개발자는 거의 없을 것이다.
회사를 옮기다보면 누군가 남겨놓은 레거시 코드를 다뤄야 한다.
이 때 대다수의 레거시 코드는 변수타입과 리비서를 명시적으로 지정하지 않아 읽기가 참 불편했던 경험이 많았다.

변수 타입


// 보통 실무에선 compose를 다룬다고 가정하면 이렇게 사용하는 게 많다.
@Composable
fun MainScreen() {
	val uiModel by viewModel.uiModel.collectAsStateWithLifecycle()
}

@Composable
fun SecondScreen() {
	val uiModel by viewModel.uiModel.collectAsStateWithLifecycle()
}

다른 화면일지어도 정의하는 방식이 모두 동일하게 uiModel 또는 state 이런식으로 받는 경우가 많다.
그래서 해당 객체가 어떤 타입인지는 당연히 viewModel 들어가서 uiModel의 타입을 확인해야된다.

리시버

리시버는 진짜 지정해주지 않으면 내가 작성한 코드지만 며칠뒤에 보면 나도 헷갈린다.
그럼 다른 사람이 본다면 정말 하나도 이해하지 못할 것이며 분석하느라 힘을 다쓸 것이다.

// 보통 나는 mapper에서 많이 경험 했던 것 같다.
fun Response.toPerson(
	friends : List<String>
) : Person {
	return Person(
    	name = friends.find { it == this.name },
        ...
    )
}

실무에서 이렇게 간단하게 맵핑하진 않겠지만 정말 간단하게 예시를 들어봐도
벌써 가독성이 떨어진다.
it이 무엇일까를 볼려면 수신객체가 무엇인지를 봐야되고 수신객체의 타입을 볼려면 파라미터를 봐야된다.

fun Response.toPerson(
    friends: List<String>
): Person {
	val me = this
    return Person(
        name = friends.find { friend -> friend == me.name }
        ...
    )
}

복잡한 코드였다면 리시버가 여러개일 것이다. 그때 리시버를 하나씩 지정해준다면
가독성이 훨씬 좋아질 것이다.


👮🏼 2장 가독성을 읽으면서..

한국어를 못하는 사람에게 있어보이는 말을 해봤자 상대방은 하나도 못알아들을 것이다. 상대방이 이해할 수 있게 말하는 게 진짜 멋있는 거고 잘하는 것이라고 생각한다.
코드도 마찬가지인 것 같다.
코드를 줄인다고 좋은 코드가 아니고, 멋있는 척을 하면 할수록 나는 좋은 코드에서 멀어지고 있었다. 오늘부터라도 이해하기 쉽게 좋은 코드를 작성하도록 노력해야겠다.

profile
어제보다 나은 개발자가 되자

0개의 댓글