フェイルセーフというのは、機器故障しても安全なほうに倒れるシステムの組み方で、原発の制御棒(BWRはフェイルセーフに反しているが、PWRはそれが解消されている)が重力で落ちるようになっていたり、原子炉自体が半分以上地下にあったり、冷却水が上から下へ流れるようになっていたり(これもBWRはフェイルセーフに反している)する仕組みです。
フェイルセーフ – Wikipedia
http://ja.wikipedia.org/wiki/%E3%83%95%E3%82%A7%E3%82%A4%E3%83%AB%E3%82%BB%E3%83%BC%E3%83%95
ちなみに、フォールレトラントのほうは、故障が起きても処理を継続できる仕組みで、フェイルセーフのように「問題が起きても大丈夫」という点では一緒なのですが、問題発生の後が異なります。
フェイルセーフは、問題発生→安全に機能停止
フォールレトラントは、問題発生→稼働率は下がるが処理を続行
という感じです。
# 情報処理の試験の解答がどうだったか覚えていないのですが…ここで解説するフェイルセーフは「安全に機能停止」のところに主眼を起きます。
さて、フェイルセーフなコードに関しては、過去にもいくつか書いていたりするのですが、再び。
1 2 | // 指定年齢以上のカラムを取り出す List<DataRow> dest = SelectMoreAge( src, 20 ) |
1 2 3 4 5 6 7 8 9 10 11 12 13 | // 指定年齢以上のカラムを取り出すメソッド List<DataRow> SelectMoreAge( DataTable src, int age ) { List<DataRow> dest = new List<DataRow>(); foreach ( DataRow r in src.Rows ) { int a = ( int )r.Item( "age" ); if ( a >= age ) { dest.Add( r ); } } return dest ; } |
こんなソースがあったとき、一発で分かるのは、
・src が null だった場合はどうするのか?
・SelectMoreAge メソッドの戻り値は null になるのか?
のようなチェックです。他にも、パラメータ age の値が
・マイナス値のときは、どうなるのか?
・0 の場合はどうなるのか?
・10000 などの大きな値(年齢とは思えない値)のときはどうなるのか?
というチェックがあります。
# 数値なので、-1,0,10000 でも余り変わらないのですが、例として。
■引数の null をどのように処理するのか?
結論から言うと、フェイルセーフの考え方でいくと、「例外を出さずに、何もなかったように処理を続ける」のが正解です。
・引数が null だから null exception を発生させる。
・引数が null だから、エラーが分かるように戻り値を null にする。
ということは「しません」。
フェイルセーフの「安全に機能停止」を求めようとすると、SelectMoreAge メソッドは処理としては、異常だけれども大きな影響を出さずに止まる、という流れに乗せます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | // 指定年齢以上のカラムを取り出すメソッド List<DataRow> SelectMoreAge( DataTable src, int age ) { List<DataRow> dest = new List<DataRow>(); ※2 if ( src != null ) { ※1 foreach ( DataRow r in src.Rows ) { int a = ( int )r.Item( "age" ); if ( a >= age ) { dest.Add( r ); } } } return dest ; } |
まずは、※1 のところで、src の null チェックをします。このときメソッドを使っている側の対応が困らないように、戻り値を ※2 のところ、あらかじめ作っておきます。
SelectMoreAge の呼び出し目的は、指定した年齢以上のデータがほしい訳ですから、0 件を返してやれば、呼び出し側は安全に停止する(数件取得できることを期待するので)ことができます。
呼び出し側では、以下のように null チェックをせずに済みます。と言いますか、null チェックを忘れても動くようになります。
1 2 3 4 5 | // 指定年齢以上のカラムを取り出す List<DataRow> dest = SelectMoreAge( src, 20 ) if ( dest.Rows.Count > 0 ) { // 数件あるときの処理をする } |
■引数が期待されたもの以外の場合は、どう処理するのか?
引数 src の値が null の時は、明らかにエラーなのですが、年齢が -1,0,10000 の場合は定かではありません。
なので、ある程度期待される範囲を制限して処理すると、より安全になります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | // 指定年齢以上のカラムを取り出すメソッド List<DataRow> SelectMoreAge( DataTable src, int age ) { List<DataRow> dest = new List<DataRow>(); if ( src != null ) { if ( 0 <= age && age <= 100 ) { foreach ( DataRow r in src.Rows ) { int a = ( int )r.Item( "age" ); if ( a >= age ) { dest.Add( r ); } } } } return dest ; } |
こんな風に、SelectMoreAge メソッドが 0 以上 100 以下の年齢を対象にしていることを明確にします。
if 文のネストが深くなってしまうので、以下のように書き換えても ok です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | // 指定年齢以上のカラムを取り出すメソッド List<DataRow> SelectMoreAge( DataTable src, int age ) { List<DataRow> dest = new List<DataRow>(); // src は null でないこと if ( src == null ) { return dest ; } // 年齢は 0 以上 100 以下であること if (!( 0 <= age && age <= 100 )) { return dest ; } // 処理をする foreach ( DataRow r in src.Rows ) { int a = ( int )r.Item( "age" ); if ( a >= age ) { dest.Add( r ); } } return dest ; } |
■メソッドの構造を決める
このようなコードを「業務風」に仕上げていくことができます。
メソッド内をブロックに分けて、どのような手続きをしているかをプロジェクトのメンバーで決めておくと、相互にコードに手をいれやすくなります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | // 指定年齢以上のカラムを取り出すメソッド List<DataRow> SelectMoreAge( DataTable src, int age ) { /*******************************************/ /* 内部変数 */ /*******************************************/ List<DataRow> dest = new List<DataRow>(); /*******************************************/ /* パラメータチェック */ /*******************************************/ // src は null でないこと if ( src == null ) { return dest ; } // 年齢は 0 以上 100 以下であること if (!( 0 <= age && age <= 100 )) { return dest ; } /*******************************************/ /* 内部処理 */ /*******************************************/ // 処理をする foreach ( DataRow r in src.Rows ) { int a = ( int )r.Item( "age" ); if ( a >= age ) { dest.Add( r ); } } /*******************************************/ /* 戻り値 */ /*******************************************/ return dest ; } |
このように適度にコメント(飾り罫線)を入れて整形します。
ひとりで書くときは、この手の装飾は必要ないのですが(行数が多くなって、テキストエディタで表示できる処理行が少なくなってしまうので)、複数名で作業する業務コードの場合は別です。
という訳で、安全性を考慮したコードを書く場合には、できるだけ null を返しません。また、例外を返すことも少ないのです(例外を拾わないことにより、アプリケーションエラーになる確率が増えるので)。
ちょっと前にあがったnullという値は必要か、を読んで。
PHPの場合は連想配列を使うことが多いので、例外は0件の配列を
返してやればうまくいくなぁと思ってました。
業務のコードはこれくらいコメント書きますよね。
なんかみんな書かないしネット上のソースもコメントないから
それが普通なのかと思ってしまう。
というかPHPにはphpDocumentorがあるので、それに則って書くべきだし。
変数をどこでも宣言できてしまうので、これも見づらくなる原因だと最近
思います。
CakePHPが制約を設けて秩序を維持しているので、なんでも自由というのも
逆に考えものかなあと。
null は、明示的に「何もない」ことを指すのが良いかと思っています。
例えば、0 個の配列を返す場合は、単に 0 個なのか、何らかの意図があって 0 個なのか、が区別できないときに、あえて null を使うというやり方です。
ただ、フェイルセーフの話で書いたように、エラーのときに null を返すと決めてしまうと、呼び出し側の判別が多くなって、運用時のエラーが増える。なので、0 を返す、ってのが安全なコードの書き方ですね、ということなのです。
・・・とか言う話は、なかなか理解して貰えないんですよね~。なぜだろう?
そうそう、コメントの飾り罫はあまり好きではないけど、業務コードではよくやります。
適度な空行を入れるか、飾り罫を入れるかという感じ。
あと、変数の位置は、本当は使う直前で宣言したほうがいいんだけど、分かりづらいっていう人が多いので、関数の先頭に宣言するかなぁ(私はやらんけど)、というテンプレートを使ってます。