エラー握りつぶし
ありがちダメなコードのダメな理由を書くコーナー。
#include <stdlib.h> #include <math.h> #include <stdio.h> int mysqrt(int x) { if (x < 0) { return 0; } else { return (int)sqrt(x); } } int main() { printf("%d\n", mysqrt(4)); printf("%d\n", mysqrt(1)); printf("%d\n", mysqrt(0)); printf("%d\n", mysqrt(-1)); printf("%f\n", sqrt(4)); printf("%f\n", sqrt(1)); printf("%f\n", sqrt(0)); printf("%f\n", sqrt(-1)); return 0; }
をコンパイルして実行すると、
$ gcc -std=c99 -pedantic -O0 -lm -g tmp.c $ ./a.out 2 1 0 0 2.000000 1.000000 0.000000 nan
と出力された。
"nan"って何?
"nan"とは"Not a Number"の略で、和訳時には非数と称されるもの*1。文字通り、数値では表せない値を表現するのに用いられる。次のような条件の場合、CPU例外として扱われるか、または計算結果としてNaNを返す。
- 数学関数に定義域外の引数を与えた。
- 0で割り算を行った。
何がダメなのか。
大抵のNaNは途中の計算にエラーがあったことを示す。そのNaNをintにキャストした結果はただの0なので(intにNaNはない)、mysqrt(0) == mysqrt(-1)というように、エラーが通常出力に紛れて判別不能になっている。
この類の間違いは正常処理に影響を与えない/与えにくいために放置されることが多い。しかし、例外処理/異常処理として扱われるべきものが正常処理に紛れ込んでいると、大量の処理結果を並べるとどこか歪なものになるだろう。
例えば、上の例では入力に負数を含む分だけ出力の0点にスパイクが立つことになる。これを「この関数はこういう特性だから〜」と後処理で切り捨てるといった手法はカーゴカルトプログラミング*2そのものだ。
〆。
基本は次の分岐表にしたがってください。
追記
こういった「失敗を検出できない仕組み」はフェイル・セーフの観点からもダメなコード。NaNを返すことができない環境でも最低限ログを出力すること。
int mysqrt(int x) { if (x < 0) { LOG_PRINTF("[%s:%d] Invalid argument! x < 0\n", __FILE__, __LINE__); return 0; } else { return (int)sqrt(x); } }