Item 14 : 변수 타입이 명확하지 않은 경우 확실하게 지정하라
코틀린에는 타입 추론 시스템이 있지만, 타입이 명확하지 않은 경우 남용하면 좋지 않다
예시
val data = getSomeData()
코드 정의로 쉽게 이동할 수 없는 깃허브 등의 환경에서 코드를 읽는 사람도 고려해야 한다
이는 가독성을 위함도 있지만, 안전을 위해서도 타입을 지정하는 것이 좋다
Item 15 : 리시버를 명시적으로 참조하라
스코프 내부에 둘 이상의 리시버가 있는 경우 리시버를 명시적으로 나타내면 좋다
apply, with, run 등의 스코프함수를 사용할 때가 대표적인 예시이다.
잘못 사용한 예시
class Node(val name: String) {
fun makeChild(childName: String) =
create("$name.$childName")
.apply { print("Created ${name}") }
fun create(name: String): Node? = Node(name)
}
fun main() {
val node = Node("parent")
node.makeChild("child")
}
// 의도한 결과: Created parent.child
// 실제 결과 : Created parent
apply 에서 name이 명시되어 있지 않음
이를 해결하기 위해 this를 사용하려고 하면 create() 가 반환하는 값이 nullable Node 라서 ? 처리를 해주어야 사용할 수 있음
-> 명시적으로 리시버를 지정하는 also, let 등을 사용하는 것이 nullable 값을 처리할 때 더 좋은 선택지이다
-> 명확하게 작성하면 코드를 안전하게 사용할 수 있으며, 가독성도 향상된다
코틀린 DSL을 사용할 때 여러 리시버를 가진 요소들이 중첩되더라도, 리시버를 명시적으로 붙이지 않는다
하지만 DSL에서 외부 스코프에 있는 리시버의 메서드에 접근이 가능하기 때문에 문제가 발생할 수도 있다
이러한 잘못된 사용을 막으려면 'DslMarker'라는 어노테이션을 사용하면 된다
Item 16 : 프로퍼티는 동작이 아니라 상태를 나타내야 한다
보통 자바에서 필드라고 하는 것을 코틀린에서는 프로퍼티라고 부른다
개념이 다르기 때문이다
프로퍼티는 단순히 데이터를 저장하는것 뿐만 아니라 getter와 setter를 가질 수 있다
val name: String? = null
get() = field?.toUpperCase()
set(value) {
if(!value.isNullOrBlnak()) {
field = value
}
}
코틀린에서 필드는 프로퍼티의 데이터를 저장해두는 backing field에 대한 식별자이며,
getter, setter를 사용자가 따로 정의하지 않은 경우에, 코틀린 코드 디컴파일시 나오는 backing field의 정체는 결국엔 자바의 필드다
* 필드는 프로퍼티에서 필수적이지 않다. -> 인터페이스에서도 프로퍼티를 정의할 수 있다
이렇듯 프로퍼티는 접근자를 나타내기 때문에 본질적으로 함수라고 볼 수도 있다
그러나 함수에서 하듯이 복잡한 로직을 포함해선 안된다
그런 경우에는 프로퍼티 getter, setter가 아닌 함수에 따로 정의해야 한다
- 연산 비용이 높거나, 복잡도가 O(1)보다 큰 경우
- 비즈니스 로직을 포함하는 경우
- 결정적이지 않은 경우 : 같은 동작을 연속 두번 했는데 다른 값이 나올 수도 있는 경우
- 변환의 경우 : 변환은 관습적으로 Int.toDouble() 같은 변환 함수를 사용하기 때문에, 변환은 오해를 불러 일으킬 수 있다
- 게터에서 프로퍼티의 상태 변경이 일어나야 하는 경우
프로퍼티는 상태를 함수는 행동을 나타낸다
'Kotlin > 이펙티브코틀린' 카테고리의 다른 글
3장 재사용성 : 23 ~ 25 (0) | 2023.08.23 |
---|---|
item 6~10 (0) | 2023.07.19 |
1장 : item 1 (0) | 2023.07.12 |