Kotlinの話-Elvis演算子
kotlin使ってコードを書いていると、ちょこちょこ思う事はあるのだが、特にどこにも書かずに時間が過ぎてしまいがち。 もうちょっとkotlin使って書いている時に思った事とかそういうのを皆が書いたのを読みたいなぁ、と思ったので、まず自分から書く。
先日、こんなコードのPRが来た。
override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
val view: View
if (convertView == null) {
view = inflater.inflate(R.layout.list_markdown, parent, false)
} else {
view = convertView
}
おぉ、valなのに未初期化な宣言でもちゃんと動くのか、kotlinコンパイラは賢いな、とか思ったが、 読む側も賢く無いといけないのでちょっと良く無い。
型を自分で書くのもどうかな、と思うので、if自体を右辺値にする方が、未初期化じゃないのが明示的になって良いと思う。
val view = if (convertView == null) {
inflater.inflate(R.layout.list_markdown, parent, false)
} else {
convertView
}
こういう右辺値のifやwhenは最初のうちは読みにくいが、慣れてくると頭の中で考えなきゃいけない事はこちらの方が少なくなるので断然読みやすい。 このコードなら右辺値はviewの値を返しているだけだろう、というのが読まなくても分かる(中でこっそり変な事してない限り)。 ifは万能すぎてなんでも出来てしまうので、右辺値にする事で用途を限定している、というメッセージになる。
ただ、kotlinではだいたいnullと比較するコードは避けるのが望ましい。特殊な場合はあるけれど。
この場合、kotlinにはelvis演算子というのがある。
公式ドキュメントのNull Safetyの所に記述がある、?:
という奴。
公式ドキュメントだと以下のようなif文が
val l: Int = if (b != null) b.length else -1
以下のように直せる、という例がある。
val l = b?.length ?: -1
?:
の前の値がnullじゃなかったらその値が、nullだったらその次の式が返される、という挙動。
なお、この?:
の後にはreturnとかthrownとかも書ける。今回の話とは関係無いが、これまた慣れるまで気持ち悪いが慣れたら積極的に使いたい文法。
先ほどのコードを直すとこうなる。
val view = convertView ?: inflater.inflate(R.layout.list_markdown, parent, false)
最初のうちはただ読みにくい暗号的でifの方が読みやすいんじゃないか?という気もするが、 慣れてくるとelvisで書ける物はelvisで書く方が良い。
まず、elvis版ではconvertViewが一回しか出てこない。
しかもnullかどうかの比較は、?
で明白。
というのはlet構文でも?
なので、このクエスチョンマークで前の値がnullだったら、
みたいな意味で統一感があり、使っているとそう感じられるようになってくるように言語が作られている。
基本的には、用途限定された機能を使った方が、その機能に慣れていれば読むのは容易だ。 一方で、細かな特殊用途用の構文や機能がたくさんになると、全部の機能に慣れるのが大変になり、 単にプログラムが暗号的になってしまうだけで、読みやすくは無い。 この辺は言語デザイナーのバランス感覚が出る所やね。 自然言語で言う所のMDL(minimum description length)原則という奴だ。
kotlinだと、長い関数の呼び出しのネストがあった時に、上の方から下の方までずっとnullableが渡ってくるのは普通はおかしい。 だいたいは入口あたりでnullな場合の処理が行われて、ほとんどの場所ではnullじゃないvalがずっと渡ってくる。 これはそういう言語デザインだと思う。
nullかどうかを曖昧にしておいて問題になる所でチェックするのではなく、入口の所でチェックする。
nullな値は結構いろいろな所で出てくるので、それを毎回きっちり処理する為には、
nullな場合とそうでない場合の処理を簡潔に書ける必要がある。
elvis演算子や?.let{}
などはそうした目的の為に導入された構文で、
だからこれらを積極的に使ってnullableを入口の所で退治するのがkotlin的なんじゃないか。