こんにちは、テクマトリックスの酒井です。

以前の記事では、プルリクエストで変更された関数の制御フローグラフを生成し、プルリクエストにコメントする仕組みを作成しました。これにはソースコード解析ツール Understand (以下、Understand)を利用しました。今回は、この仕組みを応用して Subversion に保存されたソースコードに対してグラフを生成することと、グラフ生成時の課題に取り組みました。この記事では、これらの取り組みについてご紹介します。

Subversion への対応

ソースコードが Git リポジトリで管理されている場合、 Understand を利用して Git リビジョンを指定した比較プロジェクトを作成し、現在作業中のソースコードとの間の変更差分を簡単に特定することができます。しかし、ソースコードが Subversion リポジトリで管理されている場合は、比較プロジェクトを作成することはできません。ただし、 Subversion に限らず、 2 つのリビジョンに対してチェックアウトと Understand プロジェクトの作成を行い、片方のプロジェクトから他方のプロジェクトを参照することで、変更差分を特定することができます。

また、 Subversion には GitHub のようなプルリクエストに相当する UI が通常存在しません。そのため、今回はグラフファイルを共有フォルダに保存すると同時に、以前の記事で作成したプルリクエストのコメントに似た簡易な HTML ファイルも作成して保存しました。開発者はこの HTML ファイルを開くことで、 2 つのリビジョンの変更内容をグラフィカルに確認することができます。

以上のデータフローを図にすると次のようになります。

データフロー

この仕組みはソースコード管理ツールに依存せずに利用できます。つまり、 2 つのリビジョンのソースコードがあれば、変更差分のグラフを生成することができます。

グラフ生成時の課題

グラフ生成時の課題とは

Subversion への対応を実装している最中に、グラフ生成スクリプトが終了しないという問題に直面しました。実行を観察してみると、ある関数のグラフ生成が数時間経っても終了しないことがありました。グラフが生成されないと HTML の生成もできないため、開発者に情報が提供されない状況になっていました。

対策の検討

グラフの生成に時間がかかる関数は、数百行以上の大量のコードを含んでいることが経験的に分かりました。このような関数のグラフ生成に時間がかかることは仕方がありませんが、スクリプトが終了しなくて良い理由にはなりません。グラフを生成するための Understand の API のパラメーターや Understand プロジェクトの設定には、対策に利用できそうな項目は見つかりませんでした。関数名や関数のメトリクス値に基づいてグラフ生成から除外することも検討しましたが、妥当な基準が見つからなかったため採用しませんでした。

最終的には、グラフの生成が開始された後、一定の時間が経過しても完了しない場合には中止するという方法を選びました。

対策の実施

1 つの関数のグラフを生成する子プロセスを起動し、その後一定時間が経過しても子プロセスが終了していなければ子プロセスを終了するように実装しました。以下に、実装した Python スクリプトの一部を示します。例外処理は省略しています。

import understand
import logging
from hashlib import sha256
from multiprocessing import Process, Queue
from pathlib import Path

def draw_graph_or_timeout(db_path: str, unique_name: str, image_dir: Path) -> str:
    """
    グラフを生成する子プロセスを実行し、タイムアウトを監視します。
    引数:
        db_path:     Understand プロジェクト名
        unique_name: 変更された関数を表す Understand のエンティティのユニーク名
        image_dir:   グラフファイルを出力するディレクトリ
    """
    result_queue = Queue()

    # 子プロセスを起動します
    p = Process(target=draw_graph, args=(db_path, unique_name, image_dir, result_queue))
    p.start()

    # 子プロセスが終了するか、一定時間が経過するまで待機します。状況に応じて時間を調整してください。
    p.join(timeout=60)

    # 子プロセスが終了したかどうかを確認します
    if p.exitcode is None:
        logging.warning("関数 '%s' のグラフの生成がタイムアウトしました。", unique_name)
        p.terminate()
        p.join()
        return None
    else:
        return result_queue.get(timeout=1)

def draw_graph(db_path: str, unique_name: str, image_dir: Path, result_queue: Queue) -> None:
    """
    グラフを生成します。子プロセスで実行されます。
    """
    db = understand.open(db_path)
    func = db.lookup_uniquename(unique_name)
    graph_file_name = f"controlflow_{ sha256(unique_name.encode()).hexdigest() }.svg"
    image_file = str(image_dir / graph_file_name)

    # グラフを生成します
    func.draw('Control Flow', image_file, 'Collapse=On;Comments=Off', 'Compare')

    result_queue.put(f"{image_dir.name}/{graph_file_name}")

この対策を実装することで、グラフが生成できない場合があっても確実に HTML を生成することができるようになりました!

なお、グラフ生成を開始してから中止するまでの時間は、全体の処理時間と生成された/されなかったグラフの数のバランスを見て調整すると良いと思います。

まとめ

SciTools Understand を利用して Subversion に保存されたソースコードの変更差分を確認できる制御フローグラフを生成する仕組みを作成しました。これにより、 Git 以外のソースコード管理ツールを利用している開発者も変更差分をグラフで確認し、より効果的なコードレビューが可能になります。また、グラフの生成に時間がかかる関数に対しても対策を施し、フィードバックや情報提供が確実にできるようにしました。ソフトウェア開発者の方々にとって有益な情報となることを願っています。

By tsakai

Jenkins関連のサービスやCloudBees製品を主に担当しています。 Certified CloudBees Jenkins Engineer (CCJE) および CloudBees CI DevOps Associate です。