관리 메뉴

나만을 위한 블로그

[이펙티브 코틀린] 아이템 11. 가독성을 목표로 설계하라 본문

책/Effective Kotlin

[이펙티브 코틀린] 아이템 11. 가독성을 목표로 설계하라

참깨빵위에참깨빵 2022. 6. 19. 19:59
728x90
반응형

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

개발자는 어떤 코드를 작성하는 것보다 읽는 데 많은 시간을 소모한다는 것이다.

오류를 찾기 위해 코드를 칠 때마다 오랜 시간 코드를 읽는 자신을 발견할 수 있을 것이다.

프로그래밍은 쓰기보다 읽기가 중요하다는 의미다. 따라서 항상 가독성을 생각하며 코드를 작성해야 한다.

 

인식 부하 감소

 

가독성은 사람에 따라 다르게 느낄 수 있다. 하지만 일반적으로 많은 사람들의 경험, 인식에 대한 과학으로 만들어진 어느 정도의 규칙이 있다. 아래 코드를 확인하자.

 

fun main() {
    // 구현 A
    if (person != null && perso.isAdult) {
        view.showPerson(person)
    } else {
        view.showError()
    }
    
    // 구현 B
    person?.takeIf { it.isAdult }
        ?.let(view::showPerson)
        ?: view.showError()
}

 

B가 더 짧아서 B를 골랐다면 좋은 대답이 아니다. 줄바꿈을 제거하면 A도 짧아진다. B는 일단 이해하기 어렵다.

가독성은 코드를 읽고 얼마나 빠르게 이해할 수 있는지를 의미한다. 이는 뇌가 얼마나 많은 관용구(구조, 함수, 패턴)에 익숙해져 있는지에 따라 다르다.

코틀린 초보자에겐 A가 더 이해하기 쉽다. 일반적인 관용구(if/else, &&, 메서드 호출)을 쓰고 있기 때문이다. B는 코틀린에선 꽤 일반적으로 쓰이는 관용구(safe call, takeIf, let, 엘비스 연산자, 제한된 함수 레퍼런스)를 쓰고 있다. 코틀린에서 일반적으로 쓰이는 관용구이므로 경험 많은 코틀린 개발자면 코드를 쉽게 읽을 수 있을 것이다.

하지만 숙련된 개발자만을 위한 코드는 좋은 코드가 아니다. 둘은 사실 비교조차 할 수 없을 정도로 A가 훨씬 가독성이 좋은 코드다.

 

사실 숙련된 코틀린 개발자도 이런 코드는 익숙하지 않아서 이해하는 데 꽤 시간이 걸릴 것이다. 숙련된 개발자라고 내내 코틀린만 붙잡고 있는 건 아니기 때문이다. 필자도 코틀린을 꽤 오래 썼지만 A를 이해하는 게 훨씬 쉽다. 사용 빈도가 적은 관용구는 코드를 복잡하게 만든다. 그리고 그런 관용구들을 한 문장 내부에 조합해서 쓰면 복잡성은 훨씬 더 빨리 증가한다.

 

또한 구현 A는 수정하기 쉽다. B의 경우 더 이상 함수 참조를 쓸 수 없어 코드를 수정해야 한다. 그리고 else 블록을 수정하는 일은 좀 어렵다. 엘비스 연산자의 오른쪽 부분이 하나 이상의 표현식을 갖게 하려면 함수를 추가로 써야 한다.

구현 A는 디버깅도 더 간단하다. 일반적으로 디버깅 도구조차 이런 기본 구조를 더 잘 분석하기 때문이다.

 

이렇게 일반적이지 않고 창의적인 구조는 유연하지 않고 지원도 제대로 받지 못한다. 참고로 A, B는 실행 결과가 다르다. let은 람다식의 결과를 리턴한다. 즉 showPerson이 null을 리턴하면 2번째 구현 때는 showError()도 호출한다. 익숙하지 않은 구조를 쓰면 잘못된 동작을 코드를 보며 확인하기 어렵다.

 

기본적으로 인지 부하를 줄이는 방향으로 코드를 쳐라. 뇌는 패턴을 인식하고 패턴을 기반으로 프로그램의 작동 방식을 이해한다. 가독성은 뇌가 프로그램 작동 방식을 이해하는 과정을 더 짧게 만드는 것이다. 자주 쓰는 패턴을 활용하면 이런 과정을 더 짧게 만들 수 있다. 뇌는 짧은 코드를 빨리 읽을 수 있겠지만 익숙한 코드는 더 빨리 읽을 수 있다.

 

극단적이 되지 않기

 

방금 let 때문에 예상 못한 결과가 나올 수 있다고 했다. 이걸 let은 절대 쓰면 안 된다고 이해하는 사람들이 있다. 극단적이 되지 마라. let은 좋은 코드를 만들기 위해 다양하게 활용되는 인기 있는 관용구다. 이런 관용구는 널리 쓰이며 많은 사람들이 쉽게 인식한다.

다음과 같은 경우에 let을 많이 쓴다.

 

  • 연산을 아규먼트 처리 이후로 이동시킬 때
  • 데코레이터를 써서 객체를 랩할 때

 

이 2가지를 예시로 확인한다.

 

fun main() {
    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
}

 

이 코드들은 디버그하기 어렵고 경험 적은 코틀린 개발자는 이해하기 어렵다. 따라서 비용이 발생한다. 하지만 이 비용은 지불할 가치가 있어서 써도 괜찮다. 문제가 되는 경우는 비용을 지불할 만한 가치가 없는 코드에 비용을 지불하는 경우(정당한 이유 없이 복잡성을 추가할 때)다.

 

어떤 것이 비용을 지불할 만한 코드인지 아닌지는 항상 논란이 있을 수 있다. 균형을 맞추는 게 중요하다. 일단 어떤 구조들이 어떤 복잡성을 가져오는지 등을 파악하는 게 좋다. 또한 두 구조를 조합해서 사용하면 단순하게 개별적인 복잡성의 합보다 훨씬 커진다는 걸 기억하라.

 

컨벤션

 

많은 개발자들이 함수명을 어떻게 지어야 하는지, 어떤 것이 명시적이어야 하는지, 어떤 것이 암묵적이어야 하는지, 어떤 관용구를 사용해야 하는지 등으로 토론한다. 프로그래밍은 표현력의 예술이다. 이를 위해 이해하고 기억해야 하는 몇 가지 규칙이 있다. 이런 내용들을 앞으로 하나하나 자세하게 살펴본다.

반응형
Comments