行き場のないエラー - エラー処理とassert

失敗はつまずくことではない。つまずいたままでいることだ。

またまた更新間隔が開いてしまったので、久々の更新です(^^;)。今回は「エラー処理」を取り上げてみます。「エラー処理」という単語もあいまいなのですが、今回とりあげるのは「あるメソッド・関数が本来の処理を達成できなかった場合の処理」といったところです。

エラーを伝える

さて、処理が失敗した時の最も基本的なパターンは、戻り値としてエラー値を返すことです。このような関数・メソッドは数多くあります。例えば、POSIX系のclose()関数をみてみましょう。
int close(int fd);
これはエラーで0以外の値が返ります。このように、エラーか否かを戻り値で返すものの他に、本来の戻り値がとり得ない値をエラーの意味として返すものもあります。例えば、POSIX系のopen()関数です。
int open(const char *pathname, int flags, mode_t mode);
この関数は戻り値としてfd(file descriptor)を返しますが、エラー時は-1を返す仕様です。

どちらにせよ、この関数を使用する側としては、戻り値を確認することによって、エラーが起こったか否かを確認することができます。

エラーを処理する

さて、大半の関数がこのようにエラーを戻り値で通知してくれるわけですが、そのようなエラー値を見つけた場合、一体どうすればいいのでしょうか。大抵の場合、次の3種類に分けられるでしょう。
  • 1. エラー処理としてユーザーへの通知や、代替処理、後始末などを行う。
  • 2. エラーを無視する。
  • 3. 処理をそこで止める。

1は最も基本的なパターンです。ファイルがオープンできないといった場合であれば、ファイル名が違う(あるいは存在しない)とか、パーミッションが不適切だとかいう場合がありますので、この場合はユーザー(又は上位モジュール)に通知する必要があるでしょう。場合によっては、ネットワーク越しであればリトライ等も必要かもしれません。書き込み中のエラーならファイルをクローズするなどの後処理も必要でしょう。
しかし、実際にこのようなエラー処理が行われるのはごく一部です。大抵の場合は、2の「無視」に落ちることが多いと思います。

大抵の関数はエラーを返してくれるのですが、実際全ての関数ごとにエラー処理をするのは難しい、あるいは無理がある場合がほとんどです。例えば、バグでしかエラーになりえないものに対して、適切なエラー処理を与えるというのは不可能でしょう(全てのprintfのエラー見てる人なんていませんよね)。それに、全ての関数毎にエラーを処理しようとすると、次のようなすさまじい入れ子ができあがりかねません。
  /* 処理A, B, C, Dを行う */
  if (func_A(x, y) == 0) {
      ... 
      if (func_B(x) == 0) {
          ...
          if (func_C(y) == 0) {
              ...
              if (func_D(x, y) == 0) {
                  ...
open()のようなI/Oのための関数は、途中でエラーが発生する場合もあると思いますが、論理的な関数の場合は、最初の関数が成功すれば、あとは絶対成功するはずという場合もあります。このような場合であれば、確かにエラー処理は必要ありませんし、しようもありません。しかし、エラー処理が必要ないというのと、エラーをチェックしなくて良いというのは違います。つまり、単純に無視していいとは限りません。そこで、3の「処理を止める」というのが登場します。

見てみぬふりをしない

次の例を見て下さい。
...
pthread_mutex_lock(&mutex)
shared_variable ++;
pthread_mutex_unlock(&mutex)
...
いたって単純なmutexでの排他制御コードです。ここでmutex_lockはエラーを返す可能性がありますが、エラーは確認されていません。mutex_lockがエラーになるのは、初期化忘れや、同じスレッドでの二重取得等ですが、自身での本来、このような例でエラーが確認されない理由は、それは外的要因ではなく、単なるバグです(trylockと違い、busyならまたされるだけ)。よって、ユーザーに通知するという必要もありませんし、エラー処理で復帰できる望みも薄そうです。では、どうするか。このコード例では特にエラーを見ずに無視していますが、これでいいのでしょうか。

プログラムにバグがなければ、自スレッドから再度mutexをとってみたり、初期化されていない構造体を渡したりということは発生しません。しかし、開発中にはこのような不正規な処理のために、よくデッドロックを起こしてしまうことがあります。しかも、このようなバグは、タイミング依存になったりして、なかなか発見されません。
しかし、この例の場合、関数(pthread_mutex_lock())のエラーを見ていれば、すぐに分かったはずなのです。

よって、ここは以下のようにすべきでしょう。
...
ret = pthread_mutex_lock(&mutex)
assert(ret == 0);
shared_variable ++;
ret = pthread_mutex_unlock(&mutex)
assert(ret == 0);
...
assertにひっかけてやることで、おかしな動作をした場合、すぐに 検出できます。
バグというのは、発生地点から離れれば離れるほど解析が難しくなりますので、このように発生時点で捕まえるというのは非常に重要です。
なお、標準のassertマクロなら、リリース時に消すこともできまし、検出時に行数も出してくれますので、リリース物の効率にも影響を与えません。

ライブラリの中心でエラーを叫ぶ

さて、上記の例では、エラー値を見て処理を止めていました。しかし、エラーを起こしてはいけないのはここ部分のコードだけではないでしょう。mutex_lockを使用しているコード全般に言えることです。であれば、mutex_lockしている方ではなく、mutex_lockの方でassertにひっかけてやる方がいいのではないかということも考えられます。

その通りです。pthread_mutex_lockの例で言えば、エラーを返す前に、abort()するようなコードを入れておけば、不正な処理が行われた段階でプログラムは停止します。デバッガを繋ぐなりしてあれば、バックトレースでどこが問題かもすぐ解析できます。

しかし、この方法はライブラリのソースコードがない場合はそうはいきません。また、自分がライブラリを作成する側の場合、ライブラリ内で勝手にabortするのは、常に期待値とはいえません。自分がライブラリ作成者である場合は、ライブラリ使用者のために、エラー時の挙動を選択できるようにしておいてやるというのもいいでしょう。例えば、
  • コンパイル時にエラーを返す前の処理(ログを出力、abort、HOOK関数を呼ぶ等)を選択できるようにする
  • 環境変数やAPIでエラーを返す前の処理を選択できるようにする
といったことが考えられます
エラー通知の重要な役割の一つは、上位モジュールの間違いを指摘することです。そして、その目的を達成するためには、単なるエラー通知だけでなく、ログやabort,フック関数の呼び出し等とうまく組み合わせることによって、その効果は何倍も高まります。

例外スローの場合

以上では、関数リターンとしてエラー値を返すというパターンで説明しましたが、C++やJavaの例外スローも同じようなことが言えます。例外の場合、
try {
  doAction();
} catch (...) {
  /* 無視 */
} 
として、とりあえずterminateで落ちるのだけを防ぐといったコードを時々見かけます。しかし、これはエラーを無視して突き進み、事態をどんどん悪化させることにつながりかねません。例外もエラー同様に、どこでどのように処理すべきかということを明確にした上で、設計しないと行き場のない例外が氾濫することになります。

せっかくのエラー通知。しっかりと耳を傾けてあげましょう。

4873115930 【関連書籍】
デバッグルール - 9つの原則、54のヒント David J.Agans
組込み開発者におくるMISRA‐C―組込みプログラミングの高信頼化ガイド MISRA‐C研究会
C++プログラミングの処方箋―ひと味違うコードを書くための99の鉄則 Stepehn C. Dewhurst
Cプログラミングの落とし穴 A.コーニグ
組込みソフトウェア開発における品質向上の勧め―コーディング編 情報処理推進機構 SEC
Effective C++ 【改訂第2版】 Scott Meyers
組込みプレス Vol.3

開発プロセス改善 - ところで問題は何なのか?

解法を問題の定義と取り違えるな。ことにその解法が自分の解法であるときには注意
ライト、ついてますか―問題発見の人間学』 D.C.ゴース G.M.ワインバーグ
ちょっとばたばたしてたのでひさびさの更新になります。そのばたばたの原因のひとつでもあるのですが、今年度に入ってソフトウェアプロセス改善(Software Process ImprovementSPI)の仕事が増えました。しかし、多くのソフト屋さんの例に漏れず、私もこの手の仕事はあまり気乗りしません。というよりも、「そんなことより実装させてくれ!!」というのが強いといった方がいいかもしれません。とまぁ、愚痴ってみたものの、開発プロセス改善は決して「そんなこと」ではなく、今日の組込みソフトウェア開発において非常に重要なテーマです。今回は、プロセス改善について考える機会ができたことで、改めて感じたことを少し書いてみようかと思います。

私と同様、組込み業界に身を置いておられる方も、「組込みもこれからはCMMくらい取れないと!」ということで、手順・ルールが増えたり、厳しくなったりという経験をお持ちの方も多いでしょう。しかし、CMMといえど、所詮はルールや仕組みであって、それは目的ではなくは手段です。体系的な仕組みは短期的には面倒なことでも、長期的には効果を発揮することも多いのは事実です。しかし、その裏にある目的を意識せず仕組みを作ることだけに一生懸命になっていては本末転倒です。

プロセス改善については、どうもトップダウンの「押し付け感」が伴いがちです。そのため、現場から見れば「望んでもないことを押し付けられる」、上から見れば「現場は意識が足りない」という調子になってしまうことが多いです。これはなぜでしょう。一つには、課題認識があっていないということが挙げられます。「プロセス改善なんて工数の無駄だ」「プロセスがないなんて問題だ!」というやりとりでは、絶対に収束しません。まずは、「なぜプロセス改善が必要か」ということをお互い認識をあわせる必要があります。つまり、「誰にとって」「何が」「なぜ」問題かという点をまず明らかにし、その次に初めて解決策を検討するというステップが必要がなのです。

現場の人間が「プロセス改善は工数の無駄だ」といった時に、つい「長期的な視野に立ってないからだ」ということがよく言われます。大抵の場合、それも一理あるとは思いますが、決してそれだけではありません。それよりも、「もっと他にすべきことがある」というのがあるからでしょう。プロセス改善の目的は、開発効率の改善や品質の向上にあります。そして現場の人の多くは、その目的のための近道がドキュメント管理や、手順の定義がネックであるとは思っていないのです。では、何が必要かと思っているか。大抵の人は「工数」と答えるのではないでしょうか。

では、工数とは何か?時間が足りないのか?人が足りないのか?実際はどちらも正しくないでしょう。実際は「スキル」が足りないのです。人月という単位はいうまでもなく形式的なもので、実際は全ての人が同じスキルをもっているわけではありません。そして、工数が足りないといった時に、多くの場合開発者のスキルが足りないということが多いと思います。

プロセス改善といった時に、まだまだこの「スキル問題」が議論の中心になることはあまりないような気がします。ノウハウの蓄積といったテーマがあがることがありますが、まだ少し違う気がしいます。もっとより、自由意志でのスキルアップのようなもの、これをうまく組織としてまわるようにしていくにはどうすべきか、これを考える必要があるように思います。それは自己啓発だからどうしようもない、そう言って片付けてしまうのは単なる思考放棄です。梅田信夫氏の著書「ウェブ進化論」でも紹介されていますが、Googleは20%の時間を自分のオリジナルなテーマに当てなくてはいけないという「20%-80%」ルールがあるそうです。ポイントは「当てなくてはいけない」という点です。さすがGoogleといってしまわず、このような仕組みも積極的に検討していく必要があるのではないでしょうか。これも、実開発の工数に影響を与えるかもしれませんが、「ドキュメント管理」よりははるかに技術者の「モチベーション」に効果的に働くでしょう。

さてさて、こうは書いたものの、やっぱり基本は会社にたよるのではなく自分自身の動機付けが一番。かくいう私も最近業務に追われ気味なので、ちょっと深呼吸して新しい言語でも勉強してみようかなと思う今日この頃です。
なんだかまとまりのない話になって、結局何が主題だったか分からなくなってしまいました (^^;)。
プロセス改善の話の続きはまた改めて。。

【関連書籍】
ライト、ついてますか―問題発見の人間学 ドナルド・C・ゴース G.M.ワインバーグ
デスマーチ 第2版 ソフトウエア開発プロジェクトはなぜ混乱するのか E・ヨードン
ウェブ進化論 本当の大変化はこれから始まる 梅田 望夫
ソフトウェア開発のためのプロジェクトマネジメント入門―CMM導入の手引き パンカジュ ジャロート
ソフトウェアプロセス改善と組織学習―CMMを毒にするか?薬にするか?
ザ・サーチ グーグルが世界を変えた ジョン・バッテル
人気エントリ
最近の記事
本のおすすめ

4274065979

4844337858

482228493X

4904807057

4873114799


最近のコメント
Links
プロフィール
  • Author:proger
  • 組み込み関係で仕事してます
ブログ内検索