詩と創作・思索のひろば

ドキドキギュンギュンダイアリーです!!!

Fork me on GitHub

型と名前によるGoのコード探索 ― gofind

思いつきでツールを作ってはリスのように忘れ、再発見しては新鮮な気持ちで便利に使う日々です。

一般にプログラミングにおいては、ソースコードを読むことに意外とばかにならない時間を使うもの。特に Go ではデフォルトで標準ライブラリのソースコードが手元にあり、コードを書く際よい教科書になるので、これを読むことも多いはず。

Go は静的に型付けされる言語なのでその点コードは読みやすいけれど、データ構造が不変ではないので、ある構造体のフィールドがどこで書き換わるのかを知るには、処理を追っていくしかない。名前で grep するのもひとつの手ではあるけど、精度はあまり期待できない。

そこで gofind。簡単に言うと、型やパッケージを含めた名前でもって Go のソースコードを検索するツールです。

go get github.com/motemen/gofind/cmd/gofind

使い方は以下の通り。

gofind <pkg>.<name>.[<field>] <pkg>...

コードを読んで型付けまで行うので、実行には少し時間がかかる。

第一引数が、コード中の出現を見つけたい型や関数のパッケージ名で修飾された名前。型が構造体である場合には、フィールド名をオプショナルにつけることができる。指定できるものは以下のようなところ。

  • 型名
  • 関数名
  • (構造体型の)フィールド名
  • メソッド名

以降の引数は検索対象のパッケージで、普通にインポートパスを指定してもいいし(net/http とか)、ディレクトリでもいい(./lib/web とか)。

すると、指定された型や名前を持つ変数とかメソッドの出現をハイライトしてくれる。

利用例

実例を見るのが早いので、以下でいろいろ検索してみます。

構造体型のフィールド

このフィールド、どこで更新・参照されるんだろう? というときに。gofind のもともとの動機です。

% gofind crypto/tls.Config.ServerName net/http
...
net/http/transport.go:1015:             if cfg.ServerName == "" {
net/http/transport.go:1016:                     cfg.ServerName = cm.tlsHost()
net/http/transport.go:1039:                     if err := tlsConn.VerifyHostname(cfg.ServerName); err != nil {
net/http/transport.go:2081:             ServerName:                  cfg.ServerName,
net/http/transport.go:2114:             ServerName:                  cfg.ServerName,

面白いのは、構造体リテラルのフィールド名も発見できること。画像で見るとわかりますが、ServerName: もハイライトされている。

f:id:motemen:20161028084521p:plain

構造体の無名初期化も検出できるのが便利。これは grep では発見しづらいところ。

% gofind go/ast.Package.Imports go/ast
go/ast/resolve.go:173:  return &Package{pkgName, pkgScope, imports, files}, p.errors.Err()

f:id:motemen:20161028084629p:plain

関数

go list std は標準パッケージの一覧。こうやって公式にどういう使い方がされているか発見できるので、生きたサンプルとして役立つ。

% gofind strings.Split $(go list std)
archive/tar/reader.go:781:      sparseMap := strings.Split(extHdrs[paxGNUSparseMap], ",")
crypto/tls/common.go:597:       labels := strings.Split(name, ".")
...

f:id:motemen:20161028084838p:plain

変数や構造体のフィールドで、指定された型を持つものを検索。

% gofind golang.org/x/tools/cmd/stringer.Package golang.org/x/tools/cmd/stringer
golang.org/x/tools/cmd/stringer/stringer.go:133:        g.Printf("package %s", g.pkg.name)
golang.org/x/tools/cmd/stringer/stringer.go:170:        pkg *Package     // Package we are scanning.
...

f:id:motemen:20161028084759p:plain

gofind sync.Mutex $(go list std) などとすれば、公式のソースコードでこの型がどういう名前付けをされているかわかるわけです(Go 言語における並行処理の構築部材 - 詩と創作・思索のひろば)。

メソッド

% gofind encoding/json.Encoder.Encode golang.org/x/tools/cmd/godoc
golang.org/x/tools/cmd/godoc/handlers.go:146:   json.NewEncoder(w).Encode(resp)

f:id:motemen:20161028084727p:plain

はてなで一緒に働きませんか?