怖くない例外と冗長なnullチェック(過剰な防衛)について


ReadMeFirst

最近、業務でエラーの握りつぶしとか、nullチェックとかで
いろいろ辛い気持ちになったので、IMO(我思う)な記事を書いてみました。

よくある困ったコード(phpを例に)

public function example(args) {
    if args === null {
        return false
    }
    ...(処理内容)
    return "example"
}

public function useExample() {
    ret = example()
}

を例に話してみようかなと思います。

困るところ

  • 失敗した際にfalse以上の情報を example関数から得る事ができないので、失敗理由が複数ある場合、理由別のハンドリングができない。
  • php7以前は返り値の型が指定できないのもありますが、文字列を返したり、真偽値を返したり、だと、exampleを使う側が冗長なチェックをする必要がある。

(補足) 型がないのがそもそも(ry みたいな論議もありますが
返す型は1つで固定されている方が圧倒的に使う側は楽ですし、関数の利用側が考慮することが減るので返り値の型は1つ にした方が良いです。instanceOf とかを使う羽目になった時は赤信号。

改善案

例外を使いましょう。

public function example(args) {
    if args === null {
        throw New InvalidArgumentException("error message")
    }
    ...(処理内容)
    return "example"
}

public function useExample() {
    try {
        ret = example()
    } catch (InvalidArgumentException e) {
        //badArgumentな時の回復処理
    } catch (HogeHogeException e)
        // HogeHogeExceptionな時の回復処理
    } catch (FugaFugaException e)
        // 何もしない。 Badな例
    } catch (Exception e)
        ret = false //失敗を握り潰すBadな例
    }
    ....
}

とすることでexampleの返り値はstringを守りつつ、
例外の型で失敗のContextを利用者側に伝えることができ、失敗理由に
応じた回復処理を行うことができます。

point

  • 基底の例外を使わず独自の例外を定義する

基底例外ではエラーメッセージ以外から失敗した理由や背景を伝える事ができません。
エラーの理由が言語やライブラリが吐くような 技術的な例外 はそもそも回復可能じゃないケースが多いし、想定しててもキリがないので、意図がないならcatchせずフレームワークや上位層でごそっと捕まえて、ロギングなりをした方がスッキリします。

そうでない例外のみを意識して扱かう方が良いかな
と思います。

(補足) 勿論ユースケースや処理内容にもよります。

  • 回復処理がないなら無理にcatchしない

サンプルコードに書きましたが、理由なく例外を無理に握りつぶさない、何もしないならcatchせず例外を上の層まで伝播させた方が良いです。

  • 早めにクラッシュさせること

達人プログラマ本やエッセイの死ぬはずのプログラムを無理に生かしておいてはいけないでも
語れていますが、無理に生かすと開発やテスト段階で気付きづらいので、問題を検知しやすい事も重要です。

  • そもそもこのnullチェックはエラーするべきなのか。

そのnullはユースケースにおいて、通常起き得るnullなのか? と言う観点で決めれば良いと考えています。
想定するユースケースにおいて、nullが起きないのであれば、nullで呼び出しが起きている事自体
がエラーなのでエラーを起こした方が、問題の検知が容易です。

ここら辺の呼出側、呼ばれる側の責務分担については契約による設計でも語れています。

Goでも同じ

PHPがダメなんでしょ?みたいな話に比較的なりがちですし、
Goは例外がなく、errorを値として扱うので差異はありますが、
エラーの扱いや、ここでは書いていませんが、契約プログラミングについては、活用できるかなと。

まとめ

Goでも errorを握りつぶしてlogだけして返さない コードや冗長なnilチェックが
よくあって例外というか、エラーの扱い方について、辛い気持ちになったので書いてみました。

例外については、設計が難しいという議論もありますし、使いすぎると複雑性を上げる、
tryCatch全部外してアプリが動かないなら 使い方を間違えているという強い意見もありますが
個人的には、節度と用法を守って使い方を定めればメリットを享受することができる、と
思います。

参考

プログラマの知るべき97のこと
例外設計における大罪