Jarvizとは
Jarviz(GitHub: verizon/jarviz)は Verizon が公開した Java メソッド依存分析ツールだ。 2つのコンポーネントで構成される。
| コンポーネント | 役割 | 言語 |
|---|---|---|
| jarviz-lib | JAR ファイルを解析してメソッド呼び出し関係を JSONL に出力 | Java |
| jarviz-graph | JSONL を受け取り、ブラウザ上で3Dマップを表示 | Node.js |
重要な気づき:jarviz-graph は Java を知らない。
受け取るのは JSONL ファイルだけだ。
つまり 適切な JSONL さえ用意できれば、どの言語のコードも3Dマップで可視化できる。
💡 全体フロー
[JARファイル] → jarviz-lib → [coupling.jsonl] → jarviz-graph → [3Dマップ(ブラウザ)]
他言語の場合: [ソースコード] → 自作スクリプト → [coupling.jsonl] → jarviz-graph → [3Dマップ]
JSONLフォーマットの完全解説
これがこのシリーズの核心だ。
jarviz-graph が受け取る JSONL は 1行 = 1メソッド呼び出し関係 を表す。
フィールドは6つ、すべて文字列型だ。
{"appSetName":"ECommerceSystem","appName":"order-service","sourceClass":"com.example.OrderService","sourceMethod":"createOrder","targetClass":"com.example.InventoryClient","targetMethod":"checkStock"}
{"appSetName":"ECommerceSystem","appName":"order-service","sourceClass":"com.example.OrderService","sourceMethod":"createOrder","targetClass":"com.example.PaymentGateway","targetMethod":"charge"}
{"appSetName":"ECommerceSystem","appName":"inventory-service","sourceClass":"com.example.InventoryService","sourceMethod":"checkStock","targetClass":"com.example.CacheClient","targetMethod":"get"}
{"appSetName":"ECommerceSystem","appName":"user-service","sourceClass":"com.example.UserController","sourceMethod":"register","targetClass":"com.example.UserRepository","targetMethod":"save"}
| フィールド | 意味 | 3Dマップでの役割 | 例 |
|---|---|---|---|
appSetName |
アプリグループ名(システム全体) | 最上位クラスタ / データセット識別子 | "ECommerceSystem" |
appName |
アプリ / モジュール / サービス名 | 色分けの単位(色 = appName) | "order-service" |
sourceClass |
呼び出し元クラス(FQCN推奨) | ノード(エッジの起点) | "com.example.OrderService" |
sourceMethod |
呼び出し元メソッド名 | エッジのラベル(起点側) | "createOrder" |
targetClass |
呼び出し先クラス(FQCN推奨) | ノード(エッジの終点) | "com.example.InventoryClient" |
targetMethod |
呼び出し先メソッド名 | エッジのラベル(終点側) | "checkStock" |
⚠️ フィールド名は完全一致が必要
jarviz-graph はフィールド名をそのまま読み取る。
大文字・小文字の差異や別名(例: sourceCls)は動作しない。
他言語でJSONLを自作するときも6フィールドをすべて含め、名前を正確に合わせること。
他言語でJSONLを自作する
JSONL さえ作れれば言語は問わない。各言語で呼び出し関係を抽出し、6フィールドにマッピングするだけだ。
Python(ast モジュールで静的解析)
import ast
import json
import os
from pathlib import Path
APP_SET_NAME = "MyPythonSystem"
class CallExtractor(ast.NodeVisitor):
def __init__(self, module_name: str):
self.module = module_name
self.current_class = "(module)"
self.current_func = "(module)"
self.edges: list[dict] = []
def visit_ClassDef(self, node: ast.ClassDef):
prev_class = self.current_class
self.current_class = node.name
self.generic_visit(node)
self.current_class = prev_class
def visit_FunctionDef(self, node: ast.FunctionDef):
prev_func = self.current_func
self.current_func = node.name
self.generic_visit(node)
self.current_func = prev_func
visit_AsyncFunctionDef = visit_FunctionDef
def visit_Call(self, node: ast.Call):
target_class = "(unknown)"
target_method = "(unknown)"
if isinstance(node.func, ast.Attribute):
# obj.method() 形式
target_method = node.func.attr
if isinstance(node.func.value, ast.Name):
target_class = node.func.value.id # 変数名(型は不明)
elif isinstance(node.func.value, ast.Attribute):
target_class = node.func.value.attr
elif isinstance(node.func, ast.Name):
# 直接呼び出し function()
target_method = node.func.id
target_class = self.module # 同モジュール内と仮定
self.edges.append({
"appSetName": APP_SET_NAME,
"appName": self.module,
"sourceClass": f"{self.module}.{self.current_class}",
"sourceMethod": self.current_func,
"targetClass": target_class,
"targetMethod": target_method,
})
self.generic_visit(node)
def analyze_directory(src_dir: str, output_path: str):
all_edges = []
for py_file in Path(src_dir).rglob("*.py"):
# ファイルパスからモジュール名を生成
rel = py_file.relative_to(src_dir)
module_name = str(rel.with_suffix("")).replace(os.sep, ".")
try:
tree = ast.parse(py_file.read_text(encoding="utf-8"))
extractor = CallExtractor(module_name)
extractor.visit(tree)
all_edges.extend(extractor.edges)
except SyntaxError as e:
print(f"SyntaxError: {py_file} — {e}")
with open(output_path, "w", encoding="utf-8") as f:
for edge in all_edges:
f.write(json.dumps(edge, ensure_ascii=False) + "\n")
print(f"出力完了: {len(all_edges)} エッジ → {output_path}")
if __name__ == "__main__":
analyze_directory("src", "coupling.jsonl")
言語別の抽出難易度・課題まとめ
| 言語 | 主な抽出手段 | 精度の主な課題 | 難易度 |
|---|---|---|---|
| Java | jarviz-lib / ASM / JavaParser | ポリモーフィズム・invokedynamic | 中 |
| Kotlin | Kotlin Compiler API / detekt | ラムダ・拡張関数・コルーチン | 中 |
| Python | ast モジュール(標準) | 動的型付け → 呼び出し先クラスが不確定 | 低(シンプルに書ける) |
| TypeScript | ts-morph / TypeScript Compiler API | 型推論の複雑さ・ジェネリクス | 中 |
| Go | go/ast(標準) | インターフェース経由呼び出し | 低(標準ライブラリが充実) |
✅ 多言語マイクロサービスを1つのマップで俯瞰する
appName に各サービス名を設定し、各言語の JSONL を 1ファイルに結合して jarviz-graph に渡すと、
Java・Python・Kotlin のサービスが混在するシステム全体を一つの3Dマップで俯瞰できる。
異言語間の依存関係が視覚的に明らかになる。
セットアップと起動
① jarviz-lib で JAR から JSONL を生成(Java の場合)
# jarviz-lib の JAR をダウンロード
# GitHub: https://github.com/verizon/jarviz
# JAR を解析して coupling.jsonl を生成
java -jar jarviz-lib.jar \
--artifacts order-service.jar,inventory-service.jar \
--appSetName "ECommerceSystem" \
--output coupling.jsonl
② jarviz-graph で3Dマップを起動
# リポジトリのクローン
git clone https://github.com/verizon/jarviz.git
cd jarviz/jarviz-graph
# 依存関係のインストール(Node.js 18+ が必要)
npm install
# coupling.jsonl を data/ ディレクトリにコピー
cp /path/to/coupling.jsonl data/
# 開発サーバー起動
npm start
# → http://localhost:3000 でブラウザが開く
3Dマップの読み方
| 視覚要素 | 意味 | 活用方法 |
|---|---|---|
| ノードサイズ | そのクラスへの被呼び出し数に比例 | 大きいノード = ホットスポット / 神クラス候補 |
| ノード色 | appName ごとに自動で色分け |
モジュール / サービス間の依存を視覚的に把握 |
| エッジ | 呼び出し関係(有向) | 矢印の向きで依存の方向を確認 |
| エッジ密集 | 同一クラスへの多数の呼び出し | 循環依存・過度な結合の発見 |
| 孤立ノード | 他から呼ばれていないクラス | デッドコード候補の特定 |
実用ユースケース
| ユースケース | マップでの見方 |
|---|---|
| リファクタリング影響範囲の確認 | 変更予定クラスへのエッジ(被呼び出し)を追う |
| アーキテクチャの崩れ検出 | 本来依存してはならない方向のエッジを探す(例: インフラ → ドメイン) |
| デッドコードの特定 | 孤立ノード(エッジなし or 被呼び出し0)の一覧 |
| 神クラスの発見 | 突出して大きいノード・エッジが集中するノード |
| マイクロサービス化の設計 | appNameをパッケージ単位に設定し、将来のサービス境界を探る |