はじまり
よーし、出来たぜえ・・・
お、何か作ったんかい?
ああ、昨日作ったスクリプトの関数の依存関係を示すクラス図を作りたかったんだが、
その時短になるスクリプトが出来たんだぜ。
じゃあ、早速教えてくれい。
スクリプトの概要
環境
まず、動作確認した環境は以下のとおりです。
- Python 3.9.7
- macOS Monterey
- 対応しているファイルの拡張子:PythonもしくはJavaScript(Google Apps Scriptとか基本的なJavaScriptの文法であれば行けると思います。)
何をするのか
ざっくり言うと、ソースの中を読み取って、どの関数がどの関数を参照しているかどうかをクラスダイアグラムとして、出力してくれます。
それでは、1つのファイルを対象にスクリプトを動かします。
本機能はPyPIに上げていないので、こんな感じでインストールして、
pip install git+https://github.com/landmaster135/landmasterlibrary.git
Pythonが実行できるディレクトリで以下のような感じで、実行します。printdependsonmd
は、setup.py内で'console_scripts'
に登録されているものです。
printdependsonmd '/Users/username/Downloads/BlogManagerSheet-main/youtube.gs' '.js'
実行すると、ターミナル上にMermaidの文が出力されます。
============ depends list on Markdown: start ============
```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{
}
```
============ depends list on Markdown: end ============
出力されたMermaidをREADME.mdに記述して、GitHubで表示するとこんな感じになります。
一旦上記のスクリプトを作ってクラス図にすることで、久しぶりにスクリプトを直すときに参照関係が一目で分かるようになったので、直すのが楽になりました。
何をやっているのか
処理の流れは以下の通りになっています。
ファイルから関数を取得する。(Pythonだと'def '
、JavaScriptだと'function '
を目印にして取得する。)
↓
その関数の中から、ファイルの中の関数がないかどうかを探す。
↓
依存関係を保持した辞書型オブジェクトを出力する。(ex. { '参照元': ['参照先A', '参照先B', ...] }
)
↓
辞書型オブジェクトからMermaid表記に出力する。(ex. 参照先 <|-- 参照元
)
最も煩雑だったところは、「その関数の中から、ファイルの中の関数がないかどうかを探す」〜「依存関係を保持した辞書型オブジェクトを出力する。」の部分でした。
いつから参照元の関数を切り替えるか、関数の記述ではないところを判断する部分はどこになるかなどを少し長めに考えました。
宣言部分は関数を探さないことにしたら、コードが綺麗になりました。(予約語'continue'
はすごい便利ですよね。)
スクリプトで悩ましいところ
ひとまずの目的は満たせたので良かったのですが、このスクリプトにおいて、以下の点が悩ましい・・・
- コメントや文字列の中にある関数名も拾ってきてしまう。
- 自分の関数も取ってきてしまう。
- 関数の中で複数回呼び出される場合、その回数分だけ参照関係を出力する。
- 他のファイルとの参照関係は取れない。
- モックやフィクスチャが入っていた場合を対応していない。
コメントや文字列の中にある関数名も拾ってきてしまう。
処理として、hogehoge(fuga)
みたいに関数内で使用されている場合は、参照先として捕捉して良いのですが、console.log(`hogehoge(fuga)`)
とされていたり、ロングテキストによるコメントの中などで関数名を使用されていた場合は、その関数を無視していいかどうかが一概には言えないと思っています。
console.log(`hogehoge(fuga)`)
の場合は、目印として関数名を記述しておきたいので参照しているとみなしたいこともあるかと思います。
ロングテキストによるコメントによると、改行されていると前後の行のクォーテーションを読まなければならなくなるので、実装するのがかなり大変になります・・・
自分の関数も取ってきてしまう。
これに関しても一概には言えず、ログ取得の際に自分の関数名を取得する意図があるかもしれませんし、再帰関数である可能性もあります。
そのため、自分を参照している場合は参照先から外すということも考えましたが、やめておきました。
関数の中で複数回呼び出される場合、その回数分だけ参照関係を出力する。
概要で出力したクラス図をご覧の通り、いくつかの関数から同じ関数に向けて複数本の矢印が伸びているのが確認できるかと思います。
この場合に、矢印の本数を1本にするようにした方が良いのかどうか悩みましたが、矢印の本数を複数本見せることで修正箇所が何箇所あるかどうかも確認できるようになっているので、そのまま、複数本の矢印を描画する状態にしておきました。
これは、先程挙げた「コメントや文字列の中にある関数名も拾ってきてしまう。」の点とも絡むところになります。
他のファイルとの参照関係は取れない。
あくまで、今回のスクリプトの実装範囲としては、「1つの」ファイル内の参照関係なので、他のファイルからインポートした関数の参照関係は描画できません。
出来たら更に便利なんですけどね・・・
しかし、実装にとても手間がかかりますね。コードの静的解析ツールを作るのってなかなか骨が折れるということが今回分かりました。
モックやフィクスチャが入っていた場合を対応していない。
主にテストコードが記述されているファイルに使用した場合、モックやフィクスチャが沢山入っていると思うので、そこに対応していないんですよね・・・
参照元の関数を切り替えたら、その宣言部分の行の前後を見ることになるのかな。実装が険しいですねえ・・・
おしまい
結構、抜けているところもあるけど、一旦は役立つツールになったので、よしとします。
Mermaidは、図をコードで管理できるのは良いけど、そのコードを打ち込むのが大変だから、その一助になるのはとても有り難いでやんす。
以下のリポジトリのsrc/landmasterlibrary/generaltool.py
というファイルの中に処理は入っています。ご参考ください。
以上になります!
コメント