unsmell について
unsmellは、意図した動作を保ちながら保守性を高める開発者向けのリファクタリングスキルです。対象範囲と具体的な根拠に基づき、部分的またはリポジトリ全体の改善を進めます。
主な機能
- 01範囲を定めたリポジトリ調査
- 02根拠に基づくコード臭の分析
- 03動作を維持するリファクタリング手順
- 04TypeScriptの型安全性レビュー
- 05検証と完了報告
活用シーン
- 01指定したモジュールや機能のリファクタリング
- 02リポジトリの保守性問題の調査
- 03TypeScriptの型モデルの整理
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ dobroslavradosavljevic/ skills --skill unsmellインストール後、Claude Code で/unsmellを入力して読み込まれたか確認します。
同梱ファイルagents/openai.yaml
詳細
Unsmell
意図された動作を変えずに保守性を高めます。ユーザーが対象を指定していない場合はリポジトリ全体を対象とし、対象が指定されている場合は、その範囲と、動作を維持するために必要最小限の依存関係のみを変更します。
基本ルール
- コードを評価する前に、リポジトリのガイダンスを読み、規約を把握します。
- ユーザーが変更を許可していない限り、公開されている動作、API、データ形式、互換性を維持します。
- 機械的にコードを小さくするよりも、明快でまとまりのあるコードを優先します。
- 繰り返しの行、長い名前、大きなファイル、抽象化の不足をすべて不具合とみなしてはいけません。具体的な保守コストがある場合に限ります。
- 既存の適切なローカルパターンを再利用します。競合するアーキテクチャを導入してはいけません。
- レビューしやすい段階に分けて、まとまりのあるリファクタリングを行います。無関係なフォーマット変更や推測に基づく書き換えは避けます。
- 無関係なユーザーの変更を維持します。目的がすぐに分からないという理由だけで、コードを削除してはいけません。
- 依存関係を追加するのは、その価値が保守コストを明確に上回り、かつユーザーがインストールを許可している場合に限ります。
- TypeScript では、コンパイラーを黙らせるのではなく、モデルを改善するか値の型を絞り込みます。目に見える型エラーを、隠れた型の不健全さと引き換えにしてはいけません。
対象範囲の確定
編集前に、依頼を次のように分類します。
- 全体の unsmell: 生成ファイル、ベンダーコード、ビルド出力、キャッシュ、ロックファイル、スナップショット、マイグレーションを除く、ファーストパーティのコードベース全体を調査します。ただし、これらが問題の原因として指定されている場合は対象に含めます。
- 対象を絞った unsmell: 指定されたファイル、ディレクトリ、モジュール、機能、臭いのカテゴリ、依存関係の経路のみを調査・編集します。把握のために対象外の参照先もたどりますが、対象を絞ったリファクタリングに必要な場合を除き、変更しません。
境界が曖昧で、変更範囲に実質的な影響がある場合は、対象範囲について焦点を絞った質問を1つします。それ以外の場合は、推定した対象範囲を明示して作業を進めます。
ワークフロー
1. リポジトリの構成を把握する
プロジェクトの指示、マニフェスト、ソースのルート、テスト、生成コードの目印、モジュール境界、利用可能な品質確認コマンドを調べます。主要な言語とフレームワークの規約を特定します。
全体の unsmell では、広く検索して優先度の高い箇所に取り組みます。対象を絞った unsmell では、編集範囲を不必要に広げず、対象の呼び出し元、インポート、エクスポート、テスト、契約をたどります。
2. 根拠に基づく臭いを見つける
次の点を確認します。
- 無害な構造上の類似ではなく、ずれが生じる可能性のある知識や動作の重複。
- 長すぎる関数、過大なモジュール、責務の混在、凝集度の低さ。
- 深い条件分岐、複雑な制御フロー、ブール値フラグによる動作、隠れた状態遷移。
- プリミティブ型への過度な依存、ショットガン手術、フィーチャーエンビー、漏れやすい抽象化、不適切な結合。
- 弱いコンポジション:動作を分かりにくくする継承やラッパー、責務が多すぎるコンポーネント、プロップドリリング、ドメインロジックと混在したオーケストレーション。
- 参照情報や実行時・静的な根拠で確認できる、未使用、到達不能、古くなった、または冗長なコード。
- 誤解を招く、一貫性のない、長すぎる、または省略しすぎた名前。
- ディレクトリ階層の深さ、冗長なパス要素、所有境界の不明瞭さ。
- 一貫性のないエラー処理、リソースのクリーンアップ、非同期制御フロー、重複した検証。
- 根拠で裏付けられる、繰り返し発生する高コストな処理、意図しない N+1、不要な割り当て、回避可能なレンダリング。
- 重複している、脆弱な、実装に過度に結び付いたテスト、およびリファクタリング対象の動作に関するテストの不足。
- 以下に説明する TypeScript の抜け道や型モデルの臭い。
各候補について、場所、具体的なコスト、確信度、提案する変更、動作へのリスク、影響を受ける依存関係を記録します。指摘数を満たすためだけに、問題を作り出してはいけません。
TypeScript の標準
TypeScript のコードベースでは、特に以下を見つけて解消してください。
- 明示的または暗黙的な
any、安全でないunknownの扱い、as any、as unknown as Tなどの二重アサーション、互換性を強制するためだけに使われている型アサーション。 - 正当な理由のない非 null アサーション、確実な代入を示すアサーション、不正な状態を覆い隠すオプショナルチェーン、データの欠落を隠すフォールバック値。
@ts-ignore、正当な理由のない@ts-expect-error、無効化された型情報を利用するリンタールール、ソースコードのエラーを隠すために使われている生成済み宣言ファイル。object、Function、{}、制約のないRecord<string, unknown>、曖昧な文字列識別子、有効なドメインの状態を超えて拡張されたユニオンなどの広すぎる型。- 重複または矛盾するインターフェース、ソーススキーマや API から導出できる手書きの型、乖離する可能性のある並行したランタイム定義と型定義。
- 任意プロパティの過剰な使用、ブール値の状態の組み合わせ、不可能な状態を許容する nullable なモデル。適切な場合は、判別可能なユニオンと明示的な状態遷移を優先してください。
- より強い保証をもたらさず、複雑さを増すジェネリック型パラメーター、条件型、オーバーロード、ユーティリティ型、型レベルの抽象化。
- 誤った分散、可変データを読み取り専用として公開すること(またはその逆)、未検証のインデックスアクセス、安全でないキーの反復処理、リテラル型の消失。
- 信頼できない境界でのランタイム検証の欠如。静的型は、ネットワーク、ストレージ、環境、ユーザー、解析済みデータを検証しません。
- 型のみのインポート/エクスポートの誤り、循環する型依存関係、肥大化したバレルファイル、意図しない公開エクスポート、内部実装が漏れるモジュール境界。
- 拒否を隠す Promise 型やコールバック型、破棄された Promise、同期と非同期の契約の混在、実行時の動作より狭く型付けされたエラー。
根本となる契約、データモデル、制御フローの型絞り込み、または境界での検証を修正してください。推論、satisfies、実際のランタイムチェックを伴う型述語、判別可能なユニオン、網羅性を確認する never チェック、プロジェクトでスキーマライブラリをすでに使用している場合はスキーマから導出した型を優先してください。アサーションは、不変条件が証明されていてもコンパイラーに表現できない場合にのみ使用し、範囲を狭く保ってください。不変条件が自明でない場合は、その内容を記載してください。
対象を絞った整理では、適用範囲が広がるため、コンパイラーのオプションをむやみに厳格化しないでください。TypeScript の全面的な臭いコード解消では、tsconfig の厳格さを確認し、作業範囲と互換性の制約内で発生したエラーを解消できる場合に限り、より強いオプションの推奨または有効化を検討してください。
3. 優先順位を付ける
作業は次の順で進めてください。
- 正確性に関わる危険と、安全でないライフサイクルの挙動。
- コストの大きい重複と境界の問題。
- 頻繁に変更されるコードや中心的なコードの複雑さ。
- 構成と凝集度の改善。
- 命名、ファイル構成、局所的な一貫性。
根本原因を取り除き、その後の変更を簡単にする修正を優先してください。価値の低い変更は避けてください。
4. パスごとにリファクタリングする
各パスでは、次の手順に従います。
- テスト、コントラクト、呼び出し箇所、型から現在の動作を把握します。
- コードの問題を解消するために、まとまりのある最小限の構造変更を行います。
- 移動や名前変更によって影響を受けるインポート、エクスポート、参照、テスト、ドキュメント、設定を更新します。
- 利用可能な範囲で、最も対象を絞った検証を実行します。
- 結果を読み直し、意図しない間接化、意味の変更、新たな問題がないか確認します。
次の方針をデフォルトとして適用します。
- 安定した一つの概念を表す場合に限り、共有コードを抽出します。誤った抽象化を作るより、多少の重複を残すことを優先します。
- ファイルは、任意の行数の上限ではなく、責任と変更頻度に基づいて分割します。
- 明示的な入力と出力を持つ小さな単位を組み合わせます。オーケストレーションはドメインロジックや境界の副作用から切り離します。
- 読みやすさが向上する場合は、ガード節や目的を絞ったヘルパーを使って制御フローを平坦化します。
- 名前は、単に短くするのではなく、分かりやすさを基準に変更します。短く曖昧な名前より、長くても正確な名前のほうが適切な場合があります。
- ファイルやフォルダーを移動または改名するのは、インポートの変更量に見合うほど、ナビゲーションや所有関係が改善される場合に限ります。
- デッドコードを削除する前に、動的読み込み、リフレクション、フレームワークの規約、スクリプト、外部またはパブリックな利用箇所を確認します。
- 理由、不変条件、制約を説明するコメントは残し、コードの明白な動作を説明するだけのコメントは削除します。
- TypeScript では、キャスト、抑制、より緩いジェネリクス、見せかけのデフォルト値、戻り値の型の拡大によってエラーを解決しないでください。有用な型推論とパブリックコントラクトを維持するか、強化します。
5. 指定された範囲を完了する
全体のコードの問題を解消する場合は、ファーストパーティーコードに対応可能な確度の高い、または中程度の問題が残らなくなるまで、調査とリファクタリングのパスを繰り返します。代表的な問題を一つ直しただけで終了しないでください。
対象を絞ったコードの問題解消では、指定された対象がクリーンになり、検証されるまで作業します。隣接領域を機会に乗じてリファクタリングしないでください。
6. 検証する
変更内容に応じて、リポジトリに既存のフォーマッター、リンター、型チェッカー、テスト、ビルド、アーキテクチャチェックを実行します。TypeScript では、ファイルを出力しない型チェッカーを実行し、すでに設定されている場合は型を考慮したリンティングも使用します。まず対象を絞って実行し、コードベース全体のリファクタリング後には、関連する既存チェックのうち最も広い範囲を実行します。承認なしに不足しているツールをインストールしないでください。
検証を実行できない場合は、何を検証できなかったのか、またその理由を正確に報告します。除外領域、不確かな動的参照、既存の失敗するチェックが残っている場合、コードベース全体がクリーンであると主張しないでください。
完了レポート
次の内容を要約します。
- 調査した範囲と除外事項。
- 解消した根本的な問題と、それによって得られた構造上の改善。
- 重要な名前変更、移動、抽出、統合、削除。
- 実行した検証とその結果。
- 残っている問題と、それぞれの理由:対象範囲外、確度が低い、動作変更が必要、前提条件が不足、または変更量に見合わない。
レポートは簡潔にまとめます。すべての編集内容を列挙するのではなく、完了した成果から述べてください。