daisuzz.log

Kotlinを上手に書くためにEffective Kotlinを読む

最近、Effective Kotlinを購入しました。 

Effective Kotlin by Marcin Moskala [Leanpub PDF/iPad/Kindle]

読んでいると、Kotlinの適切な書き方がわかりやすくまとめられていたので備忘録として書いていきます

mutableを使う箇所を制限する

mutableは状態管理や並行処理などの観点からなるべく使わないようにしましょうというお話です。(もちろんmutableを使わざるを得ないケースもあると思いますが)

mutableをなるべく減らすためにできることをあげていきます。

var よりも valを使う

// listの値自体を変更できてしまう
var list1 = listOf<Int>()
list1 = listOf<Int>(1,2,3)

// setterがないので変更できない
val list2 = listOf<Int>()
list2 = listOf<Int>(1, 2, 3) // compile error

mutable collectionよりもread only collectionを使う

// mutableListなので値を追加できる
val list1 = mutableListOf<Int>()
list1.add(1)

// read onlyなので値を追加できない
val list2 = listOf<Int>()
list2.add() // compile error 

down castすると、read onlyなcollectionでもmutableな操作を呼ぶことはできますが、実行時にUnsupportedOperationExceptionが発生します。めったにdown castすることはないと思いますが気をつけましょう。 (ちなみにimmutable collectionはproposalとして実装されています。 kotlinx.collections.immutable/README.md at master · Kotlin/kotlinx.collections.immutable · GitHub)

状態を変更したい場合はcopy()メソッドを使う

// mutableの場合、色々なところで変更されたageを参照してしまう。
class Person(private var age: Int)

val person1 = Person(20)
person.age = 30

// copy()メソッドを使う
data class Person(private val age: Int)

val person2 = Person(20)
val person3 = person2.copy(age = 30)

mutableなものを公開しない

// mutableをそのまま返すと、呼び出し元が自由にPersonインスタンスのphoneListを変更できてしまう
class Person(private val phoneList: MutableList<String>){
  fun getPhoneList(): MutableList<String>{
    return phoneList
  }
}

// MutableList<String>をList<String>にupcastしてread onlyにする
class Person(private val phoneList: MutableList<String>){
  fun getPhoneList(): List<String>{
    return phoneList
  }
}

公開する値には型推論を使わないようにする

型推論によって冗長な記述を減らすことができる一方で、修正によって思わぬ型が代入されてしまうことがあるので、公開する値には型推論を使わないようにする。

APIとして公開する値に型推論をされた値をそのまま使ってしまうと、コードの修正で型が変更されてしまってもそのことに気づかずに影響を与えてしまうかもしれません。

// :Phoneを削除すると、getPhoneの戻り値の型がAndroidになってしまう
class Person {
  
  val phone: Phone = Android()

  fun getPhone() = phone
}

val person = Person()
val phone = person.getPhone() // Android

// 公開する値に対しては型推論を行わないようにする
class Person {

  val phone: Phone = Android()

  fun getPhone(): Phone = phone
}

val person = Person()
val phone = person.getPhone() // Phone

型推論の良い点がよく注目を浴びていますが、こういったデメリットがあることを理解して使う場所を考える必要があります。

Collection APIの操作の回数を制限する

Collection APIは便利な機能がたくさん用意されていますが、内部で、コレクションを生成していたり、コレクションに対して操作をしているので、パフォーマンスに気をつけようというお話。Collection APIでチェーンして操作する回数を減らすことで、無駄なコレクション操作によるパフォーマンスの低下を避けることができます。

val list = listOf<Int?>(1,2,3,4,5,null)

// 無駄な回数collection apiを呼び出している
val listWithPrefix = list.filter{it != 1}.filter{it != 2}.filterNotNull()

// 条件を1つにまとめて、collection apiの呼び出し回数を減らす
val listWithPrefix = list.filter{it != 1 && it != 2 && it != null}