数値の入出力バリデーション方針。関数の引数・戻り値の妥当性チェック規約を定義。test-writer, implement, reviewerの各観点で参照。
このプロジェクトにおける数値の入出力バリデーションの統一方針を定義します。
throw new Error()を使用(カスタムエラーは現時点では不要)if (!Number.isFinite(value)) {
throw new Error('適切なエラーメッセージ');
}
検出する値: Infinity, -Infinity, NaN
理由:
NaNは比較演算が予期しない結果になる(NaN === NaNはfalse)Infinityは算術演算で予期しない振る舞いをするif (!Number.isInteger(value)) {
throw new Error('適切なエラーメッセージ');
}
検出する値: 1.5, 3.14, 0.1など
理由: ダーツの点数は整数のみ有効
if (value < min || value > max) {
throw new Error('適切なエラーメッセージ');
}
例:
> 0(正の整数)0 <= value <= 60if (!VALID_SCORES.has(value)) {
throw new Error('適切なエラーメッセージ');
}
例: 有効なダーツスコア(23, 29などは無効)
必ず以下の順序で実行:
Number.isFinite())Number.isInteger())理由: 早い段階で基本的な不正入力を排除し、より複雑なチェックに進む
export function exampleFunction(score: number): void {
// 入力値の妥当性チェック
if (!Number.isFinite(score) || !Number.isInteger(score)) {
throw new Error('点数は整数である必要があります');
}
if (score <= 0) {
throw new Error('点数は正の整数である必要があります');
}
// ビジネスロジック...
}
export function exampleFunction(throwScore: number): void {
// 入力値の妥当性チェック
if (!Number.isFinite(throwScore) || !Number.isInteger(throwScore)) {
throw new Error('投擲点数は整数である必要があります');
}
if (throwScore < 0 || throwScore > 60) {
throw new Error('投擲点数は0以上60以下である必要があります');
}
// ビジネスロジック...
}
export function exampleFunction(coordinate: number): void {
// 入力値の妥当性チェック
if (!Number.isFinite(coordinate)) {
throw new Error('座標は有限の数値である必要があります');
}
// ビジネスロジック...
}
| チェック内容 | メッセージ例 |
|---|---|
| 有限値チェック(整数期待) | {パラメータ名}は整数である必要があります |
| 有限値チェック(実数可) | {パラメータ名}は有限の数値である必要があります |
| 正の整数 | {パラメータ名}は正の整数である必要があります |
| 範囲指定 | {パラメータ名}は{最小値}以上{最大値}以下である必要があります |
注意: 有限値と整数を同時にチェックする場合、エラーメッセージは「整数である必要があります」とする(NaNやInfinityも整数ではないため)
各関数に対して以下のテストケースを作成:
describe('入力検証', () => {
test('NaNはエラーをスローする', () => {
expect(() => targetFunction(NaN)).toThrow('適切なエラーメッセージ');
});
test('Infinityはエラーをスローする', () => {
expect(() => targetFunction(Infinity)).toThrow('適切なエラーメッセージ');
});
test('-Infinityはエラーをスローする', () => {
expect(() => targetFunction(-Infinity)).toThrow('適切なエラーメッセージ');
});
test('小数値(整数期待の場合)はエラーをスローする', () => {
expect(() => targetFunction(2.5)).toThrow('適切なエラーメッセージ');
});
test('範囲外の値はエラーをスローする', () => {
expect(() => targetFunction(-1)).toThrow('適切なエラーメッセージ');
expect(() => targetFunction(999)).toThrow('適切なエラーメッセージ');
});
});
NaN, Infinity, 非整数@throwsタグを追加@paramに「正の整数のみ」などの制約を明記/**
* 残り点数がダブルでフィニッシュ可能かを判定する
*
* @param remainingScore 残り点数(正の整数のみ)
* @returns ダブルでフィニッシュ可能ならtrue、不可能ならfalse
* @throws {Error} 入力値が不正な場合
*/
export function canFinishWithDouble(remainingScore: number): boolean {
// 入力値の妥当性チェック
if (!Number.isFinite(remainingScore) || !Number.isInteger(remainingScore)) {
throw new Error('残り点数は整数である必要があります');
}
// ...
}
Number.isFinite()チェックがあるかNumber.isInteger()チェックがあるかNaN, Infinity, -Infinityのテストがあるか@throwsタグがあるか@paramに制約が明記されているか不十分な例:
export function example(score: number): void {
if (score <= 0) {
return false;
}
// ...
}
指摘内容:
NaN, Infinityのチェックがないfalseを返している(一貫性がない)改善例:
export function example(score: number): void {
if (!Number.isFinite(score) || !Number.isInteger(score)) {
throw new Error('点数は整数である必要があります');
}
if (score <= 0) {
throw new Error('点数は正の整数である必要があります');
}
// ...
}
typeofによる型チェックif (typeof value !== 'number') {
throw new Error('数値である必要があります');
}
問題: NaNやInfinityもtypeofでは'number'になる
正しい方法: Number.isFinite()を使用
value % 1 === 0での整数チェックif (value % 1 !== 0) {
throw new Error('整数である必要があります');
}
問題: Infinity % 1はNaNになり、NaN !== 0で一見動作するが意図が不明確
正しい方法: Number.isInteger()を使用
falseを返すexport function validate(value: number): boolean {
if (value < 0) {
return false; // ❌
}
return true;
}
問題:
正しい方法: 不正な入力にはエラーをスロー
if (value <= 0) {
throw new Error('正の整数である必要があります');
}
if (!Number.isInteger(value)) {
throw new Error('整数である必要があります');
}
問題: NaNやInfinityが先の条件(<= 0)をすり抜ける可能性
正しい方法: 有限値・整数チェックを先に実行
このプロジェクトの既存実装例(参照用):
checkBust (src/utils/gameLogic.ts:24-36): 残り点数と投擲点数の検証canFinishWithDouble (src/utils/gameLogic.ts:84-87): 残り点数の検証新しい関数を追加する際は、これらと同じパターンに従ってください。
Number.isFinite()とNumber.isInteger()(必要に応じて)これらのルールに従うことで、堅牢で保守性の高いコードベースを維持できます。