簡介
國慶看完 << Go 語言圣經 >>,總想做點什么,來加深下印象.以可視化的方式展示 golang 標準庫之間的依賴,可能是一個比較好的切入點.做之前,簡單搜了下相關的內容,網上也要討論,但是沒有發現直接能拿過來用的.標準庫之間,是必然存在依賴關系的,不同庫被依賴的程度必然是不一樣的.但究竟有多大差別呢?
以下內容,數據源自真實環境的 golang 1.9 版本的標準庫.所以,本文不僅是一篇可視化相關的討論文章,更是提供了一個可以直接探究 golang 標準庫間依賴關系的快速梳理工具.
數據準備
標準庫各個包之間的相互關系,可以直接通過命令獲取,然后簡單變換為一個標準的 JSON 對象:
go list -json std
示例輸出:
{"Dir": "/usr/local/go/src/archive/tar","ImportPath": "archive/tar","Name": "tar","Doc": "Package tar implements access to tar archives.","Target": "/usr/local/go/pkg/darwin_amd64/archive/tar.a","Goroot": true,"Standard": true,"StaleReason": "standard package in Go release distribution","Root": "/usr/local/go","GoFiles": ["common.go","format.go","reader.go","stat_atimespec.go","stat_unix.go","strconv.go","writer.go"],"IgnoredGoFiles": ["stat_atim.go"],"Imports": ["bytes","errors","fmt","io","io/ioutil","math","os","path","sort","strconv","strings","syscall","time"],"Deps": ["bytes","errors","fmt","internal/cpu","internal/poll","internal/race","io","io/ioutil","math","os","path","path/filepath","reflect","runtime","runtime/internal/atomic","runtime/internal/sys","sort","strconv","strings","sync","sync/atomic","syscall","time","unicode","unicode/utf8","unsafe"],"TestGoFiles": ["reader_test.go","strconv_test.go","tar_test.go","writer_test.go"],"TestImports": ["bytes","crypto/md5","fmt","internal/testenv","io","io/ioutil","math","os","path","path/filepath","reflect","sort","strings","testing","testing/iotest","time"],"XTestGoFiles": ["example_test.go"],"XTestImports": ["archive/tar","bytes","fmt","io","log","os"]
}
梳理過的數據源,參見: https://raw.githubusercontent.com/ios122/graph-go/master/data.js
可視化原理
主要涉及一下內容:
可視化顯示,使用的是 echarts
使用原始數據的 ImportPath 而不是 Name,來作為每個數據節點的唯一id.這樣是因為 golang 本身的包命名規范決定的.
使用原始數據的 Imports 字段,來確定標準庫包與包之間的相互依賴關系.golang是不允許循環依賴的,所以一些循環依賴相關的問題,不需要考慮.
節點的大小,和包被其他包引入的次數成正相關.這樣做,被依賴越多的包,圖上最終顯示時,就會越大.常用包和不常用包,一目了然.
數據整理
就是把原始數據,處理成 echarts 需要的數據,這里簡要說下最核心的思路:
echarts 顯示相關的代碼,很大程度上參考了 graph-npm
節點坐標和顏色,采用隨機坐標和顏色,以去除節點和包之間的聯系.我認為這樣處理,能更純粹地觀察標準庫包與包之間的聯系.
需要一個 edges 來記錄包與包之間的依賴關系.在每次遍歷 Imports 時,動態寫入.
需要一個 nodes 來記錄包自身的一些信息,但是其 size 參數,需要計算過所有依賴關系后再填入.
使用 nodedSize 來記錄每個包被依賴的次數,為了提升效率,它是一個字典Map.
/* 將原始數據,轉換為圖標友好的數據. ImportPath 作為唯一 id 和 標簽;Imports 用于計算依賴關系;節點的大小,取決于被依賴的次數;*/
function transData(datas){/* 存儲依賴路徑信息. */let edges = []/* 存儲基礎節點信息. */let nodes = []/* 節點尺寸.初始是1, 每被引入一次再加1. */let nodedSize = {}/* 尺寸單位1. */let unitSize = 1.5datas.map((data)=>{let itemId = data.ImportPathnodes.push({"label": itemId,"attributes": {},"id": itemId,"size": 1})if(data.Imports){data.Imports.map((importItem)=>{edges.push({"sourceID": importItem,"attributes": {},"targetID": itemId,"size": unitSize})if(nodedSize[importItem]){nodedSize[importItem] = nodedSize[importItem] + unitSize}else{nodedSize[importItem] = unitSize}})}})/* 尺寸數據合并到節點上. */nodes.map((item)=>{let itemId = item.idif(nodedSize[itemId]){item.size = nodedSize[itemId]}})return {nodes,edges}
}
效果與源碼
- github 源碼: https://github.com/ios122/graph-go
- echarts 在線預覽: http://gallery.echartsjs.com/editor.html?c=xSyJNqh8nW
相關鏈接
- echarts
- graph-npm