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つ、すべて文字列型だ。

JSONL — 基本フォーマット(1行 = 1エッジ)
{"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 モジュールで静的解析)

Python — ast で呼び出し関係を抽出 → JSONL 出力
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 の場合)

Shell — jarviz-lib の実行
# 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マップを起動

Shell — jarviz-graph のセットアップと起動
# リポジトリのクローン
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をパッケージ単位に設定し、将来のサービス境界を探る