codeprobe-code-smells について
コードの臭いとアンチパターンを調べる codeprobe スキルです。保守性、オブジェクト指向設計、結合度、可読性を対象とし、検出しきい値を設定できます。
主な機能
- 01長いメソッドと巨大クラスの検出
- 02データクランプとプリミティブ依存の特定
- 03継承とオブジェクト結合の確認
- 04デッドコードと過剰な抽象化の特定
- 05マジックナンバーと深いネストの検出
活用シーン
- 01保守性リスクのコードレビュー
- 02複雑なメソッドを簡素化する箇所の特定
- 03リファクタリング前のコード確認
- 04プロジェクト別の検出しきい値の設定
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ machineroom-dev/ codeprobe --skill codeprobe-code-smellsインストール後、Claude Code で/codeprobe-code-smellsを入力して読み込まれたか確認します。
詳細
スタンドアロンモード
オーケストレーター経由ではなく直接呼び出された場合は、まず次の手順を実行する必要があります。
- 出力仕様、実行モード、制約について、
../codeprobe/shared-preamble.mdを読み込みます(相対パスはこの SKILL.md の場所、つまりユーザーのプロジェクトではなく、同階層にあるcodeprobeスキルディレクトリを基準に解決します)。 - プロジェクトの技術スタックに基づいて、該当する参照ファイルを
../codeprobe/references/から読み込みます(同じパス解決方法を使用します)。 - ユーザーが別の指定をしない限り、デフォルトで
fullモードを使用します。
コードスメルとアンチパターンの検出
対象範囲
このサブスキルでは、次のカテゴリに分類されるコードスメルとアンチパターンを検出します。
- 肥大化 — 長すぎるメソッド、大きすぎるクラス、データの塊、プリミティブ型への執着
- オブジェクト指向の濫用 — 機能への偏り、不適切な親密さ、拒否された継承
- 変更の妨げ — ショットガン手術、変更の分散
- 不要な要素 — デッドコード、投機的な汎用化、中間者
- 結合 — 時間的結合
- 可読性 — マジックナンバー、ブール値の曖昧さ、深いネスト
検出対象外
- 生成コード — マイグレーション、コンパイル済み出力、ベンダーディレクトリ(
vendor/、node_modules/、dist/、build/、.next/)、自動生成ファイル(GraphQL codegen、Prisma client など)。 - 長いセットアップメソッドを含むテストファイル — テストのコンテキストは異なるため、テストデータを準備する長い
setUp()メソッドやbeforeEach()メソッドは想定内であり、問題ありません。 - エントリ数の多い設定ファイル — キーと値の組み合わせが50個ある設定ファイルは、「大きすぎるクラス」のスメルではありません。
- データマイグレーションファイル — これらは本質的に手続き型であり、長いメソッドを含むことがよくあります。
- リポジトリにチェックインされたサードパーティーコード(ベンダーライブラリなど)。
codeprobe-solidまたはcodeprobe-architectureですでに検出された構造上の問題 — 大きなクラスは、SRP違反や神クラスとしても検出される場合があります。このサブスキルでも引き続き検出して報告する必要がありますが、オーケストレーターが同じ場所で重複する指摘を統合します。
検出手順
設定可能なしきい値
分析の前に、プロジェクトのルートに .codeprobe-config.json ファイルがあるか確認します。存在する場合は、severity_overrides セクションを読み込み、次のデフォルト値を調整します。
| しきい値 | 設定キー | デフォルト |
|---|---|---|
| 長すぎるメソッドのLOC上限 | long_method_loc | 30 |
| 大きすぎるクラスのLOC上限 | large_class_loc | 300 |
| 深いネストの最大レベル数 | deep_nesting_max | 3 |
肥大化
| IDプレフィックス | スメル | シグナル | 検出方法 | デフォルトのしきい値 | 重大度 |
|---|---|---|---|---|---|
SMELL | 長すぎるメソッド | 関数/メソッドがLOCのしきい値を超えている | 各関数/メソッド本体の行数を数えます(空行と1行のコメントは除外)。long_method_loc のしきい値と比較します。しきい値の2倍を超えるメソッドは、重大度を「Major」に引き上げます。 | > 30 LOC | Minor |
SMELL | 大きすぎるクラス | クラスがLOCのしきい値を超えている | 各クラス定義の合計行数を数えます。large_class_loc のしきい値と比較します。しきい値の2倍を超えるクラスは、まだ「Major」でない場合、重大度を「Major」に引き上げます。「Critical」には引き上げません。大きなクラスは保守性の問題であり、本番環境の欠陥ではないためです。 | > 300 LOC | Major |
SMELL | データの塊 | 同じ3個以上のパラメーターが3箇所以上で一緒に渡されている | 関数/メソッドのシグネチャを検索します。3個以上のパラメーターのグループが、異なる3つ以上の関数シグネチャまたは呼び出し箇所に一緒に出現しているかを特定します。これらはパラメーターオブジェクトまたは値オブジェクトに抽出する必要があります。 | 3個以上のパラメーター、3箇所以上 | Minor |
SMELL | プリミティブ型への執着 | 値オブジェクトが適切な場面で文字列/整数が使われている | ドメイン概念を表す文字列/整数の変数を探します。メールアドレス(正規表現によるインライン検証)、金額(数値と通貨が別々に渡される)、電話番号、複数箇所で比較されるステータス文字列、郵便番号、複数レイヤーにわたってプレーンな文字列として渡されるUUIDなどが該当します。 | パターン認識 | Minor |
オブジェクト指向の濫用
| ID Prefix | Smell | Signal | How to Detect | Default Threshold | Severity |
|---|---|---|---|---|---|
SMELL | フィーチャーエンヴィ | メソッドが、自身のデータよりも他のオブジェクトのデータに3回以上アクセスしている | メソッドが $other->property または other.property を参照する回数と、$this->property または this.property(あるいは self.)を参照する回数を数えます。外部参照が内部参照の3倍以上であれば、そのメソッドは他のクラスに属する可能性があります。 | 外部アクセス3回以上 | 軽微 |
SMELL | 不適切な親密さ | クラスが他のクラスの private/protected な内部実装にアクセスしている | リフレクションを使ったアクセス(setAccessible(true)、protected フィールドにアクセスする __get、__set マジックメソッド)、friend class パターン、または慣例的に private とされるプロパティ(Python/JS で _ を接頭辞に付けたもの)への直接アクセスを検索します。 | 1件以上 | 重大 |
SMELL | 継承の拒絶 | サブクラスが継承しているにもかかわらず、親クラスのメソッドの大半を使用していない | サブクラスを調べます。親クラスに N 個の public メソッドがあり、サブクラスがその30%未満しかオーバーライドせず、さらに大半のメソッドで super/parent:: を呼び出していない場合、そのサブクラスには継承関係が本当に必要ではない可能性があります。 | 親クラスのメソッドの過半数が未使用 | 軽微 |
変更を妨げるもの
| ID Prefix | Smell | Signal | How to Detect | Default Threshold | Severity |
|---|---|---|---|---|---|
SMELL | ショットガン手術 | 1つの概念を変更するために5ファイル以上の編集が必要 | 多数のファイルに現れる単一の概念(フィールド名、ステータス値、ビジネスルールなど)を検索します。同じ定数、カラム名、ビジネス用語が、単一の信頼できる情報源に集約されずに5ファイル以上に現れる場合は、問題として指摘します。 | 1つの概念につき5ファイル以上 | 重大 |
SMELL | 発散的変更 | 1つのクラスが、無関係な理由で3回以上変更されている | 大規模なクラス(200 LOC超)を調べます。メソッドが、互いに無関係な個別の関心事に分かれているか確認します。1つのクラスがユーザー認証、メールの書式設定、レポート生成をすべて担っている場合、発散的変更が生じています。これら3つの領域のいずれかが変更されるたびに、このクラスも変更する必要があります。 | 個別の関心事が3つ以上 | 重大 |
不要なもの
| ID Prefix | Smell | Signal | How to Detect | Default Threshold | Severity |
|---|---|---|---|---|---|
SMELL | デッドコード | 到達不能な分岐、未使用のインポート、コメントアウトされたコードブロック | 次を検索します。(1)ファイル内のどこからも参照されていないシンボルの import/use/require 文、(2)コメントアウトされたコードブロック(ドキュメントではなくコード構文を含む、3行以上連続したコメント行)、(3)コードベースのどこからも呼び出されていない関数/メソッド(プロジェクト全体を Grep で検索)、(4)if (false) または if (0) ブロック、条件なしの return/throw/exit の後にある到達不能なコード。 | 1件以上 | 軽微 |
SMELL | 投機的一般化 | 唯一の実装しかなく、2つ目の実装も見込まれない抽象化/インターフェース | インターフェース、抽象クラス、またはジェネリック型パラメーターのうち、具体的な実装がちょうど1つしかないものを検索します。その抽象化が DI コンテナー設定やテストモックに現れず、ドメインにも将来のバリエーションを示す要素がない場合、時期尚早な抽象化として指摘します。 | 実装が1つのみ | 提案 |
SMELL | ミドルマン | ロジックを追加せず、他のクラスに処理を委譲するだけのクラス | すべてのメソッドが、注入された依存先の同じメソッドを呼び出して結果を返すだけで、ロジック、検証、変換、エラー処理を追加していないクラスを探します。このクラスは不要な間接層を追加しています。 | すべてのメソッドが単純な委譲のみ | 軽微 |
結合
| ID プレフィックス | におい | シグナル | 検出方法 | デフォルトのしきい値 | 重大度 |
|---|---|---|---|---|---|
SMELL | 時間的結合 | 特定の順序でメソッドを呼び出す必要がありますが、それを強制する仕組みがありません | 次のようなパターンを探します。(1)メソッド B より前にメソッド A を呼び出す必要があるのに、コンパイル時または実行時のチェックがない。(2)init()/setup() を process() より前に呼び出す必要があるのに、クラスでは先に process() を呼び出せてしまう。(3)共有の可変状態を扱うメソッドを順番に呼び出す必要があり、順序を入れ替えるとバグが発生する。"must call X first" や "call after Y" のようなコメントも確認します。 | 暗黙的な順序依存 | 重大 |
可読性
| ID プレフィックス | におい | シグナル | 検出方法 | デフォルトのしきい値 | 重大度 |
|---|---|---|---|---|---|
SMELL | マジックナンバー | ビジネスロジックに名前付き定数を使わず、数値や文字列リテラルをハードコードしている | 条件分岐、計算、ビジネスロジックで使われている数値リテラル(0、1、-1 を除く)と文字列リテラルを検索します。名前付き定数を使わず、ロジックの経路に現れる 86400、3.14159、"pending"、0.15 などの値を検出します。除外対象:配列インデックス、0/1 のループ境界、数学ライブラリで使われる一般的な数学定数。 | ロジックの経路にあるすべての値 | 軽微 |
SMELL | ブール値の判別困難 | ブール値のパラメーターを 2 個以上持つメソッド | bool/boolean パラメーターを 2 個以上持つ関数/メソッドのシグネチャを検索します。process(true, false, true) のような呼び出しは可読性に欠けます。名前付きパラメーター、列挙型、またはオプションオブジェクトの使用を提案します。 | ブール値のパラメーターが 2 個以上 | 軽微 |
SMELL | 深いネスト | インデントレベルがしきい値を超えている | 各関数のネストの深さを数えます。別の構文の中にネストされた if、for、while、foreach、switch、try、match は、それぞれ 1 レベルとして加算します。ネストが deep_nesting_max のしきい値を超えた場合に検出します。早期リターン、ガード節、またはメソッドの抽出を提案します。 | 3 レベル超 | 軽微 |
ID プレフィックスと修正プロンプトの例
すべての検出結果には SMELL- プレフィックスを付け、SMELL-001、SMELL-002 のように連番を振ります。
修正プロンプトの例
- "
UserService@registerの 45~90 行目をプライベートメソッドvalidateAndNormalizeInput()に抽出してください。このメソッドは 120 LOC あり、入力の検証(45~65 行目)、データの正規化(66~80 行目)、永続化(81~90 行目)という無関係な 3 つの処理を行っています。永続化はregister()に残し、ほかの 2 つの処理を抽出してください。" - "
app/Services/CacheService.phpの 55 行目にあるマジックナンバー86400を、クラスの先頭に定義した名前付き定数SECONDS_PER_DAY = 86400に置き換えてください。" - "
OrderProcessor.phpの 30 行目にあるprocessOrder(bool $isExpress, bool $requiresSignature, bool $isFragile)をリファクタリングし、3 つのブール値パラメーターの代わりにShippingOptions値オブジェクトを受け取るようにしてください。名前付きプロパティを持つShippingOptionsクラスを作成してください。" - "
PaymentGateway.phpの 88~105 行目にあるコメントアウトされたコードブロックを削除してください。これは以前の実装の不要なコードです。後で必要になった場合は、バージョン管理から復元できます。" - "
ReportGenerator.phpの 20 行目にあるgenerate()メソッドは 95 LOC あります。データ取得のロジック(25~50 行目)をfetchReportData()に、書式設定のロジック(51~85 行目)をformatReport()に抽出してください。generate()メソッドは、この 2 つの手順を調整する役割を担うようにしてください。"
コメント
Markdown 対応