null安全な言語のSwift, Kotlinを使えば、
リプレイス前より、安全安心になります!
( ̄ー ̄) ドヤッ!
本当に?
前提条件
仕様がアプリによって違うため
ここでは、文字列fooがnilまたはnullだった場合、
バグである仕様と仮定にします。
主語は、Swift, Kotlinの場合です。
以下は、僕の個人的見解です。
Objective-C時代
nilにメッセージを送っても落ちないが
バグを検知できない
NSString *foo = nil;
NSLog(@"%lu", (unsigned long)[foo length]); // 0
// nilに対するメッセージ送信結果はnil(数値を返す場合には0)が返却される
// クラッシュしないが、バグが起きたことは検知できない
Java時代
String string = null;
System.out.println(string.isEmpty());
// コンパイル時チェックできない、
// 実行時にjava.lang.NullPointerException
// コンパイル時にはチェックできなくて、実行時にクラッシュ
// Javaの@Nullableアノテーションが徹底されていないと、
// JaString型でnullが返ってくることがある
Swift リプレイス悪い例1
Implicitly Unwrapped Optionalはnilが代入できるのに、入らない前提で使ってしまう
var foo: String! = "aaaaaa"
foo = nil
print(foo.isEmpty)
// コンパイル時にはチェックできなくて、実行時にクラッシュ
// Optional型だから、nilを代入できるので
// 決してnilが入らないことを保証するString型として使ってはならない
Kotlin リプレイス悪い例1
Javaから受け取ったPlatform typeをnullが入らない前提で使ってしまう
var foo = getJavaString()
// String!型でnullが入っていた場合、
print(foo.isEmpty())
// コンパイル時にはチェックできず、実行時にnullポクラッシュ
Swift リプレイス悪い例2
Optionalチェーンで、バグを握り潰し検知をしない
Objective-Cと品質が変わっていないので意味ない
var foo: String? = nil
print(foo?.isEmpty) // nil
print(foo?.isEmpty == false) // false
// クラッシュしないがバグが起きたことを検知できない
Optinal Chain(?)でクラッシュしなくなるけど、
落ちないだけで、
バグが起きたこと検知してないと
バグの発見が遅れ、被害が大きくなる
Swift リプレイス悪い例2
var foo: String? = nil
if let foo = foo, !foo.isEmpty {
print(foo)
} else {
print("foo is nil or foo is empty")
// printはローカル端末しか出ないので、
// 本番でサーバーに送らないと、バグ検知できない
}
Swift リプレイス悪い例3
var foo: String? = nil
if let foo = foo, !foo.isEmpty {
print(foo)
} else {
fatalError("foo is nil or foo is empty")
// エラー検知できるが、本番でアプリが強制終了してしまう
}
#全然安心じゃないじゃないか
Swift リプレイス 個人的改善案
Optionalをアンラップした場合やelseの場合で、バグが起きるケースでは、
本番時サーバーにバグを報告する
強制アンラップ(!)は使わない。
var foo: String? = nil
if let foo = foo, !foo.isEmpty {
print(foo)
} else {
// FIXME: メソッド外だし ファイル名 行数出力
#ifdef DEBUG // FIXME: 関数に外だします
fatalError("foo is nil or foo is empty") // 開発時
#else
// FIXME: 本番はユーザーに適切なエラーメッセージを表示して、
// サーバーにバグを報告する
errorReport("foo is nil or foo is empty")
#endif
}
Kotlin リプレイス 個人的改善案
Javaから受け取ったPlatform typeはOptional型にしてアンラップする
Optionalをアンラップする場合や、elseの場合で
バグがある場合は、サーバーにバグを報告
強制アンラップ(!!)は使わない。
ネストが深くなるなら、Swiftのguard式,if letを移植しましょう!
var foo: String? = getJavaString()
foo?.let { foo ->
println(foo.isEmtpy())
} ?: run {
// FIXME: メソッド外だし ファイル名 行数出力
if (BuildConfig.DEBUG) {
throw(Exception("foo is null")) // FIXME: 適切なExceptionにしてください
} else {
// FIXME: 本番はユーザーに適切なエラーメッセージを表示して、
// サーバーにバグを報告する
errorReport("foo is null")
}
}
メリット: 開発時は、fatalErrorで、すぐにバグを検知できる。
本番時は、クラッシュしないので、ユーザーに適切なエラーメッセージを伝えられて、
ロールバックもできるし、サーバーにバグを報告するので、すぐにバグを検知して直せる。
バグ検知のために、ファイル名や行数も一緒に送る
デメリット: 面倒?
バグ検知するコードを書く面倒より、
バグ検知するコードがなくて、
バグ検知が遅れて、被害が広がったり、
手探りでバグを見つける方がもっと面倒なので、
nil,nullに関わらず、バグが起きるときは、
バグ検知するコードを安心のために書きましょう!