自作したパッケージの詳細をGoDocで確認できるようにする

分かりやすいコードを書いていればコメントやドキュメントは不要というエンジニアさんは多いです。

しかし気をつけてコーディングしていても技術者のレベルは様々だし、開発規模が大きくなるにつれ自作のパッケージも増え、なんのために実装したのか不明なコードが出てきて負債になるリスクもあります。

そのようなリスクを回避するためのツールとして gofmtgo vetgolint などのフォーマッターやリンターがあるわけですが、調べていたら GoDoc という便利なツールがあったので、情報を共有します。

GoDocとは

GoDocは↓下図のようにパッケージの詳細をブラウザで確認できるツールです。

GoDocの表示例

ルールに沿ってソースコードにコメントを記述することで、パブリックな関数、type、変数についての説明や実装例をよしなにまとめてくれます。

コメントはpythonのdocstringのように一定の規約に沿って記述していくのでコードの可読性を保ったまま整理できます。

GoDocの導入と起動

Goのパッケージなので go get でインストールします。

go get golang.org/x/tools/cmd/godoc

# 私の開発環境では下記で全てのtoolsをインストールしています。
# go get golang.org/x/tools

下記コマンドで起動し、 localhost:6060 でアクセスできます。

godoc -http=:6060

実装例

ディレクトリ構成

今回は下記のような構成とコードで試しました。

.
|-- cmd/
|   `-- godoc-test/
|       `-- main.go
`-- pkg/
    `-- mypkg/
        |-- math.go
        |-- math_test.go
        |-- say-hello.go
        `-- say-hello_test.go

main.go

package main

import (
	"app/pkg/mypkg"
	"fmt"
)

func main() {
	s := []int{1, 2, 3, 4, 5}
	fmt.Println(mypkg.Sum(s))

	p := &mypkg.Person{
		Name: "Hoge",
		Age:  1,
	}
	fmt.Println(p.Say())
}

math.go

package hogehoge の上にパッケージの説明文を書くとGoDocのOverviewに表示されます。

パブリック関数や変数の前の行にコメントを入れるとGoDocでよしなに表示されます。

/*
Package mypkg is my package.
*/
package mypkg

// Sum returns the sum of a series of numbers.
func Sum(s []int) int {
	total := 0
	for _, i := range s {
		total += i
	}
	return total
}

math_test.go

テストコードに Example() という名前で関数を記述すると、GoDocのExampleに表示されます。

package mypkg

import (
	"fmt"
	"testing"
)

func Example() {
	v := Sum([]int{1, 2, 3, 4, 5})
	fmt.Println(v)
}

func ExampleSum() {
	v := Sum([]int{1, 2, 3, 4, 5})
	fmt.Println(v)
}

func TestSum(t *testing.T) {
	tests := []struct {
		name  string
		input []int
		want  int
	}{
		{"All positive integers", []int{1, 2, 3, 4, 5}, 15},
		{"Partially negative integer", []int{-1, 2, 3, 4, 5}, 13},
		{"Valid negative integer", []int{1, 2, 3, -4, -5}, -3},
		{"All negative integers", []int{-1, -2, -3, -4, -5}, -15},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			output := Sum(tt.input)
			if output != tt.want {
				t.Errorf("want %!d(MISSING); got %!d(MISSING); input %!d(MISSING)", tt.want, output, tt.input)
			}
		})
	}
}
package mypkg

// Person is a struct that has name and age.
type Person struct {
	Name string
	Age  int
}

// SayHello returns "Hello!" with Person's name.
func (p *Person) SayHello() string {
	return "Hello, I'm " + p.Name
}

// SayHello returns Person's age.
func (p *Person) SayAge() string {
	return "Hello, I'm " + string(p.Age) + " years old."
}

say-hello_test.go

package mypkg

import (
	"testing"
)

func ExamplePerson_SayHello() {
	p := Person{"Hoge", 13}
	p.SayHello()
}

func ExamplePerson_SayAge() {
	p := Person{"Hoge", 13}
	p.SayAge()
}

func TestSayHello(t *testing.T) {
	p := Person{Name: "Mike", Age: 3}
	tests := []struct {
		name string
		want string
	}{
		{"Say person's name", "Hello, I'm " + p.Name},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			output := p.SayHello()
			if output != tt.want {
				t.Errorf("want %!s(MISSING); got %!s(MISSING)", tt.want, output)
			}
		})
	}
}

func TestSayAge(t *testing.T) {
	p := Person{Name: "Mike", Age: 3}
	tests := []struct {
		name string
		want string
	}{
		{"Say person's age", "Hello, I'm " + string(p.Age) + " years old."},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			output := p.SayAge()
			if output != tt.want {
				t.Errorf("want %!s(MISSING); got %!s(MISSING)", tt.want, output)
			}
		})
	}
}

結果

最終的に下図のようにドキュメントがまとまります。

まとめ

コードの意味や関数の機能をより理解しやすくするためのコメントやドキュメントを残しておくと、後から見返した時に助かるケースがあります。

GoDoc自体は使わなくても、規約に沿ってコメントを残しておくと良いかと思います。