Kotlin Tips : Intentに必要な値が入っていない場合例外をスローしたい
実際にKotlinを使っているなかで思いついたことなどをこまめにメモると良い気がしたのでなるべく書いていく
Before : デフォルト値を使ってチェックする
以下のようにActivityでintなどのプリミティブな値を受け取るとする。値はrequiredでセットされなかった場合は例外をスローすることで開発者に対して間違った利用方法であることを知らせたい。
class DetailActivity : AppCompatActivity() { companion object { fun createIntent(context: Context, id: Int): Intent = Intent(context, DetailActivity::class.java).apply { putExtra("id", id) } } var id: Int = -1 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) id = intent.getIntExtra("id", -1) if (id == -1) { throw IllegalArgumentException("Should set id. $id") } // do something } }
このコードには以下の問題点がある
- プロパティにvarを用い、デフォルト値を設定している
- プリミティブな型はlateinitが使えない
Intent.getIntExtra(key, defaultValue)
で必ずデフォルト値が必要なため仕方なく-1
を使っている-1
の意図が明快ではない- プロパティの宣言、
Intent.getIntExtra(key, defaultValue)
で-1
を用いているがここからextraにidを設定せずに起動した場合に開発者に間違った利用方法であることを知らせるために例外をスローしたい
という意図を読み取るのは難しい。仮にINVALID_ID=1
という定数を作ったとしてもvalidationしたいのかどうかを判別するにはIllegalArgumentExceptionのスローの意図を考える必要がある。
- プロパティの宣言、
After : Intent.getIntOrThrow()を生やしlazyを用いる
まずIntent.getIntExtra(key, defaultValue)
を改善する。拡張関数を用いてkeyが存在しない場合に例外をスローする関数を追加する。
extensions.kt
fun Intent.getIntOrThrow(key: String): Int = this.extras.get(key).let { if (it !is Int) { throw IllegalArgumentException("Extras don't have Int value specified by key $key") } return it }
次にプロパティにlazyを用いてIntent.getIntOrThrow()
を呼び出す。
class DetailActivity : AppCompatActivity() { companion object { fun createIntent(context: Context, id: Int): Intent = Intent(context, DetailActivity::class.java).apply { putExtra("id", id) } } val id: Int by lazy { intent.getIntOrThrow("id") } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setup(id) } }
これにより以下のことができた。
- 不必要なデフォルト値を除去できた
- Intent.getIntOrThrow()によりrequiredな値である事を示せた
- lazyによりプロパティをvarからvalにできた
さらなる議論
val id: Int by lazy { intent.getIntOrThrow("id") }
はbeforeに比べて明らかに意図が明快になったが十分かどうかについてはまだ議論ができそうだ。たとえばより明快にIntent.getRequiredInt(key:String)
という名前にしても良いかもしれない。対となるIntent.getOptionalInt(key:String)
といった名前の関数を用意するとより意味のあるコードになるかもしれない。
val id: Int by lazy { intent.getRequiredInt("id") } val name: String? by lazy { intent.getOptionalString("name") }