連載
» 2019年11月07日 11時00分 公開

山浦恒央の“くみこみ”な話(123):ソフトウェア技術者のためのバグ百科事典(2)まだあるぞ、うるう年バグ (2/3)

[山浦恒央 東海大学 大学院 組込み技術研究科 非常勤講師(工学博士),MONOist]

4.うるう年のバグを防ぐポイント

 前回、うるう年を防ぐポイント、うるう年バグの兆候を示しました。下記に、詳細を記します。

4.1 うるう年を防ぐ4つのポイント 

 うるう年のバグを防ぐためのポイントを下記に示します。

  1. 仕様でうるう年を考慮しているか
  2. コードにうるう年の処理を記述しているか
  3. うるう年の2月29日周辺のテストができているか
  4. うるう年の12月31日のテストができているか

 今回は、例題として、以下のプログラムを用いて考えてみましょう(リスト1)。

//曜日取得関数
int week_of_the_day(int year, int month, int day){
	int res = 0;
	int temp_day = 0;
	int day_table[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
	//エラー処理
	if (year <= 0 || year >= 3000){
		return -1;
	} else if (month <= 0 || month >= 13){
		return -1;
	}
	//うるう年の判定
	if ( (year % 4 == 0) && ( (year % 100 != 0) || (year % 400 == 0))){
		//うるう年かつ2月の場合は1日増やす
		if (month == 2) {
			day_table[month-1]++;
		}
	}
	//エラー処理
	if (day <= 0 || day > day_table[month-1]){		
		return -1;
	} 		
	// 1月か2月の場合は、年-1、月+12とする
	if (month == 1 || month == 2){
		year = year - 1;
		month = month + 12;
	}
	// ツェラー公式から曜日を求める
	res = (year + year / 4 - year / 100 + year / 400 + (13 * month + 8) / 5 + day) % 7;	
	return res;
}
リスト1 曜日取得プログラム

 このリスト1は、曜日取得関数です。ツェラー公式は、年、月、日から曜日を求める公式です。この公式では、1月と2月は、前年の13月、14月と考えて計算します。main関数から呼び出しをかける場合は、以下のように記述します。

res = week_of_the_day(2000,1,19);

 この例題を用いて、以下に詳細を示します。

4.1.1 仕様でうるう年を考慮しているか

 筆者の経験上、仕様担当者が仕様書、設計書にうるう年の場合の異常処理を書いていない可能性があり得ます(仕様担当者には、プログラマーがうるう年のことを考えるのが当たり前との思いもあるようです)。その場合、リスト1のようなうるう年の判定処理が抜け落ち、うるう年の日にちの加算処理ができない場合があります。

4.1.2 コードにうるう年の処理を記述しているか

 うるう年のバグを防ぐには、最低限、うるう年の判定処理が書いてあるか確認しましょう。リスト1では、以下の処理です。

	//うるう年の判定
	if ( (year % 4 == 0) && ( (year % 100 != 0) || (year % 400 == 0))){
		//うるう年かつ2月の場合は1日増やす
		if (month == 2) {
			day_table[month-1]++;
		}
	}

 入力した年がうるう年で、2月の場合は、日を+1して日数を調整しています。日付に関連したコードレビューや単体テストを行う際は、うるう年の処理が記載してあることを確認しましょう。

4.1.3 うるう年の2月29日周辺のテストができているか

 うるう年のテストをする工程は、単体テストです※1)。その際、うるう年の2月28日、2月29日のテストができているか確認しましょう。リスト1で単体テストをするには、例えば、以下のように行います。

	//曜日を取得する
	res = week_of_the_day(2020, 2, 28);
	res = week_of_the_day(2020, 2, 29);

 これにより、うるう年の1日が機能しているか確認できますね。

※1)単体テストの後の工程で、うるう年のテストをしても構いませんが、時間がかかりすぎます。また、単体テストを過ぎると、うるう年のバグを見つけるのは困難でしょう。

4.1.4 うるう年の12月31日のテストができているか

 うるう年のテストでは、2月29日以外にも12月31日を確認しましょう。今回の例では、下記のようにテストします。

	res = week_of_the_day(2020, 12, 31);

 今回のプログラムでは、12月31にしても影響はありませんが、日数を配列で確保している場合は、予期せぬ1日でバッファーオーバーランとなる可能性があります。2月29日だけではなく、12月31日にも着目しましょう。

4.2 うるう年バグの兆候

 完璧な人間が存在しないのと同じように、完璧なプログラムも存在しません。ソフトウェアには、一定程度のバグがあると認識したうえで、何をするべきかを考えることが大事です。

4.2.1 2月29日、12月31日にバグが発生する

 2月29日、12月31日にバグが発生しているか確認しましょう。筆者もそうなのですが、バグの現象ばかりに目が行ってしまい、日付が関係していると推察できない可能性があります※2)。発生事象も大事ですが、日時にも着目しましょう。

※2)必ずしも、この日になるとは限りませんが、この日が多いようです。なお、前回の記事でも紹介しました公衆電話のバグでは1月31日に発生しています。

4.2.2 それまでは普通に動作しているプログラムが突然動かなくなる

 うるう年バグは、時限爆弾と同じです。数年間、「何事もなく動いていた」場合は、うるう年のバグを疑いましょう。もちろん、ただのハードウェアの故障も考えられますので、数あるバグの1つとしてください。

4.2.3 一定時間が経過すると正しく動作する

 うるう年のバグの特徴的なところは、存在しない日付を過ぎると、プログラムが元通りになることです。例えば、プログラムが2月29日を存在しない日と判定したとします。24時間後、プログラムが3月1日となった場合、通常の動作に戻ります。つまり、バグが自然治癒するのです(したように見える)。

 一定時間で自然治癒し、発生日時が2月29日か12月31日だと、うるう年バグの可能性が高まります。

Copyright © ITmedia, Inc. All Rights Reserved.