マジレスすると『Optional(2018)年』を恐れる必要はない

あけましておめでとうございます。新年早々おもしろツイートがバズっていました。

これは、ネタとしてはおもしろいんですが、 Optional について良く知らないと

「 null 安全とか言っても『Optional(2018)年』 みたいな新しい問題を生んでしまうんだな。」

とミスリーディングされてしまう可能性があります。なので、

  • 『Optional(2018)年』と表示されること自体は問題ではなく、むしろ null 安全( null safety )のために必要である
  • 『Optional(2018)年』は確実に防ぐことができ、心配する必要はない

ということをマジレスしてみます。

そもそも何が問題なのか

今回『Optional(2018)年』と表示されてしまったのは、おそらく次のようなコードを書いてしまったものと考えられます。

// Swift
let year: Optional<Int> = 2018
print("\(year)年あけましておめでとうございます。")
実行結果
Optional(2018)年あけましておめでとうございます。

この手のケースでこれまで問題だったのは year が意図せず null (nil) になってしまった場合に『null年』(『nil年』)と表示されてしまうこと でした。しかし、 null 安全によって逆に『Optional(2018)年』と表示される状況を引き起こしてしまったように見えるというのが今回の問題です。

『null年』より『Optional(2018)年』の方がずっと良い

『null年』と『Optional(2018)年』はその表記だけ見ているとどちらも同様に問題あるものに思えますが、少し冷静に考えてみると 『Optional(2018)年』の方がずっと良い ことに気付きます。

null 安全がサポートされていない環境で『null年』を根絶するのは大変です。いくら動作テストをしても、特殊な状態の組み合わせによってテストしきれなかった null が発生するかもしれません。もちろん、不要だと思われる場合でも null チェックを書けば『null年』を防ぐことはできます。しかし、そのスタイルでは null になり得るかどうかに関わらず、コードを null チェックだらけにしなければなりません。まさにこの ”【アンチパターン】全部nil(null)かもしれない症候群” です。

『null年』の根絶が大変な一方で、『Optional(2018)年』の根絶は簡単です。今回のケースのように文字列に Optional<Int> を誤って埋め込んでしまっても、 『Optional(2018)年』のように表示されるなら一度でも動作テストをすれば簡単にバグに気付く ことができます。バグに気付けば、次のようにコードを修正できます。

// Swift
let year: Optional<Int> = 2018
if let year = year {
  print("\(year)年あけましておめでとうございます。")
} else {
  // `year` が `nil` だったときの処理
}
実行結果
2018年あけましておめでとうございます。

今回のように、通知を表示する機能を実装したけれども、それを 一度も実行しないままリリースするということは通常の開発フローでは考えづらい です。一度でも実行して確認していれば今回の問題は防げたはずです。

『Optional(2018)年』はコンパイル時に検出される

しかし、『null年』より簡単に防げるとはいえ、実行時にしか『Optional(2018)年』に気付けないなら少々残念です。実は、 Swift では『Optional(2018)年』は コンパイル時に警告として検出されます

main.swift:2:9: warning: string interpolation produces a debug description for an optional value; did you mean to make this explicit?
print("\(year)年あけましておめでとうございます。")

コンパイル時の警告はエラーではないので、無視して実行することはできます。しかし、警告は常に対処して発生しないようにしておくべきものです。仮に都度対処するのが大変でも、リリース前には対処しておくべきでしょう。やむを得ず残ってしまう警告があるとしても、問題のある警告が残ってないかリリース前にチェックするのは品質を担保するために必須の作業です。これはもはや null 安全とは関係のない一般的な開発の話です。

百歩譲って山のような警告に埋もれて一覧の中で見逃してしまったとしましょう。しかし、少なくとも標準的な開発環境であればエディタ上で次のように警告が表示されます。警告を意図的に無視するくらいでなければ、コードを書いているときにこれを見逃すのは難しいのではないでしょうか。

null-safe-warning.png

一度も実行して動作テストを行わず、警告も無視したままアプリをリリースするというのは相当なレアケースではないかと思います。もし、そのような無茶をするわけでなければ、『Optional(2018)年』を恐れる必要はありません。

『Optional(2018)』を警告なしに表示する方法

やや余談ですが、どうしても『Optional(2018)』のように表示したいときには、 String.init(describing:) を使うことで、警告なしに Optional を文字列に変換できます。

let year: Optional<Int> = 2018
print(String(describing: year))
実行結果
Optional(2018)

Kotlin では『null年』を確実に防ぐことはできない

Kotlin では次のコードを実行すると『2018年あけましておめでとうございます。』と表示されます。

// Kotlin
fun main(args: Array<String>) {
    val year: Int? = 2018
    println("${year}年あけましておめでとうございます。")
}

これではせっかく IntInt? が区別されているのに『null年』を防ぐことが困難です。実際にこのコードを実行して表示結果を確認しても、 year が誤って Int? になってしまっている(もしくは null チェックが必要な)ことに気付けません。

しかも、 Kotlin は Int? を文字列に埋め込んだり print しようとしても、 Swift のように警告を出してくれません。 Kotlin では『Optional(2018)年』は起こりませんが、『null年』を防ぐ手段は乏しいのが現状です。 null safety という言葉の生みの親である Kotlin が『null年』を確実に防ぐことができないのは残念です。

まとめ

  • 『Optional(2018)年』と表示されるなら、一度でも動作テストをすればバグに気付ける。『Optional(2018)年』はまさに null 安全のために必要な機能。
  • 『Optional(2018)年』を生むコードはコンパイル時に警告が出るので、警告を無視しない限り気付ける。

今回の問題は

  • 通知を表示するコードを書いた後に一度も動作テストをせずリリース
  • コンパイル時警告を確認しないままリリース

という二つが重なって発生した問題だと考えられます。そのような無茶をしない限り 『Optional(2018)年』は確実に防ぐことができる問題であり、恐れる必要はありません