読者です 読者をやめる 読者になる 読者になる

ぺぷしのーげん

アプリケーションエンジニア(C#er)による雑記ブログ

例外発生を防ぐためにTryParseを使おう!←ん?本当にそれでいいのか?

f:id:hazakurakeita:20151120235535p:plain

あるとき、キャストやParseでは例外が発生する可能性があるからTryParseを使おうと言っている人がいました。でもこの説明ちょっと違和感ありますね?

 

例外は発生してはいけないのか

例外はエラー。だから悪いもの。発生させてはいけないもの。そんなイメージがあるのも事実です。例えば下記のようなコードは例外が発生するでしょう。

    string str = null;
    int.Parse(str);

 このままのコードを書くことはほとんどないでしょうが、引数で渡された変数や、返り値はどんな状態になることがあるか分かりません。何かの計算に失敗していたり、ファイルのパスが間違っていたりして変数がnullであることは結構ある話です。しかし、次のように書けば変数がnullでも例外が発生しません。

    string str = null;
    int value;
    int.TryParse(str, out value);

 やったね!…なのでしょうか?本当にこれでいいのでしょうか?

 

問題は何も解決していない

TryParseを使用したことで例外は発生しなくなりました。しかし、これで皆んなハッピーなのでしょうか。いいえ。問題は何も解決していません。もし続きは下記の通りだとどうなってしまうでしょうか。

    string str = null;
    int value;
    int.TryParse(str, out value);
    int result = Calculator(10, value);
    
    ...
    
    private void Calculator(int val1, int val2){
      return val1 / val2;
    }

 MSDNを見てみると、TryParseは失敗すると変換結果は0にすると書いてありました。上記のコードの場合、変数valueはTryParseの失敗により0になります。

Int32.TryParse メソッド (String, Int32) (System)

例外は発生しないので、処理は先に進みます。そして別のメソッドで割り算を実行したときに例外が発生してしまいます。ゼロ除算エラーの発生です。

このときデバックは面倒になります。最初にCalculatorメソッドを確認することになります。そして、引数にゼロが含まれていることが分かります。すると問題点が幾つか思い浮かびます。

  • 誰がこのメソッドにゼロを渡したのでしょう?
  • なぜゼロを渡したのでしょう?
  • そもそもこのCalculatorメソッドはゼロを渡されることを考慮してないのは仕様なんでしょうか?
  • それともバグでしょうか?

場合によってはパニックです。Calculatorメソッドで例外が発生しないと、更に異常な値が別の処理に持っていかれ、更に問題を拡散させていくかもしれません。これが深くなると、原因不明・再現性の極めて低い不具合を生んでしまいます。

 

例外を発生させないことは間違い。ハンドルするのが正解

例外は積極的に発生させるべきでしょう。そして、それをきちんとハンドルしましょう。

    try{
      string str = null;
      int.Parse(str);
    }
    catch(ArgumentNullException ex)
    {
      log.write(ex.Message);
      return;
    }
    catch(FormatException ex)
    {
      log.write(ex.Message);
      return;
    } 

 こうしておけばログも残せて安心です。再現性が低い不具合がクライアントで発生してもログで原因を推測したり、対策することも可能になるかもしれません。昔ながらの技法だとreturn false;も多用しがちですが、これもthrow new Exceptionで対応しましょう。例外とはエラーではなくて、強制的に処理を止める強力なツールだと考えると良いかもしれません。将来、自分が書いたコードを自分以外の誰がどんな使い方をするか分かりませんからね。

 

おしまい。