코틀린은 간결성을 목표로 설계된 프로그래밍 언어가 아니라, 가독성(readability)을 좋게 하는 데 목표를 둔 프로그래밍 언어다.
개발자가 코드를 작성하는 데는 1분이 걸리지만, 이를 읽는 데는 10분이 걸린다.
(참고)
let
public inline fun <T, R> T.let(block: (T) -> R): R
public inline fun <T, R> T.run(block: T.() -> R): R
정리
정리
let 으로 인해 예상치 못한 결과가 나올 수 있다고 했다.
그렇다고 let 을 쓰지말고, 무조건 if-else를 쓰는게 좋다고 이해하면 안된다.
-> 극단적이 되지 말자. let은 좋은 코드를 만들기 위해 널리/다양하게 활용되는 관용구이다.
// person != null 일 때만 실행하고 싶은 코드가 있는경우
fun printName() {
person?.let {
print(it.name)
}
}
students
.filter { it.result >= 50 }
.joinToString(separator = "\n") {
"${it.name} ${it.surname}, ${it.result}"
}
.let(::print) // print 를 뒤로 이동시킨 경우
var obj = FileInputStream("/file.gz")
.let(::BufferedInputStream)
.let(::ZipInputStream)
.let(::ObjectInputStream)
.readObject() as SomeObject
사람에 따라 가독성에 대한 관점이 다르다.
operator fun String.invoke(f: ()->String): String = this + f()
infix fun String.and(s: String) = this + s
위의 코드는 아래와 같은 컨벤션 규칙들을 위반한다.
fun Int.factorial(): Int = (1..this).product()
fun Iterable<Int>.product(): Int =
fold(1) { acc, i -> acc * i }
---
print(10 * 6.facorial()) // 10 * (6!) = 7200
// 연산자 오버로딩
opertaor fun Int.not() = factorial()
print(10 * !6) // 7200
print(10 * 6.not()) // 7200
연산자 오버로딩을 이용하면 실제 팩토리얼과 비슷하게 ! 기호를 이용해서 표현이 가능하다.
하지만 함수의 이름이 not 이므로 논리 연산에 맞게 사용해야지, 팩토리얼 연산에 사용하면 안된다.
연산자 오버로딩이라는 자유는 많은 개발자가 해당 기능을 오용하게 만든다.
코틀린의 표준 함수인데도 잘못 이해해서 사용하기 쉽다. 개발자가 연산자 오버로딩을 사용할 때에는 항상 신중해야한다.
일반적인 의미로 사용되고 있지 않다면, 연산자를 볼 때마다 연산자를 개별적으로 이해해야 하기 때문에 코드를 이해하기 어렵다.
관례를 충족하는지 어긋나는지 확실하지 않은 경우가 문제이다.
의미가 명확하지 않다면, infix 를 활용한 확장 함수를 사용하는 것이 좋다.
infix fun Int.timesRepeated(operation: ()->Unit) = {
repeat(this) { operation() }
}
val tripledHello = 3 timesRepeated { print("Hello") } // 2항 연산자 처럼 사용
tripleHello() // 출력 : HelloHelloHello
또는 톱레벨 함수를 사용한다.
repeat(3) { print("Hello") } // 출력: HelloHelloHello
도메인 특화 언어(Domain Specific Language, DSL) 를 설계할 때는 연산자 오버로딩 규칙을 무시해도 된다.
코틀린에서는 void 대신, Unit 과 Nothing 이라는 타입을 제공해준다.
Unit?도 사용하면 괜찮지 않을까? 라는 생각으로 간혹 Unit을 Boolean처럼 사용하는 경우가 있다.
// Boolean 을 이용하는 경우
fun isCorrectKey(key: String): Boolean = ...
if (isCorrectKey(key)) return
// Unit? 을 이용하는 경우
fun verifyKey(key: String): Unit? = ...
verifyKey(key) ?: return
Unit?을 사용하면 읽는 입장에서 verifyKey(Key)가 무엇을 반환하는지 예측하기 어렵고, 오류가 생겼을 때 이유를 찾기 힘들게 만든다. Unit?을 사용하지 말자.
따라서 Boolean 을 사용하는 형태로 변경하는 것이 좋다.
기본적으로 Unit? 을 리턴하거나, 이를 기반으로 연산하는 것은 좋지 않습니다.
코틀린은 개발자가 타입을 지정하지 않아도 타입을 지정해서 넣어주는 굉장히 수준 높은 타입 추론 시스템을 갖추고 있다.
유형이 명확할 때 코드가 짧아지므로 코드의 가독성이 크게 향상한다. 하지만 유형이 명확하지 않을 때는 남용하면 좋지 않다.
kotlin의 property
class Person(name: String) {
var name = name
get() = field // field 사용
set(value) {
field = value
}
}
---
var date: Date
get() = Date(millis) // 매번 Date객체 생성해서 날짜를 저장하고
set(value) {
millis = value.time // 객체는 millis 필드만 가진다.
// 별도의 property로 옮기고 wrap/unwrap하도록 코드를 변경만 하면 다른 곳에서 이 프로퍼티를 많이 참조하고 있더라도 괜찮다.
}
코틀린의 프로퍼티는 필드가 아니라 접근자를 나타낸다.
interface Person {
val name: String // 프로퍼티 (필드 X)
}
open class Supercomputer {
open val theAnswer: Long = 42 // getter 생성됨
}
class AppleComputer : Supercomputer() {
override val theAnswer: Long = 1_800_275_2273 // getter를 override
}
이와 같은 이유로 프로퍼티를 위임할 수 있다. (property delegation)
// 코틀린에서 제공하는 표준 대리자 lazy() 함수에 프로퍼티를 위임합니다.
val db: Database by lazy { connectToDb() }
프로퍼티는 본질적으로 함수이므로 확장 프로퍼티를 만들 수도 있다.
프로퍼티를 함수 대신 사용할 수도 있지만, 그렇다고 완전히 대체해서 사용하는 것은 좋지 않다.
원칙적으로 프로퍼티는 상태를 나타내거나 설정하기 위한 목적으로만 사용하는 것이 좋고, 다른 로직들을 포함하지 않아야 한다.
property의 접근자 메서드에 너무 많은 책임을 할당하면 가독성이 낮아지고, 불필요한 종속성이 생기게 된다.
코드에서 인자(Argument)의 의미가 명확하지 않은 경우가 많습니다.
아래와 같은 경우, 개발자는 이게 구분자separator인지, 접두사prefix인지 지레 짐작하게 된다.