業務コードで使う例外処理のたった1つの方法

いわゆる「○つの方法」という釣りタイトルですが、ワタクシ的には例外処理に関しては一択です。
例外処理については、方法論的にも色々あるのは分かっているのですが、「業務」という範囲で考えると、という選択です。

UIがある場合とない場合で、ちょっとだけ書き方が分かれます。

// UIのある場合
int func(...) {
 try {
  // 全ての処理
 } catch ( Exception ex ) {
  // エラーメッセージを表示
  MessageBox.Show( ... );
 }
}
// UIのない場合
int func(...) {
 try {
  // 全ての処理
 } catch ( Exception ex ) {
  // そのまま例外を返す
  throw ;
 }
}

こんな風に、関数/メソッド内の処理を【全て】try catch で囲ってしまいます。
乱暴ですよね…ですが、この方法って VB3.0 の頃からの鉄板な方法だったりします。

sub func(...) 
ON ERROR GOTO L_ERROR

 ' 内部のすべての処理

 exit sub
L_ERROR:
 ' エラーメッセージを表示
 MsgBox(...)
end sub

のように、VB の頃(.NETではない)は、ON ERROR GOTO を使っていました。
さて、この頃 C++ はどうだったかというと…実は C++ は余り流行ってはいなくて、いまだ C 言語が主流でした。なので C 言語の場合は、

void func( ... ) 
{
	int err ;
	
	err = subfunc( ... );
	if ( err != 0 ) {
		// エラー処理
	}
}

な風に、戻り値でエラーを判別するしかなかったのです。

この鉄板の例外処理でログイン処理を書いてみると、

class Principal {
  string _username ;
  
  public bool Logon( string username, string password ) {
    try {
      if ( username == "" ) 
        throw new Exception("ユーザー名が空白");
      if ( password == "" ) 
        throw new Exception("パスワードが空白");
      if ( username == "root" ) 
        throw new Exception("rootユーザーはログインできない");
        
      SqlConnection cn = new SqlConnection(...);
      SqlCommand cmd = new SqlCommand( cn, 
        @"SELECT count(*) from usres 
          where @username = username and @password = password" );
      cmd.Parameters.Add(new SqlParameter("@username", username ));
      cmd.Parameters.Add(new SqlParameter("@password2, password ));
      cn.Open();
      int cnt =(int)cmd.ExecuteScaler();
      cn.Close();
      if ( cnt == 0 ) 
        throw new Exception("ログインできない");
        
      // ユーザー名を保持して正常終了
      this._username = username ;
      return true;
    } catch ( Exception ex )
      throw ;
    }
  }
} 

なにやら妖しげなコードになりますが(苦笑)、業務コードの場合はこっちのほうがすんなり通るし頑健なのです。

実は、このコード例外の throw が大量に書いてあるので安全なコードとは程遠いような気もしますが、ライブラリとしての使い方ではなくて業務コードをこのスタイルにしていまうと、exception の catch が必須になるので、これはこれで安全なのです。

フェイルセーフなコードを書くには? | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/2173

さて、この業務コードを利用する側も try catch を多用します。

/// ログインボタンのクリック
protected void Login_OnClick(...)
{
	try {
		Principal user = new Principal();
		string username = TextUserName.Text ;
		string password = TextPassword.Text ;
		// username, password のチェックは一切しない。
		user.Logon( username, password );
		// 成功時にフォームにユーザー権限を保持する
		this.User = user ;
	} catch {
		// 例外発生は、一律「ログインできない」とする。
		MessageBox.Show("ログインできませんでした");
	}
}

こういう書き方を一律すると「正常系」を一気に書くことができます。
この発想のベースは、VB3.0 の頃からある ON ERROR GOTO なんですけどね。

こんな風な例外処理を取る場合、いくつかの前提条件があります。

・データベースアクセスが基本、正常に行われること。
・処理の流れが基本、正常系で流れること。
・プログラムのバグは、基本皆無であること(実行時にデバッグログ等を取らない)。
・ユーザーへは基本、アプリケーションエラーを知らせないこと。

ということです。このプログラムの書き方は、プログラムの潜在バグを隠してしまうというデメリットがあるのです。ですが、ワタクシ的には「リリースしたプログラムは、ユーザーの目の前で落ちないことが最優先ではなかろうか?」と考える訳でで、突如として落ちないことを最優先にする(それが潜在バグを隠すものであったとしても)と、こういう例外処理の使い方もありかなぁと考えています。

ちなみに、そこそこの業務以外では、こういう書き方をしません。アリだとは思うのですが、業務コードを書くときに限ってという話です。手元のツールを作るときなんかは DEBUG 定義などを利用します。

カテゴリー: 開発 パーマリンク

業務コードで使う例外処理のたった1つの方法 への2件のフィードバック

  1. masuda のコメント:

    [.NET] 例外を Throw しなおすときは、 例外を付けてはいけない ( その1 ): biac の それさえもおそらくは幸せな日々@nifty
    http://bluewatersoft.cocolog-nifty.com/blog/2008/03/net_throw_142f.html

    ええ、まぁ、ええやんッ!!! 所詮例外なんだから、っていうスタンスで来ているので。
    ・・・とはいえ、昔は throw new Exception( ex ) を使っていた気も。ちょっと調べなおします。

  2. masuda のコメント:

    違った。
    throw ex のように書くと、自分で例外を発生するという書き方になっちゃうので、文法的にも実装的にもまずいです。
    子関数の例外であることを示す場合には、throw だけを書かないと駄目ですね。

コメントは停止中です。