golang 標準庫間依賴的可視化展示

簡介

預覽

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

效果與源碼

相關鏈接

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章