リファクタリングパス
リファクタリングはかつて、感覚から始まりました。コードが扱いにくい:新しいものをどこに追加するかスクロールしなければならないクラス、一つの分岐を変更するために頭の中に保持しなければならない関数、8つのファイルを訪れる必要がある機能。同じ感覚が、何をそのままにすべきかも教えてくれました。触ったことのない汚いコードは害を及ぼさないので、放っておいても構いません。
コードを書くエージェントも、エージェントが書いたコードを主に読む開発者も、そうした感覚を持ちません。そのため、スメルが気づかれないまま蓄積します。エージェントは、関数に30番目の分岐を追加しても文句を言いません。そしてリファクタリングを依頼されると、誰も触らないコードも含めて、乱雑に見えるものを片付けようとします。このスキルは、その感覚の両半分を、測定可能な3つの要素として提供します。
- どこで感じるか:ヒートマップ。 摩擦は、変更が集中する場所でのみ問題になります。研究によると、ファイルサイズとファイルの変更頻度は、単一のスメルよりもメンテナンス上の問題を予測するのに優れています。
- 何を感じるか:スメル。 確立されたスメルのカタログ。それぞれについて、何をカウントするか、いつスメルではないか、そして長年の実践で構築された名前付きリファクタリングの手順書:references/smells.md。
- どれだけ感じるか:ウォークスルー。 次に予定されている、または発生しそうな変更を取り上げ、それが触れるすべてのものをリストアップします。
そして、判定は「どこ」と「何」を組み合わせます:
| スメルあり | スメルなし | |
|---|---|---|
| 頻繁に変更される | リファクタリングする | 問題ないと宣言する |
| ほとんど変更されない | そのままにする;せいぜい「後で」メモ | 何もしない |
「問題ない」の枠は「リファクタリング」の枠と同じくらい重要です。それは直感の抑制部分であり、レポートに表示されます。
2つの質問サイズ。 パスはリポジトリ全体をカバーします(ステップ1~7)。特定の領域に関する質問(「Xをリファクタリングすべきか?」)は、その領域のみに対して同じステップを実行します。複数の項目がない限りチェックリストはスキップされ、答えは「いいえ」になることもあります。どちらの場合も、最初にステップ1のスクリプトを --checklists 付きで実行します。以前のパスからのチェックリストがリストされた場合は、以前のパスの後 を先に読んでください。
1. どこ:ヒートマップ
python3 <このスキルのディレクトリ>/scripts/heatmap.py -C <repo> <scope ...>
<このスキルのディレクトリ> は、このSKILL.mdが置かれているフォルダです。python3 がコマンドとして使えない場合(Windowsでよくある)、python や py を使用してください。
- スコープ:ソースフォルダとテストフォルダ(例:
src testsやapp)。ドキュメントやアセットは含めません。プロジェクトのREADME、ディレクトリ一覧、およびエージェントの指示(AGENTS.md、CLAUDE.md)を読んで判断してください。テストファイル(名前(Board.spec.ts、test_x.py、XTest.php...)やフォルダで識別)はテーブルやペアから除外し、複数のファイルと一緒に変更される場合に別途リストアップされます。他のテスト名には--test GLOB、テストに見えるがそうでないファイルには--not-test GLOB(ヒートマップは疑わしいものを表示します)、生成ファイルには--ignore GLOBを追加してください。 - 期間:デフォルトでは最後の200コミット。ユーザーが開始点を指定した場合(「v2.0以降」、「課金追加以降」、「過去3ヶ月」)は、
--sinceで設定します:タグ、ブランチ、コミット、または日付。ボットのコミット、.git-blame-ignore-revsにリストされたバルクフォーマット、そして以前のチェックリストに記録されたコミットは自動的に除外されます。--exclude-commitsで、ベンダーインポートなど、触れたすべてをホットに見せてしまうようなコミットを除外します。 - データ不足? 期間内のコミットが約30未満の場合、カウントからホットファイルとコールドファイルを区別できません。期間を広げるか、履歴が短すぎてどこも指し示せないと判断し、スメルのみで評価してください。シャロークローン(ヒートマップがそう表示します)の場合は
git fetch --unshallowが必要です;ユーザーに確認してください。
--help ですべての列とオプションを説明します。
読み方
- ホットスポットはコミット数が多くサイズも大きいファイルで、リストでは最初に表示されます:コミット数×サイズで順序付けされます。小さなファイルへの多数のコミットは、各機能ごとに編集される設定ファイルのように、しばしば問題ありません。誰も触らない大きなファイルは、そのままにしておいてもコストがかかりません。
- まだホット?
recentは期間の後半(時間ではなくコミット数の半分)におけるコミット数をカウントし、lastは最新のコミット日です。両方を期間の日付と照らし合わせてください:recentが8で、すべてが後半の開始日に集中している場合、それはすでに冷めたバーストです。縮小したファイルは、おそらくすでに対処済みです。 relはファイルサイズに対する変更行数です。相対的チャーンは、絶対的チャーンよりもはるかに欠陥を予測します。1を大幅に超える場合、そのファイルは期間中に書き換えられたか成長したことを意味します。growth と併せて読んでください:コミットごとに成長するだけのファイルは追加のみの成長です。期間中に生まれたファイルは、成長がサイズと等しくなります。- ペアは同じコミットで一緒に変更されるファイルです。グループは互いにペアとなる3つ以上のファイルで、それらすべてを変更したコミット数でカウントされます;重複するグループは1つの領域としてまとめてリストされます。
- 異なるレイヤーからのファイルが強くペアになる場合、またはレイヤーをまたぐグループがある場合、通常はそれらにまたがる1つの機能(ショットガン手術)を意味します。
- 各ペアを結びつけるものを確認してください。インポート、呼び出し、または何かが名前を付ける契約(ルート、スキーマ、コントローラーとそのページ間の型ファイル)は可視的な依存関係です:境界を越えた1つの機能。そのようなリンクなしで一緒に変更されるファイル(例:ルールの2つのコピー、手作業で同期を保っている2つのリスト)は、隠れた依存関係を持ち、最も強い結合です。
- 他の多くのファイルとペアになるファイルは、すべての変更が通過するハブです。
- 自身のファイル以外にも複数のファイルと一緒に変更されるテストは、それらの内部に結合しており、リファクタリングを妨げます。ただし、それらのファイルがテストが追従しているだけのグループである場合は除きます。
- 多くのコミットがペアリングには大きすぎる場合(
--max-files)、スクワッシュマージを行うリポジトリのように、値を引き上げるか、ペアを部分的に読み取ってください。
- なぜ変更されるか。 上位のファイルのコミットメッセージ(
--show)を読み、機能ごとにグループ化してください。1つのファイルを変更する無関係な理由が多数ある場合、それは divergent change(発散する変更)であり、カウントよりも直接的にここに現れます。
2. 何:スメル
- ホットファイルを測定する:コードベース内で、同じ言語内で比較します。
python3 <このスキルのディレクトリ>/scripts/measure.py -C <repo> <scope ...>は、各ファイルをその言語の70パーセンタイル、80パーセンタイル、90パーセンタイルによるバンドに分類し、--show <file>で各関数を表示します。「大きい」は80パーセンタイルまたは90パーセンタイルのバンドです(バンドはカタログの下限も適用します);70パーセンタイルはより詳細な調査に値します。パーセンタイルが1つのファイルに偏っていると表示された場合は、測定 の基準点も参照してください。残り(ブランチ、パラメータ、凝集性)のカウント方法や、プロジェクトのリンターを使用する方法もそこに記載されています。 - 自分で上位2~3のコアファイルを読む。 数値は指し示しますが、読むことで判断します。
- 他のホットエリアをレビューする。 サブエージェントを実行できる場合は、2~3のエリア(レイヤーまたはディレクトリ)をそれぞれに割り当て、並行して実行します。各エリアのブリーフィングは references/review-brief.md から行ってください。サブエージェントがない場合は、自分でエリアを1つずつ、同じブリーフィングに従ってレビューします。
- 各スメルを確立された名前(長い関数、ショットガン手術、繰り返されるスイッチ…)で特定し、カウントする前にそのエントリの「スメルではないとき」を確認します。名前はその手順書へのインデックスです。
- 欠けているアイデアを探す。 スメルはクラスターで移動し、クラスターは通常、コードが常に使用しているが名前を付けたことのない1つのものを指します:状態、種類の定義、設定の単一ソース。スメルは一緒に移動する を参照してください。
- テストがホットコードに到達しているか確認する:未テストコード を参照してください。
- 気づいたバグはF項目としてリストに追加しますが、今は修正しません。まだ再現されていないバグは疑いに過ぎません:「確認:編集が週末ルールをスキップする」のようにタイトルを付け、証拠をメモに記します。その最初のステップは失敗するテストです;失敗するテストがない場合は、見つけた内容とともにスキップされます。
3. どのくらい:ウォークスルー
これは、かつての感覚に最も近いものです。また、項目を説得力のあるものにします。
- 次の変更を見つけます。計画された変更は推測された変更よりも優先されるので、まずユーザーが言及したもの(「チャンク7を始める前に」)から、次にプロジェクトの計画やロードマップ(あれば)から、そしてコミットメッセージから取得します:最も頻繁に行われる変更の種類(「新しいレポートタイプを追加」、「設定を追加」、「画面を追加」)。最も一般的な2~3種類を取り上げ、1種類だけにしないでください。
- 各種類について、その種類の最新の実際のコミットを取得し、それが触れたものをリストアップします(
git show --stat)。それが期間より古い場合でも、同じように使用し、その日付を記載します;今日のコードでのウォークスルーは、何が移動したかを示します。プロジェクトのドキュメントやエージェントの指示にその変更のレシピがある場合は、そのステップ数をカウントします。1つのものを追加するための5ステップのレシピは、それが難しいことを示しています。 - 書き込まずに、次の変更をウォークスルーします。読まなければならないファイル、編集する場所、同期を保つ必要のあるコピーをすべてリストアップします。
- 今日のカウントと、提案するリファクタリング後のカウントを書き留めます:「新しいレポートタイプは今日7つのファイルと3つのスイッチに触れますが、リファクタリング後は1つのファイルに触れるだけになるでしょう」。
計画された作業が行われるコールドファイルは、もはやコールドではありません:次の変更がそのコストを支払うため、テーブルの上部の行に配置します。次の変更が今日安価である場合、スメルはどんなに見えようと、ほとんどコストがかかっていません。
4. すべての調査結果を確認する
レビュアーの調査結果は事実ではなくリードであり、あなた自身の第一印象も同じです。リストに追加する前に、引用された行を開き、カウントを再実行し、成り立たないものを削除または修正します。レポートで確認した内容を記載してください。間違った項目によってユーザーがリファクタリングに進むと、確認にかかる1分よりもはるかに大きなコストが発生します。
次に、抑制を適用します。問題を見つけるように依頼されたレビュアーは常に何かを見つけますが、すべての調査結果を修正すると、発生しえないケースのために余分なレイヤーとテストが残ります。コールドなもの、「スメルではないとき」の記述によって除外されるもの、そしてウォークスルーが安価であると示すものは除外します。
5. 優先順位付け
すべての項目に安定したIDを付けます。ユーザーはIDで項目を参照するためです(「R4を開始」)、後続のセッションで行われる可能性があります。括弧内の名前は、チェックリスト(ステップ6)上の項目のフィールドです;F、L、D項目は note にそれらを記載します。
- R1、R2… リファクタリング。提案順に並べます。それぞれについて:
- 欠けているアイデアを結果として命名したタイトル(「CheckoutControllerをリファクタリング」ではなく「チェックアウトの状態に名前を付ける」)(
title); - スメルを名前、数値、
file:lineとともに(smells); - ステップ3から、それが容易にする変更の種類(
eases)、およびその変更のコスト(cost:todayとafter); - 手順書のステップとしての計画(
plan); - ファイル、サイズ(S、M、L)、リスク(低、中、高)(
files、size、risk); - 動作が変更されていないことを確認する方法(
verify)。
- 欠けているアイデアを結果として命名したタイトル(「CheckoutControllerをリファクタリング」ではなく「チェックアウトの状態に名前を付ける」)(
- F1… その過程で見つかった小さな修正。それぞれ個別のコミットにします。
- L1… 後で:スメルはあるがコールド、または近くのコードが次に触れられたときに行う価値があるもの。
- D1… ユーザーの判断:ドキュメント規約など、すべての変更をコスト高にする作業方法の変更。決定ではなくトレードオフを提示します。
リスクに対する見返りでランク付けします。見返りは、変更が将来の作業をどの程度容易にするかです:ヒートマップとウォークスルーが証拠です。スメルの優先順位は、どの種類が重要になりがちかを示します。長く複雑なコード、およびファイルをまたぐ変更が最も重要です。Feature EnvyやMiddle Manなどの結合スメルは最も重要ではなく、通りすがりに修正 されます。
リストは次のように順序付けます:
- ブロッカーが最初:内部に結合されたテスト(後続のすべてのステップが何かを壊したように見える原因となる)、およびテストでカバーされていないコード(その項目はテストの固定から始まるか、確認できないことを明示します(ステップ7));
- 次に、コピーペースト経路を削除する小さな項目;
- 大規模な移動はその後。多くの場合、最初にシームが必要だからです;
- 自分で検証できないもの(どのように聞こえるか、感じるか、見えるか)は最後。ユーザーが確認する必要があるためです。
6. チェックリストとレポートを書く
ステップ1のコマンドを --page .refactor-pass/<YYYY-MM-DD>.html で再実行します(今日の日付を使用)。assets/page.html からチェックリストを作成し、heatmap ブロックにヒートマップを書き込みます;再実行すると、ヒートマップのみが更新され、他は変更されません。pass ブロックをその下に書き込みます。project、started、base、updated は既に入力されています:
summary:全体の状態を2~3文で;items:すべての項目。ステップ5のフィールド、status(todo、doing、waiting(ユーザー待ち)、done、またはskipped(理由を outcome に記載))、およびcommitsとoutcome(新しい項目は空)。引き継がれた項目はfromを保持します。各項目はIDとステータスを1行で開始します。{"id": "R1", "status": "todo",のように、その後に他のフィールドが続きます。コメントの上のブロックに完全な形式が示されています。JSON整形ツールがレイアウトするようにはしないでください。ステップ7のステータス更新がその1行を編集するためです;left_alone:それぞれwhatとwhyを持つ;checks:プロジェクトの指示からのチェック、または指示がない場合は手作業で確認するもの;earlier:以前のパスの後、そのチェックリストと結果。チェックリスト上で完了したリファクタリングごとに1つ:そのid、title、eases、cost、次に来た実際のchangeとそれがactualに触れたもの(まだ来ていない場合は両方とも空)、およびnote。それ以外の場合はnull;log:dateとtextを持つもののリスト。新しいものから順に。
編集のたびに、python3 <このスキルのディレクトリ>/scripts/check_page.py <page> を実行します。壊れた括弧、スペルミスのフィールド、重複ID、または1行で開始していない項目を検出します。環境がHTMLページを公開またはプレビューできる場合は、編集内容を表示して、ユーザーが作業を依頼する前(またはステップ7の更新後)に確認できるようにします。
7. 作業して記録する
エージェントまたはユーザーがリファクタリングを実行するための注意事項です。
- 1つずつ、順番に。 すべてのリファクタリングを実行しようとしないでください。各R項目を開始し、上から順に進みます。
- テストが失敗した場合は、停止して調査します。
verifyチェックに合格しない変更はコミットしないでください。問題の原因を特定し、修正するか、項目をスキップしてoutcomeに理由を記録します。 - F項目は、それが属するR項目と一緒に、または単独でコミットします(ただし、分割されたまま)。
- 各完了した項目について、チェックリストのステータスとコミットハッシュを更新します。
earlierセクションに結果を追加します。 実際の変更が後で行われた場合(おそらく別のセッションで)、その情報をここに追加します。
以前のパスの後
以前のパスのチェックリストが存在する場合、その結果は earlier セクションにあります。新しいパスを開始する前に:
- 以前のR項目のうち、完了していないものやスキップされたものを確認します。
- それらは新しいパスに引き継がれるべきですか?もし前回のパスが「R3:支払いプロバイダーを統合する」を提案し、それがまだ行われていない場合、新しいパスでも引き続きホットである可能性があります。
fromフィールドを使用して、以前のチェックリストのIDを参照しながら、新しいチェックリストに追加します。"from": "2024-01-15/R3"のように。- 新しいヒートマップは、それ以降に行われた変更を反映するため、以前とは異なる領域を指し示す可能性があることに注意してください。
設定
PYTHON:Pythonインタープリタのパス(デフォルト:python3)SCOPE:スコープのデフォルト(例:src tests)MAX_FILES:1つのコミットで追跡する最大ファイル数(デフォルト:30)