簡介
國慶看完 << 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