10
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

null安全な言語でも、バグ検知を怠れば安心ではない

Last updated at Posted at 2017-04-24
1 / 19

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に関わらず、バグが起きるときは、
バグ検知するコードを安心のために書きましょう!

10
9
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?