Swiftでas!と書く場合のガイドライン

as!って言うのは、要するにダウンキャストできない場合にプログラムを終了させるものである。

if let x = expr as? SomeClass {
  f(x)
} else {
  fatalError()
}

と書くのであれば、

f(expr as! SomeClass)

と、同じことなので、as!で書いた方が良い。その方がタイプ数が減るし、コードの見た目も簡潔になって理解しやすくなる。長いコードはそれだけで苦痛だし、なにか間違ったプログラミングをしていることを示すシグナルにもなる。ダウンキャスト失敗の場合に終了するためだけにifを書くことは、全体的なコードの乱雑さを増してしまい、本当の問題の見落としに繋がる。

ただし、as?とかas!とかダウンキャストをしてる時点で、そのプログラムには潜在的な問題があると考えることもできて、本当にダウンキャストが必要なのか3回くらい考えた方が良い。

as!とObjective Cのキャストの違い

  • Objective Cのキャストは常に成功する
  • as!は失敗してエラーになることがある

この違いをまず頭に入れよう。次のObjective Cプログラムはエラーにならずに実行できる。

NSArray *x = (NSArray *)@"Hello World";

キャストは、コンパイルエラーにならないし実行時エラーにもならない。NSArrayにあってNSStringにないメソッドを呼び出すと実行時エラーになるが、キャストそのものは失敗しない。この性質は、3年に1回くらいは便利なこともあるが、大体は不便である。不正にキャストしたxが、ずっと先の全然ちがうところで実行時エラーを起こしたりするので、間違いを探すのに苦労することになる。

一方、次のSwiftプログラムはコンパイルはできるが実行するとエラーになる。

let x = "Hello World" as! Array<String>

Swiftは、不正なキャストに失敗してエラーを発生させる能力を獲得したのだ*1。これは、Objective Cに比べるとかなり改善されていて、不正なキャストからずっと先の全然違うところで実行時エラーになってデバッグに苦労することがなくなる。

この点で、Objective CのキャストとSwiftのキャストはかなり性質が違うものである。isKindOfClass:でテストせずにキャストするObjective Cのコードはかなり書かないほうが良いが、as!するSwiftのコードはかなりマシであると言える。

さらに、キャストに失敗したときに何が起きるかは決まっている。あなたのプログラムが終了するだけである。iOSに影響が及ぶことはないし(あったとしたらそれはOSの問題だ)、iPhoneが壊れることもないし、鼻から悪魔が出てくることもない。(ただし、アプリケーションやユーザーの性質によっては、それでも強制終了しない方が良い場合はあるとは思う。)

as! した方が良い場合

ダウンキャストに失敗した場合にそこから回復する手段がないとき、つまり直ちにプログラム終了するしかない場合はas!してしまう方が良い。「キャストに失敗したらプログラム終了」というのがas!の意味なので、わざわざ冗長にifを書く必要はどこにもない。

as?した方が良い場合

ダウンキャストに失敗しても処理が続けられる場合は、as!してはいけない。例えば、View Controllerに書かれているような、ユーザーとのインタラクションを担うようなコードの場合は、ダウンキャストの失敗というなにかプログラム実行の大前提が崩れるような事態にも、適切なエラー回復の手段を提供できる可能性がある。

どのくらい頑張ってエラー回復するかというのは、アプリケーションやユーザーの性質に関する問題なのでなんとも言えない。

コードレビューをしていてas!を見つけたら

本当にプログラムを終了する以外に回復の方法がないのか、議論して良いし、議論するべきである。適切な回復の方法があるのであれば、そちらに直す。また、as!as?もしないことについても検討する。標準ライブラリとかObjective Cとの相互運用を除けば、「enumにしてswitchする」「protocolを追加して分岐しなくて良いようにする」などの方法がある。

Lintとどうつきあうか

個人的には、as!に警告するルールはオフにするのがお勧めだけど(僕はLint大嫌いなので、多分これは少し極端な見解だろうとは思う)。まー現実的には、堂々と

// swiftlint:disable:next force_cast

と書くのが良いだろう。これを見たレビュアーは強制終了するほかにエラー回復の方法がないのかあなたに説明を求めるだろうし、あなたは説明できなくてはいけない。

*1:ちなみに、不正なキャストに失敗する能力は、JavaC++などのCやObjective Cよりも新しい言語は、だいたい1990年代に獲得しているものである。2016年にもなって失敗する能力が欠けているObjective Cが異端であるとも言える。