Java もなかなか良い言語だと思いますが、 Android だとやはり Kotlin のほうが優勢な気がしている AIR Design for Apps 事業部 Android エンジニアの松下です。
最近 Kotlin でのエラーハンドリングについて色々考えているので、それについてつらつらと書いてみようと思います。
Kotlin のエラーハンドリングについて
まず、 Kotlin には Java と違って検査例外が存在しません。おさらいですが、検査例外とは以下のようなものです。
// java class HogeException extends Exception {} class Hoge { public static void hoge() throws HogeException { throw new HogeException(); } public static void main(String[] args) { new Hoge().hoge(); // error: unreported exception HogeException; must be caught or declared to be thrown } }
try-catch
するか、 throws
で更に上に投げるかをコンパイル時に精査して、していない場合はコンパイルエラーにします。
この機能の使われ方の是非はともかくとして、 まあそれなりにフールプルーフっぽい機能です。
ご存知の通り、AltJava である Kotlin や Scala にはこの機能は存在しません。なので、以下のようなコードを書くと実行時エラーとなります。
// kotlin class HogeException: Exception() fun hoge() { throw HogeException() } fun main(args: Array<String>) { hoge() }
実行時エラーを防ごうとすると、エラーを投げられていないか?というのを目視で確認しなければなりません。まあこの程度の規模なら全然問題はないのですが、 interface
などで包まれていると、その interface
を実装しているクラスを見て回らないといけなかったり、大規模になるとそれだけで結構時間が持っていかれてしまいます。
やっぱり、実行時エラーよりコンパイルエラーのほうが気持ち的には楽です。特定の操作時のみエラーで落ちるとか…嫌じゃないですか。
Either とは
(2つのうちの)どちらか一方の
Weblio より引用
https://ejje.weblio.jp/content/either
という意味のようです。
Either
は関数型プログラミング言語でモナドとかいうなにかの一部のようです。まあこのあたりは今回は説明しません。できません。*1
Scala だと標準ライブラリに入っています。 Kotlin だと Arrow のような関数型プログラミング支援ライブラリを使うことになると思いますが、 Either だけ使いたいならこれはちょっと大きすぎるような気がしているので、考え方だけ持ち帰って貰えれば大丈夫です。
関数型プログラミング言語における Either
も、先述の通り、 2 つのうちどちらかを返すものです。
(以下、 Scala のコードが混じりますが、 Kotlin のコードも乗せておきます。)
// scala
Either[HogeException, Int]
// kotlin Either<HogeException, Int>
であれば、 HogeException
と Int
のどちらかを表す型ということになります。
具体的には以下のように使います。
// scala val a: Either[HogeException, Int] = Right(123) val b: Either[HogeException, Int] = Left(new HogeException())
// kotlin val a: Either<HogeException, Int> = Right(123) val b: Either<HogeException, Int> = Left(HogeException())
Either
では右が正しいものとする慣習があるようなので、正常系を右、異常系を左とすると混乱しないと思います。*2
値を取り出すときはダウンキャストなどをして取り出します。
// scala val a: Either[HogeException, Int] = Right(123) a match { case Right(i) => println(i) case Left(e) => println(e) }
// kotlin val a: Either<HogeException, Int> = Right(123) when(a) { is Right -> println(a.value) is Left -> println(a.value) }
このように、値を取り出す前にはエラーハンドリングを型で半強制できるというのが Either
の強みです。(もちろん、 case Left(e) => throw e
とかしたら意味ないですよ)
(他にも、 flatMap
とかで処理をつなげたりできますが、ここでは省略します。)
でも、 try-catch でよくない?
もちろん、 try-catch
もサクッと使えて良いと思います。
しかし、どれを catch
すればよいのか、そもそもどの処理を catch
する必要があるのか、というのに悩んではいませんか。
結局めんどくさくて、 catch(e: Exception)
みたいになってしまっていませんか。
そう、Kotlin の try-catch
は型安全ではないのです。どのエラーが発生しうるか、コンパイル時にはわかりません。
一方 Either でのエラーハンドリングは型安全です。どのエラーが発生しうるかを型で表現することができます。
静的型付け言語の強みを活かして、安全かつ高速に開発を進めたいと思っているのであれば、少しだけ、勉強してみると良いかもしれません。
Result について
そういえば Kotlin 1.5 から Result が戻り値に指定できるようになりました。
Either
と違って、異常系の型を指定することはできません( Throwable
で固定)が、エラーが発生しうるというのを呼び出し側に伝える用途としては十分使えると思います。
ライブラリを導入するよりは、だいぶハードルが低いはずなので、興味があればこれも触ってみると良いと思います。
最後に
弊社ではいっしょに働く Android エンジニア、iOS エンジニアを募集しています!
ご興味のある方はぜひご応募いただけますと嬉しいです。
Androidエンジニアの方はこちらから: www.wantedly.com
iOSエンジニアの方はこちらから: www.wantedly.com
腕に覚えのある方はリードエンジニアの求人もありますので是非ご検討ください。Android、iOSの両方の開発ができるエンジニアも大歓迎です! www.wantedly.com