チョットできるGoプログラマーになるための詳解GoDoc

Goを書いている方なら誰しもお世話になっているのがGoDoc。標準ライブラリのリファレンスだけではなく、pkg.go.devで、Go製のサードパーティ製パッケージのドキュメントもブラウザでいつでも閲覧できます。

書くことも、読むことも多いGoDocですが、細かい仕様がまとまって説明されていることがあまりないため、Go 1.10リリースパーティにあわせていろいろ細かく調べてみて、その後少し改訂しました。現時点で、世界で一番詳しい説明だと思います。

**2022/08/22追記** これを投稿した2018年8月当時は世界で一番詳しかったのですが、Go 1.19で[Go Doc Comments](https://tip.golang.org/doc/comment)というページが追加されました。単なる書き方だけではなく、要素の種類ごとの推奨の書き方なども解説されています。今となっては世界で2番目となりましたが、日本語で読みたい方のために残しておきます。

# さて、GoDocはいくつあるでしょう？

GoDocといっても、ツールがいくつかあります。

| ツール名 | UI |
|:--|:-:|
| go doc  | CLI  |
| godoc  | CLI  |
| godoc -http  | Web  |
| gddo (godoc dot org)サーバー  | Web  |

上の2つがCLIで、下の2つがブラウザです。歴史的な経緯を見てみましょう。

* 〜1.1: go docはバンドルされているツールで、ソースもgo本体に同梱
* 1.2: go docは別のリポジトリにわけられてgodocになり、go本体から外れた
* 1.3: godocで-analysisオプションが追加
* 1.5: 新しい"go doc"コマンドがgo本体に同梱
* 1.11: godocがウェブだけになるため、go docを使えというアナウンスが出るように
* 1.12: godocが-httpだけをサポートしてCLIの機能は削除予定
* 1.13: godocのwebサーバーが同梱されなくなって手動でのインストールが必要に
* 1.13～: 既存のgodoc.orgから、go modのプロキシサーバーの情報をもとにドキュメントをホスティングするpkg.go.devが運用開始

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

わかりましたか？よくわかりませんよね？まあ、godocとgo docは同じドキュメントツールですが、別のツールです。本家と元祖みたいなものとして、長らく平行で提供されてきました。ちょうどこの記事を書いている少し前にリリースされた1.11で、godocの方がウェブだけになるとリリースノートに書かれました。

また、godoc.orgのサーバーはローカルのgodocコマンドよりも機能が増えていたり、テンプレートが微妙に違ったりします。とはいえ、ドキュメントを書くときはこれらの違いはそこまで気にすることはありません。

**2020/09/19追記**: 現在はpkg.go.devが稼働を開始し、godoc.orgのページにアクセスするとこちらへのリダイレクトをするかどうかを問い合わせるバナーが出ます。将来的にはこちらに一本化されると思います。開発者が行うべきこと、記述すべきことは差はありません。このドキュメントでもいくつかpkg.go.devについて言及しています。

# 基礎編：GoDocについて知るべきこと

実際に書く方法を学ぶにはだいたい次のことを把握していれば良いでしょう。順番に説明していきます。

* 基本ルール
* 出力結果
* マークアップ
* Exampleテスト
* ビルド・チェック方法

@po3rin さんのエントリー「[Goを始めて1年間で最高にお世話になったGo関連ブックマークを晒します](https://qiita.com/po3rin/items/0d8fef14bfe222f334b7)」で知ったのですが、godocを試しながら確認できるPlayGroundがあります。これで試しながら確認してみるのをオススメします。

https://bradleyjkemp.dev/godoc-playground/

## 基本ルール

[Effective Goのコメントの章](https://golang.org/doc/effective_go.html#commentary)を見ると、いろいろ書かれています。ダイジェストを紹介します。

    Comments that appear before top-level declarations, with no 
    intervening newlines, are extracted along with the declaration
    to serve as explanatory text for the item.

    トップレベルの宣言の直前に空行なしで書かれたコメントは、宣言文とともに抽出されて
    その要素の説明用テキストとして提供されます。

    Every package should have a package comment, a block comment
    preceding the package clause.

    すべてのパッケージは、package節の前のブロックコメントであるパッケージ
    コメントを持つべきです。

    Inside a package, any comment immediately preceding a top-level
    declaration serves as a doc comment for that declaration. Every 
    exported (capitalized) name in a program should have a doc comment.

    パッケージ内では、トップレベルの宣言の直前にあるコメントがその宣言の
    ドキュメンテーションコメントとして提供されます。プログラム内のすべての
    エクスポート（大文字）名はコメントを持つべきです。

    If every doc comment begins with the name of the item it describes,
    the output of godoc can usefully be run through grep.

    すべてのドキュメンテーションのコメントがそれが記述する項目の名前で始まる場合、
    godocの出力とgrepを組み合わせて便利に使用できます。

この最後の項目はgolintでチェックをするとよくgodoc関連のエラーに遭遇しますよね？なぜコメントの書き出しのルール（コメント対象の名前で書き始める）なんてあるのかというと、この最後の段落に答えがありました。他のツールとの組み合わせを考えた上でのルールということですね。比較的ウザいやつですが。元々はshouldではなくて、推奨するぐらいの軽いものだったようです。

大雑把にはこんな感じです。

```go
// Action is an interface of actions in scenario.
type Action interface {
	Type() ActionType
}
```

## 空行はあけるのか？あけないのか？

空行をはさんではいけない、というのがハマりどころで、初心者がよく混乱するところだと思います。結果をみてみて、出力されていないときは、まずこの空行を見直してみてください。

なお、build constraintsは、空行が必要です。

* [GoのBuild Constraintsに関するメモ](https://qiita.com/hnw/items/7dd4d10e837158d6156a)

cgoは``import "C"``の直前に書きますが、こちらはgodoc同様、空行をあけるとダメです。

* [cgoやってみた](https://qiita.com/shunsukuda/items/e9646e1a6acc863f3594)

Goの三大ハマりポイントだと思っています。

## 出力結果の項目

godocの生成された結果を見ると、たくさん項目があるように見えますが、プログラマーが書くべきところは「説明」「サンプル」だけです。あとは自動生成されるコンテンツです。また、最初のリポジトリへのリンクと、最後のおまけのフッターはgodoc.orgだけのコンテンツです。

| 要素 | godoc.orgのみ | 自動生成 | 自分で書く |
|:--|:-:|:-:|:-:|
| リポジトリへのリンク ``*``  | ✔︎  |   |   |
| タイトル (パッケージ名)  |   | ✔︎  |   |
| import文  |   | ✔︎  |   |
| パッケージ説明  |   |   | ✔︎  |
| パッケージサンプル  |   |   | ✔︎  |
| パッケージ内容、サンプル、ソースファイル一覧へのインデックス  |   | ✔︎  |   |
| for _, member := range members  |   |   |   |
|　　　　メンバータイトル  |   | ✔︎  |   |
|　　　　メンバー宣言  |   | ✔︎  |   |
|　　　　メンバー説明  |   |   | ✔︎  |
|　　　　メンバーサンプル  |   |   | ✔︎  |
| おまけのフッター (依存しているパッケージ、リフレッシュリンク、GitHubのバッジ)   | ✔︎  |   |   |

**2020/09/19追記**

pkg.go.devの場合は、overviewとしてREADMEが追加されるようになりましたし、ライセンスもコードからとってきてくれるようになりました。githubを見に来てくれた人にはREADMEに書かないと伝わらないし、godoc.orgを見に来てくれた人にはパッケージドキュメントを書かないといけないし、両方に転記しないといけない、というのが以前はありましたが、pkg.go.devではリファレンス以外のドキュメントはREADMEに書いておくことで二重管理を減らすことができるようになりました。

## マークアップ

それでは書き方を見ていきます。みなさん、何かしらのマークアップは知っていると思うので著名なものと比較してみます。

**2022/8/22更新** Go 1.19の更新を追加

|   | reStructuredText | Markdown | godoc |
|:-:|:-:|:-:|:-:|
| セクションタイトル  | 最大28階層  | 6階層  | 1階層  |
| 段落  | ✔  |✔   | ✔  |
| インラインスタイル | 太字・斜体・固定幅  | 太字・斜体・固定幅  |   |
| URL自動リンク  | ✔  | ✔  | ✔  |
| 画像  | ✔  | ✔  |   |
| リスト  | ✔  | ✔  | ✔︎（ネストなし) |
| テーブル  | 4種類の記法  | GFM  |   |
| フォーマット済みテキスト  | ✔  | ✔  | ✔  |
| コードハイライト  | ✔  | GFM  |   |
| ラベル付きリンク  | ✔  | ✔  | ✔ |
| 水平線  | ✔  | ✔  |   |
| 脚注  | ✔  |   |   |
| 宣言リスト  | ✔  |   |   |
| フィールドリスト  | ✔  |   |   |
| オプションリスト  | ✔  |   |   |
| 置換  | ✔  |   |   |
| コメントアウト  | ✔  |   |   |
| リンクターゲットへのリンク  | ✔  |   | ✔ |
| 置換  | ✔  |   |   |
| Todo  | Sphinx (todo)  |   | ✔ (notes)  |
| サイドバー  | ✔  |   |   |
| トピック  | ✔  |   |   |
| 巻頭句  | ✔  |   |   |
| 数式  | ✔  |   |   |
| 目次  | ✔  |   |   |
| HTMLメタタグ  | ✔  |   |   |
| イメージマップ  | ✔  |   |   |
| Raw HTML | ✔ | ✔ |  |
| HTMLクラス | ✔ | |  |
| 外部ファイルのinclude | ✔ | |  |
| docテスト  | Sphinx (python) |   | ✔ (example test)  |
| RFCへのリンク  | Sphinx  |   | godoc.org  |
| packageへのリンク  | Sphinx (domain)  |   | godoc.org  |
| 非推奨(Deprecated) | Sphinx | | ✔ (notes) |

reSTにしかない記法はいっぱいあるのが目立ちますが、それと比べると、godocのシンプルさが際立ちます。表はありません。自然言語で書ききるか、フォーマット済みテキスト（ハイライトなし）はあるので、それを駆使してリストを表現したり、アスキーアートで表を表現しているプロジェクトもあります。

使える要素にしぼると、使える要素はごくわずかです。

* 段落

    空行で区切りられた行のかたまりのテキストが段落になります。

* セクションタイトル

　　　　　　　Title Case (先頭がすべて大文字）の１行の段落がセクションタイトルになります。階層はありません。Go 1.19からは``#``を前置するMarkdownスタイルのタイトルも使えます。

* フォーマット済みテキスト

    空白でインデントされたところはフォーマット済みのテキストとなります。

* リンク

    URLはHTMLのリンクになります。Go 1.19からは`[   ]`でくくる方式のリンクがあります。これを使ってリンクラベルを記述できます。

    > // Package json implements encoding and decoding of JSON as defined in
    > // [RFC 7159]. The mapping between JSON and Go values is described
    > // in the documentation for the Marshal and Unmarshal functions.
    > //
    > // For an introduction to this package, see the article
    > // “[JSON and Go].”
    > //
    > // [RFC 7159]: https://tools.ietf.org/html/rfc7159
    > // [JSON and Go]: https://golang.org/doc/articles/json_and_go.htmlpackage json

    また `[*bytes.Buffer]`といった書き方で、ドキュメントの項目へのハイパーリンクが表現できます。ポインタの有無は任意で、パッケージ名+要素、あるいはパッケージ名+構造体+メンバーなどがかけます。同一パッケージ内であればパッケージ名の省略も可能です。

* リスト

    Go 1.19で追加されました。MarkdownやらreSTやらの他のマークアップと近い記法で書けますが、インデントが必要です。これはGo 1.19以前ではコードブロックとなって後方互換性が維持されるようにこのような記法になっていると思われます。

    > // PublicSuffixList provides the public suffix of a domain. For example:
    > //   - the public suffix of "example.com" is "com",
    > //   - the public suffix of "foo1.foo2.foo3.co.uk" is "co.uk", and
    > //   - the public suffix of "bar.pvt.k12.ma.us" is "pvt.k12.ma.us".
    > //
    > // Clean returns the shortest path name equivalent to path
    > // by purely lexical processing. It applies the following rules
    > // iteratively until no further processing can be done:
    > //
    > //  1. Replace multiple slashes with a single slash.
    > //  2. Eliminate each . path name element (the current directory).
    > //  3. Eliminate each inner .. path name element (the parent directory)
    > //     along with the non-.. element that precedes it.
    > //  4. Eliminate .. elements that begin a rooted path:
    > //     that is, replace "/.." by "/" at the beginning of a path.

* ノート

    出力結果の最後の方に、BUG、TODO、Deprecatedのような追加のメモのセクションができて、ノートがついた要素へのリンク集となります。

    デフォルトではBUGのみがノートの項目として設定されていますが、ノートの種類を自分で設定するには、godocコマンドで、 ``-notes="BUG|TODO"``のように正規表現で指定します。書き方は後で紹介します。

次のスクリーンショットは、各マークアップと、出力結果の対比表です。

<img width="675" alt="Screen Shot 2018-08-28 at 0.50.47.png" src="https://qiita-image-store.s3.amazonaws.com/0/6105/355faba9-6343-e1a7-6a64-bf1adafac9ad.png">

## Deprecatedノート

Deprecatedについては、次のエントリーが詳しいです。ドキュメントに対しては特に表示が大きく変わったりはないのですが、GoLandとかのツールによってはこれを検知して警告を出してくれたりします。覚えておいて損はないでしょう。

* [Goのパッケージ開発者が関数などのDeprecated(非推奨)を利用者に伝える & 利用者が検知する方法](https://qiita.com/po3rin/items/fcd1b1d888538153a39f)

```go
// IntSort sorts an array using the provided comparator
//
// Deprecated: This function is deprecated
func IntSort(a []int) (err error) {
```

## godoc.orgのみに追加されたマークアップ

ローカルでは効果がないものの、godoc.orgにアップロードしたときだけ有効になるマークアップが2つあります。後者は意識しなくても、英語で書いているとうっかり適用されることも多いかと思います。

* RFCへのリンク: ``RFC 822``のようなテキストがあると、tools.ietf.org以下のページへのリンクが貼られます。``RFC 822 section 1.2.3``のように書くと、セクション番号も付与できます。
* パッケージへのリンク: ``package time``のように書くと、timeパッケージへのリンクが作成されます。

# Exampleテスト

Goプログラマーが作成できる要素は，コメントとExampleの2つだけです。もう片方の大事な片割れも紹介します。Exampleテストは、ユニットテストの1形態です。Pythonをやったことがあるのであれば、doctestと言えば伝わりやすいでしょう。Exampleテストを書くと、godoc.orgでサンプルコードとして表示されます。また、go testでテストとしても実行が可能です。

まずはサンプルを提示しておきます。

```go

package mockconn_test

import (
    "fmt"
    "github.com/shibukawa/mockconn"
)

func Example() {
    mock := mockconn.New(nil)
    mock.SetExpectedActions(
        mockconn.Read([]byte("welcome from server")),
        mockconn.Write([]byte("hello!!")),
        mockconn.Close(),
    )
    buffer := make([]byte, 100)
    n, _ := mock.Read(buffer)
    fmt.Println(string(buffer[:n]))
    // Output:
    // welcome from server
    mock.Write([]byte("hello!!"))
    mock.Close()
}
```

Exampleテストのキーポイントは次の通りです

* テストファイルであること（``XX_test.go``)
* 通常のパッケージと異なる、テストパッケージ（``package [パッケージ名]_test``）であること
* テストターゲットと対応する名前の関数名を持つ
    * パッケージのサンプルであれば ``Example()``。
    * トップレベル要素（型、インタフェース、関数）であれば、``Example[要素名]()``。
    * 構造体のメソッドであれば、``Example[構造体名]_[メソッド名]()``。
* テストコードはprint文とその結果で表す
    * ``fmt``パッケージを使って出力する
    * コメントには期待される出力を書く
*  パッケージ内にヘルパー関数を作って、それを利用しても良い
    *  godocは必要なヘルパー関数を集めてきて、出力するHTMLドキュメントの中に一緒に出力してくれる。

テストとして実行した結果は次のようになります。

```
=== RUN   TestWriteError
--- PASS: TestWriteError (0.00s)
=== RUN   Example
--- PASS: Example (0.00s)
=== RUN   ExampleConn_Verify
--- FAIL: ExampleConn_Verify (0.00s)
got:
Error: socket scenario 1 - Write() expected="hello!!" actual="good morning!!".
Error: mock socket scenario 1 - there is remained data to write: "hello!!"
Error: Unconsumed senario exists - 2/2
want:
Error: socket scenario 1 - Write() expected="hello!!" actual="good morning!!"
Error: mock socket scenario 1 - there is remained data to write: "hello!!"
Error: Unconsumed senario exists - 2/2
FAIL
exit status 1
FAIL	github.com/shibukawa/mockconn	0.002s
```

ドキュメントとしては次のように見えます。

<img width="895" alt="Screen Shot 2018-08-28 at 1.21.09.png" src="https://qiita-image-store.s3.amazonaws.com/0/6105/a8262694-ddc9-9e44-6155-a3b0542fb429.png">

# ビルド方法

他の言語のドキュメントツールをやったことがある人にとって、戸惑うポイントがどのようにビルドして、ウェブサイトの情報を更新するか、でしょう。結論から言ってしまえば、ビルドコマンドといったローカルのツールなどは存在していません。

## (新)pkg.go.devの場合

1. github.comなどにアップした後に、そのパッケージをgo mod経由アクセスすると裏で自動で生成・更新されます

https://dev.to/koddr/how-to-update-version-s-cache-of-your-package-in-pkg-go-dev-39ij

## (旧)godoc.orgの場合

1. github.comなどにアップした後に、そのパッケージの名前のURLにアクセスすると裏で自動で生成されます
2. 一度ビルドされると、自動では更新されません。
2. 最下部に「refresh now」というリンクがあり、このリンクを辿ると再取得とビルドが行われます。

## ローカル環境の場合

1. godocコマンドを起動すると、ドキュメントが作成されます。

# Local Godoc サーバーの追加機能

godocサーバー（``-http``）には便利な追加機能がいくつかあります。業務で開発したローカルなプライベートパッケージのドキュメントをホストしておくことがあると思いますが、その場合にはこのあたりを知っておくと良いでしょう。

* 静的解析
* インデックスの利用（``-index``）
* オリジナルのテンプレートの利用（``-template``）
* Exampleテストのプレイグランドの有効化（``-play``）・・・ただし使えない

## godocサーバーによる静的解析

詳細は次のところに書かれています。

* https://golang.org/lib/godoc/analysis/help.html

2つのオプションが提供されています。

* ``godoc -analysis=type``
    * コンパイラのエラー
    * 識別子の解決
    * 型情報: サイズ、アラインメント、メソッドの集合、インタフェース
* ``godoc -analysis=pointer``
    * コールグラフのナビゲーション
    * パッケージ内部のコールグラフ
    * チャネルのペア（送信と受信）の解析

例えば、次のコードで、``ticker.C``の受信の演算子を選択すると、このチャネルを受信している箇所が表示されます。

<img width="810" alt="Screen Shot 2018-08-28 at 1.43.10.png" src="https://qiita-image-store.s3.amazonaws.com/0/6105/a203f96b-2a4c-c801-2308-e48493dea92e.png">

## インデックスの利用

ローカルのgodocサーバーで、検索機能が使えるようになります。ただし、インデックスの作成には多くの時間がかかります。事前にインデックスを作成し、ファイルへの書き出しをしておくと時間が短縮できます。

<img width="612" alt="Screen Shot 2018-08-28 at 1.45.13.png" src="https://qiita-image-store.s3.amazonaws.com/0/6105/74e33e72-3c15-dcac-9108-24e7285cc5fe.png">

## オリジナルのテンプレートの利用（``-template``）

オリジナルのテンプレート（HTML/JS/CSS）を使って飾り付けることができます。

ゼロから作るのは大変ですが、次の場所をコピーして作成すると楽になるでしょう。

* ``$GOPATH/src/golang.org/x/tools/godoc/static`` 

変わったことがわかるように、背景色を大胆にいじってみました。

<img width="988" alt="Screen Shot 2018-08-28 at 1.46.42.png" src="https://qiita-image-store.s3.amazonaws.com/0/6105/3a08259f-c9b3-72ff-e97a-4acbceb59dc7.png">

## Exampleテストのプレイグランドの有効化

godoc.orgのような、自由に試せるプレイグランドを提供しますが、どうも、godoc.orgで動いている仕組みをそのままリモートで使っているようです。そのため、ローカルの``$GOPATH``内にあるパッケージを利用したくても使えません。つまりは標準ライブラリしか使えないので使い道は特にないでしょう。

1.10のリリースパーティで質問されましたが、プレイグランドのサービスのURLを選択するオプションはありません。

<img width="630" alt="Screen Shot 2018-08-28 at 1.45.00.png" src="https://qiita-image-store.s3.amazonaws.com/0/6105/4584330d-c1c1-dfd0-df2b-0fb334c98e96.png">

## おまけ

**2020/9/19追記**: READMEによくついている、godocへのリンクとなっているバッジがありますよね？現在は生成ツールがあるのでこちらを利用してください。

* https://pkg.go.dev/badge/

godoc.org時代はこんな感じで作っていました。

```md

[![GoDoc](https://godoc.org/github.com/shibukawa/my-lib?status.svg)](https://godoc.org/github.com/shibukawa/my-lib)
```

# まとめと参考文献

長々と説明してきましたが、開発者が心がけるべきことはあまりないです。

* パッケージ、およびパブリックなトップレベルの要素（構造体、インタフェース、関数）に対して書く
* 一番大事なのは空行をあげずにブロックコメントを付与する
* マークアップもごく限られていて、複雑なことはあまりできない

* The Go Blog “Godoc: documenting Go code”
    * https://blog.golang.org/godoc-documenting-go-code
* Effective Go: commentary
    * https://golang.org/doc/effective_go.html#commentary
* Markup specification of standard go doc
    * https://github.com/golang/go/blob/master/src/go/doc/comment.go
* Markup specification only for godoc.org
    * https://github.com/golang/gddo/blob/master/gddo-server/template.go
* Example test
    * https://blog.golang.org/examples
    * https://golang.org/pkg/testing/#hdr-Examples
* Static analysis features of godoc 
    * https://golang.org/lib/godoc/analysis/help.html
* How to update version's cache of your package in pkg.go.dev?
    * https://dev.to/koddr/how-to-update-version-s-cache-of-your-package-in-pkg-go-dev-39ij