🕌

TypeScriptで関数を書くときに気をつけている事

2024/11/22に公開

TypeScriptで関数を書く際に、気をつけていることを記載しました。
BMIを計算する処理を例にして説明いたします。

引数をobject形式にする

引数をobject形式にする事で名前付き引数に近い形式になり、順不同になります。引数の順番に意味が無い関数でも、使用時にどの順番で引数を渡すか迷う事がありません。

const calculateBMI = (p: { height: number; weight: number }) => {
  // BMIの計算処理
};

calculateBMI({ height: 170, weight: 60 });
calculateBMI({ weight: 60, height: 170 });

以下は、引数がobjectではありません。この関数だけ見ると特に違和感は無いと思います。しかし、上記に比べると情報量が減ります。特にheight, weightともにnumberなので使用時に順番に迷うことがあります。

const calculateBMI = (height: number, weight: number) => {
  // BMIの計算処理
};

calculateBMI(170, 60);

適切なブロックコメントを記載する

以下のようなブロックコメントを記載します。メソッドを使用時にエディタにヒントが表示され開発の補助になります。

/**
 * [BMI](https://ja.wikipedia.org/wiki/%E3%83%9C%E3%83%87%E3%82%A3%E3%83%9E%E3%82%B9%E6%8C%87%E6%95%B0)
 */
const calculateBMI = (p: {
  /**
   * 身長
   */
  height: number;
  /**
   * 体重
   */
  weight: number;
}) => {
  // BMIの計算処理
};

calculateBMI({ height: 170, weight: 60 });
calculateBMI({ weight: 60, height: 170 });
  • 身長, 体重は、和訳のコメントなので適切なコメントではありません。
  • TS Playgroundでも確認可能です。

拡張性を担保する

引数をobject形式にするに付随するメリットですが、引数を追加する場合オプショナルにする事が容易になります。

/**
 * [BMI](https://ja.wikipedia.org/wiki/%E3%83%9C%E3%83%87%E3%82%A3%E3%83%9E%E3%82%B9%E6%8C%87%E6%95%B0)
 * `height`, `weight`以外の引数がある場合、制度の高い結果が取得できます。
 */
const calculateBMI = (p: {
  gender?: 'male' | 'female';
  height: number;
  weight: number;
  age?: number;
}) => {
  // 複雑なBMIの計算処理
};

// 引数が順不同
calculateBMI({ height: 170, weight: 60, age: 20, gender: 'male' });

// 新たに追加した引数は、オプショナルな為互換性を担保しやすい
calculateBMI({ height: 170, weight: 60 });

テストを書く

正しい実装である事を検証する事は重要です。また、特殊なケースなどはコメントを追記すれば、コードリーディングの手助けになります。
今回のようなシンプルなメソッドであれば、test.eachが適切だと思います。

/**
 * [BMI](https://ja.wikipedia.org/wiki/%E3%83%9C%E3%83%87%E3%82%A3%E3%83%9E%E3%82%B9%E6%8C%87%E6%95%B0)
 * `height`, `weight`以外の引数がある場合、制度の高い結果が取得できます。
 */
export const calculateBMI = (p: {
  gender?: 'male' | 'female';
  height: number;
  weight: number;
  age?: number;
}) => {
  // 複雑なBMIの計算処理
  return 20; // 検証用のコードなのでハードコーディングです。
};
import { calculateBMI } from './service';
import { describe, expect, test } from 'vitest';

describe('calculateBMI', () => {
  type TestData = {
    param: Parameters<typeof calculateBMI>[0];
    expected: ReturnType<typeof calculateBMI>;
  };

  const testData: TestData[] = [
    {
      param: {
        height: 170,
        weight: 60,
      },
      expected: 20,
    },
    {
      param: {
        height: 170,
        weight: 60,
        // genderがmaleの場合は、XXXを考慮してBMIがZZZになる
        gender: 'male',
      },
      expected: 20,
    },
    {
      param: {
        height: 170,
        weight: 60,
        gender: 'male',
        // ageが20未満の場合は、XXXを考慮してBMIがZZZになる
        age: 18,
      },
      expected: 20,
    },
  ];

  test.each(testData)('入力値と結果 %o', ({ param, expected }) => {
    expect(calculateBMI(param)).toEqual(expected);
  });
});
chot Inc. tech blog

Discussion