가독성을 목표로 설계하라
- 개발자는 어떤 코드를 작성하는 것보다 읽는 데 많은 시간을 소모한다.
인식 부하 감소
- 기본적으로 ‘인지 부하’를 줄이는 방향으로 코드를 작성해라.
- 구현 A가 훨씬 가독성 좋은 코드다.
- 일반적인 관용구를 사용해서 이해하기 쉽도록 작성해야된다.
- 구현 A가 수정하기 더 쉽고, 디버깅도 간단하다.
- 구현 B는
showPerson()
호출에 null이 발생하면,showError()
도 호출된다. 이처럼 잘못된 동작이 발생할 수 있다.
극단적이 되지 않기
- 위의 내용이 ’let은 절대로 쓰면 안 된다’를 의미하는 것은 아니다.
- let을 사용하면 좋은 경우
- nullable 가변 프로퍼티의 안전 호출이 필요할 때
- 연산을 아규먼트처리 후로 이동시킬 때
- 데코레이터를 사용해서 객체를 랩할 때
- 인식 부하 비용이 발생해도 지불만한 가치가 있는지 고려해야된다.
안전 호출을 사용하면 스마트 캐스팅이 가능한 이유: 안전 호출을 사용하면 공유중인 변수를 직접 참조하지 않고, 지역 변수로 할당해 중간에 값이 바뀌지 않도록 한다.
컨벤션
- 사람에 따라서 가독성에 대한 관점이 다르다.
- 많은 개발에서 함수 이름을 어떻게 지어야 하는지, 어떤 것이 명시적이어야 하는지, 어떤 것이 암묵적이어야 하는지, 어떤 관용구를 사용해야 하는지 등으로 토론한다.
- 표현력을 높이는 규칙
- 연산자는 의미에 맞게 사용해야 한다.
- ‘람다를 마지막아규먼트로 사용한다’라는 컨벤션을 사용하면 코드가 복잡해질 수 있다.
- 함수 이름과 실제 함수 내부에서 이루어지는 처리가 맞아야된다.
- 이미 있는 것을 다시 만들 필요는 없다.
연산자 오버로드를 할 때는 의미에 맞게 사용하라
- 코틀린에서 각 연산자의 의미는 항상 같게 유지된다.
- 코틀린의 모든 연산자는 구체적인 이름을 가진 함수에 대한 별칭일 뿐이다.
분명하지 않은 경우
- 관례를 충족하는지 아닌지 확실하지 않은 경우,
infix
를 활용한 확장 함수를 사용하는 것이 좋다. - 또는 톱레벨 함수를 사용하는 것도 좋다.
규칙을 무시해도 되는 경우
- DSL를 설계할 때
Unit?을 리턴하지 말라
Unit?
은Unit
또는null
이라는 값을 가질 수 있다.- 따라서
Boelan
과Unit?
은 서로 바꿔서 사용할 수 있다. - 하지만
Unit?
으로 불을 표현하는 것은 오해의 소지가 있으며, 예측하기 어려운 오류를 마들 수 있다. - 기본적으로
Unit?
을 리턴하거나, 이를 기반으로 연산하지 않는 것이 좋다.
변수 타입이 명확하지 않은 경우 확실하게 지정하라
- 가독성을 위해 코드를 설계할 때 읽는 사람에게 중요한 정보를 숨겨서는 안 된다.
- 가독성 향상 이외에 안전을 위해서도 타입을 지정하는 것이 좋다.
리시버를 명시적으로 참조하라
여러 개의 리시버
- 스코프 내부에 둘 이상의 리시버가 있는 경우, 리시버를 명시적으로 나타내면 좋다.
- 리시버가 명확하지 않다면, 명시적으로 리시버를 적어서 이를 명확히 해라.
- 레이블 없이 리시버를 사용하면, 가장 가까운 리시버를 의미한다.
- 외부에 있는 리시버를 사용하려면, 레이블을 사용해야 한다.
class Node(val name: String) {
fun makeChild(childName: String): Node? {
return create("$name.$childName").apply {
print("Created ${this?.name} in ${this@Node.name}")
}
}
fun create(name: String): Node? {
return Node(name)
}
}
DSL 마커
- 코틀린 DSL을 사용할 때는 여러 리시버를 가진 요소들이 중첩되더라도, 리시버를 명시적으로 붙이지 않는다. DSL은 원래 그렇게 사용하도록 설계되었기 때문이다.
- 하지만 DSL에서는 외부의 함수를 사용하는 것이 위험한 경우가 있다.
- 암묵적으로 외부 리시버를 사용하는 것을 막으려면
DslMarker
라는 메타 어노테이션을 사용한다.
- 암묵적으로 외부 리시버를 사용하는 것을 막으려면
프로퍼티는 동작이 아니라 상태를 나타내야 한다
- 프로퍼티는 사용자 정의 세터와 게터를 가질 수 있다.
- 코드에서
field
라는 식별자는 프로퍼티의 데이터를 저장해두는 백킹 필드에 대한 레퍼런스다. - 백킹 필드는 세터와 게터의 디폴트 구현에 사용되므로, 따로 만들지 않아도 디폴트로 생성된다.
val
을 사용해서 읽기 전용 프로퍼티를 만들 때는field
가 만들어지지 않는다.- 프로퍼티 대신 함수를 사용하는 것이 좋은 경우
- 연산 비용이 높거나, 복잡도가 O(1)보다 큰 경우: 관습적으로 프로퍼티를 사용할 때 연산 비용이 많이 필요하다고 생각하지 않는다.
- 비즈니스 로직을 포함하는 경우: 관습적으로 코드를 읽을 때 프로퍼티가 로깅, 리스너 통지, 바인드된 요소 변경과 같은 단순한 동작 이상을 할 거라고 기대하지 않는다.
- 결정적이지 않는 경우: 같은 동작을 연속적으로 두 번 했는데 다른 값이 나올 수 있다면, 함수를 사용하는 것이 좋다.
- 변환의 경우: 변환은 관습적으로
Int.toDouble()
과 같은 변환 함수로 이루어진다. - 게터에서 프로퍼티의 상태 변경이 일어나야 하는 경우: 관습적으로 게터에서 프로퍼티의 상태 변화를 일으킨다고 생각하지는 않는다.
- 많은 사람은 경험적으로, 프로퍼티는 상태 집합을 나타내고, 함수는 행동을 나타낸다고 생각한다.
이름 있는 아규먼트를 사용하라
- 이름 있는 아규먼트를 사용할 때 장점
- 이름을 기반으로 값이 무엇을 나타내는지 알 수 있다.
- 파라미터 입력 순서와 상관 없으므로 안전하다.
- 이름 있는 아규먼트를 추천하는 경우
- 디폴트 아규먼트의 경우
- 같은 타입의 파라미터가 많은 경우
- 함수 타입의 파라미터가 있는 경우(마지막 경우 제외)
디폴트 아규먼트의 경우
- 일반적으로 함수 이름은 필수 파라미터들과 관련되어 있기 때문에 디폴트 값을 갖는 옵션 파라미터의 설명이 명확하지 않다.
같은 타입의 파라미터가 많은 경우
- 파라미터가 모두 다른 타입이라면, 위치를 잘못 입력하면 오류가 발생할 것이므로 쉽게 문제를 발견할 수 있다.
함수 타입 파라미터
- 이름을 사용하지 않으면 어떤 함수 타입이 어떤 파라미터인지 확인하기 힘들다.
코딩 컨벤션을 지켜라
- 컨벤션을 지켰을 때 장점
- 어떤 프로젝트를 접해도 쉽게 이해할 수 있다.
- 다른 외부 개발자도 프로젝트의 코드를 쉽게 이해할 수 있다.
- 다른 개발자도 코드의 작동 방식을 쉽게 추측할 수 있다.
- 코드를 병합하고, 한 프로젝트의 코드 일부를 다른 코드로 이동하는 것이 쉽다.
- 컨벤션을 지킬 때 도움되는 두 가지 도구
- IntelliJ 포매터
- ktlink
- 많은 파라미터를 갖고 있는 클래스는 다음과 같이 각각의 파라미터를 한 줄씩 작성하는 방법을 사용한다.
- 다음과 같이 작성하는 것은 완전히 다르다.
- 위 코드는 2가지 측면에서 문제가 될 수 있다.
- 모든 클래스의 아규먼트가 클래스 이름에 따라서 다른 크기의 들여쓰기를 갖는다.
- 클래스가 차지하는 공간의 너비가 너무 크다.