Chapter 1 ์•ˆ์ •์„ฑ

Item 1 ๊ฐ€๋ณ€์„ฑ์„ ์ œํ•œํ•˜๋ผ

์ฝ”ํ‹€๋ฆฐ์€ ๋ชจ๋“ˆ๋กœ ํ”„๋กœ๊ทธ๋žจ์„ ์„ค๊ณ„ํ•ฉ๋‹ˆ๋‹ค.

๋ชจ๋“ˆ์€ ํด๋ž˜์Šค, ๊ฐ์ฒด, ํ•จ์ˆ˜, ํƒ€์ž…๋ณ„์นญ (type alias), ํ†ฑ๋ ˆ๋ฒจ (top-level) ํ”„๋กœํผํ‹ฐ๋“ฑ ๋‹ค์–‘ํ•œ ์š”์†Œ๋กœ ๊ตฌ์„ฑ๋ฉ๋‹ˆ๋‹ค.

์ผ๋ถ€๋Š” ์ƒํƒœ (state) ๋ฅผ ๊ฐ€์งˆ ์ˆ˜๋Š”๋ฐ, ์ฝ๊ณ  ์“ธ ์ˆ˜ ์žˆ๋Š” ํ”„๋กœํผํ‹ฐ (read-write property)
var ๋ฅผ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜, mutable ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์ƒํƒœ๋ฅผ ๊ฐ€์งˆ ์ˆ˜ ์žˆ๋‹ค.

var a = 0
var list: MutableList<Int> = mutableListOf()

์š”์†Œ๊ฐ€ ์ƒํƒœ๋ฅผ ๊ฐ–๋Š” ๊ฒฝ์šฐ, ํ•ด๋‹น ์š”์†Œ์˜ ๋™์ž‘์€ ์‚ฌ์šฉ ๋ฐฉ๋ฒ•๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ๊ทธ ์ด๋ ฅ (history)์—๋„ ์˜์กดํ•˜๊ฒŒ ๋œ๋‹ค.

class BankAccount {
  var balence = 0.0
    private set

  fun deposit(depositAmount: Double) {
    balance += depositAmount
  }

  @Throw(InsufficientFunds::class)
  fun withdraw(withdrawAmount: Double) {
    if (balance < withdrawAmount) {
      throw InsufficientFunds()
    }

    balance -= withdrawAmount
  }
}

class InsufficientFunds: Exception()
val account = BankAccount()

val expectInitialAmount = 0.0
val expectDepositAmount = 100.0
val expectWithdrawAmount = 50.0

assertThat(account.balance)
    .`as`("์ดˆ๊ธฐ๊ฐ’์€ $expectInitialAmount ์ผ๊ฒƒ์ด๋‹ค.")
    .isEqualTo(expectInitialAmount)

// ๊ณ„์ขŒ ์ž”์•ก์˜ ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝ
account.deposit(expectDepositAmount)

assertThat(account.balance)
    .`as`("$expectDepositAmount ์„ ์ ์žฌํ•˜์˜€์œผ๋‹ˆ ํ˜„์žฌ ๊ฐ’์€ $expectDepositAmount ์ผ๊ฒƒ์ด๋‹ค.")
    .isEqualTo(expectDepositAmount)

// ๊ณ„์ขŒ ์ž”์•ก์˜ ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝ
account.withdraw(expectWithdrawAmount)

assertThat(account.balance)
    .`as`("$expectDepositAmount ๊ฐ’์—์„œ $expectWithdrawAmount ์„ ์ œ์™ธํ•˜์˜€์œผ๋‹ˆ ${expectDepositAmount - expectWithdrawAmount} ์ผ๊ฒƒ์ด๋‹ค.")
    .isEqualTo(expectDepositAmount - expectWithdrawAmount)

์ƒํƒœ๋ฅผ ๊ฐ–๊ฒŒ ํ•˜๋Š” ๊ฒƒ์€ ์–‘๋‚ ์˜ ๊ฒ€์ž…๋‹ˆ๋‹ค.

์‹œ๊ฐ„์˜ ๋ณ€ํ™”์— ๋”ฐ๋ผ์„œ ๋ณ€ํ•˜๋Š” ์š”์†Œ๋ฅผ ํ‘œํ•œํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์—๋Š” ์œ ์šฉํ•˜์ง€๋งŒ, ์ƒํƒœ๋ฅผ ์ ์ ˆํ•˜๊ฒŒ ๊ด€๋ฆฌํ•˜๋Š” ๊ฒƒ์ด ๋งค์šฐ ์–ด๋ ต์Šต๋‹ˆ๋‹ค.

  1. ํ”„๋กœ๊ทธ๋žจ์„ ์ดํ•ดํ•˜๊ณ  ๋””๋ฒ„๊ทธํ•˜๊ธฐ ํž˜๋“ค์–ด์ง„๋‹ค.

    • ์ƒํƒœ๋ฅผ ์ถ”์ ํ•˜๋ฉด์„œ ๋””๋ฒ„๊น…ํ•˜๊ธฐ ์›ํ™œํ•˜์ง€ ์•Š๋‹ค.
    • ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ•˜๊ฑฐ๋‚˜ ์ˆ˜์ •ํ•˜๊ธฐ ์–ด๋ ต๋‹ค.
    • ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ์ƒํ™ฉ ๋˜๋Š” ์˜ค๋ฅ˜๋ฅผ ๋ฐœ์ƒ์‹œ์ผฐ์„๋•Œ ํ•ด๊ฒฐ์ด ์‰ฝ์ง€ ์•Š๋‹ค.
  2. ๊ฐ€๋ณ€์„ฑ(mutableility)์ด ์žˆ์œผ๋ฉด, ์ฝ”๋“œ์˜ ์‹คํ–‰์„ ์ถ”๋ก ํ•˜๊ธฐ ์–ด๋ ต๋‹ค.

    • ์‹คํ–‰ํ•˜๋Š” ์‹œ์ ์˜ ๊ฐ’์ด ๋‹ฌ๋ผ์งˆ ์ˆ˜ ์žˆ๋‹ค.
    • ์ฝ”๋“œ ์‹คํ–‰์„ ์˜ˆ์ธกํ•˜๊ธฐ ์œ„ํ•ด ๊ฐ’์„ ์ถ”์ •ํ•ด์•ผ ํ•œ๋‹ค.
    • ๋””๋ฒ„๊น… ํ•  ๋•Œ ๋งˆ๋‹ค ๋™์ผํ•œ ๊ฐ’์„ ์œ ์ง€ํ•œ๋‹ค๊ณ  ํ™•์‹ ํ•  ์ˆ˜ ์—†๋‹ค.
  3. ๋ฉ€ํ‹ฐ์Šค๋ ˆ๋“œ ํ”„๋กœ๊ทธ๋žจ์ผ ๋•Œ ์ ์ ˆํ•œ ๋™๊ธฐํ™”๊ฐ€ ํ•„์š”ํ•˜๋‹ค.

    • ๋ณ€๊ฒฝ์ด ์ผ์–ด๋‚˜๋Š” ๋ชจ๋“  ๋ถ€๋ถ„์— ๋Œ€ํ•ด์„œ ์ถฉ๋Œ์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋‹ค.
  4. ํ…Œ์ŠคํŠธํ•˜๊ธฐ ์–ด๋ ต๋‹ค.

    • ๋ชจ๋“  ์ƒํƒœ์— ๋Œ€ํ•ด์„œ ํ…Œ์ŠคํŠธ๊ฐ€ ํ•„์š”ํ•˜์—ฌ ๋ณ€๊ฒฝ์ด ๋งŽ์œผ๋ฉด ๋” ๋งŽ์€ ํ…Œ์ŠคํŠธ ์กฐํ•ฉ์ด ํ•„์š”ํ•˜๋‹ค.
  5. ์ƒํƒœ ๋ณ€๊ฒฝ์ด ์ผ์–ด๋‚  ๋•Œ, ๋ณ€๊ฒฝ์„ ๋‹ค๋ฅธ ๋ถ€๋ถ„์— ์•Œ๋ ค์ค˜์•ผ ํ•˜๋Š” ์ผ€์ด์Šค๊ฐ€ ์žˆ๋‹ค.

    • ์ •๋ ฌ๋˜์–ด ์žˆ๋Š” ๋ฆฌ์ŠคํŠธ์— ๊ฐ€๋ณ€์š”์†Œ๋ฅผ ์ถ”๊ฐ€ํ•œ๋‹ค๋ฉด, ์š”์†Œ์— ๋ณ€๊ฒฝ์ด ์ผ์–ด๋‚ ๋•Œ ์žฌ ์ •๋ ฌ์ด ํ•„์š”ํ•˜๋‹ค.

๋‹ค์Œ ์ฝ”๋“œ์˜ println ๋˜๋Š” ๊ฐ’์€ 1000 ์ด ์•„๋‹ ํ™•๋ฅ ์ด ๋งค์šฐ ๋†’๋‹ค.

var num = 0
for (i in 1..1000) {
  thread {
    Thread.sleep(10)
    num += 1
  }
}

Thread.sleep(5000)

println(num)

์ฝ”๋ฃจํ‹ด์„ ์‚ฌ์šฉํ•œ๋‹ค๊ณ  ํ•ด์„œ ์ผ๋ถ€ ํ•ด์†Œ๊ฐ€ ๊ฐ€๋Šฅํ•˜์ง€๋งŒ, ๋ฌธ์ œ๊ฐ€ ์‚ฌ๋ผ์ง€๋Š” ๊ฒƒ์€ ์•„๋‹ˆ๋‹ค.

suspend fun main() {
  var num = 0
  coroutineScope {
    for (i in 1..1000) {
      launch {
        delay(10)
        num += 1
      }
    }
  }
  println(num)
}

์œ„ ์ฝ”๋“œ๋Š” ์‹คํ–‰ํ•  ๋•Œ๋งˆ๋‹ค ๋‹ค๋ฅธ ์ˆซ์ž๊ฐ€ ๋‚˜์˜จ๋‹ค.

synchronized ๋ธ”๋Ÿญ์„ ์‚ฌ์šฉํ•˜์—ฌ๋„ ๋ฌธ์ œ๋Š” ํ•ด๊ฒฐ๋˜์ง€ ์•Š๋Š”๋‹ค.

๋ณ€ํ•  ์ˆ˜ ์žˆ๋Š” ์ง€์ ์€ ์ค„์ผ์ˆ˜๋ก ์ข‹๋‹ค. (๋ณ€๊ฒฝ์ด ์ผ์–ด๋‚˜์•ผ ํ•˜๋Š” ๋ถ€๋ถ„์€ ํ™•์‹คํ•˜๊ฒŒ ๊ฒฐ์ •ํ•˜๊ณ  ์‚ฌ์šฉํ•˜๋„๋ก ํ•˜์ž)

์ฝ”ํ‹€๋ฆฐ์—์„œ ๊ฐ€๋ณ€์„ฑ ์ œํ•œํ•˜๊ธฐ

์ฝ”ํ‹€๋ฆฐ์€ ๊ฐ€๋ณ€์„ฑ์„ ์ œํ•œํ•  ์ˆ˜ ์žˆ๊ฒŒ ์„ค๊ณ„๋˜์–ด ์žˆ๋‹ค.

์ฝ๊ธฐ ์ „์šฉ ํ”„๋กœํผํ‹ฐ(val)

์ฝ”ํ‹€๋ฆฐ์€ val ์„ ์‚ฌ์šฉํ•ด ์ฝ๊ธฐ ์ „์šฉ ํ”„๋กœํผํ‹ฐ๋กœ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค.

val a = 10
a = 20 // Error

์ฝ๊ธฐ ์ „์šฉ ํ”„๋กœํผํ‹ฐ๊ฐ€ ์™„์ „ํžˆ ๋ถˆ๊ฐ€๋Šฅํ•œ๊ฒƒ์€ ์•„๋‹ˆ๋‹ค.

์ฝ๊ธฐ ์ „์šฉ ํ”„๋กœํผํ‹ฐ๊ฐ€ mutable ๊ฐ์ฒด๋ฅผ ๋‹ด๊ณ  ์žˆ๋‹ค๋ฉด ๋‚ด๋ถ€์ ์œผ๋กœ ๋ณ€ํ•  ์ˆ˜ ์žˆ๋‹ค.

val list = mutableListOf(1, 2, 3)
list.add(4)

println(list) // [1, 2, 3, 4]

์ฝ๊ธฐ ์ „์šฉ ํ”„๋กœํผํ‹ฐ๋Š” ๋‹ค๋ฅธ ํ”„๋กœํผํ‹ฐ๋ฅผ ํ™œ์šฉํ•˜๋Š” ์‚ฌ์šฉ์ž ์ •์˜ getter ๋กœ๋„ ์ •์˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

var name: String = "Marcin"
var surname: String = "Moskala"

val fullName
  get() = "$name $surname"

fun main() {
  println(fullName) // Marcin Moskala

  name = "Maja"
  println(fullName) // Maja Moskala
}

์ด๋ ‡๊ฒŒ var ํ”„๋กœํผํ‹ฐ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” val ํ”„๋กœํผํ‹ฐ๋Š” var ํ”„๋กœํผํ‹ฐ๊ฐ€ ๋ณ€ํ•  ๋•Œ ๋ณ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ฐ’์„ ์ถ”์ถœํ•  ๋•Œ๋งˆ๋‹ค ์‚ฌ์šฉ์ž ์ •์˜ ๊ฒŒํ„ฐ๊ฐ€ ํ˜ธ์ถœ๋˜๋ฏ€๋กœ ์ด๋Ÿฌํ•œ ์ฝ”๋“œ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

fun calculate(): Int {
  println("Calculating... ")
  return 42
}

val fizz = calculate()
val buzz
  get() = calculate()

@Test
fun `์ฝ๊ธฐ ์ „์šฉ ํ”„๋กœํผํ‹ฐ(val)`() {
  println(fizz)
  println(fizz)
  println(buzz)
  println(buzz)
}
42
42
Calculating... 
42
Calculating... 
42

์ฝ”ํ‹€๋ฆฐ์˜ ํ”„๋กœํผํ‹ฐ๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ์บก์Šํ™”๋˜์–ด ์žˆ๊ณ , ์ถ”๊ฐ€์ ์œผ๋กœ ์‚ฌ์šฉ์ž ์ •์˜ ์ ‘๊ทผ์ž (getter, setter) ๋ฅผ ๊ฐ€์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

var ๋Š” getter ์™€ setter ๋ฅผ ์ œ๊ณตํ•˜์ง€๋งŒ val ์€ ๋ณ€๊ฒฝ์ด ๋ถˆ๊ฐ€๋Šฅํ•˜๋ฏ€๋กœ getter ๋งŒ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ val ์„ var ๋กœ ์˜ค๋ฒ„๋ผ์ด๋“œ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

interface Element {
  val active: Boolean
}

class ActualElement: Element {
  override var active: Boolean = false
}

val ์€ ์ฝ๊ธฐ ์ „์šฉ ํ”„๋กœํผํ‹ฐ์ง€๋งŒ, ๋ณ€๊ฒฝํ•  ์ˆ˜ ์—†์Œ (๋ถˆ๋ณ€: immutable) ์„ ์˜๋ฏธํ•˜๋Š” ๊ฒƒ์€ ์•„๋‹ˆ๋‹ค.

val name: String? = "Marton"
val surname: String = "Braun"

val fullName: String?
  get() = name?.let { "$it $surname" }

val fullName2: String? = name?.let { "$it $surname" }

fun main() {
  if (fullName != null) {
    println(fullName.length) // ERROR
  }
  
  if (fullName2 != null) {
    println(fullName2.length)
  }
}

fullName ์€ getter ๋กœ ์ •์˜ํ–ˆ์œผ๋ฏ€๋กœ ์Šค๋งˆํŠธ ์บ์ŠคํŠธ (smart-cast) ๋ฅผ ํ•  ์ˆ˜ ์—†๋‹ค.

์Šค๋งˆํŠธ ์บ์ŠคํŠธ (smart-cast)
์ปดํŒŒ์ผ๋Ÿฌ๊ฐ€ ์ž๋™์œผ๋กœ ํƒ€์ž…์„ ํ™•์ธํ•ด์ฃผ๊ฑฐ๋‚˜ null ์ฒดํฌ ๋“ฑ์„ ์ง€์›ํ•ด์ค€๋‹ค.

๊ฐ€๋ณ€ ์ปฌ๋ ‰์…˜๊ณผ ์ฝ๊ธฐ ์ „์šฉ ์ปฌ๋ ‰์…˜ ๊ตฌ๋ถ„ํ•˜๊ธฐ

Iterable, Collection, Set, List ์ธํ„ฐํŽ˜์ด์Šค๋Š” ์ฝ๊ธฐ ์ „์šฉ์ด๋‹ค.

๋•Œ๋ฌธ์— ๋ณ€๊ฒฝ์„ ์œ„ํ•œ ๋ฉ”์„œ๋“œ๋Š” ์—†๋‹ค.

๋ฐ˜๋ฉด์— MutableIterable, MutableCollection, MutableSet, MutableList ์ธํ„ฐํŽ˜์ด์Šค๋Š” ์ฝ๊ณ  ์“ธ ์ˆ˜ ์žˆ๋Š” ์ปฌ๋ ‰์…˜์ด๋‹ค.

์ด์ฒ˜๋Ÿผ mutable ์ด ๋ถ™์€ ์ธํ„ฐํŽ˜์ด์Šค๋Š” ๋Œ€์‘๋˜๋Š” ์ฝ๊ธฐ ์ „์šฉ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ƒ์†๋ฐ›์•„ ๋ณ€๊ฒฝ์„ ์œ„ํ•œ ๋ฉ”์„œ๋“œ๋ฅผ ์ถ”๊ฐ€ํ•œ ๊ฒƒ์ด๋‹ค.

'์ฝ”ํ‹€๋ฆฐ ์ปฌ๋ ‰์…˜ ์ธํ„ฐํŽ˜์ด์Šค'

์•„๋ž˜์™€ ๊ฐ™์ด ์ปฌ๋ ‰์…˜ ์ธํ„ฐํŽ˜์ด์Šค์˜ ๋‹ค์šด์บ์ŠคํŒ…์€ ์‚ฌ์šฉํ•˜๋ฉด ์•ˆ๋œ๋‹ค.

์ด๋Š” ์ถ”์ƒํ™”๋ฅผ ๋ฌด์‹œํ•˜๋Š” ํ–‰์œ„์ด๋ฉฐ, ์˜ˆ์ธก ๋ถˆ๊ฐ€๋Šฅํ•œ ๊ฒฐ๊ณผ๋ฅผ ์ดˆ๋ž˜ํ•œ๋‹ค.

val list = listOf(1, 2, 3)

if (list is MutableList) {
  list.add(4)
}

ํ•˜์ง€๋งŒ ์œ„ ์ฝ”๋“œ์˜ ์‹คํ–‰ ๊ฒฐ๊ณผ๋Š” ํ”Œ๋žซํผ์— ๋”ฐ๋ผ ๋‹ค๋ฅธ ๊ฒฐ๊ณผ๋ฅผ ๋ณด์—ฌ์ค€๋‹ค.

๋งŒ์•ฝ ์ฝ๊ธฐ ์ „์šฉ์—์„œ mutable ๋กœ ๋ณ€๊ฒฝํ•ด์•ผ ํ•œ๋‹ค๋ฉด, ๋ณต์ œ(copy) ๋ฅผ ํ†ตํ•ด์„œ ์ƒˆ๋กœ์šด mutable ์ปฌ๋ ‰์…˜์œผ๋กœ ๋งŒ๋“œ๋Š” list.toMutableList ๋ฅผ ํ™œ์šฉํ•ด์•ผ ํ•œ๋‹ค.

val list = listOf(1, 2, 3)

val mutableList = list.toMutableList()
mutableList.add(4)

์œ„์™€ ๊ฐ™์ด ๋ณต์ œ (copy) ๋ฅผ ํ†ตํ•ด์„œ ์ž‘์„ฑํ•œ ์ฝ”๋“œ๋Š” ๊ธฐ์กด์˜ ๊ฐ์ฒด์— ์–ด๋– ํ•œ ์˜ํ–ฅ์„ ์ฃผ์ง€ ์•Š์œผ๋ฉฐ ์—ฌ์ „ํžˆ immutable ์ด๋ผ ์ˆ˜์ •ํ• ์ˆ˜ ์—†์œผ๋ฏ€๋กœ, ์•ˆ์ „ํ•˜๋‹ค๊ณ  ํ•  ์ˆ˜ ์žˆ๋‹ค.

๋ฐ์ดํ„ฐ ํด๋ž˜์Šค์˜ copy

immutable ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์žฅ์ ์ด ์žˆ๋‹ค.

  1. ํ•œ ๋ฒˆ ์ •์˜๋œ ์ƒํƒœ๊ฐ€ ์œ ์ง€๋˜๋ฏ€๋กœ, ์ฝ”๋“œ๋ฅผ ์ดํ•ดํ•˜๊ธฐ ์‰ฝ๋‹ค.
  2. immutable ๊ฐ์ฒด๋Š” ๊ณต์œ ํ–ˆ์„ ๋•Œ๋„ ์ถฉ๋Œ์ด ๋”ฐ๋กœ ์ด๋ฃจ์–ด์ง€์ง€ ์•Š์œผํ”„๋กœ, ๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ๋ฅผ ์•ˆ์ „ํ•˜๊ฒŒ ํ•  ์ˆ˜ ์žˆ๋‹ค.
  3. immutable ๊ฐ์ฒด์— ๋Œ€ํ•œ ์ฐธ์กฐ๋Š” ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์œผ๋ฏ€๋กœ, ์‰ฝ๊ฒŒ ์บ์‹œํ•  ์ˆ˜ ์žˆ๋‹ค.
  4. immutable ๊ฐ์ฒด๋Š” ๋ฐฉ์–ด์  ๋ณต์‚ฌ๋ณธ (defensive copy) ๋ฅผ ๋งŒ๋“ค ํ•„์š”๊ฐ€ ์—†๋‹ค. (๊นŠ์€ ๋ณต์‚ฌ๋„ ํ•„์š” ์—†์Œ)
  5. immutable ๊ฐ์ฒด๋Š” ๋‹ค๋ฅธ ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค๋•Œ ํ™œ์šฉํ•˜๊ธฐ ์ข‹์œผ๋ฉฐ, ์˜ˆ์ธกํ•˜๊ธฐ ์‰ฝ๋‹ค.
  6. immutable ๊ฐ์ฒด๋Š” ์…‹ (Set) ํ˜น์€ ๋งต (Map) ์˜ ํ‚ค๋กœ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

์…‹ (Set) ํ˜น์€ ๋งต (Map) ์€ ๋‚ด๋ถ€์ ์œผ๋กœ ํ•ด์‹œ ํ…Œ์ด๋ธ” (Hash-table) ์„ ์‚ฌ์šฉํ•˜๊ณ 
์ฒ˜์Œ ์š”์†Œ๋ฅผ ๋„ฃ์„๋•Œ ์š”์†Œ์˜ ๊ฐ’์„ ๊ธฐ๋ฐ˜์œผ๋กœ ๋ฒ„ํ‚ท์„ ๊ฒฐ์ •ํ•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

๋งŒ์•ฝ immutable ํ•œ ๊ฐ์ฒด์— ๋ณ€๊ฒฝ ๊ฐ€๋Šฅํ•œ ๊ธฐ๋Šฅ์„ ๋„ฃ์–ด์•ผ ํ•œ๋‹ค๊ณ  ๊ฐ€์ •ํ–ˆ์„๋•Œ ๋‹ค์Œ๊ณผ ๊ฐ™์ด immutable ํ•˜๊ฒŒ ์ž‘๋™ํ•ด์•ผ ํ•œ๋‹ค.

class User(
  val name: String,
  val surname: String
) {
  fun withSurname(surname: String) = User(name, surname)

  override fun toString(): String {
    return """
      User (
        name: $name
        surname: $surname
      )
    """.trimIndent()
  }
}

var user = User("Blue", "Berry")
user = user.withSurname("Verry")
println(user)
User (
  name: Blue
  surname: Verry
)

withSurname ์™€ ๊ฐ™์ด ๋ณ€๊ฒฝ ๊ฐ€๋Šฅํ•œ ๋ถ€๋ถ„ ์— ๋Œ€ํ•ด ๋ฐฉ์–ด์  ๋ณต์‚ฌ๋ณธ์„ ์ œ๊ณตํ•˜๊ธฐ ์–ด๋ ต๋‹ค๋ฉด data ํ•œ์ •์ž๋ฅผ ์‚ฌ์šฉํ•˜์ž

data ํ•œ์ •์ž์—์„œ ์ œ๊ณตํ•˜๋Š” copy ๋ผ๋Š” ์ด๋ฆ„์˜ ๋ฉ”์„œ๋“œ๋ฅผ ๋งŒ๋“ค์–ด ์ค€๋‹ค.

copy ๋ฉ”์„œ๋“œ๋ฅผ ํ™œ์šฉํ•˜๋ฉด, ๋ชจ๋“  ๊ธฐ๋ณธ ์ƒ์„ฑ์ž ํ”„๋กœํผํ‹ฐ๊ฐ€ ๊ฐ™์€ ์ƒˆ๋กœ์šด ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค์–ด ๋‚ผ ์ˆ˜ ์žˆ๋‹ค.

data class User(
  val name: String,
  val surname: String
) {
  override fun toString(): String {
    return """
      User (
        name: $name
        surname: $surname
      )
    """.trimIndent()
  }
}

var user = User("Blue", "Berry")
user = user.copy(surname = "Verry")
println("user")
User (
  name: Blue
  surname: Verry
)

๋‹ค๋ฅธ ์ข…๋ฅ˜์˜ ๋ณ€๊ฒฝ ๊ฐ€๋Šฅ ์ง€์ 

๋ณ€๊ฒฝ ๊ฐ€๋Šฅํ•œ ์ง€์ ์„ ๋งŒ๋“ค์–ด์•ผ ํ•œ๋‹ค๊ณ  ํ•  ๋•Œ ๋‘๊ฐ€์ง€ ๋ฐฉ๋ฒ•์ด ์žˆ๋‹ค.

์•„๋ž˜ ์ฝ”๋“œ์™€ ๊ฐ™์ด ๋‘๊ฐ€์ง€ ์„ ํƒ์ง€๊ฐ€ ์žˆ๋‹ค.

list1 ์€ mutable ์ปฌ๋ ‰์…˜์„ ๋งŒ๋“œ๋Š” ๊ฒƒ์ด๊ณ 
list2 ์€ var ๋กœ ์ฝ๊ณ  ์“ฐ๋Š” ํ”„๋กœํผํ‹ฐ๋ฅผ ๋งŒ๋“œ๋Š”๊ฒƒ ์ด๋‹ค.

val list1: MutableList<Int> = mutableListOf()
var list2: List<Int> = listOf()

์•„๋ž˜์™€ ๊ฐ™์ด ๋‘ ๊ฐ์ฒด์˜ ๋ณ€๊ฒฝ์„ ์š”ํ• ๋•Œ ๋‘๊ฐ€์ง€ ๋ชจ๋‘ ๋ณ€๊ฒฝ ๊ฐ€๋Šฅ ์ง€์  (mutating point) ์ด ์žˆ์ง€๋งŒ,
๊ทธ ์œ„์น˜๊ฐ€ ๋‹ค๋ฅด๋‹ค.

list1.add(1)
list2 = list2 + 1

์ฒซ๋ฒˆ์งธ list1.add(1) ์€ ๊ตฌ์ฒด์ ์ธ ๊ตฌํ˜„์ด ๋ฆฌ์ŠคํŠธ ๋‚ด๋ถ€์— ์žˆ์ง€๋งŒ
๋‘๋ฒˆ์งธ list2 = list2 + 1 ๋Š” ํ”„๋กœํผํ‹ฐ ์ž์ฒด๊ฐ€ ๋ณ€๊ฒฝ ๊ฐ€๋Šฅ ์ง€์ ์ด๋‹ค.

๋”ฐ๋ผ์„œ ๋ณ€๊ฒฝ ๊ฐ€๋Šฅ์ง€์ ์„ ์ง์ ‘์ ์œผ๋กœ ์ œ์–ด๊ฐ€๋Šฅํ•œ ๋‘๋ฒˆ์งธ๊ฐ€ ๋ฉ€ํ‹ฐ์Šค๋ ˆ๋“œ ๊ธฐ๋ฐ˜์—์„œ๋Š” ๋” ์•ˆ์ •์„ฑ์ด ์ข‹๋‹ค๊ณ  ํ•  ์ˆ˜ ์žˆ๋‹ค.

๋งŒ์•ฝ mutable ๋ฆฌ์ŠคํŠธ ๋Œ€์‹  mutable ํ”„๋กœํผํ‹ฐ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ ์‚ฌ์šฉ์ž ์ •์˜ setter ๋ฅผ ํ™œ์šฉ (ํ˜น์€ ์ด๋ฅผ ์‚ฌ์šฉํ•˜๋Š” Delegates) ํ•˜์—ฌ ๋ณ€๊ฒฝ์„ ์ถ”์  ํ•  ์ˆ˜ ์žˆ๋‹ค.

var names by Delegates.observable(listOf<String>()) { _, old, new ->
  println("Names changed form $old to $new")
}

names += "Fabio"

println(names)

names += "Bill"

println(names)
Names changed form [] to [Fabio]
[Fabio]
Names changed form [Fabio] to [Fabio, Bill]
[Fabio, Bill]

mutable ์ปฌ๋ ‰์…˜๋„ ์ด์ฒ˜๋Ÿผ ๊ด€์ฐฐ (observe) ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋งŒ๋“œ๋ ค๋ฉด, ์ด์ฒ˜๋Ÿผ ์ถ”๊ฐ€์ ์ธ ๊ตฌํ˜„์ด ํ•„์š”ํ•˜๋‹ค.

mutable ํ”„๋กœํผํ‹ฐ์— ์ฝ๊ธฐ ์ „์šฉ ์ปฌ๋ ‰์…˜์„ ๋„ฃ์–ด ์‚ฌ์šฉํ•˜๋Š”๊ฒƒ์ด ๊ฐ€์žฅ ์‰ฝ๋‹ค.

private ์œผ๋กœ ์ œํ•œํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

var announcements = listOf<Announcement>()
  private set

์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝํ•˜๋Š” ๋ชจ๋“  ๋ฐฉ๋ฒ•์€ ์ฝ”๋“œ๋ฅผ ์ดํ•ดํ•˜๊ณ  ์œ ์ง€ํ•ด์•ผ ํ•˜๋ฏ€๋กœ ๋น„์šฉ์ด ๋ฐœ์ƒํ•œ๋‹ค.

๋”ฐ๋ผ์„œ ๊ฐ€๋ณ€์„ฑ์„ ์ œํ•œํ•˜๋Š”๊ฒƒ์ด ๊ฐ€์žฅ ์ข‹๋‹ค.

๋ณ€๊ฒฝ ๊ฐ€๋Šฅ ์ง€์  ๋…ธ์ถœํ•˜์ง€ ์•Š๊ธฐ

data class User(val name: String)
    
class UserRepository {
  private val storedUser: MutableMap<Int, String> = mutableMapOf()
  
  fun loadAll(): MutableMap<Int, String> {
    return storedUser
  }
}

loadAll ์„ ์‚ฌ์šฉํ•˜์—ฌ private ์ƒํƒœ์ธ UserRepository ๋ฅผ ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.

val userRepository = UserRepository()

val storedUsers = userRepository.loadAll()
storedUsers[4] = "Kirill"

println(userRepository.loadAll()) // {4=Kirill}

์œ„ ์ฝ”๋“œ๋Š” ๊ฐ‘์ž‘์Šค๋Ÿฝ๊ฒŒ ์ˆ˜์ •์ด ์ผ์–ด๋‚˜๋ฉด ์œ„ํ—˜ํ•  ์ˆ˜ ์žˆ๋‹ค.

์ด๋Š” ๋‹ค์Œ ๋‘๊ฐ€์ง€ ๋ฐฉ๋ฒ•์œผ๋กœ ํ•ด๊ฒฐ์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

๋ฐฉ์–ด์  ๋ณต์ œ

๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋ฆฌํ„ด๋˜๋Š” mutable ๊ฐ์ฒด๋ฅผ ๋ณต์ œํ•˜์—ฌ ๋ฐฉ์–ด์  ๋ณต์ œ (defensive coping) ๋ฅผ ์ˆ˜ํ–‰ํ•œ๋‹ค.

์ด ๋•Œ data ํ•œ์ •์ž๋กœ ๋งŒ๋“ค์–ด์ง€๋Š” copy ๋ฉ”์„œ๋“œ๋ฅผ ํ™œ์šฉํ•˜๋ฉด ์ข‹๋‹ค.

class UserHolder {
  private val user: MutableUser()

  fun get(): MutableUser {
    return user.copy()
  }
}

๊ฐ€๋ณ€์„ฑ ์ œ์–ด

์ปฌ๋ ‰์…˜์€ ๊ฐ์ฒด๋ฅผ ์ฝ๊ธฐ ์ „์šฉ ์Šˆํผํƒ€์ž…์œผ๋กœ ์—…์บ์ŠคํŠธ ํ•˜์—ฌ ๊ฐ€๋ณ€์„ฑ์„ ์ œํ•œํ•  ์ˆ˜ ์žˆ๋‹ค.

data class User(val name: String)

class UserRepository {
  private val storedUsers: MutableMap<Int, String> = 
    mutableMapOf()

  fun loadAll(): Map<Int, String> {
    return storedUsers
  }
}

์ •๋ฆฌ

๊ฐ€๋ณ€์„ฑ์„ ์ œํ•œํ•œ immutable ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹์€ ์ด์œ ๋ฅผ ์ •๋ฆฌํ•ด๋ณด๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

  • var ๋ณด๋‹ค๋Š” val ์„ ์‚ฌ์šฉํ•˜๋ผ
  • mutable ํ”„๋กœํผํ‹ฐ ๋ณด๋‹ค๋Š” immutable ํ”„๋กœํผํ‹ฐ๋ฅผ ์‚ฌ์šฉํ•˜๋ผ
  • mutable ๊ฐ์ฒด๋‚˜ ํด๋ž˜์Šค ๋ณด๋‹ค๋Š” immutable ๊ฐ์ฒด๋‚˜ ํด๋ž˜์Šค๋ฅผ ์‚ฌ์šฉํ•˜๋ผ
  • ๋ณ€๊ฒฝ์ด ํ•„์š”ํ•œ ๋Œ€์ƒ์„ ๋งŒ๋“ค์–ด์•ผ ํ•œ๋‹ค๋ฉด, immutable data ํด๋ž˜์Šค๋กœ ๋งŒ๋“ค๊ณ  copy ๋ฉ”์„œ๋“œ๋ฅผ ํ™œ์šฉํ•˜๋ผ
  • ์ปฌ๋ ‰์…˜์— ์ƒํƒœ๋ฅผ ์ €์žฅํ•ด์•ผ ํ•œ๋‹ค๋ฉด, ์ฝ๊ธฐ ์ „์šฉ ์ปฌ๋ ‰์…˜์„ ์‚ฌ์šฉํ•˜๋ผ
  • ๋ณ€์ด ์ง€์ ์„ ์ ์ ˆํ•˜๊ฒŒ ์„ค๊ณ„ํ•˜๊ณ , ๋ถˆํ•„์š”ํ•œ ๋ณ€์ด ์ง€์ ์€ ๋งŒ๋“ค์ง€ ๋งˆ๋ผ
  • mutable ๊ฐ์ฒด๋ฅผ ์™ธ๋ถ€์— ๋…ธ์ถœํ•˜์ง€ ๋งˆ๋ผ

immutable ๊ฐ์ฒด์™€ mutable ๊ฐ์ฒด๋ฅผ ๊ตฌ๋ถ„ํ•˜๋Š” ๊ธฐ์ค€์€ ๊ฐ€๋ณ€์„ฑ์ด๋‹ค.

Item 2 ๋ณ€์ˆ˜์˜ ์Šค์ฝ”ํ”„๋ฅผ ์ตœ์†Œํ™” ํ•˜๋ผ

์ƒํƒœ๋ฅผ ์ •์˜ํ•  ๋•Œ๋Š” ๋ณ€์ˆ˜์™€ ํ”„๋กœํผํ‹ฐ์˜ ์Šค์ฝ”ํ”„๋ฅผ ์ตœ์†Œํ™” ํ•˜๋Š”๊ฒƒ์ด ์ข‹๋‹ค.

  • ํ”„๋กœํผํ‹ฐ ๋ณด๋‹ค๋Š” ์ง€์—ญ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜๋Š”๊ฒƒ์ด ์ข‹๋‹ค.
  • ์ตœ๋Œ€ํ•œ ์ข์€ ์Šค์ฝ”ํ”„๋ฅผ ๊ฐ–๊ฒŒ ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.

์˜ˆ๋ฅผ ๋“ค๋ฉด ๋ฐ˜๋ณต๋ฌธ ๊ฐ™์€ ๊ฒฝ์šฐ ๋ณ€์ˆ˜๋ฅผ ๋‚ด๋ถ€์—์„œ๋งŒ ์‚ฌ์šฉ๊ฐ€๋Šฅํ•˜๊ฒŒ ์ž‘์„ฑํ•˜๋Š”๊ฒƒ์ด ์ข‹๋‹ค.

val a = 1

fun fizz() {
  val b = 2
  println(a + b)
}

fun buzz() {
  val c = 3
  println(a + c)
}

์œ„์˜ ์˜ˆ์ œ์ธ ๊ฒฝ์šฐ fizz ์™€ buzz ํ•จ์ˆ˜์˜ ์Šค์ฝ”ํ”„์—์„œ ์™ธ๋ถ€ ์Šค์ฝ”ํ”„์— ์žˆ๋Š” ๋ณ€์ˆ˜์— ์ ‘๊ทผ ๊ฐ€๋Šฅํ•˜์ง€๋งŒ ์™ธ๋ถ€์—์„œ ๋‚ด๋ถ€์˜ ์Šค์ฝ”ํ”„์—๋Š” ์ ‘๊ทผ์ด ๋ถˆ๊ฐ€๋Šฅํ•˜๋‹ค.

/** 
 * BAD
 **/
var user: User
for (i in users.indices) {
  user = users[i]
  println("User at $i is $user")
}

/** 
 * Good
 **/
for (i in user.indices) {
  val user = users[i]
  println("User at $i is $user")
}

/** 
 * BEST
 **/
for ((i, user) in users.withIndex()) {
  println("User at $i is $user")
}

์Šค์ฝ”ํ”„๋ฅผ ์ข๊ฒŒ ๋งŒ๋“œ๋Š” ๊ฒƒ์ด ์ข‹์€ ์ด์œ ๋Š” ํ”„๋กœ๊ทธ๋žจ์„ ์ถ”์ ํ•˜๊ณ  ๊ด€๋ฆฌํ•˜๊ธฐ ์‰ฝ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

๋ณ€์ˆ˜๋Š” ์ฝ๊ธฐ ์ „์šฉ ๋˜๋Š” ์ฝ๊ณ  ์“ฐ๊ธฐ ์ „์šฉ ์—ฌ๋ถ€์™€ ์ƒ๊ด€์—†์ด, ๋ณ€์ˆ˜๋ฅผ ์ •์˜ํ•  ๋•Œ ์ดˆ๊ธฐํ™”๋˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

/** 
 * BAD
 **/
val user: User
if (hasValue) {
  user = getValue()
} else {
  user = User()
}

/** 
 * Good
 **/
val user: User = if (hasValue) {
  getValue()
} else {
  User()
}

์—ฌ๋Ÿฌ ํ”„๋กœํผํ‹ฐ๋ฅผ ํ•œ๊บผ๋ฒˆ์— ์„ค์ •ํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ์—๋Š” ๊ตฌ์กฐ๋ถ„ํ•ด ์„ ์–ธ (destructuring declaration) ์„ ํ™œ์šฉํ•˜๋Š”๊ฒƒ์ด ์ข‹๋‹ค.

/** 
 * BAD
 **/
fun updateWeather(degrees: Int) {
  val description: String
  val color: Int
  if (degrees < 5) {
    description = "cold"
    color = Color.BLUE
  } else if (degrees < 23) {
    description = "mild"
    color = Color.YELLOW
  } else {
    description = "hot"
    color = Color.RED
  }
  // other codes...
}

/** 
 * Good
 **/
fun updateWeather(degrees: Int) {
  val (description, color) = when {
    degrees < 5 -> "cold" to Color.BLUE
    degrees < 23 -> "mild" to Color.YELLOW
    else -> "hot" to Color.RED
  }
  // other codes...
}

๊ฒฐ๋ก ์ ์œผ๋กœ ๋ณ€์ˆ˜์˜ ์Šค์ฝ”ํ”„๊ฐ€ ๋„“์œผ๋ฉด ๋งค์šฐ ์œ„ํ—˜ํ•˜๋‹ค.

์บก์ฒ˜๋ง

๋งŒ์•ฝ ๋ณ€๊ฒฝ ๊ฐ€๋Šฅํ•œ ๋ณ€์ˆ˜์˜ ์œ ํšจ๋ฒ”์œ„๊ฐ€ ๋„“์œผ๋ฉด ์ด๋ฅผ ์ฐธ์กฐํ•˜๋Š” ์™ธ๋ถ€ ๋กœ์ง์—์„œ ํ•ด๋‹น ๋ณ€์ˆ˜์˜ ์ƒํƒœ๊ฐ’์„ ์บก์ฒ˜ํ•˜์—ฌ ๋ณ€๊ฒฝํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์˜๋„ํ•˜์ง€ ์•Š์€ ๊ฒฐ๊ณผ๊ฐ€ ๋‚˜์˜ค๊ฒŒ ๋˜๋ฒ„๋ฆฐ๋‹ค.

๋‹ค์Œ์€ ์†Œ์ˆ˜๋ฅผ ๊ตฌํ•˜๋Š” ์•Œ๊ณ ๋ฆฌ์ฆ˜์˜ ๊ตฌํ˜„์˜ˆ์ œ์ด๋‹ค.




ย 








val primes: Sequence<Int> = sequence {
  var numbers = generateSequence(2) { it + 1 }

  var prime: Int
  while (true) {
    prime = number.first()
    yield(prime)
    numbers = numbers.drop(1)
      .filter { it % prime != 0 }
  }
}

์œ„ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•˜๋ฉด ๋‹ค์Œ ๊ฒฐ๊ณผ๊ฐ€ ๋‚˜์˜จ๋‹ค.

print(primes.take(10).toList())
// [2, 3, 5, 6, 7, 8, 10, 11, 12]

์ด๋Ÿฌํ•œ ์ž˜๋ชป๋œ ๊ฒฐ๊ณผ๋Š” 4๋ฒˆ์งธ ๋ผ์ธ์˜ prime ์ด๋ผ๋Š” ๋ณ€์ˆ˜๋ฅผ ์บก์ฒ˜ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

๋•Œ๋ฌธ์— ๋ณ€์ˆ˜์˜ ์œ ํšจ๋ฒ”์œ„๊ฐ€ ๋„“์œผ๋ฉด ํ•ญ์ƒ ์ž ์žฌ์ ์ธ ์บก์ฒ˜๋ฌธ์ œ๋ฅผ ์ฃผ์˜ํ•ด์•ผ ํ•œ๋‹ค.

๊ฐ€๋ณ€์„ฑ์„ ์ตœ๋Œ€ํ•œ ํ”ผํ•˜๊ณ  ์Šค์ฝ”ํ”„ ๋ฒ”์œ„๋ฅผ ์ข๊ฒŒ ๋งŒ๋“ค๋ฉด ์ด๋Ÿฌํ•œ ๋ฌธ์ œ๋ฅผ ๊ฐ„๋‹จํžˆ ํ”ผํ•  ์ˆ˜ ์žˆ๋‹ค.

์ •๋ฆฌ

์—ฌ๋Ÿฌ๊ฐ€์ง€ ์ด์œ ๋กœ ๋ณ€์ˆ˜์˜ ์Šค์ฝ”ํ”„๋Š” ์ข๊ฒŒ ๋งŒ๋“ค์–ด์„œ ํ™œ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹๋‹ค.

๋žŒ๋‹ค๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ ๋ณ€์ˆ˜๋ฅผ ์บก์ณํ•œ๋‹ค๋Š” ๊ฒƒ์„ ๊ธฐ์–ตํ•ด์•ผ ํ•œ๋‹ค.

Item 3 ์ตœ๋Œ€ํ•œ ํ”Œ๋žซํผ ํƒ€์ž…์„ ์‚ฌ์šฉํ•˜์ง€ ๋ง๋ผ

์ž๋ฐ”์—์„œ String ์€ nullable ํƒ€์ž… (null ์„ ํ—ˆ์šฉํ•˜๋Š” ํƒ€์ž…) ์ด๊ธฐ ๋•Œ๋ฌธ์—

์ฝ”ํ‹€๋ฆฐ์—์„œ์˜ ์ž๋ฐ” ์ฝ”๋“œ๋ฅผ ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ์—๋Š” ํ”Œ๋ž˜ํผ ํƒ€์ž… (platform type) ์œผ๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ๊ณผ์ •์„ ๊ฑฐ์นœ๋‹ค.

ํ”Œ๋žซํผ ํƒ€์ž… (platform type)
๋‹ค๋ฅธ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์–ธ์–ด์—์„œ ์ „๋‹ฌ๋˜์–ด nullable ์ธ์ง€ ์•„๋‹Œ์ง€ ์•Œ ์ˆ˜ ์—†๋Š” ํƒ€์ž…

๋•Œ๋ฌธ์— null ์ด ์•„๋‹ˆ๋ผ๊ณ  ์ƒ๊ฐ ๋˜๋Š”๊ฒƒ์ด null ์ผ ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์œ„ํ—˜ํ•˜๋‹ค๋Š” ๊ฒƒ์ด๋‹ค.

์„ค๊ณ„์ž๊ฐ€ ๋ช…์‹œ์ ์œผ๋กœ ์–ด๋…ธํ…Œ์ด์…˜์œผ๋กœ ํ‘œ์‹œํ•˜๊ฑฐ๋‚˜, ์ฃผ์„์œผ๋กœ ๋‹ฌ์•„๋†“์ง€ ์•Š์œผ๋ฉด, ์–ธ์ œ๋“ ์ง€ ๋™์ž‘์ด ๋ณ€๊ฒฝ๋  ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ๋‹ค.

์ž๋ฐ”๋ฅผ ์ฝ”ํ‹€๋ฆฐ๊ณผ ๊ฐ™์ด ์‚ฌ์šฉํ•  ๋•Œ, ์ž๋ฐ” ์ฝ”๋“œ๋ฅผ ์ง์ ‘ ์กฐ์ž‘ํ•  ์ˆ˜ ์žˆ๋‹ค๋ฉด, ๊ฐ€๋Šฅํ•œ @Nullable ๊ณผ @NotNull ์–ด๋…ธํ…Œ์ด์…˜์„ ๋ถ™์—ฌ์„œ ์‚ฌ์šฉํ•˜๋„๋ก ํ•˜์ž

import org.jetbrains.annotations.NotNull;

public class UserRepo {
  public @NotNull User getUser() {
    // codes...
  }
}

๊ฐ ํ”Œ๋žซํผ ๋ณ„๋กœ ์ง€์›๋˜๋Š” ๊ด€๋ จ ์–ด๋…ธํ…Œ์ด์…˜์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

Platform Library Annotation
JetBrains org.jetbraions.annotations @Nullable @NotNull
Android androidx.annotation
com.android.annotations
android.support.annotations
@Nullable @NonNull
JSR-305 javax.annotation @Nullable @CheckForNull @Nonnull
JavaX javax.annotation @Nullable @CheckForNull @Nonnull
FindBugs edu.umd.cs.findbugs.annotations @Nullable @CheckForNull @PossiblyNull @Nonnull
ReactiveX io.reactivex.annotations @Nullable @CheckForNull @Nonnull
Eclipse org.eclipse.jdt.annotation @Nullable @Nonnull
Lombock lombock.NonNull @NonNull

๋˜ํ•œ ํ”Œ๋žซํผ ํƒ€์ž…์„ ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ, ์ด๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋‹ค๋ฅธ ์ฝ”๋“œ๊นŒ์ง€ ์ „ํŒŒ์œ„ํ—˜์„ฑ์ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๋ฌธ์ œ๊ฐ€ ๋  ์ˆ˜ ์žˆ๋‹ค.









ย 



class RepoImpl: UserRepo {
  overrid fun getUserName(): String? {
    return null
  }
}

fun main() {
  val repo: UserRepo = RepoImpl()
  val text: String = repo.getUserName() 
  println("User name length is ${text.length}")
}

์œ„ ์ฝ”๋“œ์—์„œ๋Š” 9๋ฒˆ์งธ ๋ผ์ธ์—์„œ Runtime ์‹œ์— NPE ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒ ํ•œ๋‹ค.

์ •๋ฆฌ

ํ”Œ๋žซํผ ํƒ€์ž…์„ ์‚ฌ์šฉํ•˜๋Š” ์ฝ”๋“œ๋Š” ํ•ด๋‹น ๋ถ€๋ถ„๋งŒ ์œ„ํ—˜ํ•  ๋ฟ ์•„๋‹ˆ๋ผ, ์ด๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ณณ๊นŒ์ง€ ์˜ํ–ฅ์„ ์ค„ ์ˆ˜ ์žˆ๋Š” ์œ„ํ—˜ํ•œ ์ฝ”๋“œ์ด๋‹ค.

Item 4 inferred ํƒ€์ž…์œผ๋กœ ๋ฆฌํ„ดํ•˜์ง€ ๋ง๋ผ

์ฝ”ํ‹€๋ฆฐ์˜ ํƒ€์ž… ์ถ”๋ก  (type inference)์€ JVM ์„ธ๊ณ„์—์„œ ๊ฐ€์žฅ ๋„๋ฆฌ ์•Œ๋ ค์ง„ ์ฝ”ํ‹€๋ฆฐ์˜ ํŠน์ง•์ด๋‹ค.

ํƒ€์ž…์„ ํ• ๋‹นํ• ๋•Œ inferred ํƒ€์ž…์€ ์˜ค๋ฅธ์ชฝ์— ์žˆ๋Š” ํ”ผ ์—ฐ์‚ฐ์ž์— ๋งž๊ฒŒ ์„ค์ •ํ•ด์•ผ ํ•œ๋‹ค.






ย 


open class Animal
class Zebra: Animal()

fun main() {
  var animal = Zebra()
  animal = Animal() // ์˜ค๋ฅ˜: Type mismatch 
}

์ด๋Ÿด ๊ฒฝ์šฐ ํƒ€์ž…์„ ๋ช…์‹œ์ ์œผ๋กœ ์ง€์ •ํ•˜์—ฌ ๋ฌธ์ œ ํ•ด๊ฒฐ์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

animal ๊ฐ์ฒด๋Š” Zebra ํƒ€์ž…์œผ๋กœ ์ œํ•œ (bounded) ๋˜๊ธฐ ๋•Œ๋ฌธ์— ๋ถ€๋ชจ ํƒ€์ž…์˜ ๊ฐ์ฒด๋ฅผ ์ˆ˜์šฉํ•  ์ˆ˜ ์—†๋‹ค.





ย 



open class Animal
class Zebra: Animal()

fun main() {
  var animal: Animal = Zebra()
  animal = Animal()
}

๋ชจ๋“ˆ (ํ˜น์€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ) ๋ฅผ ์ง์ ‘์ ์œผ๋กœ ์กฐ์ž‘ํ•  ์ˆ˜ ์—†๋Š” ๊ฒฝ์šฐ ๋ฌธ์ œ ํ•ด๊ฒฐ์ด ๋ถˆ๊ฐ€๋Šฅํ•˜๋‹ค.

๋ฆฌํ„ด ํƒ€์ž…์€ ์™ธ๋ถ€์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋ช…์‹œ์ ์œผ๋กœ ์ง€์ •ํ•ด ์ฃผ๋Š” ๊ฒƒ์ด ์ข‹๋‹ค.

์ •๋ฆฌ

ํƒ€์ž…์„ ํ™•์‹คํ•˜๊ฒŒ ์ง€์ •ํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ์—๋Š” ๋ช…์‹œ์ ์œผ๋กœ ํƒ€์ž…์„ ์ง€์ •ํ•ด์•ผ ํ•œ๋‹ค๋Š” ์›์น™๋งŒ ๊ฐ–๊ณ  ์žˆ์œผ๋ฉด ๋œ๋‹ค.

๋•Œ๋ฌธ์— ์ด๋Š” ์ค‘์š”ํ•œ ์ •๋ณด๋กœ์จ ์ˆจ๊ธฐ์ง€ ์•Š์•„๋„ ๋œ๋‹ค.

์™ธ๋ถ€์—์„œ ์ฐธ์กฐํ•˜๋Š” API ๋ฅผ ๋งŒ๋“ค ๊ฒฝ์šฐ ๋ฐ˜๋“œ์‹œ ํƒ€์ž…์„ ์ง€์ •ํ•˜๋ผ.

inferred ํƒ€์ž…์€ ํ”„๋กœ์ ํŠธ ์ง„ํ–‰ ์‹œ, ์ œํ•œ์ด ๋„ˆ๋ฌด ๋งŽ์•„์ง€๊ฑฐ๋‚˜ ์˜ˆ์ธกํ•˜์ง€ ๋ชปํ•œ ๊ฒฐ๊ณผ๋ฅผ ๋‚ผ ์ˆ˜ ์—†๋‹ค๋Š” ๊ฒƒ์„ ๋ช…์‹ฌํ•˜๋ผ.

Item 5 ์˜ˆ์™ธ๋ฅผ ํ™œ์šฉํ•˜์—ฌ ์ฝ”๋“œ์— ์ œํ•œ์„ ๊ฑธ์–ด๋ผ

์ฝ”ํ‹€๋ฆฐ์—์„œ๋Š” ๋™์ž‘์— ์ œํ•œ์„ ๊ฑธ ๋•Œ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ฐฉ๋ฒ•์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

  • require ๋ธ”๋Ÿญ : Arguments ๋ฅผ ์ œํ•œํ•  ์ˆ˜ ์žˆ๋‹ค.
  • check ๋ธ”๋Ÿญ : ์ƒํƒœ์™€ ๊ด€๋ จ๋œ ๋™์ž‘์„ ์ œํ•œํ•  ์ˆ˜ ์žˆ๋‹ค.
  • assert ๋ธ”๋Ÿญ : ํ…Œ์ŠคํŠธ ๋ชจ๋“œ์—์„œ๋งŒ ์ž‘๋™ํ•˜๋ฉฐ, ๊ฐ’์˜ ์ง„์œ„์—ฌ๋ถ€ ํŒ๋‹จ์„ ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • return ๋˜๋Š” throw ์™€ ํ•จ๊ป˜ํ•˜๋Š” Elvis ์—ฐ์‚ฐ์ž

์‚ฌ์šฉ์˜ˆ์‹œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

fun pop(num: Int = 1): List<T> {
  require(num <= size) {
    "Can't remove more elements than current size"
  }
  check(isOpen) { 
    "Can't pop from close stack"
  }
  val ret = collection.take(num)
  collection = collection.drop(num)
  assert(ret.size == num)
  return ret
}

์œ„์™€ ๊ฐ™์ด ์ฝ”๋“œ์ƒ์—์„œ ์ œํ•œ์„ ๊ฑธ์–ด์ฃผ๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์žฅ์ ์žˆ๋‹ค.

  1. ์ œํ•œ์„ ๊ฑธ๋ฉด ๋ฌธ์„œ๋ฅผ ์ฝ์ง€ ์•Š๋Š” ๊ฐœ๋ฐœ์ž๋„ ๋ฌธ์ œ๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.
  2. ๋ฌธ์ œ๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ ํ•จ์ˆ˜๊ฐ€ ์˜ˆ์ƒํ•˜์ง€ ๋ชปํ•œ ๋™์ž‘์„ ํ•˜์ง€ ์•Š๊ณ  ์˜ˆ์™ธ๋ฅผ throw ํ•œ๋‹ค.
  3. ์ฝ”๋“œ๊ฐ€ ์–ด๋Š์ •๋„ ์ž์ฒด์ ์œผ๋กœ ๊ฒ€์‚ฌ๊ฐ€ ๋œ๋‹ค.
  4. ์Šค๋งˆํŠธ ์ผ€์ŠคํŠธ๋ฅผ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๊ณ , ์บ์ŠคํŒ… (ํƒ€์ž…๋ณ€ํ™˜) ์„ ์ ๊ฒŒ ํ•  ์ˆ˜ ์žˆ๋‹ค.

์•„๊ทœ๋จผํŠธ

ํ•จ์ˆ˜๋ฅผ ์ •์˜ํ•  ๋•Œ ํƒ€์ž… ์‹œ์Šคํ…œ์„ ์ด์šฉํ•ด์„œ Arguments ์— ์ œํ•œ์„ ๊ฑฐ๋Š” ์ฝ”๋“œ๋ฅผ ๋งŽ์ด ์‚ฌ์šฉ

/**
 * ์ˆซ์ž๋ฅผ ์•„๊ทœ๋จผํŠธ๋กœ ๋ฐ›์•„ ํŒฉํ† ๋ฆฌ์–ผ์„ ๊ณ„์‚ฐ
 * @param n ์ˆซ์ž(์–‘์˜ ์ •์ˆ˜)
 **/
fun factorial(n: Int): Long {
  require(n >= 0)
  return if (n <= 1) 1
    else factorial(n - 1) * n
}
/**
 * ์ขŒํ‘œ ๋ชฉ๋ก์„ ๊ตฌํ•œ๋‹ค.
 * @param points ์ขŒํ‘œ ๋ชฉ๋ก(๋นˆ ๊ฐ’์ด ์•„๋‹˜)
 **/
fun findClusters(points: List<Point>): List<Cluster> {
  require(points.isNotEmpty())
  /**
   * Other codes...
   **/
}
/**
 * ๋ฉ”์ผ ๋ณด๋‚ด๊ธฐ
 * @param user ์‚ฌ์šฉ์ž ์ •๋ณด (์ด๋ฉ”์ผ ์ •๋ณด๊ฐ€ ํฌํ•จ๋˜์–ด์•ผ ํ•จ)
 * @param message ๋ฉ”์„ธ์ง€
 **/
fun sendEmail(user: User, message: String) {
  requireNotNull(user.email)
  require(isValidEmail(user.email))
  /**
   * Other codes...
   **/
}

์œ„์™€ ๊ฐ™์ด ์œ ํšจ์„ฑ ์ฝ”๋“œ๋Š” ๋งจ ์•ž์— ๋ฐฐ์น˜๋˜์–ด์•ผ ํ•œ๋‹ค.

require ํ•จ์ˆ˜๋Š” ์กฐ๊ฑด์„ ๋งŒ์กฑํ•˜์ง€ ๋ชปํ•  ๋•Œ, ๋ฌด์กฐ๊ฑด์ ์œผ๋กœ IllegalArgumentException ์„ ๋ฐœ์ƒ

๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋žŒ๋‹ค ํ˜•ํƒœ๋กœ ๋ฉ”์„ธ์ง€๋ฅผ ์ •์˜ํ•  ์ˆ˜ ์žˆ๋‹ค.

fun factorial(n: Int): Long {
  require(n >= 0) {
    "Can't calculate factorial of $n because it is smaller than 0"
  }
  
  return if (n <= 1) 1 else factorial(n - 1) * n
}

์ƒํƒœ

์–ด๋–ค ๊ตฌ์ฒด์ ์ธ ์กฐ๊ฑด์„ ๋งŒ์กฑํ•  ๋•Œ๋งŒ ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์•ผ ํ• ๋•Œ๊ฐ€ ์žˆ๋‹ค.

์˜ˆ๋ฅผ ๋“ค๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ฒฝ์šฐ

์œ„์™€ ๊ฐ™์€ ํŠน์ • ์ƒํƒœ๋ฅผ ๋งŒ์กฑํ–ˆ์„๋•Œ์—๋Š” check ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

/**
 * ๊ฐ์ฒด๋ฅผ ๋ฏธ๋ฆฌ ์ดˆ๊ธฐํ™”๋˜์–ด ์žˆ๋Š” ์ƒํƒœ์—๋งŒ ์‹คํ–‰ํ•˜๊ณ  ์‹ถ์€ ํ•จ์ˆ˜ 
 **/
fun speak(text: String) {
  check(isInitialized)
  /**
   * Other codes...
   **/
}
/**
 * ์‚ฌ์šฉ์ž๊ฐ€ ๋กœ๊ทธ์ธ๋˜์–ด ์žˆ์„๋•Œ์—๋งŒ ์ฒ˜๋ฆฌํ•˜๊ณ  ์‹ถ์€ ํ•จ์ˆ˜
 **/
fun getUserInfo(text: String) {
  checkNotNull(token)
  /**
   * Other codes...
   **/
}
/**
 * ๊ฐ์ฒด์˜ ์‚ฌ์šฉ ์ค€๋น„๊ฐ€ ์™„๋ฃŒ๋˜์–ด ์žˆ์„๋•Œ๋งŒ ์‚ฌ์šฉํ•˜๊ณ  ์‹ถ์€ ํ•จ์ˆ˜
 **/
fun next(): T {
  check(isOpen)
  /**
   * Other codes...
   **/
}

check ํ•จ์ˆ˜๋Š” require ์™€ ๋น„์Šทํ•˜์ง€๋งŒ, ์กฐ๊ฑด์„ ๋งŒ์กฑํ•˜์ง€ ๋ชปํ•  ๋•Œ IllegalStateException ์„ ๋ฐœ์ƒ์‹œํ‚จ๋‹ค.

์ผ๋ฐ˜์ ์œผ๋กœ require ๋ธ”๋Ÿญ์„ ๋’ค์— ๋ฐฐ์น˜ ์‹œํ‚จ๋‹ค.

Assert ๊ณ„์—ด์˜ ํ•จ์ˆ˜ ์‚ฌ์šฉ

ํ•จ์ˆ˜์˜ ์˜ฌ๋ฐ”๋ฅธ ๊ตฌํ˜„ ํ™•์ธ์„ ์œ„ํ•ด ๊ฒ€์ฆํ•˜๋Š” ํ•จ์ˆ˜

๊ตฌํ˜„๋ฌธ์ œ๋กœ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋Š” ์ถ”๊ฐ€์ ์ธ ๋ฌธ์ œ๋ฅผ ์˜ˆ๋ฐฉํ•˜๋ ค๋ฉด, ๋‹จ์œ„ํ…Œ์ŠคํŠธ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹๋‹ค.

@Test
fun `Stack pops correct number of elements`() {
  val stack = Stack(20) { it }
  val ret = stack.pop(10)
  assertEquals(10, ret.size)
}

ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๊ฐ€ ์•„๋‹Œ ์‹คํ–‰ ์ฝ”๋“œ์—์„œ assert ๋ฅผ ์‚ฌ์šฉํ• ์ˆ˜๋„ ์žˆ์œผ๋ฉฐ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์žฅ์ ์ด ์žˆ๋‹ค.

  • Assert ๊ณ„์—ด์˜ ํ•จ์ˆ˜๋Š” ์ฝ”๋“œ ์ž์ฒด๋ฅผ ์ ๊ฒ€ํ•˜๋ฉฐ, ๋” ํšจ์œจ์ ์œผ๋กœ ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ํŠน์ • ์ƒํ™ฉ์ด ์•„๋‹Œ ๋ชจ๋“  ์ƒํ™ฉ์— ๋Œ€ํ•ด ํ…Œ์ŠคํŠธ ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ์‹คํ–‰ ์‹œ์ ์— ์ •ํ™•ํ•˜๊ฒŒ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ์‹ค์ œ ์ฝ”๋“œ๊ฐ€ ๋” ๋น ๋ฅธ ์‹œ์ ์— ์‹คํŒจํ•˜๊ธฐ ๋งŒ๋“ค๋ฉฐ, ์–ธ์ œ ์–ด๋””์„œ ์‹คํ–‰ํ–ˆ๋Š”์ง€ ์‰ฝ๊ฒŒ ์ฐพ์„์ˆ˜ ์žˆ๋‹ค.

์‹คํ–‰์‹œ์ ์— assert ๋Š” ์˜ˆ์™ธ๋ฅผ throw ํ•˜์ง€ ์•Š๋Š”๋‹ค๋Š” ๊ฒƒ์„ ๋ช…์‹ฌ

nullability์™€ ์Šค๋งˆํŠธ ์บ์ŠคํŒ…

์ฝ”ํ‹€๋ฆฐ์—์„œ๋Š” require ์™€ check ๋ธ”๋Ÿญ์œผ๋กœ ํŠน์ • ์กฐ๊ฑด์„ ํ™•์ธํ•ด์„œ true ๊ฐ€ ๋‚˜์™”๋‹ค๋ฉด, ํ•ด๋‹น ์กฐ๊ฑด์€ ์ดํ›„๋กœ๋„ true ์ผ๊บผ๋ผ๊ณ  ๊ฐ€์ •ํ•œ๋‹ค.

@kotlin.internal.InlineOnly
public inlin fun require(value: Boolean): Unit {
  contract {
    returns() implies value
  }
  require(value) { "Failed requirement." }
}

์ด๋ฅผ ํ™œ์šฉํ•ด์„œ ํƒ€์ž… ๋น„๊ต๋ฅผ ํ–ˆ๋‹ค๋ฉด, ์Šค๋งˆํŠธ ์บ์ŠคํŠธ ๊ฐ€ ์ž‘๋™๋œ๋‹ค.



ย 


fun changeDress(person: Person) {
  require(person.outfit is Dress)
  val dress: Dress = person.outfit
}

์ด๋Ÿฌํ•œ ํŠน์ง•์€ ๋Œ€์ƒ์ด null ์ธ์ง€ ํ™•์ธํ•  ๋•Œ ๋งค์šฐ ์œ ์šฉํ•˜๋‹ค.

class Person(val email: String?)

fun sendEmail(person: Person, message: String) {
  require(person.email != null)
  val email: String = person.email
  /**
   * Other codes...
   **/
}

์ด๋ ‡๊ฒŒ null ์ธ์ง€ ์•„๋‹Œ์ง€ ํ™•์ธํ•˜๋Š” ์ผ€์ด์Šค๋Š” requireNotNull, checkNotNull ์ด๋ผ๋Š” ํŠน์ˆ˜ํ•œ ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜๋„ ๊ดœ์ฐฎ๋‹ค.

๋‘˜๋‹ค ์Šค๋งˆํŠธ ์บ์ŠคํŠธ๋ฅผ ์ง€์›ํ•˜๋ฏ€๋กœ, ๋ณ€์ˆ˜๋ฅผ ์–ธํŒฉ (unpack) ํ•˜๋Š” ์šฉ๋„๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

class Person(val email: String?)
fun validateEmail(email: String) { /*...*/ }
fun sendEmail(person: Person, text: String) {
  val email = requireNotNull(person.email)
  validateEmail(email)
  /**
   * Other codes...
   **/
}
fun sendEmail(person: Person, text: String) {
  requireNotNull(person.email)
  validateEmail(person.email)
  /**
   * Other codes...
   **/
}

nullability ๋ฅผ ๋ชฉ์ ์œผ๋กœ throw ๋˜๋Š” return ์„ ๋‘๊ณ  Elvis ์—ฐ์‚ฐ์ž (?๐Ÿ˜ƒ ๋ฅผ ํ™œ์šฉํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ๋‹ค.

fun sendEmail(person: Person, text: String) {
  val email: String = person.email ?: return
}

์œ„์™€ ๊ฐ™์ด ์˜ค๋ฅ˜๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค์ง€ ์•Š๊ณ  ๋‹จ์ˆœํ•˜๊ฒŒ ํ•จ์ˆ˜๋ฅผ ์ค‘์ง€ ์‹œํ‚ค๊ฑฐ๋‚˜

์•„๋ž˜์™€ ๊ฐ™์ด ๋กœ๊ทธ๋ฅผ ๋…ธ์ถœ ์‹œํ‚จํ›„์— ์ข…๋ฃŒ์‹œํ‚จ๋‹ค.

fun sendEmail(person: Person, text: String) {
  val email: String = person.email ?: run {
    log("Email not sent, no email address")

    return
  }
}

Elvis ์—ฐ์‚ฐ์ž๋Š” nullable ์„ ํ™•์ธํ•  ๋•Œ ๊ต‰์žฅํžˆ ๋งŽ์ด ์‚ฌ์šฉ๋˜๋Š” ๊ด€์šฉ์ ์ธ ๋ฐฉ๋ฒ•์ด๋‹ค.

์ •๋ฆฌ

์˜ˆ์™ธ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ฝ”๋“œ์— ์ œํ•œ์„ ๊ฑฐ๋Š” ๋ฐฉ๋ฒ•์€ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ด๋“์„ ์–ป์„์ˆ˜ ์žˆ๋‹ค.

  • ์ œํ•œ์„ ๋” ์‰ฝ๊ฒŒ ๊ฑธ ์ˆ˜ ์žˆ๋‹ค.
  • ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋” ์•ˆ์ •์ ์œผ๋กœ ์ง€ํ‚ฌ ์ˆ˜ ์žˆ๋‹ค.
  • ์ฝ”๋“œ๋ฅผ ์ž˜๋ชป ์“ฐ๋Š” ์ƒํ™ฉ์„ ๋ง‰์„ ์ˆ˜ ์žˆ๋‹ค.
  • ์Šค๋งˆํŠธ ์บ์ŠคํŒ…์„ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

๋˜ํ•œ ์ด๋ฅผ ์œ„ํ•œ ํ™œ์šฉ๋ฐฉ์•ˆ์˜ ๋งค์ปค๋‹ˆ์ฆ˜์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

  • require ๋ธ”๋Ÿญ : ์•„๊ทœ๋จผํŠธ์™€ ๊ด€๋ จ๋œ ์˜ˆ์ธก์„ ์ •์˜ํ•  ๋•Œ ์‚ฌ์šฉํ•˜๋Š” ๋ฒ”์šฉ์ ์ธ ๋ฐฉ๋ฒ•
  • check ๋ธ”๋Ÿญ : ์ƒํƒœ์™€ ๊ด€๋ จ๋œ ์˜ˆ์ธก์„ ์ •์˜ํ•  ๋•Œ ์‚ฌ์šฉํ•˜๋Š” ๋ฒ”์šฉ์ ์ธ ๋ฐฉ๋ฒ•
  • assert ๋ธ”๋Ÿญ : ํ…Œ์ŠคํŠธ ๋ชจ๋“œ์—์„œ ํ…Œ์ŠคํŠธํ•  ๋•Œ ์‚ฌ์šฉํ•˜๋Š” ๋ฒ”์šฉ์ ์ธ ๋ฐฉ๋ฒ•
  • ๊ทธ ์™ธ : return ํ˜น์€ throw ์™€ ํ•จ๊ป˜ Elvis ์—ฐ์‚ฐ์ž์™€ ์‚ฌ์šฉํ•˜๊ธฐ

์ด์™ธ์—๋„ ๋‹ค๋ฅธ ์˜ค๋ฅ˜๋“ค์„ ๋ฐœ์ƒ ์‹œํ‚ฌ๋•Œ throw ๋ฅผ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

Item 6 ์‚ฌ์šฉ์ž ์ •์˜ ์˜ค๋ฅ˜๋ณด๋‹ค๋Š” ํ‘œ์ค€ ์˜ค๋ฅ˜๋ฅผ ์‚ฌ์šฉํ•˜๋ผ

Item 5 ์—์„œ ์•Œ์•„๋ณด์•˜๋˜ (require, check, assert) ๋ฅผ ์‚ฌ์šฉํ•˜๋”๋ผ๋„ ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ์˜ค๋ฅ˜๋ฅผ ๋งŒ๋‚ ์ˆ˜ ์žˆ๋‹ค.

์˜ˆ๋ฅผ ๋“ค๋ฉด ์•„๋ž˜์™€ ๊ฐ™์ด JSON ์„ ํŒŒ์‹ฑํ•˜๋‹ค ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค๋ฉด JsonParsingException ์„ ๋ฐœ์ƒ์‹œํ‚ค๋Š”๊ฒƒ์ด ์ข‹๋‹ค.

inline fun <reified T> String.readObject(): T {
  //...
  if (incorrectSign) {
    throw JsonParsingException()
  }
  //...
}

์ง์  ์˜ค๋ฅ˜๋ฅผ ์ •์˜ ํ•˜๋Š”๊ฒƒ ๋ณด๋‹ค๋Š” ์ตœ๋Œ€ํ•œ ํ‘œ์ค€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ ์˜ค๋ฅ˜๋ฅผ ์‚ฌ์šฉํ•˜๋Š”๊ฒƒ์ด ์ข‹๋‹ค.

ํ‘œ์ค€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ ์˜ค๋ฅ˜๋Š” ๋งŽ์€ ๊ฐœ๋ฐœ์ž๊ฐ€ ์•Œ๊ณ  ์žˆ์œผ๋ฏ€๋กœ, ์ด๋ฅผ ์žฌ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹๋‹ค.

์ผ๋ฐ˜์ ์œผ๋กœ ์‚ฌ์šฉ๋˜๋Š” ์˜ˆ์™ธ๋ฅผ ๋ช‡๊ฐ€์ง€ ์ •๋ฆฌํ•ด๋ณด๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

์˜ˆ์™ธ ์ข…๋ฅ˜ ์„ค๋ช…
IllegalArgumentException
IllegalStateException
require ์™€ check๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ throw ํ•  ์ˆ˜ ์žˆ๋Š” ์˜ˆ์™ธ
IndexOutOfBoundsException ์ธ๋ฑ์Šค ํŒŒ๋ผ๋ฏธํ„ฐ์˜ ๊ฐ’์˜ ๋ฒ”์œ„๋ฅผ ๋ฒ—์–ด๋‚ฌ๋‹ค๋Š”๊ฒƒ์„ ์•Œ๋ ค์คŒ
ConcurrentModificationException ๋™์‹œ ์ˆ˜์ •(concurrent modification) ์„ ๊ธˆ์ง€ํ•˜์˜€์œผ๋‚˜, ๋ฐœ์ƒํ•จ
UnsupportedOperationException ์‚ฌ์šฉ์ž๊ฐ€ ์‚ฌ์šฉํ•˜๋ ค๊ณ  ํ–ˆ๋˜ ๋ฉ”์„œ๋“œ๊ฐ€ ํ˜„์žฌ ๊ฐ์ฒด์—์„œ๋Š” ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋‹ค๋Š” ๊ฒƒ์„ ์•Œ๋ ค์คŒ
๊ธฐ๋ณธ์ ์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋Š” ๋ฉ”์„œ๋“œ๋Š” ํด๋ž˜์Šค์— ์—†๋Š” ๊ฒƒ์ด ์ข‹๋‹ค.
NoSuchElementException ์กด์žฌํ•˜์ง€ ์•Š๋Š” ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœ

Item 7 ๊ฒฐ๊ณผ ๋ถ€์กฑ์ด ๋ฐœ์ƒํ•  ๊ฒฝ์šฐ null ๊ณผ Failure ๋ฅผ ์‚ฌ์šฉํ•˜๋ผ

ํ•จ์ˆ˜๊ฐ€ ์›ํ•˜๋Š” ๊ฒฐ๊ณผ๋ฅผ ๋‚ด๋†“์„ ์ˆ˜ ์—†์„๋•Œ

  • ์„œ๋ฒ„๋กœ๋ถ€ํ„ฐ ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ์–ด ๋“ค์ด๋ ค๊ณ  ํ–ˆ๋Š”๋ฐ, ๋„คํŠธ์›Œํฌ ๋ฌธ์ œ๋กœ ์ด์Šˆ๊ฐ€ ์ƒ๊ธด๊ฒฝ์šฐ
  • ์กฐ๊ฑด์— ๋งž๋Š” ์ฒซ๋ฒˆ์งธ ์š”์†Œ๋ฅผ ์ฐพ์œผ๋ ค ํ–ˆ๋Š”๋ฐ, ์š”์†Œ๊ฐ€ ์—†๋Š” ๊ฒฝ์šฐ
  • ํ…์ŠคํŠธ๋ฅผ ํŒŒ์‹ฑํ•ด์„œ ๊ฐ์ฒด๋ฅผ ๋งŒ๋“œ๋ ค๊ณ  ํ–ˆ๋Š”๋ฐ, ํ˜•์‹์ด ๋งž์ง€ ์•Š๋Š” ๊ฒฝ์šฐ

์ด๋Ÿฌํ•œ ์˜ค๋ฅ˜ ์ƒํ™ฉ์„ ์ฒ˜๋ฆฌ ๊ฐ€๋Šฅํ•œ ๋ฉ”์ปค๋‹ˆ์ฆ˜์€ ๋‹ค์Œ ๋‘๊ฐ€์ง€๊ฐ€ ์žˆ๋‹ค.

  1. null ๋˜๋Š” ์‹คํŒจ๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š” sealed ํด๋ž˜์Šค๋ฅผ ๋ฆฌํ„ด
  2. ์˜ˆ์™ธ๋ฅผ throw ํ•œ๋‹ค.

์œ„ ๋‘๊ฐ€์ง€๋Š” ์ค‘์š”ํ•œ ์ฐจ์ด์ ์ด ์žˆ๋‹ค.

์˜ˆ์™ธ๋Š” ์ •๋ณด๋ฅผ ์ „๋‹ฌํ•˜๋Š” ๋ฐฉ๋ฒ•์œผ๋กœ ์‚ฌ์šฉํ•ด์„œ๋Š” ์•ˆ๋œ๋‹ค. ์˜ˆ์™ธ๋Š” ์ž˜๋ชป๋œ ํŠน์ •ํ•œ ์ƒํ™ฉ์„ ๋‚˜ํƒ€๋‚ด์•ผ ํ•˜๋ฉฐ, ์ด๋ฅผ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•œ๋‹ค.

์˜ˆ์™ธ๋Š” ์˜ˆ์™ธ์ ์ธ ์ƒํ™ฉ์ด ๋ฐœ์ƒํ–ˆ์„ ๋•Œ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹๋‹ค.

์ด๋Ÿฌํ•œ ์ด์œ ๋ฅผ ์ •๋ฆฌํ•˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

  • ๋งŽ์€ ๊ฐœ๋ฐœ์ž๋“ค์€ ์˜ˆ์™ธ๊ฐ€ ์ „ํŒŒ๋˜๋Š” ๊ณผ์ •์„ ์ œ๋Œ€๋กœ ์ถ”์ ํ•˜์ง€ ๋ชปํ•œ๋‹ค.
  • ์ฝ”ํ‹€๋ฆฐ์˜ ๋ชจ๋“  ์˜ˆ์™ธ๋Š” unchecked ์˜ˆ์™ธ์ด๋‹ค.
  • ์˜ˆ์™ธ์™€ ๊ด€๋ จ๋œ ์‚ฌํ•ญ์„ ๋‹จ์ˆœํ•˜๊ฒŒ ๋ฉ”์„œ๋“œ๋“ฑ์œผ๋กœ ์‚ฌ์šฉํ•˜๋ฉด์„œ ํŒŒ์•…ํ•˜๊ธฐ ํž˜๋“ค๋‹ค
  • ์˜ˆ์™ธ๋Š” ์˜ˆ์™ธ์ ์ธ ์ƒํ™ฉ์„ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด ๋งŒ๋“ค์–ด์กŒ์œผ๋ฉฐ, ๋ช…์‹œ์ ์ธ ํ…Œ์ŠคํŠธ๋งŒํผ ๋น ๋ฅด๊ฒŒ ๋™์ž‘ํ•˜์ง€ ์•Š๋Š”๋‹ค.
  • try-catch ๋ธ”๋Ÿญ ๋‚ด๋ถ€์— ์ฝ”๋“œ๋ฅผ ๋ฐฐ์น˜ํ•˜๋ฉด, ์ปดํŒŒ์ผ๋Ÿฌ๊ฐ€ ํ•  ์ˆ˜ ์žˆ๋Š” ์ตœ์ ํ™”๊ฐ€ ์ œํ•œ๋œ๋‹ค.

์œ„ 1๋ฒˆ ์—์„œ null ๊ณผ Failure ์€ ์˜ˆ์ƒ๋˜๋Š” ์˜ค๋ฅ˜๋ฅผ ํ‘œํ˜„ํ•  ๋•Œ ๊ต‰์žฅํžˆ ์ข‹๋‹ค.

์ถฉ๋ถ„ํžˆ ์˜ˆ์ธก๊ฐ€๋Šฅํ•œ ๋ฒ”์œ„์˜ ์˜ค๋ฅ˜๋Š” null ๊ณผ Failure ๋ฅผ ์‚ฌ์šฉํ•˜๊ณ , ์˜ˆ์ธกํ•˜๊ธฐ ์–ด๋ ค์šด ์˜ˆ์™ธ์ ์ธ ๋ฒ”์œ„์˜ ์˜ค๋ฅ˜๋Š” ์˜ˆ์™ธ๋ฅผ throw ํ•ด์„œ ์ฒ˜๋ฆฌํ•˜๋Š”๊ฒƒ์ด ์ข‹๋‹ค.

inline fun <reified T> String.readObjectOrNull(): T? {
  //...
  if (incorrectSign) {
    return null
  }
  //...
  return result
}

inline fun <reified T> String.readObject(): Result<T> {
  //...
  if (incorrectSign) {
    return Failure(JsonParsingException())
  }
  //...
  return Success(result)
}

sealed class Result<out T>
class Success<out T>(val result: T): Result<T>()
class Failure(val throwable: Throwable): Result<Noting>()

class JsonParsingException: Exception()

์ด๋ ‡๊ฒŒ ํ‘œ์‹œ๋˜๋Š” ์˜ค๋ฅ˜๋Š” ๋‹ค๋ฃจ๊ธฐ ์‰ฌ์šฐ๋ฉฐ ๋†“์น˜๊ธฐ ์–ด๋ ต๋‹ค. null ์„ ์ฒ˜๋ฆฌํ•ด์•ผ ํ•œ๋‹ค๋ฉด, ์‚ฌ์šฉ์ž๋Š” ์•ˆ์ „ํ•œ ํ˜ธ์ถœ(safety-call) ๋˜๋Š” Elvis ์—ฐ์‚ฐ์ž๋ฅผ ์ด์šฉํ•œ ๋„ ์•ˆ์ •์„ฑ(null-safety) ๊ธฐ๋Šฅ์„ ํ™œ์šฉํ•œ๋‹ค.

val age = userText.readObjectOrNull<Person>()?.age ?: -1

Result ์™€ ๊ฐ™์€ ๊ณต์šฉ์ฒด (union type) ์„ ๋ฆฌํ„ดํ•˜๊ธฐ๋กœ ํ–ˆ๋‹ค๋ฉด, when ํ‘œํ˜„์‹์„ ์‚ฌ์šฉํ•ด์„œ ์ฒ˜๋ฆฌํ•˜์ž

val person = userText.readObjectOrNull<Person>()
val age = when(person) {
  is Success -> person.age
  is Failure -> -1
}

์ด๋Ÿฌํ•œ ์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ ๋ฐฉ์‹์€ try-catch ๋ธ”๋ก๋ณด๋‹ค ํšจ์œจ์ ์ด๋ฉฐ, ๋” ์‰ฝ๊ณ  ๋ช…ํ™•ํ•ฉ๋‹ˆ๋‹ค.

Sealed Class (๋ด‰์ธ ํด๋ž˜์Šค)
๊ฐ’์ด ์ œํ•œ๋œ ์ง‘ํ•ฉ์˜ ์œ ํ˜•์ค‘ ํ•˜๋‚˜๋ฅผ ๊ฐ€์งˆ์ˆ˜ ์žˆ๋Š” ์ œํ•œ๋œ ํด๋ž˜์Šค ๊ณ„์ธต ๊ตฌ์กฐ๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š”๋ฐ ์‚ฌ์šฉํ•œ๋‹ค.
์ถ”์ƒ ํด๋ž˜์Šค์˜ ์ผ์ข…์ด์ง€๋งŒ ์ง์ ‘์ ์œผ๋กœ ์ธ์Šคํ„ด์Šคํ™” ํ•  ์ˆ˜ ์—†๊ณ , ์ถ”์ƒ ๋ฉค๋ฒ„๋ฅผ ๊ฐ€์งˆ ์ˆ˜ ์žˆ๋‹ค.
Sealed ํด๋ž˜์Šค์˜ ์ด์ ์€ enum ํด๋ž˜์Šค์™€ ์œ ์‚ฌํ•˜๊ฒŒ when ์ ˆ ์‚ฌ์šฉ์‹œ else ์ ˆ์ด ํ•„์š”๊ฐ€ ์—†๋‹ค.

๊ฐœ๋ฐœ์ž๋Š” ํ•ญ์ƒ ์ž์‹ ์ด ์š”์†Œ๋ฅผ ์•ˆ์ „ํ•˜๊ฒŒ ์ถ”์ถœํ•  ๊ฑฐ๋ผ ์ƒ๊ฐํ•˜๊ธฐ ๋•Œ๋ฌธ์— nullable ๋ฅผ ๋ฆฌํ„ดํ•˜๋ฉด ์•ˆ๋œ๋‹ค.

๊ฐœ๋ฐœ์ž์—๊ฒŒ null ์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒฝ๊ณ ๋ฅผ ์ฃผ๋ ค๋ฉด getOrNull ์„ ์‚ฌ์šฉํ•ด์„œ ๋ฐ˜ํ™˜๋˜๋Š” ๊ฐ’์˜ ๋ฒ”์œ„๋ฅผ ๋ช…์‹œ์ ์œผ๋กœ ์•Œ๋ ค์ฃผ๋Š” ๊ฒƒ์ด ์ข‹๋‹ค.

Item 8 ์ ์ ˆํ•˜๊ฒŒ null์„ ์ฒ˜๋ฆฌํ•˜๋ผ

null ์€ '๊ฐ’์ด ๋ถ€์กฑํ•˜๋‹ค(lack of value)'๋ผ๋Š” ๊ฒƒ์„ ๋‚˜ํƒ€๋‚ธ๋‹ค.

null ์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค๋Š” ๊ฒƒ์€ ์—ฌ๋Ÿฌ ์˜๋ฏธ๋ฅผ ๊ฐ€์งˆ ์ˆ˜ ์žˆ๋‹ค.

  • String.toIntOrNull() ์€ String ์„ Int ๋กœ ์ ์ ˆํ•˜๊ฒŒ ๋ณ€ํ™˜ํ•  ์ˆ˜ ์—†๋Š” ๊ฒฝ์šฐ null ์„ ๋ฆฌํ„ดํ•œ๋‹ค.
  • Iterable<T>.firstOrNull(() -> Boolean) ์€ ์ฃผ์–ด์ง„ ์กฐ๊ฑด์— ๋งž๋Š” ์š”์†Œ๊ฐ€ ์—†๋Š” ๊ฒฝ์šฐ null ์„ ๋ฆฌํ„ดํ•œ๋‹ค.

์ด์ฒ˜๋Ÿผ null ์€ ์ตœ๋Œ€ํ•œ ๋ช…ํ™•ํ•œ ์˜๋ฏธ๋ฅผ ๊ฐ–๋Š” ๊ฒƒ์ด ์ข‹๋‹ค.

null์„ ์•ˆ์ „ํ•˜๊ฒŒ ์ฒ˜๋ฆฌํ•˜๊ธฐ

null ์€ ์•ˆ์ „ํ•˜๊ฒŒ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ• ์ค‘ ๋„๋ฆฌ ์‚ฌ์šฉ๋˜๋Š” ๋ฐฉ๋ฒ•์œผ๋กœ๋Š” ์•ˆ์ „ ํ˜ธ์ถœ (safety-call) ์Šค๋งˆํŠธ ์บ์ŠคํŒ… (smart casting) ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

printer?.print()        // ์•ˆ์ „ ํ˜ธ์ถœ
if (printer != null) 
  printer.print()       // ์Šค๋งˆํŠธ ์บ์ŠคํŒ…

์œ„ ๋‘๊ฐ€์ง€ ๋ชจ๋‘ printer ๊ฐ€ null ์ด ์•„๋‹๋•Œ print ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•œ๋‹ค.

nullable ๋ณ€์ˆ˜๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๋Œ€ํ‘œ์ ์ธ ๋ฐฉ๋ฒ•์œผ๋กœ๋Š” Elvis ์—ฐ์‚ฐ์ž๋ฅผ ์ด์šฉํ•˜์—ฌ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค.

Elvis ์—ฐ์‚ฐ์ž๋Š” ์˜ค๋ฅธ์ชฝ์— return ๋˜๋Š” throw ๋ฅผ ํฌํ•จํ•œ ๋ชจ๋“  ํ‘œํ˜„์‹์ด ํ—ˆ์šฉ๋œ๋‹ค.

val printerName1 = printer?.name ?: "Unnamed"
val printerName2 = printer?.name ?: return
val printerName3 = printer?.name ?: 
  throw Error("Printer must be named")

return ๊ณผ throw ๋ชจ๋‘ Noting(๋ชจ๋“  ํƒ€์ž…์˜ ์„œ๋ธŒํƒ€์ž…) ์„ ๋ฆฌํ„ดํ•˜๊ฒŒ ์„ค๊ณ„๋˜์–ด ๊ฐ€๋Šฅํ•˜๋‹ค. (link)
Subtype of all types:Noting return type is Noting

null ์„ ์ ์ ˆํ•˜๊ฒŒ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•˜์—ฌ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•œ ๋ฐฉ๋ฒ•๋“ค์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

๋ฐฉ์–ด์  ํ”„๋กœ๊ทธ๋ž˜๋ฐ (defensive programming)

  • ๋ชจ๋“  ๊ฐ€๋Šฅ์„ฑ์„ ์˜ฌ๋ฐ”๋ฅธ ๋ฐฉ์‹์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๋„๋ก ๋ฐฉ์–ดํ•˜๋Š” ๋ฐฉ์‹

๊ณต๊ฒฉ์  ํ”„๋กœ๊ทธ๋ž˜๋ฐ (offensive programming)

  • ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ์ƒํ™ฉ์ด ๋ฐœ์ƒํ–ˆ์„ ๋•Œ ๊ฐœ๋ฐœ์ž๊ฐ€ ์ˆ˜์ •ํ•˜๊ฒŒ ์œ ๋„ํ•˜๋Š” ๋ฐฉ์‹

์˜ค๋ฅ˜ throw ํ•˜๊ธฐ

๊ฐœ๋ฐœ์ž๋Š” ์ž‘์„ฑํ•œ ์ฝ”๋“œ๋Š” ์˜ˆ์ƒ ๊ฐ€๋Šฅํ•œ ํ˜•ํƒœ๋กœ ๋™์ž‘๋˜๊ธธ ์›ํ•˜๋ฉฐ, ๋‹ค๋ฅธ ๊ฐœ๋ฐœ์ž๊ฐ€ ๊ทธ ์ฝ”๋“œ๋ฅผ ๋ณด๊ณ  ์„ ์ž…๊ฒฌ์ฒ˜๋Ÿผ '๋‹น์—ฐํžˆ ๊ทธ๋Ÿด๊ฒƒ์ด๋‹ค' ๋ผ๊ณ  ์ƒ๊ฐํ•˜๋Š” ๋ถ€๋ถ„์ด ์žˆ๋‹ค.

๊ทธ ๋ถ€๋ถ„์—์„œ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ๊ฒฝ์šฐ ๊ฐœ๋ฐœ์ž์—๊ฒŒ ์˜ค๋ฅ˜๋ฅผ ๊ฐ•์ œ๋กœ ๋ฐœ์ƒ์‹œ์ผœ ์ฃผ๋Š” ๊ฒƒ์ด ์ข‹๋‹ค.

์˜ค๋ฅ˜๋ฅผ ๊ฐ•์ œ๋กœ ๋ฐœ์ƒ์‹œํ‚ฌ ๋•Œ๋Š” throw, !!, requireNotNull, checkNotNull ๋“ฑ์„ ํ™œ์šฉํ•ด์„œ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ์ด ์ข‹๋‹ค.

not-null assertion(!!) ๊ณผ ๊ด€๋ จ๋œ ๋ฌธ์ œ

nullable ์„ ์ฒ˜๋ฆฌํ•  ๋•Œ ๊ฐ€์žฅ ๊ฐ„๋‹จํ•œ ๋ฐฉ๋ฒ•์€ not-null assertion(!!) ์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ ์ด๋‹ค.

๋งŒ์•ฝ ์ด ๊ฐ’์ด null ์•„๋‹ˆ๋ผ๊ณ  ์ƒ๊ฐํ•˜๊ณ  ๋‹ค๋ฃจ๋ฉด, NPE(Null pointer exception) ์ด ๋ฐœ์ƒํ•œ๋‹ค.

!! ์€ ์‚ฌ์šฉํ•˜๊ธฐ ์‰ฝ์ง€๋งŒ ์ข‹์€ ํ•ด๊ฒฐ๋ฐฉ๋ฒ•์€ ์•„๋‹ˆ๋ฉฐ, ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•  ๋•Œ, ์–ด๋– ํ•œ ์„ค๋ช…๋„ ์—†๋Š” ์ œ๋„ค๋ฆญ ์˜ˆ์™ธ (generic excpetion) ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.

fun largestOf(vararg nums: Int): Int = 
  nums.max()!!
largestOf() // NPE

๋•Œ๋ฌธ์— ์ด๋Ÿฐ ์ž ์žฌ์ ์ธ ์œ„ํ—˜์„ ํ”ผํ•˜๊ธฐ ์œ„ํ•ด !! ์—ฐ์‚ฐ์ž์˜ ์‚ฌ์šฉ์€ ์ง€์–‘ํ•ด์•ผ ํ•œ๋‹ค.

TIP

max ํ•จ์ˆ˜๋Š” kotlin 1.4 ๋ถ€ํ„ฐ deprecate ๋˜๋ฉฐ, kotlin 1.5 ์ด์ƒ๋ถ€ํ„ฐ๋Š” error๋กœ ํ‘œ๊ธฐ๋œ๋‹ค.
์ด๋ฅผ ๋Œ€์‹ ํ•  maxOrNull ์„ ์ง€์›ํ•œ๋‹ค (link)

์˜๋ฏธ ์—†๋Š” nullability ํ”ผํ•˜๊ธฐ

nullability ๋Š” ์ ์ ˆํ•˜๊ฒŒ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด ์ถ”๊ฐ€๋น„์šฉ์ด ๋ฐœ์ƒํ•œ๋‹ค.

๋”ฐ๋ผ์„œ ํ•„์š”ํ•œ ๊ฒฝ์šฐ๊ฐ€ ์•„๋‹ˆ๋ผ๋ฉด, nullability ์ž์ฒด๋ฅผ ํ”ผํ•˜๋Š” ๊ฒƒ์ด ์ข‹๋‹ค.

null ๊ฐ’ ์ž์ฒด์˜ ์–ด๋– ํ•œ ์˜๋ฏธ๋ฅผ ๋ถ€์—ฌํ•ด์„œ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ ์˜๋ฏธ๊ฐ€ ์—†์„ ๊ฒฝ์šฐ์—๋Š” null ์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š”๊ฒƒ์ด ์ข‹๋‹ค.

lateinit ํ”„๋กœํผํ‹ฐ์™€ notNull ๋ธ๋ฆฌ๊ฒŒ์ดํŠธ

lateinit ํ•œ์ •์ž๋Š” ํ”„๋กœํผํ‹ฐ๊ฐ€ ์ดํ›„์— ์„ค์ •๋  ๊ฒƒ์ž„์„ ๋ช…์‹œํ•˜๋Š” ํ•œ์ •์ž์ด๋‹ค.

lateinit ์‚ฌ์šฉ์—๋Š” ๋น„์šฉ์ด ๋ฐœ์ƒํ•˜๋ฉฐ, ์ดˆ๊ธฐํ™” ์ „์— ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.

lateinit ๊ณผ nullable ์„ ๋น„๊ตํ•˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ฐจ์ด์ ์ด ์žˆ๋‹ค.

  • !! ์—ฐ์‚ฐ์ž๋กœ ์–ธํŒฉ(unpack)ํ•˜์ง€ ์•Š์•„๋„ ๋œ๋‹ค.
  • ์–ด๋– ํ•œ ์˜๋ฏธ๋ฅผ ๊ฐ–๋Š” null ์„ ์‚ฌ์šฉํ•˜๊ณ  ์‹ถ์„๋•Œ nullable ๋กœ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค.
  • ํ”„๋กœํผํ‹ฐ๊ฐ€ ์ดˆ๊ธฐํ™”๋œ ์ดํ›„์—๋Š” ์ดˆ๊ธฐํ™”๋˜์ง€ ์•Š๋Š” ์ƒํƒœ๋กœ ๋Œ์•„๊ฐˆ ์ˆ˜ ์—†๋‹ค.

lateinit ์‚ฌ์šฉ์‹œ์—๋Š” ๋ฐ˜๋“œ์‹œ ์ดˆ๊ธฐํ™” ๋ ๊ฑฐ๋ผ๊ณ  ์˜ˆ์ƒํ•œ ๊ฒฝ์šฐ์˜ ์ƒํ™ฉ์—์„œ ์‚ฌ์šฉํ•œ๋‹ค.

JVM ์˜ Int, Long, Double, Boolean ๊ฐ™์ด ๊ธฐ๋ณธํƒ€์ž…๊ณผ ์—ฐ๊ฒฐ๋œ ํƒ€์ž…์€ lateinit ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋‹ค.

์ด๋Ÿฌํ•œ ๊ฒฝ์šฐ์—๋Š” lateinit ๋ณด๋‹ค ๋Š๋ฆฌ์ง€๋งŒ Delegates.notNull ์„ ์‚ฌ์šฉํ•œ๋‹ค.

class DoctorActivity: Activity() {
  private var doctorId: Int by arg(DOCTOR_ID_ARG)
  private var fromNotification: Boolean by arg(FROM_NOTIFICATION_ARG)
}

Item 9 use ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฆฌ์†Œ์Šค๋ฅผ ๋‹ซ์•„๋ผ

๋” ์ด์ƒ ๋ฆฌ์†Œ์Šค๊ฐ€ ํ•„์š”ํ•˜์ง€ ์•Š์„๋•Œ close ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•ด์„œ ๋ช…์‹œ์ ์œผ๋กœ ๋‹ซ์•„์•ผ ํ•˜๋Š” ๋ฆฌ์†Œ์Šค์— ์‚ฌ์šฉ

Kotlin/JVM ์—์„œ ์‚ฌ์šฉํ•˜๋Š” ์ž๋ฐ” ํ‘œ์ค€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์—์„œ ํ•ด๋‹นํ•˜๋Š” ๋ฆฌ์†Œ์Šค๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

์œ„ ๋ฆฌ์†Œ์Šค๋“ค์€ AutoCloseable ์„ ์ƒ์†๋ฐ›๋Š” Closeable ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ (implement) ํ•˜๊ณ  ์žˆ๋‹ค.

๋ชจ๋“  ๋ฆฌ์†Œ์Šค๋“ค์€ ์ตœ์ข…์ ์œผ๋กœ ๋ฆฌ์†Œ์Šค์— ๋Œ€ํ•œ ๋ ˆํผ๋Ÿฐ์Šค๊ฐ€ ์—†์–ด์งˆ ๋•Œ, GC๊ฐ€ ์ฒ˜๋ฆฌํ•œ๋‹ค.

ํ•˜์ง€๋งŒ ์ฒ˜๋ฆฌ๊ฐ€ ์™„๋ฃŒ๋  ๋•Œ๊นŒ์ง€์˜ ๋งค์šฐ ๋งŽ์€ ๋ฆฌ์†Œ์Šค์˜ ๋น„์šฉ๊ณผ ๊ธด ์ฒ˜๋ฆฌ์‹œ๊ฐ„์„ ์š”ํ•œ๋‹ค.

๋”ฐ๋ผ์„œ ๋ช…์‹œ์ ์œผ๋กœ close ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•ด์ฃผ๋Š” ๊ฒƒ์ด ์ข‹๋‹ค.

Java ์—์„œ๋Š” ์ „ํ†ต์ ์œผ๋กœ try-finally ๋ธ”๋Ÿญ์„ ์‚ฌ์šฉํ•˜์—ฌ ์ฒ˜๋ฆฌ

fun countCharactersInFile(path: String): Int {
  val reader = BufferedReader(FileReader(path))
  try {
    return reader.lineSequence().sumBy { it.length }
  } finally {
    reader.close()
  }
}

JDK7 ๋ถ€ํ„ฐ๋Š” try-with-resource ์ง€์›

int countCharactersInFile(String path) throws IOException {
  try (
    BufferedReader reader = BufferedReader(FileReader(path))
  ) {
    return reader.lineSequence().sumBy { it.length }
  }
}

Closeable ๊ฐ์ฒด์— use ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

fun countCharactersInFile(path: String): Int {
  val reader = BufferedReader(FileReader(path))
  render.use {
    return render.lineSequence().sumBy { it.length }
  }
}

๋žŒ๋‹ค ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ๋ฆฌ์‹œ๋ฒ„๊ฐ€ ์ „๋‹ฌ๋˜๋Š” ํ˜•ํƒœ๋„ ์žˆ์œผ๋ฏ€๋กœ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ค„์—ฌ์„œ ์‚ฌ์šฉ๋„ ๊ฐ€๋Šฅํ•˜๋‹ค.

fun countCharactersInFile(path: String): Int {
  BufferedReader(FileReader(path)).use { reader ->
    return render.lineSequence().sumBy { it.length }
  }
}

ํŒŒ์ผ ๋ฆฌ์†Œ์Šค๋ฅผ ํ•œ์ค„์”ฉ ์ฝ์–ด์„œ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒฝ์šฐ์—๋Š” useLine ํ•จ์ˆ˜๋ฅผ ์ด์šฉํ•˜์ž

fun countCharactersInFile(path: String): Int {
  File(path).useLines { lines ->
    lines.sumBy { it.length }
  }
}

์œ„์™€ ๊ฐ™์ด ์ฒ˜๋ฆฌํ•˜๋ฉด ๋ฉ”๋ชจ๋ฆฌ์— ํŒŒ์ผ์˜ ๋‚ด์šฉ์„ ํ•œ์ค„์”ฉ๋งŒ ์œ ์ง€ํ•˜๋ฏ€๋กœ, ๋Œ€์šฉ๋Ÿ‰ ํŒŒ์ผ๋„ ์ ์ ˆํ•˜๊ฒŒ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค.

์ •๋ฆฌ

use ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด Closeable, AutoCloseable ์„ ๊ตฌํ˜„ํ•œ ๊ฐ์ฒด๋ฅผ ์‰ฝ๊ณ  ์•ˆ์ „ํ•˜๊ฒŒ ์ฒ˜๋ฆฌ ๊ฐ€๋Šฅํ•˜๋‹ค.

Item 10 ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋ฅผ ๋งŒ๋“ค์–ด๋ผ

๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋Š” ๊ฐœ๋ฐœ์ž๊ฐ€ ๋งŒ๋“ค๊ณ  ์žˆ๋Š” ์š”์†Œ๊ฐ€ ์ œ๋Œ€๋กœ ๋™์ž‘ํ•˜๋Š”์ง€ ๋น ๋ฅด๊ฒŒ ํ”ผ๋“œ๋ฐฑ ๊ฐ€๋Šฅํ•˜๋ฏ€๋กœ ๊ฐœ๋ฐœํ•˜๋Š”๋™์•ˆ ํฐ ๋„์›€์ด ๋œ๋‹ค.

  • ์žฅ์ 
    • ํ…Œ์ŠคํŠธ๊ฐ€ ์ž˜ ๋œ ์š”์†Œ๋Š” ์‹ ๋ขฐ ๊ฐ€๋Šฅํ•˜๋‹ค.
    • ํ…Œ์ŠคํŠธ๊ฐ€ ์ž˜ ๋งŒ๋“ค์–ด์ ธ ์žˆ์œผ๋ฉด ๋ฆฌํŽ™ํ† ๋ง ํ•˜๊ธฐ ์‰ฝ๋‹ค.
    • ์ˆ˜๋™์œผ๋กœ ํ…Œ์ŠคํŠธํ•˜๋Š” ๊ฒƒ ๋ณด๋‹ค ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋กœ ํ™•์ธํ•˜๋Š” ๊ฒƒ์ด ๋น ๋ฅด๋‹ค.
  • ๋‹จ์ 
    • ํ…Œ์ŠคํŠธ ์ž‘์„ฑ์‹œ ์‹œ๊ฐ„์ด ๊ฑธ๋ฆฐ๋‹ค.
    • ํ…Œ์ŠคํŠธ ํ™œ์šฉ์ด ์šฉ์ดํ•˜๊ฒŒ ์ฝ”๋“œ๋ฅผ ์กฐ์ •ํ•ด์•ผ ํ•œ๋‹ค.
    • ์ข‹์€ ํ…Œ์ŠคํŠธ๋ฅผ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด ๋งŽ์€ ๋…ธ๋ ฅ์„ ๊ธฐ์šธ์—ฌ์•ผ ํ•œ๋‹ค.

๋‹จ์œ„ ํ…Œ์ŠคํŠธ๊ฐ€ ํ•„์š”ํ•œ ์ฝ”๋“œ์˜ ์ข…๋ฃŒ

  • ๋ณต์žกํ•œ ์ฝ”๋“œ ๋ญ‰์น˜
  • ์ˆ˜์ •์ด ๋นˆ๋ฒˆํ•˜๊ณ  ๋ฆฌํŽ™ํ† ๋ง์ด ๋นˆ๋ฒˆํ•˜๊ฒŒ ์ผ์–ด๋‚  ์ˆ˜ ์žˆ๋Š” ๋ถ€๋ถ„
  • ๋น„์ง€๋‹ˆ์Šค ๋กœ์ง
  • ๊ณต์šฉ API ๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ์ ‘ํ•ฉ๋ถ€
  • ๋งŽ์€ ๋ฌธ์ œ๋ฅผ ์ž ์žฌ์ ์œผ๋กœ ๋‚ดํฌํ•˜๊ณ  ์žˆ๋Š” ์ฝ”๋“œ
  • ์ˆ˜์ •์ด ํ•„์š”ํ•œ ํ”„๋กœ๋•์…˜ ๋ฒ„๊ทธ

์ •๋ฆฌ

๋น„์ง€๋‹ˆ์Šค ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ๋Š” ์ตœ์†Œํ•œ ๋ช‡๊ฐœ์˜ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๊ฐ€ ๊ผญ ํ•„์š”ํ•˜๋‹ค.