Aikido

隠れたバグを防ぐために、条件式での代入を避けるべき理由

可読性

ルール
してはいけない 課題を 課題を 条件文の 条件式の中に 
代入 代入  条件 論理 を組み合わせることで コードを エラーが発生しやすくなる
 難しく 理解しにくく 理解しにくくなる。 課題を 課題を  論理的 チェックを 

対応 言語:** JavaScript、 TypeScript、 Python、 PHP

はじめに

条件文内の代入演算子は、コンパイラやリンターが見落としがちなバグの一般的な原因です。典型的な間違いは、if文で==または===(比較)の代わりに=(代入)を使用することですが、問題はより根深いものです。条件式での意図的な代入であっても、読み取り、レビュー、デバッグが困難なコードを生み出します。代入と評価が同じ行で行われる場合、読者はどの操作が優先され、どの値が実際にテストされているかを頭の中で解析する必要があります。

なぜ重要なのか

なぜ重要なのか

バグの導入: タイプミスによる変更 === to = 構文エラーを引き起こすことはなく、ただ静かに動作を変更します。条件は比較結果ではなく、割り当てられた値(truthy/falsy)に評価されます。

コードの可読性:読者は条件式が値をテストすることを期待し、変更することを期待しません。両方が同時に発生する場合、メンテナーはどの変数がいつ変更されているかを追跡する必要があります。

コード例

❌ 非準拠:

function processUser(userData) {
    if (user = userData.user) {
        console.log(`Processing user: ${user.name}`);
        return user.id;
    }
    return null;
}

function validateInput(value) {
    if (result = value.match(/^\d{3}-\d{2}-\d{4}$/)) {
        return result[0];
    }
    return false;
}

誤っている理由: 条件文内の代入は、意図的なものなのか、それともタイプミスなのかを不明瞭にします。最初の例は === が意図されていたバグである可能性があり、2番目の例は正規表現マッチングと代入が混在しているため、コードの流れを追うのが困難になります。

✅ 準拠済み:

function processUser(userData) {
    const user = userData.user;
    if (user) {
        console.log(`Processing user: ${user.name}`);
        return user.id;
    }
    return null;
}

function validateInput(value) {
    const result = value.match(/^\d{3}-\d{2}-\d{4}$/);
    if (result) {
        return result[0];
    }
    return false;
}

これが重要である理由: 代入と条件を分離することで、意図が明確になります。読者はすぐに ユーザー が最初に抽出され、その後テストされます。正規表現の一致結果がキャプチャされ、その後評価されます。曖昧さがなく、認知負荷もありません。また、~のようなタイプミスもありません。 = vs === 明らかになります。

まとめ

代入と条件式を分離することは、特定の種類のバグを防止するシンプルなルールです。結合された操作を解析する際の認知的オーバーヘッドは、簡潔さから得られるとされる利点を上回ります。代入と評価が明確に区別された、明確で明示的なコードは、可読性を向上させ、バグを減らし、コードレビューをより効果的にします。

よくある質問

ご質問がありますか?

ファイル読み込みのように、条件式での代入が慣用的なケースについてはどうですか?

while (line = file.readline()) のような記述が一般的な言語においても、現代のベストプラクティスでは明示的な分離が推奨されます。JavaScriptでは、for (const line of fileLines) のようにイテレータプロトコルを使用します。Python 3.8以降では、条件式での代入が真に必要とされる場合にウォーラス演算子 := を使用することで意図を明確にできますが、それでも個別のステートメントの方がより明確になるかを検討してください。

代入と条件式を分離することにパフォーマンスへの影響はありますか?

いいえ。現代のJavaScriptエンジンは、両方のパターンを同一に最適化します。分離によって変数宣言が1つ追加されますが、コンパイル後のランタイムコストはゼロです。知覚されるパフォーマンスの違いは、バグの防止と可読性のメリットに比べてごくわずかです。まずは明確なコードを書き、プロファイリングで実際のボトルネックが特定された場合にのみ最適化してください。

if ((match = regex.exec(str)) !== null) のようなパターンをどのように処理しますか?

それを2つのステートメントに分割します:const match = regex.exec(str); if (match !== null)。あるいは、より良い現代的な代替手段を使用します:const match = str.match(regex); if (match)。match()が失敗時にnullを返し、それがfalsyであるため、明示的なnullチェックは冗長になります。これにより明瞭さが向上し、意図が明確になります。

戻り値のために意図的に使用される代入についてはどうですか?

意図的であることと、それが良いプラクティスであることは異なります。条件式で代入の戻り値に依存するコードは、メンテナンス上の危険を生み出します。将来の編集者が、タイプミスのように見えるものを「修正」してしまう可能性があります。このパターンをどうしても使用する必要がある場合は、その理由を説明するコメントを追加してください。ただし、コードをより明確に再構築できないか再検討することをお勧めします。

このルールは三項演算子に適用されますか?

はい。`const x = (y = getValue()) ? y : defaultValue`のような記述は避けてください。これはif文よりも読みにくいです。代わりに、`const y = getValue(); const x = y ? y : defaultValue;`を使用してください。あるいは、より良い方法として、nullish coalescing演算子を使用してください: `const x = getValue() ?? defaultValue;`。現代の演算子は、これらの扱いにくいパターンを避けるために特別に存在します。

リンターや静的解析ツールは、このパターンをどのように扱いますか?

ほとんどの最新のリンターは、デフォルトまたは設定により、条件式内の代入を警告します。通常、意図的な代入を示すために `((x = y))` のように余分な括弧を必要としますが、これはコードの臭い(コードスメル)です。リンターの例外を無効にして、コードを適切に修正する方が良いでしょう。静的解析ツールはCI/CD中にこれらのパターンを検出し、本番環境への到達を防ぐことができます。

今すぐ、安全な環境へ。

コード、クラウド、ランタイムを1つの中央システムでセキュアに。
脆弱性を迅速に発見し、自動的に修正。

クレジットカードは不要です。 | スキャン結果は32秒で表示されます。