コードレビューで気付きにくい言語仕様の話

  • diary
  • tech

この文章は日記として書きました。 うちの会社のアプリが急に使えなくなったという話が弊社 slack の#general チャンネルで話されていた。気になって会話を遡り状況を整理してみると、iOS 版及び Web は問題ないが Android 版だけが週末からずっと通信できなくなっているとのことだった。この不具合が報告されているのは一人で自分を含め他の人は問題なく動いていた。僕の所属部署はそのアプリを保守する立場にあり、自分はそのアプリのコードベースに詳しかったので原因を調査してみることにした。
まず調査のきっかけとして、一番手軽でかつ情報がありそうなサーバーログを Kibana を使って探した。しかし直近のログから該当ユーザーが送信したと思われるものは 1 件も見当たらなかった。そこでネットワークリクエストを投げる前にクライアント側でエラーが発生していると考え、モバイルアプリ側のエラー収集サービスである Crashlytics で該当の user_id に関連するエラーを探した。こちらは多数見つかった。日時の新しいセッションを一つ選びスタックトレースを追ってみると API リクエストをする際に呼ばれる認証系のコード内で「String から long への変換」に失敗してクラッシュしていることがわかった。初歩的で単純な例外に思えたが該当の行を読んでもなぜNumberFormatException: For input string "null"となるのかまるで理解できなかった。重要なのは null.toLong() ではなく "null".toLong()であるところだった。周辺のコードを読む限りにおいて文字列の"null"なんて入る余地が無いように思えた。僕の頭では解決できそうになかったので、認証周りに詳しい同僚に調査の続きをお願いした。
結果から言うと問題は同僚が解決してくれた。原因は Kolin のAny?.toString()の振る舞いで、このメソッドは直感に反してString?ではなくStringを返り型とする。ではレシーバーが null だった時にどうするか、文字列の"null"を返すのだ。原因がわかり問題は既に修正された。

toString - Kotlin Programming Language

Returns a string representation of the object.
Can be called with a null receiver, in which case it returns the string "null".

Kotlin と AndroidStudio の優れた静的解析のおかげで Nullable の扱いによるミスは減ったように思うけど、今回のケースでは逆に IDE の検査も PR 時の lint も警告しなかったことで安心して不具合を入れてしまったのではないかとふと思った。
nullがレシーバーの場合に"null"文字列を返す仕様はどのようにして入ってしまったんだろうかと思いを馳せた出来事だった。