這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
簡介
國慶看完 << 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.5 datas.map((data)=>{ let itemId = data.ImportPath nodes.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.id if(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
相關連結