零弐壱蜂

Boolean型の変数に適した命名規則

背景

Boolean 型の変数に適した命名規則をあらためて考えてみた。命名は小さなことのように思えるが、コードベースが大きくなるほど、その重要性は増していく。

命名規則に従えば、コードの可読性が向上しメンテナンス性も良くなる。特に Boolean 型変数は条件分岐やフラグとして頻繁に使用されるため、その命名の良し悪しがコード全体の理解しやすさに大きく影響する。

問題事例

同じプロジェクト内で開発者によって様々な命名パターンが混在したとする。

// 開発者Aによるコード
const expired = checkDate(date);
if (expired) {
  /* ... */
}

// 開発者Bによるコード
const isExpired = checkDate(date);
if (isExpired) {
  /* ... */
}

// 開発者Cによるコード
const notExpired = !checkDate(date);
if (notExpired) {
  /* ... */
}

このような不統一は、コードレビューやデバッグの際に混乱を招きバグの原因にもなる。チーム内で一貫した命名規則を採用することで、これらの問題を回避できる。

プレフィクス

Boolean 型の変数には、 ishasshould などのプレフィクスが一般的に利用される(このほかにもcanwilldidmustなども利用される)。これらのプレフィクスを利用することで、変数がBoolean型というのが明示的になり、コードの可読性が向上する。また、IDEの補完機能も効果的になる。

// isを使った例
const isExpired = true;
const isFinished = false;

// hasを使った例
const hasPermission = true;
const hasError = false;

// shouldを使った例
const shouldShowModal = true;
const shouldFetchData = false;

プレフィクスの重要性

プレフィクスを利用することで、その変数が真偽値であると明確になり、コードを読む際にその変数が何を意味しているのか直感的に理解できる。例えば、isExpired という変数名は、その変数が有効期限切れかどうかを明示している(expired という変数名だけでは、期限切れがどういう状態なのかが明確ではない)。

プレフィックスの適切な選択方法

どのプレフィックスを使うべきかは、変数が表す状態や関係によって決まる。

プレフィックス使用場面
is状態や特性を表す場合isActiveisVisibleisReady
has所有や存在を表す場合hasChildrenhasErrorhasAccess
can能力や権限を表す場合canEditcanDeletecanVote
should予期される行動や判断を表す場合shouldUpdateshouldRefresh
will今後の動作や意図を表す場合willChangewillExecute
did過去の動作完了を表す場合didUpdatedidProcess
must特定の制約や要件を表す場合mustVerifymustInclude
// 適切なプレフィックス選択の例

// 状態を表す場合は `is` を使用
const isLoading = true;
const isAuthenticated = false;

// 所有や存在を表す場合は `has` を使用
const hasProfile = user.profile !== null;
const hasRequiredFields = checkFields(data);

// 能力や権限を表す場合は `can` を使用
const canEditDocument = checkPermission(user, 'edit');
const canSubmitForm = formIsValid && !isSubmitting;

チーム全体で同じプレフィックス選択基準を共有することで、コードの一貫性が高まり、読み手の理解が容易になる。

条件の明示

変数名には、その変数が何に対する条件であるかを明示する名前を付けると良い。

// 有効期限切れかどうか
const isExpired = true;

// 許可があるかどうか
const hasPermission = true;

否定形を避ける

Boolean型の変数では否定形を利用せず、肯定形で命名することを推奨する。否定形の変数は読み手に混乱を与える可能性がある。例えば、isNotExpiredisValidでは、前者が混乱を招く可能性が高い。

// 🆖避ける例
const isNotExpired = false;

// 🆗推奨する例
const isExpired = true;

否定形を使うと変数の意味が逆転して読みにくくなり、条件式も複雑になる。肯定形(例:isValid)を使うことで理解しやすくなる。

同じ意味の単語を避ける

同じ意味を持つ異なる単語を利用すると、読み手に混乱を与える可能性がある。例えば、isAvailableisEnabledは、どちらも機能が利用可能であるかどうかを示すが、単語が異なるために混乱を招く。

// 同じ意味を持つ単語を避ける例
const isAvailable = true; // 機能が利用可能か?
const isEnabled = true; // 機能が有効化されているか?
const isAllowed = true; // 機能が許可されているか?

// 避ける必要がない例(プレフィックスが異なる)
const hasPermission = true; // 権限を所有しているか
const canAccess = true; // アクセスする能力があるか

上記の問題を解決する方法

同じ意味の単語による混乱を避けるためには、プロジェクト内で定義したものにしたがって命名することが重要である。

// プロジェクトで統一した用語を使う例
// 機能の有効化状態には常に `isEnabled` を使用すると決めた場合
const isEnabled = true; // 機能が有効化されているか?

同じ意味の単語(例:isAvailableisEnabledisAllowed)を違う目的で使うと、読み手に混乱を与える。一方、hasPermissioncanAccessはプレフィックスが異なるため意味の違いが明確。プロジェクト全体で共通の命名規則を使うことが重要である。

プロジェクトの命名規則やコーディング規約に従う

各プロジェクトには独自の命名規則やコーディング規約が存在する場合がある。それに従うことで、ほかの開発者もコードを理解しやすくなる。

// プロジェクトの命名規則に従った例
const isExpiredDate = true;
const hasEditPermission = true;

プロジェクト全体で決めた命名規則やコーディング規約を守ることで、コードの理解、修正、保守が容易になる。一賛性と統一性を重視し、各プロジェクトの方針に合わせる。

Linter で矯正する(ESLint)

Webフロントエンド開発では、ESLint と @typescript-eslint を利用してBoolean型変数の命名規則を自動的に強制できる。@typescript-eslint/naming-conventionルールを適切に設定することで、チーム全体のコードの一貫性を保つことができる。

基本的なESLint設定

// .eslintrc.js
module.exports = {
  // ...
  rules: {
    '@typescript-eslint/naming-convention': [
      'error',
      {
        selector: 'variable',
        types: ['boolean'],
        prefix: ['is', 'has', 'should'],
      },
    ],
  },
};

より高度な設定例

コードベースやチームのニーズに応じて、より詳細な設定もできる。

// .eslintrc.js
module.exports = {
  // ...
  rules: {
    '@typescript-eslint/naming-convention': [
      'error',
      // Boolean型の変数に対する設定
      {
        selector: 'variable',
        types: ['boolean'],
        // より多くのプレフィックスを許可
        prefix: ['is', 'has', 'should', 'can', 'will', 'did', 'must', 'was', 'were'],
        format: ['camelCase'],
      },
      // Boolean型のクラスプロパティに対する設定
      {
        selector: 'classProperty',
        types: ['boolean'],
        prefix: ['is', 'has', 'should', 'can'],
        format: ['camelCase'],
      },
      // Boolean型の関数パラメーターに対する設定
      {
        selector: 'parameter',
        types: ['boolean'],
        prefix: ['is', 'has', 'should', 'can'],
        format: ['camelCase'],
      },
    ],
  },
};

ESLintの設定に関するチームでの討論ポイント

このルールを導入する前に、チームで以下の点について討論することを推奨する。

  1. どのプレフィックスを許可するか(ishascanなど)
  2. 各種プレフィックスの使い分け基準
  3. 既存コードをルールに適合させるための移行計画

チームでルールに関する共通理解を持つことが、ルール導入の成功に繋がる。

リファクタリング例

Boolean型変数の命名規則にしたがってリファクタリングする具体的な例を示す。

リファクタリング前のコード

// リファクタリング前の例
function processOrder(order, userVerified, premium, notification) {
  // 注文処理の条件分岐
  if (!userVerified) {
    return { success: false, error: 'User not verified' };
  }

  // プレミアム機能の判定
  if (premium) {
    applyDiscount(order);
  }

  // 注文完了処理
  const result = finalizeOrder(order);

  // 通知を送るかどうか
  if (notification) {
    sendNotification(order.userId);
  }

  return { success: true, data: result };
}

リファクタリング後のコード

// リファクタリング後の例
function processOrder(
  order,
  isUserVerified, // userVerified → isUserVerified
  isPremiumUser, // premium → isPremiumUser
  shouldNotify, // notification → shouldNotify
) {
  // 注文処理の条件分岐
  if (!isUserVerified) {
    return { success: false, error: 'User not verified' };
  }

  // プレミアム機能の判定
  if (isPremiumUser) {
    applyDiscount(order);
  }

  // 注文完了処理
  const result = finalizeOrder(order);

  // 通知を送るかどうか
  if (shouldNotify) {
    sendNotification(order.userId);
  }

  return { success: true, data: result };
}

リファクタリングのメリット

  1. 可読性の向上: 変数はBoolean型であることが明確になり、パラメータの目的も理解しやすくなる
  2. IDEサポートの改善: 自動補完機能がプレフィックスで絞り込めるようになる
  3. チーム全体の一貫性: プロジェクト全体で同じ命名規則が適用される

まとめ

Boolean型の変数に適した命名規則を採用することで、コードの可読性とメンテナンス性を効果的に高められる。プレフィックスを適切に利用し、否定形を避け、同じ意味を持つ異なる単語を避けることで、理解しやすいコードに近付ける。また、ESLintのようなツールを活用することで、これらの規則をチーム全体で実施することが可能となる。