Skip to content

Latest commit

 

History

History
210 lines (139 loc) · 13.7 KB

File metadata and controls

210 lines (139 loc) · 13.7 KB

解析内容と出力形式

コンポーネント

psapは名前空間をコンポーネントとして扱います。

--depth 2の場合、App\Domain\Model\UserApp\Domainに属します。解析対象外のクラスへの依存はコンポーネント間の指標に含めません。

名前空間深度の選び方

--depthはディレクトリ探索の上限ではなく、解析したすべての型をどの名前空間階層でコンポーネントに束ねるかを指定します。

たとえば次の型がある場合、depthによって境界が変わります。

App\Domain\Model\Order
App\Domain\Service\Checkout
設定 コンポーネント
--depth 2 どちらもApp\Domain
--depth 3 App\Domain\ModelApp\Domain\Service

同じコンポーネント内の依存はCa、Ce、循環依存の対象になりません。そのためdepthを増やすと、それまで内部依存だった関係がコンポーネント間依存となり、指標や循環が新たに見えることがあります。反対に、実装詳細の名前空間まで独立コンポーネントとして扱うと、設計上はひとつのモジュールであるコードを過剰に分割する場合があります。

次の順で粒度を選びます。

  1. 既定の--depth autoで全体像を見る。autoは全型に共通する名前空間の次の階層を選ぶ
  2. 1コンポーネントに複数の独立した責務が含まれる、または内部の依存を確認したい場合だけdepthを1段増やす
  3. 隣り合うdepthのレポートを比較し、実際のアーキテクチャ境界に合う方を採用する
  4. CI、閾値、循環ベースラインでは採用したdepthを明示して固定する

depthが違えばコンポーネント境界も変わるため、Ca、Ce、I、A、Dや循環数を同じ条件の時系列データとして比較することはできません。

depthとパフォーマンス

depthを変更しても、対象ファイルの探索、PHPの構文解析、クラス間依存の抽出は同じ対象に対して1回行われます。この部分の処理量はdepthではなく、ファイル数、型数、依存参照数に左右されます。

一方、depthを深くしてコンポーネント数が増えると、次の処理と出力は大きくなる可能性があります。

  • コンポーネントごとのCa、Ce、I、A、D計算
  • コンポーネント間依存グラフのノードとエッジ
  • 循環依存の探索
  • HTML、Mermaid、PlantUMLなどの点、行、ノード

特にCaの計算は各コンポーネントに対して他コンポーネントのクラス依存を確認するため、コンポーネント数が多いプロジェクトでは影響が大きくなります。循環検出自体はコンポーネント数とコンポーネント間エッジ数に対して線形ですが、その前段で依存グラフを構築する必要があります。

最大depthを機械的に指定せず、意味のある名前空間境界で止めてください。レポートが大きすぎる場合はdepthを戻すほか、解析対象パスをパッケージ単位に分ける、生成物やテストを--excludeする方法があります。

解析カバレッジ

解析カバレッジは、選択したPHPファイルのうちpsapの解析パイプラインを完了できた割合です。レポートを設計判断に使う前に、解析対象の欠落がないか確認できます。

項目 定義
Discovered ソースパス配下で発見した、重複を除く.phpファイル。--exclude適用前
Selected DiscoveredからExcludedを除き、解析対象として選択したファイル
Analyzed 読み込み、UTF-8検証、PHPパース、名前解決を完了したファイル
Excluded --excludeに一致し、意図的に解析対象から外したファイル
Skipped 選択したが、読み込み、文字コード、パース、名前解決の問題で完了できなかったファイル
Analysis coverage = Analyzed / Selected

Excludedは意図的に対象から外したファイルなので、カバレッジの分母に含めません。Selectedが0件の場合は、何も解析していない状態を成功と誤認させないためN/Aと表示します。型宣言がないPHPファイルも、解析処理が正常に完了すればAnalyzedに含みます。

カバレッジ100%が示すのは、選択したPHPファイルをすべて処理できたことです。文字列で指定されたクラス、Reflection、DI設定など、解析しない参照まで検出したことを意味しません。

構造化診断

スキップしたファイルや評価できない解析結果は、理由、発生箇所、推奨する対処を持つ診断として出力します。JSONのdiagnosticsには次の値が含まれます。

項目 内容
code 外部ツールが判定に使える安定した識別子
severity infowarningerror
file / line 問題を特定できたソース位置。該当しない場合はnull
message 人が直接読むための英語メッセージ
context パーサーの詳細やFQCNなどの追加情報
actions 推奨する対処の識別子。exclude_fileには--excludeも付与

連携ツールは、変更される可能性のあるmessageではなくcodeで分岐してください。従来のJSON warningsは後方互換のため当面の間残し、同じ診断から生成した日本語文字列を出力します。textとMarkdownは英語、CLIの標準エラーは日本語で診断を表示します。

指標

指標 計算 見方
Ca 外部から内部へ依存するクラス数 大きいほど多くの外部クラスから使われている
Ce 内部から外部へ依存するクラス数 大きいほど多くの外部クラスを使っている
I Ce / (Ca + Ce) 0に近いほど安定、1に近いほど不安定
A 抽象型数 / 総型数 0は具象のみ、1は抽象のみ
D |A + I - 1| 0に近いほど安定度と抽象度のバランスがよい

interfaceabstract classを抽象型として数えます。classenumtraitは具象型です。

CaとCeがどちらも0のコンポーネントには、苦痛ゾーンや無駄ゾーンの判定を付けません。

解析結果が1コンポーネントだけの場合、Ca、Ce、I、Dは評価できません。テキストとMarkdownではN/A、JSONではnullになります。Aは単独のコンポーネントでも計算します。

安定度と抽象度の考え方

多くのコンポーネントから依存されている場所は、変更の影響が大きいため気軽に変えられません。そこがインターフェース中心(抽象)になっていれば、依存する側は抽象にぶら下がったままでいられるので、実装を差し替えても影響が波及しません。逆に、依存されているのに具象のままだと、変更のたびに影響が広がります。これが苦痛ゾーンです。反対に、誰からも依存されていないのに抽象的な場所は、客の来ない受付窓口のようなものです。これが無駄ゾーンです。

  • 不安定度 I
    • どれだけ「依存する側」か
    • I が低い = みんなから依存されている = おいそれと変更できない(安定)
    • I が高い = 誰にも依存されていない = 自由に変更できる(不安定)
  • 抽象度 A
    • インターフェースや抽象クラスの割合

つまり、依存され具合(安定度)と抽象度は釣り合っているのが望ましい、というのがSAPの考え方です。psapはこのバランスの崩れをDとして数値化します。一言でいうと「依存される者は抽象であれ、依存されない者は具象でよし」です。

循環依存

循環しているコンポーネント全体をひとつのグループとして検出します。各グループには次の情報が含まれます。

  • 循環に含まれる全コンポーネント
  • 実際に一周する代表最短経路
  • 代表経路に含まれないコンポーネント
  • 原因となるクラス間依存
  • 依存を作った構文、ファイル、行番号

大きな循環でも、まず短い経路から原因を追えます。

解析する参照

  • 継承、インターフェイス、trait
  • プロパティ、引数、戻り値の型
  • union型、intersection型、nullable型
  • new、静的呼び出し、静的プロパティ、クラス定数
  • instanceofcatch
  • PHP Attribute
  • @var@param@return@throws

docblockでは配列、generic、union、intersection、nullable型を分解し、useと名前空間を使ってクラス名を解決します。壊れたdocblockは無視します。

次の参照は解析しません。

  • class_exists('X')などの文字列参照
  • new $classNameなどの動的参照
  • 無名クラスの宣言と内部依存
  • 解析対象パス外のクラス

設定ファイル、ディレクトリベースのコンポーネント分類には対応していません。

出力形式

text

ターミナルで結果を確認するための形式です。解析カバレッジ、指標、問題領域、循環経路を表示します。-vを付けると所属クラスも表示します。

json

他のツールから処理するための形式です。fileCoveragediagnostics、全コンポーネント、クラス間依存、循環、依存根拠を含みます。fileCoverage.analysisCoverageは0から1の数値で、Selectedが0件の場合はnullです。

markdown

生成AIやコードレビューへ渡すための形式です。次の順で情報をまとめます。

  • 解析条件と概要
  • 解析カバレッジ
  • 優先して確認する箇所
  • 循環依存とコード位置
  • 依存の多いコンポーネント
  • 全コンポーネントの指標
  • 構造化診断と指標の読み方

大きな解析結果では依存根拠の例を制限し、省略件数を表示します。助言そのものは生成せず、生成AIが判断するための事実を出力します。

mermaid

IとAの散布図を生成します。

MermaidのquadrantChartでは円弧を描けないため、苦痛ゾーンと無駄ゾーンを象限ラベルとして近似表示します。点のI、A、Dとコンポーネントの分類は、HTMLを含む他の形式と同じ解析結果です。

循環依存に含まれるコンポーネントは、点のラベルに[cycle]を付け、通常より大きい赤い太枠の点として強調します。これは循環経路そのものではなく、I/A上で循環に関与するコンポーネントを見つけるための表示です。クラス依存やコード位置を含む循環の詳細は、text、markdown、JSON、PlantUMLで確認できます。

html

IとAの散布図を、ブラウザで探索できる自己完結HTMLとして生成します。外部通信やCDNは使いません。

  • 点へマウスを重ねるかキーボードフォーカスを移すと、コンポーネント名とI、A、D、Ca、Ceを表示
  • 点または一覧を選ぶと、コンポーネントに含まれるクラスを固定表示
  • 詳細欄の指標カードへマウスを重ねるかフォーカスすると、正式名称、計算方法、値の見方を表示
  • コンポーネント名やクラス名の検索、ゾーン、最小Dによる絞り込み
  • 同じ座標のコンポーネントをひとつの積層点として表示し、詳細欄から個別に選択
  • 循環依存がある場合、循環グループ数、代表となる最短経路、コンポーネント間・クラス間の依存、依存を検出したファイルと行番号を表示
  • 循環に含まれるコンポーネントを選ぶと詳細欄に通知し、該当する循環グループへ移動
  • 発見、選択、解析、除外、スキップしたPHPファイル数と解析カバレッジを表示
  • 既定の英語表示と、HTML内の言語セレクターによる日本語表示の切り替え

HTMLの点はクラスではなく、--depthで束ねた名前空間コンポーネントです。所属クラスは点を選択した後の詳細欄で確認します。

HTMLでは、実際のゾーン判定と同じく、(0, 0)(1, 1)を中心とする半径0.5の境界を円弧で表示します。そのため、象限で近似表示するMermaidとはゾーンの形が異なりますが、点の指標と座標は共通です。

plantuml

コンポーネントの依存グラフ、指標、循環依存を図示します。

.pumlファイルの生成と、PlantUML/Graphvizによる画像化は別工程です。依存関係が多い大規模な解析結果では、PlantUMLは多数のノードとエッジの配置を計算するため、I/A上へ点を配置するMermaidよりも画像化のCPU時間やメモリ消費が大きくなる場合があります。そのため、解析と.pumlの生成に成功していても、画像化に長時間を要したり、完了しなかったりすることがあります。

大規模な対象では、次の方法で描画対象を小さくしてください。

  • --depthを小さくして、コンポーネントを粗く束ねる
  • ディレクトリ単位で解析対象を分割する
  • --excludeでテスト、fixture、生成データなどを除外する

全体を俯瞰しながらコンポーネントを絞り込みたい場合は、自己完結HTML形式が適しています。MermaidとPlantUMLは、対象を絞った静的な図として使うと確認しやすくなります。