



まず、プログラム中の処理には、「正常時の処理」と「エラー(異常時の)処理」がありますね。
ソースコード上では、正常時の処理が中心にくるような書き方をするのがおすすめです。なぜなら、エラー処理は「だいたいの場合は発生しない状況」を扱う処理だからです。
正常時の処理を中心に据えておけば、そのプログラムをほかの人が見るときや、あとで自分が見たときに理解しやすいでしょう。


/* 指定されたファイルにテキストを書き込む。
* - pText: テキスト
* - pPathToFile: ファイルのパス
* 正常に書き込めたらtrue、エラーが発生したらfalseを返す。
*/
bool WriteTextToFile(const char *pText, const char *pPathToFile) {
assert(pText);
assert(pPathToFile);
FILE *fp = fopen(pPathToFile, "w");
if (fp == 0) {
return false; /* エラー:ファイルを開けなかった */
}
if (fputs(pText, fp) != EOF) {
fclose(fp);
return true; /* 正常 */
}
fclose(fp);
return false; /* エラー:テキストを書き込めなかった */
}
この関数は、ファイルに文字列を書き込み、うまくいったらtrue
を返すというものです。でも、正常時の流れがどこにあるのかが見つけにくくなってしまっています。


- エラーの判定に一貫性がありません。
fopen()
では失敗したかどうかを判定しているのに対し、fputs()
では成功したかどうかを判定しています。 - すべての処理に成功したとき、関数の最後までたどり着くことなく
return
しています。 fopen()
は1カ所しかないのに、fclose()
は2カ所で呼び出されています。



流れが分かりやすい書き方

- 正常でもエラーでも、関数の最後まで処理を到達させる。
if
文では処理が成功したかどうかを判定し、エラー処理が必要な場合はelse
に収める。
このポリシーにしたがうと、上の関数は次のようになります。
bool WriteTextToFile(const char *pText, const char *pPathToFile) {
assert(pText);
assert(pPathToFile);
bool bSuccess = false;
FILE *fp = fopen(pPathToFile, "w");
if (fp) {
if (fputs(pText, fp) != EOF) {
bSuccess = true;
}
fclose(fp);
}
return bSuccess;
}
どうでしょう。正常時の流れがよく見えるようになったのではないでしょうか。正常時の流れが見えれば、その関数がどのようなアルゴリズムで書かれているのかが明確になります。


上の例でも、ごく自然な流れで、fopen()
とfclose()
の呼び出しが1度ずつになりました。さらに、エラーが発生したとき、実は「false
を返す」以外に何もしていなかったということが明らかになりました。


この書き方は、必ずしも万能というわけではありません。判定すべきことが増えてくると、if
のネストが深くなってしまうのです。処理をサブルーチンに分ければネストの問題は解決できますが、ほかの書き方をしたくなるケースも出てくるでしょう。

もう一つの書き方

- 正常時は、関数の最後まで処理を到達させる。
- エラーを検出したら、即座に
return
する。
このポリシーにしたがうと、先ほどの関数は次のように書けます。
bool WriteTextToFile(const char *pText, const char *pPathToFile) {
assert(pText);
assert(pPathToFile);
FILE *fp = fopen(pPathToFile, "w");
if (fp == 0) {
return false;
}
if (fputs(pText, fp) == EOF) {
fclose(fp);
return false;
}
fclose(fp);
return true;
}
エラーケースが多数考えられる関数は、こちらのポリシーで書いたほうがシンプルになることがよくあります。そもそも、if
文のネストが深くならないのでこちらのほうが好き、という人もいるかもしれません。
ただし、プログラムの「構造化」という面では、return
を何度も書くのは好ましいとはいえません。この例のfclose()
ように、同じ処理を2回以上書かなければならない場面も出てきます。



基本的には、1つ目に紹介したポリシーで書くようにするのがおすすめです。それで、ちょっとやりづらいなと感じたときは、2つ目のポリシーを試してみてください。
