「コードの臭い」は、プログラマーが悪いコードに共通するソフトウェア設計パターンを説明するために使用する非公式な用語です。コードの臭いは、それ自体がバグや欠陥としてではなく、コードの品質が悪いことを示す警告サインとして理解するのが一番です。
「コードの臭い」という用語は、Kent Beck氏によって命名され、彼がマーティン・ファウラーと共著した1999年の名著『リファクタリング:既存コードの設計改善』、特に「コードの悪臭」というタイトルの章で広く知られるようになりました。Fowler氏自身は、コードの臭いを「通常はシステム内のより深い問題に対応する表面的な兆候」と簡潔に表現している。1
本物のソフトウェア・バグとは異なり、コードの臭いはソースコードのコンパイル、実行、目的の実行を妨げるものではなく、その存在が必ずしも実際の問題を示すものでもありません。家に入ってみると、何か異色の鍵やカビの臭いがしたりする状況を想像してみてください。その臭いの原因は、冷蔵庫内の期限切れ食品や、外に持ち出すべき生鮮食品かもしれません。より深刻な場合、カビや壁の中で腐敗している何かから発生する可能性もありますが、中には完全に安全に食べられる毒のあるチーズから発生する可能性もあります。いずれにせよ、臭いはさらなる検査が必要です。
経験豊富なソフトウェア・エンジニアにとって、コード内の特定のパターンは、単に「においがする」ものではありません。時間が経つにつれて、その存在を特定の複雑さ、非効率性、または将来の問題と本能的に関連付ける可能性があります。特定のコードの臭いに名前を付けて広めることで、こうした逆効果なパターン(または「アンチパターン」)とそれらが違反する設計原則に慣れやすくなります。そのため、コード・レビュー中にこれらのアンチパターンに気づき、修正される可能性が高くなります。Fowler氏が言うように、「匂いとは、定義上、すぐに気づくことができるもの、つまり嗅ぎ分けることができるものだ」。
コードの臭いは、対処しないままにしておくと、技術的負債の主な原因となります。ある調査によると、コード・レビューの不具合の75%はプログラムの実行には影響しないが、「ソフトウェアの進化性には影響する」とのことです。2たとえ今日はあるアンチパターンや悪い習慣がバグを起こしなくても、後でコードベースのバグやクラッシュ、セキュリティーの脆弱性が現れるリスクを大幅に高めます。少なくとも、既知のコードの臭いがあると、コードの可読性と保守性が低下し、理解、更新、改善が難しくなります。
AI活用のグローバル・トレンドや日本の市場動向を踏まえたDX、生成AIの最新情報を毎月お届けします。本ニュースレターは【日本語】で配信しています。登録の際はIBMプライバシー・ステートメントをご覧ください。
多くの一般的なコードの臭いは、Beck氏とFowler氏がリファクタリングで名付けた名前で知られていますが、標準的で普遍的に受け入れられた単一のコードの臭いのリストは存在しません。しかし、特定のコードの臭いを、特定の名前で呼ぶべきか、別の名前で呼ぶべきか、あるいは別のサブタイプのサブタイプではなく、独自のアンチパターンとして分類するかどうかは、あまり重要ではありません。重要なのは、コードの臭いの名前と説明が、ソフトウェア開発チームが生産的な設計原則を共有して実装するのに十分に役立つほど明確かつ直感的であるかどうかです。
同様に、コードの臭いのさまざまなカテゴリーについて、普遍的に合意された分類法は存在しません。本稿は主に、2023年にJerzyk氏とMadeyski氏によって提案された分類体系に基づいています。3彼らの調査結果は、codesmells.orgで管理されている便利なカタログに掲載されました。このカタログには、追加のコンテキスト、コード例、およびそれぞれに適したリファクタリング手法が記載されています。
Jerzyk氏とMadeyski氏は、コードの臭いの最も一般的に参照される分類法は、2006年にMäntylä氏とLassenius氏が提案した5つのグループ分けに従っていると指摘しています。彼らは、Fowler氏とBeck氏が提唱した22のコードの臭い(および彼ら自身の1つ)を、Bloaters、Object-Orientation Abusers、Change Preventers、Dispensables、Couplersという5つの異なるグループに分類しました。4正式に公開された文献と「灰色の資料」(ブログ、フォーラム、ウィキなど)の両方を徹底的にレビューする独自の試みを経て、Jerzyk氏とMadeyski氏は、56種類の「コードの臭い」を9つのグループに分類しました(この段落の前半で名前を付けた5つを含む)。
このセクションは主に、彼らが提案した9つのカテゴリー構造に従っていますが、そのようなグループ化は非公式かつ主観的なものであることに注意することが重要です。最も有用な分類法は、直感的に見つけられるものです。コードの臭いが引き起こすさまざまな種類の複雑さや非効率性を理解することで、個々の臭いを機械的に記憶するよりも、コードの臭いに関するより有意義な概念を得ることができます。Mäntylä氏は「コードの臭いを羅列した長いリストでは、全体像を見失いやすい」と述べています。5
Bloatersとはコードの臭いの一種で、メソッドやクラス、コード・ブロックが膨れ上がりすぎて扱いにくくなることがよくあります。これにより、可読性が低下し、コードの保守や変更が難しくなります。
Bloatersの例としては、以下のようなものがあります。
データの塊:どこにでも頻繁に一緒に現れる変数のグループ。
大規模クラス:多くのことを実行しようとしすぎて、変数が多すぎる、一貫性が欠けたクラス。
ロング関数(longメソッド):コード行数が多すぎるメソッド。
長いパラメーター・リスト:関数が正しく動作するためには、多すぎる引数が必要です。
プリミティブな固執:特殊な小さなオブジェクトの代わりにプリミティブなデータ型を使用します。
Change Preventersとは、その名のとおり、ソフトウェアの修正、追加、その他の開発を妨げるコードの臭いのことです。変更防止策の典型的な例は、単一の単純な変更を実装するためだけに、複数の場所で一連の編集を強制するコード構造です。
この種のコードの臭いは、ロバートCマーティンの単一責任原則(SRP)に違反しています。この原則はで、1つのコードモジュールへの変更は、1つの場所からしか発生できないようにすべきだと主張しています。Martin氏はSRPに関するブログ記事で「同じ理由で変化するものを集めます。さまざまな理由で変化するものは切り離してください。」と提案しています。6.
Change Preventersの例としては、次のようなものがあります。
ショットガン手術:1つの変更を実装するには、散在する多数のモジュールに同時に修正を加える必要があります(大規模クラスとは本質的に逆です)。
分岐型変更:散弾銃手術の逆のように、1つの変更を実施するには、1つのクラス内で多くの修正が必要になります。
コールバック地獄:多数のメソッドが多数のインデントされたタブと波括弧に深くネストされ、原因と結果が不明瞭になり、コードの可読性と保守性が損なわれるコード構造。
データ・ディーラーは、必要以上に多くのクラスや関数を通してデータを渡す。これにより、不必要な依存関係と複雑さが生まれ、その結果、データを保持または配布するだけのコード要素と、意味のある動作を提供しないことがよくあります。
データ・ディーラーの例としては、以下のようなものがあります。
仲介者:そのクラスは他者に任せることだけが仕事です。
トランプ・データ:データが、そのデータ自体を全く利用しない一連のメソッドを「ヒッチハイク」してしまうことです。
グローバルデータ:コードベース内のどこからでも変数を変更できる構造。そのため、何らかの不具合が発生した場合、コードベース内のすべての関数が疑わしいとみなされます。
Dispensablesとは、直感的に言って、削除しても問題のないコード要素のことです。これらを削除することで、コードベースはよりクリーンになり、読みやすくなりますが、全体的な機能には大きな影響はありません。
Dspensablesの例としては、以下のようなものがあります。
コメント:コメントは一般的に良いものですが、時にはコードの臭いを隠す「消臭剤」のように使われ、改善ではなく説明に使われることがあります。例えば、コメントがコードの特定のセクションで何が起こっているかを説明している場合、そのコメントは、直感的でない、またはそれ自体では読みにくいコードを隠すために使用されています。個々のコメントは、時間の経過とともに冗長になったり古くなったりすることもあります。より具体的なコメントの動作は、他のコードの臭いに表れています。
データクラス: フィールドとアクセサのみを含み、意味のある挙動を欠くクラス。
デッド・コード:時間の経過に伴うリファクタリングやその他の変更によって不要になったため、もはや実行されなくなったコード要素。
重複コード:複数の場所で同一または非常に似たコード構造が使われている場合。
遅延クラス(別名:遅延要素):存在するにはあまりにも機能が少なすぎるクラスまたは関数。
推測的一般性:将来の主要な機能をサポートするために不要なコードが追加された場合。
機能的悪用とは、オブジェクト指向のコードベースに関数型パターンを無理やり押し込むことで、オブジェクト指向プログラミングの原則を無視するコードの臭いのことです。
機能的悪用の例には、次のようなものがあります。
ループ:現代のオペレーションの代わりに従来のループを使うこと。Fowler氏はほとんどすべてのループを時代遅れだと考えていたが、Jerzyk氏は特に命令型ループが主な問題であると主張しました。
可変データ: 予期せぬ変化で予測不可能な副作用を引き起こす変数。
副作用(別名:不純な機能):その名前や本来の目的が示唆する以上のことを行うメソッド。
語彙乱用者とは、不適切な命名規則、一貫性のない書式設定、または難解な構文に起因するコードの臭いのことです。簡単に言えば、コードの文言が対応するコードの動作と直感的に一致せず、可読性が妨げられているインスタンスのことです。
語彙乱用者の例には、次のようなものがあります。
マジック・ナンバー:適切な説明や文脈なしにコードに名前のない数字が挿入されています。
誤ったコメント:周囲のコードが変更されたために、もはや正確ではなくなったコメント。コメントは実際には実行されないため、リンターやその他の自動チェックをすり抜けてしまうことがよくあります。
不思議な名前:関数や変数の名前が不適切で、実際の意図が隠されています。
誤ったメソッド名:一般的な慣習や期待に基づいて、意図的に誤解を招くような名前を持つ関数。例えば、getSomethingという関数は実際には何も返しません。
難読化ツールとは、不必要に複雑で入り組んだ、あるいは「巧妙な」方法で記述されたコード要素であり、コードの本来の意図を不必要な抽象化で隠してしまうものです。これらのコードの臭いにより、ソースコードは読みにくくなり、将来のプログラマーが必要に応じてコードを理解して変更することが困難になります。
難読化ツールの例としては、次のようなものがあります。
垂直方向の分離:関連するコード要素間の距離が不必要に大きい場合。例えば、メソッドの先頭で宣言された変数が、50行後まで使用されない場合など。
意図が不明瞭:その目的が直感的にも文脈的にも明確ではない関数、変数、名前、数値などのCategories。
複雑なブール式:二重否定や複雑な
巧妙なコード:動作するコードではあるが、既に広く理解されている組み込み機能やその他の従来型の解決策が存在する状況において、理解しにくい独自の言語を代わりに用いるコード。
Object-Orientation Abusers(またはオブジェクト指向の悪用者)とは、オブジェクト指向設計原則を完全に、あるいは正しく適用できていないコードの臭いのことです。例えば、switchステートメントは手続き型プログラミングでは便利ですが、オブジェクト指向プログラミングでは使用しないでください。
オブジェクト指向の悪用者の例には以下のようなものがあります。
異なるインターフェースを持つ代替クラス:まったく異なるメソッド名を使用して同様の機能を実行するクラス。
遺贈拒否:継承メソッドを必要としない、または使用しないサブクラス。
switch文(別名:条件付き複雑性、別名:繰り返しswitch文):コードベース全体で全く同じswitch文が重複して使用されています。
一時フィールド:通常は必要とされない場所で作成される変数で、特定の状況でのみ使用されるのが一般的です。
コードの臭いを理解する主なメリットは、それらを認識することでより効果的なリファクタリングが可能になることです。つまり、ソースコードの外部の挙動や機能を変更することなく更新する方法となります。技術的負債を減らし、長期にわたってコードベースへの迅速かつ効果的な改善と追加を促進するために、リファクタリングを通じて定期的にコードを整理することが不可欠です。
リファクタリングは主に、コードの臭いを特定して所在地するプロセスとして理解できます。その目標は、機能に悪影響を与える前に問題を修正プログラムすることであり、その時点でのプロセスはリファクタリングというよりはデバッグに似ています。
IBM® Bobなどの最新のエージェント・エンジニアリング・プラットフォームは、多くの場合、自動化されたリファクタリング提案をリアルタイムで提供します。コードの臭いの定義を丸暗記するのではなく、実際のコードベースのコンテキストで一般的なコードの臭いについて徹底的にトレーニングを行います。これにより、こうしたプラットフォームのAIコード・リファクタリング機能は、開発者が後で理解や修正が困難になるような扱いにくいAI生成コードの増加を伴わずに、アウトプット規模を拡張することを可能にします。
セキュリティーで保護された意図認識型の開発を実現するAIパートナー、IBM® Bobにより、ソフトウェア・デリバリーを加速します。
企業向けツールを活用し、AIアプリケーションの開発、デプロイ、管理をより迅速に実行します。
インテリジェントなAIモダナイゼーションにより、レガシー・システムを再構築します。