wanna be dev 🧑‍💻

Cool 하고 Sick한 개발자가 되고 싶은 uzun입니다

A.K.A. Kick-snare, hyjhyj0901, h_uz99 solvedac-logo

Kotlin

[이펙티브 코틀린] 2장 👀 가독성 Readability (item 11..18)

Kick_snare 2023. 6. 18. 16:35
728x90

by Janko Ferlič

item 11 : 가독성을 목표로 설계하라

짧은 것보다 익숙한게 읽기 좋다

A는 범용적인 관용구, B는 코틀린 문법의 관용구를 사용하고 있다.

/* Style A */

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

/* Style B */

person?.takeIf { it.isAdult } 
    ?.let(::view.showPerson)
    ?: view.showError()
  • B가 cool 해보일 수 있지만 쉽게 읽을 수 있는 코드가 아니므로 A가 더 가독성이 좋다고 할 수 있다.
  • B와 같은 코드는 수정에도 불리하다.
  • B에서 만약 showError가 null을 리턴한다면 else 에 해당하는 view.showError() 문도 실행 될 수 있다.

뇌는 기본적으로 짧은 코드를 빠르게 읽을 수 있겠지만, 익숙한 코드는 더 빠르게 읽는다!

그렇다고 해서 let 같은걸 아예 쓰지말라는건 아니고요

  • 연산을 인자 처리 후로 이동 시킬 때
  • 데코레이터를 사용해서 객체를 wrap할때
students.filter { it.result >= 50 }
    .joinToString("\n") { "${it.name} ${it.surname}, ${it.result}" }
    .let(::print)

적절히 쓰면 좋다. 뭐든 적당히..

item 13 : Unit?을 리턴하지 말라

반환 값이 없는 함수의 return 값을 보통 Unit 으로 주는데 Boolean 리턴하는 거면 Unit / Unit? 주면 대체 가능하지 않나요?

No! 오해의 소지가 있으며 예측하기 어려운 오류를 만든다.
EX) getData()?.let { view.showData() } ?: view.showError()

기본적으로 Unit? 을 리턴하거나, 이를 기반으로 연산하지 않는 것이 좋다

item 14 : 변수 타입이 명확하지 않은 경우 확실하게 지정하라

val data = getData()

타입이 getData() 함수 반환 값에 의해 정해지며 숨겨져있다. 이는 가독성이 떨어뜨리는 행위이며, github 같은 곳에서 코드를 본다면 알기 더 힘들다.

또한 1장에서 플랫폼 타입(item3) + 예측 하기 힘듦(item4) 문제로 안정성에도 문제가 있다.

item 15 : 리시버를 명확하게 참조하라

  • 짧게 적을 수 있다는 이유만으로 리시버를 제거하지 말라
  • 여러 개의 리시버가 있는 상황 등에는 리시버를 명시적으로 적어 주는 것이 좋다.
  • 명시하면 어떤 리시버의 함수인지를 명확하게 알 수 있으므로, 가독성이 향상된다.

item 16 : 프로퍼티는 동작이 아니라 상태를 나타내야 한다

필드가 아니라 접근자다

  • 자바의 field와는 다르다
  • gettersetter를 가질 수 있다. (접근자)
  • 다른 var 프로퍼티로 부터 정의되면 파생 프로퍼티(drived property)가 존재한다.
  • 기본적으로 캡슐화 되어 있다. (get/set을 통한 unwrap/wrap)
// interface도 property를 가질 수 있다.
interface Person { val name: String }

open class Supercomputer {  
    open val theAnswer: Long = 42
}

// super class의 property를 override 할 수 있다.
class AppleComputer: Supercomputer() {
    override val theAnswer: Long = 1_800_275_2273
}

// delation 가능하다.
val db: Database by lazy { connectToDb() }

그렇기 때문에

  • 인터페이스에도 프로퍼티를 정의할 수 있다.
  • getter를 가지므로 override 가 가능하다.
  • delegation도 가능하다.

그렇다고 프로퍼티를 함수처럼 쓰지는 말라

  • 프로퍼티는 함수 대신 사용할 수 있지만 완전히 대체해서는 안된다.
  • 상태를 나타내거나 설정하기 위한 목적으로 사용되어야 한다.
  • 이 프로퍼티를 함수로 정의한다면 get/set을 붙일건가?
    • 아니라면 프로퍼티로 쓰지마라...
  • 이럴때는 프로퍼티 말고 함수를 쓰자
    1. 연산 비용 또는 복잡도가 높은 경우
    2. 비즈니스 로직을 포함한 경우
    3. 결정적이지 않은 경우 (실행할 때마다 값이 바뀌는 등)
    4. getter에서 상태변경이 있는 경우

item 17 : 이름 있는 아규먼트를 사용하라

argument에 이름을 붙이면 이럴때 좋다

  • default 아규먼트의 겨웅
  • 같은 타입의 파라미터가 많은 경우
  • 함수 타입의 파라미터가 있는 경우

마지막에 위치한 함수 하나는 lambda로 처리하곤 한다. 근데 경험 상 compose의 경우 compose builder 제외하고는 그냥 모두 이름을 붙이는 게 가독성 측면에서 좋은 경우가 많은 것 같다고 생각합니다.

728x90