Plan 9とGo言語のブログ

主にPlan 9やGo言語の日々気づいたことを書きます。

GoDocドキュメントの書き方

この記事はQiitaで公開されていました

Goでドキュメントを書くとき、一般的にはGoDocを使うと思います。GoDocはシンプルにみえて、実際は色々な書き方をサポートしていますし、ブラウザで単純に表示する以外の読み方もあるので、一通りの方法をまとめてみました。

ドキュメントの書き方

GoDocではソースコードの中に、ある決まった形でコメントを書くと、そのコメントをドキュメントとして扱うことができます。具体的には、パッケージ、型、定数、構造体フィールド、関数・メソッドそれぞれの直前に、空行を入れずコメントを書きます。これらの前に改行を入れてしまうと、ただのコメントになってしまいます。

装飾について

GoDocは、大きなドキュメントのために、ヘッダと整形されたテキストの2通り装飾ができます。ただし、リストやテーブルなどは対応していません。

ヘッダ

以下の条件を全て満たせば、ヘッダとして認識されます。

  • 大文字で開始される(日本語文字は適用外)
  • 1行だけで構成されている
  • 句読点を含まない
  • 直前の要素がヘッダではない
  • 最初の要素ではない

最後の2つは、

/*
Paragraph 1

Paragraph 2

Paragraph 3
*/
package qiita

というコメントブロックがあった場合、Paragraph 1はコメントブロック中の最初にある要素なので、ヘッダにはなりません。また、Paragraph 3は直前の要素がヘッダなので、通常の段落になります。

整形済みテキスト

他の要素よりもインデントを増やせば整形されたテキストになります。

リンク

URLを書けばそのままリンクに変換されます。任意のテキストにリンクを付けることはできません。

具体例

以下で、それぞれのコメントがどのように表示されるかを紹介します。

パッケージドキュメント

パッケージに対するコメントは、ドキュメントの一番上にOverviewとして表示されます。ここには、パッケージのざっくりとした説明を書いたり、パッケージ全体を通して使われる仕様などの情報を記載するために記述します。以下はパッケージコメントの例です。

// Copyright xxx

// +build darwin

/*
Godoc はGoのパッケージドキュメント情報をQiitaで紹介するためのパッケージです。そのままサンプルとして使えます。
リポジトリは https://github.com/lufia/godoc-sample です。

How to read a document

ドキュメントを読むためにはgo docコマンドまたはgodocコマンドが使えます。

How to write a document

パッケージドキュメントはpackage句の直前に書く必要がありますが、
Build constraintsはpackageよりも前に書かなければなりません。
そのため、記述する順番としては、Build constraintsが先になります。

空行を入れると、別の段落として区切ることができます。

   インデントすると、ソースコードのような
   整形されたテキストも書けます

Heading

アルファベットの大文字で始まり、句読点を含まない1行だけの段落があれば、
それはヘッダとして装飾されます。ただし、ヘッダを2つ以上続けることはできません。
次の行はヘッダになりますが、その次は同じルールにも関わらず普通の段落です。

This is a header

This is not a header

*/
package qiita

このコメントをGoDocで読むと、以下のように表示されます。

f:id:lufiabb:20200328212429p:plain
Overview

パッケージ一覧での表示

パッケージドキュメントの最初に現れる文は、パッケージ一覧ページでも使われます。パッケージドキュメントが1文以上書かれていても、最初の文だけがパッケージ一覧ページに概要として使われ、残りは無視されます。例えば日本語の場合、読点(。)で文が終わります。英語の場合はピリオドで終わりますが、この場合は後ろにスペースがなければ文の終わりとしては認識しません。

f:id:lufiabb:20200328212508p:plain
Package概要

型とフィールド

型宣言の直前にコメントを書くと、それは型に対するドキュメントになります。もし型が構造体だったなら、フィールドのコメントも一緒にドキュメントとして表示されます。フィールドの上に書いてもいいし、行末に書いても構いません。例えば以下のコードは、

// Article は1つの記事を表します。
type Article struct {
    // 記事のタイトル
    Title string
    
    // 記事本文
    Body string
    
    // 状態
    Status PostStatus // Draft or Publish
}

次のように表示されます。

f:id:lufiabb:20200328212539p:plain
型とフィールドの表示例

変数・定数

変数や定数も同じで、直前のコメントがドキュメントになります。

// PostStatus は記事の投稿状態を表現します。
type PostStatus int

// 記事の投稿状態。
const (
    StatusDraft   PostStatus = iota // 下書き
    StatusPublish                   // 公開済み
)

このコードは、次のように表示されます。

f:id:lufiabb:20200328212608p:plain
定数の表示例

constの直前と定数それぞれにドキュメントが付きます。これはvarも同様です。

メソッド・関数

メソッドや関数へのコメントもドキュメントになります。

// NewArticle はタイトルをtitleに設定した新しい記事を作成します。
func NewArticle(title string) *Article {
    return &Article{Title: title}
}

// Save は、記事aの状態をデータベースに保存します。
func (a *Article) Save() error {
    // BUG(lufia): 保存機能は未実装です。
    // TODO(lufia): 実装する
    return nil
}

これをドキュメントにすると、以下のように表示されます。

f:id:lufiabb:20200328212638p:plain
関数とメソッドの表示例

GoDocは、型に関連するメソッドや関数を、なるべく近くに表示するように並び替えます。

コード例を書く

GoDocでは、パッケージを使ったコードサンプルをExampleとして掲載できます。コード例はコメントではなく、テストコードとしてExampleで始まる名前のメソッドを実装すると、名前に対応した場所にドキュメントとして表示されるものです。対応する場所とは以下の通りです。

関数名 場所
Example() パッケージ全体
ExampleFunc() Func関数
ExampleType_Func() Type型のFuncメソッド

また、それぞれのExample関数名の終わりに、最初が小文字で始まる名前を付けると、別のパターンとして複数のコード例を書くことができます。一通り包括した例を挙げます。

package qiita_test

import (
    "fmt"
    "log"

    "github.com/lufia/godoc-sample"
)

func Example() {
    a := qiita.NewArticle("テスト")
    fmt.Println(a.Title)
    // Output: テスト
}

func Example_other() {
    a := qiita.NewArticle("テスト")
    a.Body = "サンプル"
    fmt.Println(a.Body)
    // Output: サンプル
}

func ExampleNewArticle() {
    a := qiita.NewArticle("テスト")
    fmt.Println(a.Status)
    // Output: 0
}

func ExampleNewArticle_otherStatus() {
    a := qiita.NewArticle("テスト")
    a.Status = qiita.StatusPublish
    fmt.Println(a.Status)
    // Output: 1
}

func ExampleArticle_Save() {
    a := qiita.NewArticle("テスト")
    a.Save()
}

func ExampleArticle_Save_errorHandling() {
    a := qiita.NewArticle("エラー")
    if err := a.Save(); err != nil {
        log.Fatalln(err)
    }
}

上記のコードで、パッケージ名をqiita_testとしてパッケージ本体と分けているのは、そうした方が、コード例にパッケージ名を記述することができて親切だからです。Goのパッケージは通常、異なるパッケージを同じディレクトリに入れることはできませんが、_testの場合は外部テストパッケージ(external test といって特別らしいです。

この例を表示させると、以下のようになります。

f:id:lufiabb:20200328212714p:plain
Exampleリスト

NewArticle関数の例は次のように表示されます。他も同じです。

f:id:lufiabb:20200328212741p:plain
Exampleの表示例

また、コード例のなかには、1つの関数で表現できない大きな例も必要になるかもしれません。その場合、1つの*_test.goファイルにExample関数を1つだけ書いて、さらに、Exampleの外に定義したなにかを1つ以上参照しましょう。パッケージグローバルに他の宣言が行われていると、GoDocはファイル全体をひとつの大きなコード例として扱います。以下はその例です。

package qiita_test

import (
    "fmt"

    "github.com/lufia/godoc-sample"
)

// グローバルに1つ以上、Example以外の何かが必要
const defaultTitle = "untitled"

// 1つだけExampleの実装がされていること
func Example_wholeFileExample() {
    a := qiita.NewArticle(defaultTitle)
    fmt.Println(a.Title)
    // Output: untitled
}

通常のExampleは、関数の中しか例として表示しませんが、このExampleはファイル全体を表示します。

f:id:lufiabb:20200328212823p:plain
Example全体例

ノート

コードの途中で、// BUG(who): xxxのようにコメントを書くと、それもドキュメントの末尾に表示してくれます。例えば、上記のSave()メソッドを次のように変更してみましょう。

// Save は、記事aの状態をデータベースに保存します。
func (a *Article) Save() error {
    // BUG(lufia): 保存機能は未実装です。
    // TODO(lufia): 実装する。
    return nil
}

これを、-notes=(正規表現)オプションを指定したGoDocで表示すると、次のように脚注として表示できます。

f:id:lufiabb:20200328212906p:plain
ノート例

GoDocのデフォルトで表示されるノートはBUG(who)だけですが、godocのオプションでgodoc -notes='.*'のようにラベルを正規表現で指定すると、BUG(who)以外のラベルも一緒に表示します。

他にも、公式ドキュメントを眺めると、

// Deprecated: xx

// See: https://xxx

などが使われていますが、これらはノートではなく普通のテキストとしてドキュメントに表示されます。

参考情報

ドキュメントを読む方法

godoc.org

オンラインのGoDocは、URLのパス部分にimport pathを与えると、パッケージのドキュメントを表示してくれるサービスです。例えばこの記事で書いたサンプルリポジトリの場合、以下のURLで参照できます。

このサービスはGOOS=linuxで動作しているようで、他のOSをターゲットとしているファイルは対象となりません。クエリパラメータとしてGOOSGOARCHがサポートされているので、例えばPlan 9/386のドキュメントを表示したければ以下のURLにアクセスすると読めます。

godocコマンド

godocコマンドは標準パッケージに含まれませんので、go getでインストールして使います。

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

このコマンドは、一般的にはgodoc -http=:6060のようにHTTPサーバとして動作させて、ブラウザで参照することが多いと思われます。ブラウザで閲覧すると、Goの公式サイトと似たようなページが表示されますが、公式と異なり、godocのパッケージ一覧ページには、ローカルの$GOPATH以下にある全パッケージがリストされています。

Go 1.11から、型や関数の右側に対応したバージョンが表記されるようになりました。このデータは $GOROOT/api/go*.txt から読み込んでいるようです。

静的解析

公式やオンラインのgodoc.orgと比べて特に便利だと思うのは、-analysis=オプションの存在です。-analysis=オプションは以下の2つの値をとります。

  • type
  • pointer

-analysis=typeとすると、型がどのインターフェイスを実装しているかを調べることができます。以下の画像はbufio.Readerの例です。io.Readerなどのインターフェイスを実装していることが確認できます。

f:id:lufiabb:20200328213011p:plain
analysis=typeの例

また、ソースコードを表示した場合には、普段より詳細な解析が行われています。こちらもbufio.Readerの例です。カーソルをリンクに乗せると、定義がその場で表示されますし、定義された場所までのリンクも追加されています。

f:id:lufiabb:20200328213031p:plain
ツールチップ

また、-analysis=pointerとすると、上記に加えて静的コールグラフ等の情報が追加されます。以下はbufio.Reader.ReadByte()が呼び出す関数のグラフです。

f:id:lufiabb:20200328213053p:plain
呼び出す関数グラフ

ソースコードを表示して、funcの部分に追加されたリンクを選択すると、その関数を呼び出しているソースコードがリストされます。以下の例はbufio.Reader.ReadByte()を呼び出している場所です。

f:id:lufiabb:20200328213106p:plain
呼び出しグラフ

コマンドラインモード

コマンドラインモードはGo 1.11が最終バージョンです。以後サポートされなくなります。

godocは、ブラウザで使う以外にも、コマンドラインでドキュメントを読むためにも使うことができます。コマンドラインモードの使い方は

$ godoc [options] full/path/to/pkg [name ...]

のように引数としてパッケージと名前(省略可能)を受け取ります。godocは、nameを省略すると、パッケージに含まれる全てのドキュメントをコンソールに出力します。nameを与えると、その部分だけ抽出します。注意すべき点として、パッケージのimport pathは必ず完全なimport pathで渡す必要があります。

コマンドラインモードのgodocは、-srcオプションを与えると、対象のソースコードを標準出力に書き出します。他にも-qオプションを使うと、検索することも可能です。

$ godoc -src sync WaitGroup # WaitGroupのソースを表示

$ godoc -q 'Buffer' # BufferをGoDocから検索

参考情報

go docコマンド

goコマンドのサブコマンドです。godocコマンドラインモードと利用する場面は似ています。godocよりも機能はシンプルで、コマンドラインでのドキュメント出力しかできませんが、引数の渡し方が少し便利になっています。例えばgodocはパッケージをフルパスで渡す必要がありましたが、go doc

$ go doc appengine.Context

のように、パッケージが一意に特定できるならimport pathの一部だけを渡せば検索してくれます。また、特定メソッドだけを調べる場合、以下のように.でつなげて引数で渡すと、メソッドのドキュメントだけを絞り込んで表示します。

$ go doc oauth2.Config.AuthCodeURL

参考情報

その他

この記事ではパッケージのドキュメント例を紹介しましたが、他にも、presentコマンドを使うと、Go関連のスライドでよく使われる形式のスライドが作れたり、記事が作成できたりします。