Claude Code でリファクタリングや新機能追加を行うと、既存機能の出力品質が意図せず劣化することがある。機能は正しく動いておりテストも通るが、ユーザーが期待する情報が出力から消えている。この記事では、実際に遭遇した「静かなデグレード」の事例と、出力仕様テストによる対策を紹介する。
何が起きたか
日本株・BTC のトレーディングシステムで、日次の投資提案を GitHub Issues に自動投稿している。このシステムにポートフォリオ統合最適化機能を追加した際、以下の流れで問題が発生した。
- 統合最適化機能を追加。成功時は 1 つの統合 Issue に「市場概要」「総評」を含む詳細な分析を出力する設計
- 失敗時のフォールバックとして、銘柄別 Issue + サマリー Issue を作成するパスも実装
- テスト全パス、PR マージ
- ある日、ポートフォリオ最適化が失敗しフォールバックが発動
- サマリー Issue を見るとポートフォリオ一覧とリンクテーブルだけ — 「結局ホールドすべき?買うべき?」がわからない
統合パスには存在する「銘柄横断の総評」が、フォールバックパスでは最初から実装されていなかった。しかしテストは両方とも通っていた。
サマリー Issue の Before / After
デグレード状態(修正前):
| |
修正後:
| |
修正前は「データの一覧」しかなく、修正後は「判断に必要な分析」が加わっている。
なぜ Claude Code で起きやすいか
新しいパスに注力し、既存パスは最低限になる
Claude Code はタスクを忠実に実行する。「ポートフォリオ最適化を追加して」と指示すると、統合パス(成功時)は丁寧に設計される一方、フォールバックパス(失敗時)は「最低限動く」安全網として実装される。
人間の開発者でも同じことは起きるが、Claude Code はユーザーとして出力を見た経験がないため、「この情報がないと困る」という暗黙の期待を推測しにくい。
会話が切れるとコンテキストが消える
統合最適化を実装した会話と、フォールバックの問題に気づく会話は別セッション。前回の設計意図や「統合パスと同等の出力品質を保つ」という暗黙の要件は引き継がれない。
テストが「動作」を検証し「品質」を検証しない
既存のテストは以下を検証していた。
- 銘柄別 Issue が作成される
- DB に紐付けが保存される
- サマリー Issue が作成される
しかし「サマリー Issue に総評セクションが含まれるか」は検証していなかった。テストは全パスするため、デグレードに気づけない。
対策
以下の 3 つのアプローチで静かなデグレードを防止できる。
出力仕様をテストする(最も効果的)
「Issue が作成される」だけでなく「Issue に何が書かれているか」までテストする。
| |
これにより、将来のリファクタリングで総評が消えたら即座にテストが落ちる。
ただし、Claude Code に「テストを書いて」とだけ指示すると、「Issue が作成される」レベルの動作確認テストで終わりがちだ。出力内容のアサーションまで書かせるには、CLAUDE.md にルールとして明記するのが効果的。
| |
CLAUDE.md はセッションをまたいで参照されるため、毎回プロンプトで指示する必要がない。特に重要な関数にはコードコメントで # テスト時: 出力に必須セクション(総評, アクション分布)が含まれることを検証 のように補足しておくと、さらに確実になる。
複数パスがある機能は出力同等性を意識する
CLAUDE.md やコードコメントに明記しておく。
| |
Claude Code はコードコメントや CLAUDE.md のルールを忠実に守るため、明示しておくだけで効果がある。
出力のゴールデンファイルを用意する
ゴールデンファイルとは、「正しい出力はこうあるべき」という期待値を記録したファイルのこと。テスト時に実際の出力と比較し、差分があればテストを落とす。
たとえば、サマリー Issue のテンプレートと期待値ファイルを以下のように用意する。
テンプレート(Jinja2):
# {{ date }} 総合投資評価
## 現在のポートフォリオ
{{ portfolio_table }}
## 銘柄別計画
{{ plan_table }}
## 銘柄別サマリー
{{ per_stock_summary }}
## 総評
### アクション分布
{{ action_distribution }}
### 全体判断
{{ overall_judgement }}
## 子課題チェックリスト
{{ checklist }}
ゴールデンファイル(tests/golden/summary_issue.md):
| |
テストコード:
| |
完全一致が厳しい場合(日付やデータが動的に変わる等)は、必須セクションの見出しだけを抽出して比較する方法もある。
| |
この方法なら、テンプレートに新しいセクションを追加した際にゴールデンファイルも更新する必要があり、フォールバックパスだけ取り残される事態を防げる。
ゴールデンファイルを Claude Code に自動運用させるには
CLAUDE.md に「ゴールデンファイルを用意すること」と書くのは有効だが、それだけでは不十分な場面がある。実用的には 3 層の仕組みを組み合わせる。
| 層 | 何を書くか | 効果 |
|---|---|---|
| CLAUDE.md | 「外部出力を生成するテンプレートを修正する際は、対応するゴールデンファイルも更新すること」 | プロジェクト全体のルール |
| テストの存在自体 | tests/golden/ にファイルがあれば、テンプレート変更時にテストが落ちる | 仕組みで強制(指示に頼らない) |
| コードコメント | テンプレートに # golden: tests/golden/summary_issue.md と記載 | 対応関係が明示的になる |
一番強いのは 2 層目(テストの存在自体) だ。既にゴールデンファイルとテストがあれば、テンプレートを変更した時点でテストが落ちるので、Claude Code は自動的にゴールデンファイルも更新する。CLAUDE.md のルールは「最初の 1 回」ゴールデンファイルを作らせるために必要であり、2 回目以降はテストの仕組みが守ってくれる。
CLAUDE.md には「用意すること」よりも、いつ用意すべきか の判断基準を書くほうが実用的だ。
| |
まとめ
| 項目 | 内容 |
|---|---|
| 問題 | リファクタリングで新パスを追加した際、既存パスの出力品質がデグレード |
| 原因 | テストが「動作」のみ検証し、出力内容(品質)を検証していなかった |
| 対策 | 出力の必須セクションをアサーションでテスト |
| 教訓 | Claude Code は機能を正しく動かすが、ユーザー体験の同等性は暗黙の要件。明示的にテストで守る |
Claude Code は強力だが万能ではない。特に「正しく動くが情報が足りない」という静かなデグレードは、テストをすり抜けやすい。出力仕様のテストを習慣にすることで、AI 開発でも品質を維持できる。