【Python】1つのファイル内における関数の依存関係をMermaidの書式で出力する
はじまり



スクリプトの概要
こういうツールを作ったら便利なんじゃないかと思って、あったらあったで便利だったという話です。
何をするのか
ざっくり言うと、ソースの中を読み取って、どの関数がどの関数を参照しているかどうかをクラスダイアグラムとして、出力してくれます。
スクリプトを実行すると、ターミナル上にMermaidの文が出力されます。
============ depends list on Markdown: start ============# Mermaidの中身============ depends list on Markdown: end ============Mermaidの中身は例えばこんな感じです。
classDiagram getSpreadsheet <|-- getSheet getSheet <|-- getSheet getSheet <|-- getIssuesFromGss isPostedIssue <|-- getIssuesFromGss getDatesByRecords <|-- makeFoldersYetExist getDatesByRecords <|-- makeFoldersYetExist getDatesByRecords <|-- makeFoldersYetExist getDatesByRecords <|-- makeFoldersYetExist getDatesNotExist <|-- makeFoldersYetExist getDatesNotExist <|-- makeFoldersYetExist getDatesNotExist <|-- makeFoldersYetExist getDatesNotExist <|-- makeFoldersYetExist makeFolderByDates <|-- makeFoldersYetExist makeFolderByDates <|-- makeFoldersYetExist makeFolderByDates <|-- makeFoldersYetExist makeFolderByDates <|-- makeFoldersYetExist getIssuesFromGss <|-- getPartsOfIssues getIssuesFromGss <|-- getPartsOfIssues getIssuesFromGss <|-- getPartsOfIssues getIssuesFromGss <|-- getPartsOfIssues getPartsOfRecords <|-- getPartsOfIssues getPartsOfRecords <|-- getPartsOfIssues getPartsOfRecords <|-- getPartsOfIssues getPartsOfRecords <|-- getPartsOfIssues getPartsOfIssues <|-- makeFoldersIntoDrive getPartsOfIssues <|-- makeFoldersIntoDrive getPartsOfIssues <|-- makeFoldersIntoDrive getPartsOfIssues <|-- makeFoldersIntoDrive makeFoldersYetExist <|-- makeFoldersIntoDrive makeFoldersYetExist <|-- makeFoldersIntoDrive makeFoldersYetExist <|-- makeFoldersIntoDrive makeFoldersYetExist <|-- makeFoldersIntoDrive makeFoldersYetExist <|-- makeFoldersIntoDrive makeFoldersYetExist <|-- makeFoldersIntoDrive makeFoldersYetExist <|-- makeFoldersIntoDrive makeFoldersYetExist <|-- makeFoldersIntoDrive getFolderMovingInfo <|-- getFolderMovingInfo getFolderMovingInfo <|-- getFolderMovingInfo moveFoldersForYoutube <|-- moveFoldersForYoutube getDatesAlreadyPosted <|-- moveFoldersAlreadyPosted moveFoldersAlreadyPosted <|-- moveFoldersAlreadyPosted getFolderMovingInfo <|-- moveFoldersAlreadyPosted moveFoldersAlreadyPosted <|-- moveFoldersAlreadyPosted getFolderMovingInfo <|-- moveFoldersAlreadyPosted moveFoldersAlreadyPosted <|-- moveFoldersAlreadyPosted getFolderMovingInfo <|-- moveFoldersAlreadyPosted getFolderMovingInfo <|-- moveFoldersAlreadyPosted moveFoldersAlreadyPosted <|-- moveFoldersAlreadyPosted moveFoldersForYoutube <|-- moveFoldersAlreadyPosted moveFoldersAlreadyPosted <|-- moveFoldersAlreadyPosted moveFoldersForYoutube <|-- moveFoldersAlreadyPosted moveFoldersAlreadyPosted <|-- moveFoldersAlreadyPosted moveFoldersForYoutube <|-- moveFoldersAlreadyPosted moveFoldersForYoutube <|-- moveFoldersAlreadyPosted moveFoldersAlreadyPosted <|-- moveFoldersAlreadyPosted getPartsOfRecords <|-- manageFoldersInDrive getPartsOfRecords <|-- manageFoldersInDrive getPartsOfIssues <|-- manageFoldersInDrive getPartsOfRecords <|-- manageFoldersInDrive moveFoldersAlreadyPosted <|-- manageFoldersInDrive moveFoldersAlreadyPosted <|-- manageFoldersInDrive moveFoldersAlreadyPosted <|-- manageFoldersInDrive moveFoldersAlreadyPosted <|-- manageFoldersInDrive moveFoldersAlreadyPosted <|-- manageFoldersInDrive moveFoldersAlreadyPosted <|-- manageFoldersInDrive moveFoldersAlreadyPosted <|-- manageFoldersInDrive moveFoldersAlreadyPosted <|-- manageFoldersInDrive class getSpreadsheet{ } class getSheet{ } class isPostedIssue{ } class getIssuesFromGss{ } class getPartsOfRecords{ } class getDatesByRecords{ } class getDatesNotExist{ } class makeFolderByDates{ } class makeFoldersYetExist{ } class getPartsOfIssues{ } class makeFoldersIntoDrive{ } class getDatesAlreadyPosted{ } class getFolderMovingInfo{ } class moveFoldersForYoutube{ } class moveFoldersAlreadyPosted{ } class manageFoldersInDrive{ }出力されたMermaidをREADME.mdに記述して、GitHubで表示するとこんな感じになります。

一旦上記のスクリプトを作ってクラス図にすることで、久しぶりにスクリプトを直すときに参照関係が一目で分かるようになったので、直すのが楽になりました。

何をやっているのか
処理の流れは以下の流れになっています:
- ファイルから関数を取得する。(Pythonだと
'def '、JavaScriptだと'function 'を目印にして取得する。) - その関数の中から、ファイルの中の関数がないかどうかを探す。
- 依存関係を保持した辞書型オブジェクトを出力する。(ex.
{ '参照元': ['参照先A', '参照先B', ...] }) - 辞書型オブジェクトからMermaid表記に出力する。(ex.
参照先 <|-- 参照元)
最も煩雑だったところは、2〜3の部分でした。 いつから参照元の関数を切り替えるか、関数の記述ではないところを判断する部分はどこになるかなどを少し長めに考えました。
宣言部分は関数を探さないことにしたら、コードが綺麗になりました。(予約語'continue'はすごい便利ですよね。)
スクリプトで悩ましいところ
ひとまずの目的は満たせたので良かったのですが、このスクリプトにおいて、以下の点が悩ましい・・・
- コメントや文字列の中にある関数名も拾ってきてしまう。
- 自分の関数も取ってきてしまう。
- 関数の中で複数回呼び出される場合、その回数分だけ参照関係を出力する。
- 他のファイルとの参照関係は取れない。
- モックやフィクスチャが入っていた場合を対応していない。
コメントや文字列の中にある関数名も拾ってきてしまう。
処理として、hogehoge(fuga)みたいに関数内で使用されている場合は、参照先として捕捉して良いのですが、下記のように記述されていたり、ロングテキストによるコメントの中などで関数名を使用されていた場合は、その関数を無視していいかどうかが一概には言えません。例えば、目印として関数名を記述しておきたいので参照しているとみなしたいこともあるかと思います。
console.log(`hogehoge(fuga)`)ロングテキストによるコメントによると、改行されていると前後の行のクォーテーションを読まなければならなくなるので、実装するのがかなり大変になります・・・
自分の関数も取ってきてしまう。
これに関しても一概には言えず、ログ取得の際に自分の関数名を取得する意図があるかもしれませんし、再帰関数である可能性もあります。 そのため、自分を参照している場合は参照先から外すということも考えましたが、やめておきました。
関数の中で複数回呼び出される場合、その回数分だけ参照関係を出力する。
概要で出力したクラス図をご覧の通り、いくつかの関数から同じ関数に向けて複数本の矢印が伸びているのが確認できるかと思います。 この場合に、矢印の本数を1本にするようにした方が良いのかどうか悩みましたが、矢印の本数を複数本見せることで修正箇所が何箇所あるかどうかも確認できるようになっているので、そのまま、複数本の矢印を描画する状態にしておきました。
これは、先程挙げた「コメントや文字列の中にある関数名も拾ってきてしまう。」の点とも絡むところになります。
他のファイルとの参照関係は取れない。
あくまで、今回のスクリプトの実装範囲としては、「1つの」ファイル内の参照関係なので、他のファイルからインポートした関数の参照関係は描画できません。 出来たら更に便利なんですけどね・・・
しかし、実装にとても手間がかかりますね。コードの静的解析ツールを作るのってなかなか骨が折れるということが今回分かりました。
モックやフィクスチャが入っていた場合を対応していない。
主にテストコードが記述されているファイルに使用した場合、モックやフィクスチャが沢山入っていると思うので、そこに対応していないんですよね・・・
参照元の関数を切り替えたら、その宣言部分の行の前後を見ることになるのかな。実装が険しいですねえ・・・
おしまい


以上になります!
記事を共有
この記事が役に立ったなら、ぜひ他の人と共有してください!