Kotlinに流れ着いたエンジニアがハマりがちなもの

Happy Holidays!!

24日目は食べログapp開発チームの@fdashです。よろしくお願いいたします。


はじめに

食べログではWeb開発(Ruby)→iOS開発(Obj-c/Swift)と経験して、現在はAndroid開発(Java/Kotlin)をメインでやっています。

KotlinはSwiftやRubyと近い書き味ですが、それゆえ微妙な差が落とし穴になりやすいです。

今回は、Swift/Rubyと同じような感覚で書くとハマりがちなもの(実際僕がよくハマるもの)をご紹介します。


検証環境

言語
バージョン

Kotlin
1.3.11

Swift
4.2.1

Ruby
2.5.3


Iterable#firstが例外を投げる

APIのレスポンスをViewModelに変換する時にめちゃくちゃハマったやつ。

Kotlin

val hoge = listOf(1, 2, 3)

hoge.first{ it == 4 } // => NoSuchElementException!!
hoge.firstOrNull { it == 4 } // => null 本当はこっちを使わなければいけなかった

https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/first.html

https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/first-or-null.html

一致する要素が見つからなければnullだろうと思っていたら、まさかの例外を投げられます。

Swift

[1, 2, 3].first{ $0 == 4 } // => nil

Ruby

(書いてて気づいたが#firstじゃなかった)

[1, 2, 3].find{|e| e == 4} # => nil

Swift/Rubyはnilを返します。

Kotlinで該当する要素が無ければ何も表示しなくていいやという軽い気持ちでで#firstを使うと速攻クラッシュします。

表示がおかしいとかではなくて落ちます。


Any?#toString"null"という文字列を返す

オブジェクトを文字列化してtextViewにセットする時によく間違える。

Kotlin

var hoge: Any? = null

hoge.toString() // => "null" まさかの文字列!!
hoge?.toString() // => null 期待する動作はこっち
hoge?.toString() ?: "" // => "" もしくはこっち

https://github.com/JetBrains/kotlin/blob/v1.3.11/core/builtins/native/kotlin/Library.kt#L25 (ドキュメントが見つからなかったので定義を張っておきます)

Ruby

nil.to_s # => ""

Rubyは空文字を返してくれます。

つい軽い気持ちでtoStringしがちですが、アプリ上に"null"というTextViewが現れるとすごく悲しい気持ちになります。

ちなみに、Androidアプリ開発においてnull"null"という文字列に変換したいケースが全く思いつかないため、見つけたら多分バグじゃないかな。


#letは必ず評価される。

Swiftのif letと似ているのでうっかりしがちですが、Kotlinだとあくまでもletは関数です。

Kotlin

var hoge: Int? = null

hoge.let {
// このブロック内は必ず評価される。hogeがnullであっても。
// itとhogeはInt?のまま
}

// 期待していたのはこっち(もしくは普通にif式でどうぞ)
hoge?.let {
// hogeがNonNullの場合のみこのブロック内が評価される
// SmartCastされるのでitとhogeはIntとして扱える
}

Kotlinだとif式でもSmartCastが効くので、↑のケースだと素朴にif式で書けばよいと思います。

軽い気持ちでletしてはいけない。

https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/let.html

Swift

var hoge: Int? = nil

if let hoge = hoge {
// hogeが非Optionalの時だけ評価される
}

Swiftはあくまでもifとの組み合わせ

Ruby

hoge.yield_self do

# 必ず評価される
end

hoge&.yield_self do
# hogeがnilなら評価されない
end

あまり使わないかもしれませんが、RubyのKernel#yield_selfもKotlinと同じ挙動です。

あと、明日リリースされるであろうRuby2.6ではKernel#thenというエイリアスが追加されるみたいです :tada:

https://docs.ruby-lang.org/ja/latest/method/Object/i/yield_self.html

https://bugs.ruby-lang.org/issues/14594


細かいシンタックス違いシリーズ(Kotlinだけ異なるパターン)

シンタックスエラーになるので困りはしないが、つい時間を無駄にするやつたちをまとめてご紹介。


ifの括弧

Kotlin: if (hoge) // ( )必須

Swift: if hoge

Ruby: if hoge

これが一番時間を無駄にするやつ。


null/nil

Kotlin: null

Swift: nil

Ruby: nil

Unresolved reference: nilと表示されるまで気づかない。


this/self

Kotlin: this

Swift: self

Ruby: self

これもまぁ。。


名前付き引数

Kotlin: hogeFunc(hoge = 123, fuga = "abc")

Swift: hogeFunc(hoge: 123, fuga: "abc")

Ruby: hoge_func(hoge: 123, fuga: "abc")

まだ慣れない。


List/Array

Kotlin: listOf(1, 2, 3) / mutableListOf(1, 2, 3)

Swift: [1, 2, 3]

Ruby: [1, 2, 3]

Mutable/Immutableがあるから仕方ないけれど。


Map/Dictionary

Kotlin: mapOf(1 to "hoge", 2 to "fuga")

Swift: [1: "hoge", 2: "fuga"]

Ruby: {1: "hoge", 2: "fuga"}

独特。。。


分かっていても間違えてしまうときの対策

僕はどの言語も基本的にJetBrains製IDEを使っていて操作感が変わらないため、しばしば自分が今どの言語を書いているのかわからなくなります。。。

その対策としては、IDEごとにThemeを変えて自分が今どの言語を書いているか把握しやすくしています。

Android Studio
AppCode
RubyMine

Material Oceanic
(Material Theme UIプラグインを利用)
Light
(標準の白テーマ)
Darcula
(標準のダークテーマ)

androidstudio.png
appcode.png
rubymine.png

これで多少は凡ミスが減った(気がします)。


大事なこと


  • 他の言語と似ているものほど気をつける

  • 自分が今どの言語を書いているのか意識出来る環境にしておく

  • 軽い気持ちでコードを書かない

現場からは以上です。

明日、2018年のAdventCalendarを締めくくるのは、@kamina_zzzさんによる「Ruby のメモリについて」です。お楽しみに!