ポエム
Swift

安易な気持ちで `as? Int ?? 0` みたいなコードを書いてはいけません(戒め

※タイトル通り、「安易な」気持ちで書いてはいけないのであって、「書くな」ではありません。

Swift は型制約が非常に強く、そして意図しなかった nil によるクラッシュの回避のために Optional というものも導入されていることは、Swift 開発してるみなさんはご存じでしょう。

そして、開発している際に、なるべく nil を回避したいので、Optional をなるべく使わないようにするのもよくあるでしょう。

それゆえ、どうしても Optional のアンラップをしなくてはならないことがあります。例えばサーバから受け取ったデータのキャストなど、プログラム内部で完結していないデータを処理する時。その場合、サーバからデータを受け取ったら、必要に応じて型変換したりする必要が出てきますね。

では、とりあえず何も考えずに as? Int ?? 0 と書けばいいのですか?

サーバが本当に Int のデータをそのまま返してくる保証はどこにあるのか?

Obj-C の時代は、.intValue とか書いとけば、ランタイムがいろいろよしなりにやってくれますが、Swift はそううまくいきません。String のデータで返してくるかもしれないし、もしかすると小数で返して来るかもしれません。

例えば小数で返ってきたとします。そんな時は当然 as? Int でキャストしようとしても nil になってしまいます。でもとりあえず何かしら返ってきてます。それなのにデフォルト値の ?? 0 の方がセットされてしまいます。

バグです。

! は使いたくない。その気持ちはわかります。なんか危険な匂いするし、もし本当に万が一サーバが何かしらのバグが発生して本当に何も返ってこなかったり数字じゃないものが返ってきた時、アプリ側の問題じゃないのに落ちて評判下がるし。

でも落ち着いて考えてほしい。あなたが「この方がサーババグってもアプリ落ちないし安全w」とかいう安易な気持ちで書いてしまった ?? 0 が、逆にバグの発覚を遅らせてるだけでリリース前に気づけたはずのバグをリリースに持ち込んでしまう危険性があるのです。

じゃあどうすればいいか

Debug ビルドだけ落とすコードを書くのです。そうすればもしアプリ側に問題があれば Debug ビルドですぐに気づくし、仮にもし万が一本当にサーバがバグったとしても一般ユーザが使っている Release ビルドに致命的なクラッシュを食らわずに済むのです。

例えばこんな感じです:

as? Int ?? {
    assertionFailure("Int に落とし込めませんでした")
    return 0
}()

assertionFailure(String) を呼び出すことで、Debug ビルドだけここで落ちてしまいますが、Release ビルドはこのコードコンパイルされないのでスルーされます。

もちろん、as? Int だけではありません、他の大体の nil じゃないものがくるはずだけど、コード上どうしても Optional の戻り値を返すメソッドを使うしかない場合でもこれは有効です。具体的に言うと、アプリ開発者としてこの条件は保証されているはずだと外部(主にサーバサイド)から伝えられているけど、アプリ側としてはそうじゃなかった時にどうしようもない、でもどうにか Release ビルドのクラッシュだけは避けたいし避ける術もある時はこの手が使えます。

さらに踏み込んだエラー処理の話は是非 Koher さんの Swiftのエラー4分類が素晴らしすぎるのでみんなに知ってほしいと言う記事を読んでほしいです。

※※※

???「そもそもアプリが数字もらったのに Int に落とし込めなかったのをすぐに発見できなかったのはテスト書かなかったせいなのでは?」
はい、全くもってその通りでございます。