Goでコメントを書く方法
はじめに
ほぼすべてのプログラミング言語には、コードにコメントを追加するための構文がありますが、Goも例外ではありません。コメントは、プログラム内の行であり、コードがどのように機能するか、またはなぜそのように書かれているかを人間の言語で説明するものです。これらはコンパイラには無視されますが、注意深いプログラマーには無視されません。コメントは、共同作業者や将来の自分が落とし穴を避けるために貴重な文脈を追加し、保守性の高いコードを書くのに役立ちます。
任意のパッケージ内の通常のコメントは、そのコードが何をするかを説明します。これらは、パッケージの開発者へのメモや警告です。ドキュメントコメントは、パッケージの各コンポーネントが何をするか、そしてどのように機能するかをまとめ、例のコードやコマンドの使用方法を提供します。これらは、ユーザーに対する公式のパッケージのドキュメントです。
この記事では、Goパッケージの一部から抜粋した実際のコメントを通じて、Goのコメントの見た目だけでなく、何を伝えるべきかを示します。
普通のコメント
Goのコメントは、二つのスラッシュ(//)で始まります。その後にスペースを入れます(必須ではありませんが、慣用的です)、そしてコメントを書きます。コメントはコードの直上または右側に表示されることがあります。直上の場合、コードと整列するためにインデントをします。
このHello Worldプログラムには、独自の行に単一のコメントが含まれています。 (Kono Hello World puroguramu ni wa, dokuzen no gyo ni tan’itsu no komento ga fukumareteimasu.)
package main
import "fmt"
func main() {
// Say hi via the console
fmt.Println("Hello, World!")
}
Note
このコメントはとても短いため、コードの右側に直接表示するインラインコメントとして表示されることもあります。
. . .
fmt.Println("Hello, World!") // Say hi via the console
. . .
大半のコメントは、このような短いものを除けば、独自の行に表示されます。
より長いコメントは複数行に渡ります。Goでは、非常に長いコメントを開始および終了するために/*と*/のタグを使用したCスタイルのブロックコメントがサポートされていますが、これらは特別な場合にのみ使用されます(詳しくは後で説明します)。通常の複数行コメントは、ブロックコメントタグを使用するのではなく、各行を//で始めます。
以下は、適切にインデントされた多くのコメントが含まれたコードの一部です。1つの複数行コメントが強調されています。
package main
import "fmt"
const favColor string = "blue" // Could have chosen any color
func main() {
var guess string
// Create an input loop
for {
// Ask the user to guess my favorite color
fmt.Println("Guess my favorite color:")
// Try to read a line of input from the user.
// Print out an error and exit, if there is one.
if _, err := fmt.Scanln(&guess); err != nil {
fmt.Printf("%s\n", err)
return
}
// Did they guess the correct color?
if favColor == guess {
// They guessed it!
fmt.Printf("%q is my favorite color!\n", favColor)
return
}
// Wrong! Have them guess again.
fmt.Printf("Sorry, %q is not my favorite color. Guess again.\n", guess)
}
}
これらのコメントのほとんどは、実際には不要なものです。このような小さくシンプルなプログラムには、このような数多くのコメントは必要ありません。そして、ほとんどのコメントはコード自体で明らかなことを言っています。Goの文法や制御フロー、データ型などの基本を理解している他のGoプログラマーには信頼できます。スライスの反復や2つの浮動小数点数の乗算が行われることをコメントで宣言する必要はありません。
ただし、これらのコメントの中で一つは有用です。
良いコメントは、なぜ良いのかを説明します。
プログラムにおいて、最も便利なコメントは、コードが何をするかやどのようにそれを行うかを説明するのではなく、なぜそれを行うのかを説明するコメントです。時にはなぜかわからない場合もありますが、それもインラインコメントとして指摘することは便利です。
const favColor string = "blue" // Could have chosen any color
このコメントはコードでは伝えられないことを述べています:「青」という値はプログラマによって任意に選ばれたということです。言い換えると、// これを自由に変更してください。
多くのコードはなぜかという理由を持っています。以下はGo標準ライブラリのnet/httpパッケージにある関数で、非常に役立つ2つのコメントがあります。
. . .
// refererForURL returns a referer without any authentication info or
// an empty string if lastReq scheme is https and newReq scheme is http.
func refererForURL(lastReq, newReq *url.URL) string {
// https://tools.ietf.org/html/rfc7231#section-5.5.2
// "Clients SHOULD NOT include a Referer header field in a
// (non-secure) HTTP request if the referring page was
// transferred with a secure protocol."
if lastReq.Scheme == "https" && newReq.Scheme == "http" {
return ""
}
referer := lastReq.String()
if lastReq.User != nil {
// This is not very efficient, but is the best we can
// do without:
// - introducing a new method on URL
// - creating a race condition
// - copying the URL struct manually, which would cause
// maintenance problems down the line
auth := lastReq.User.String() + "@"
referer = strings.Replace(referer, auth, "", 1)
}
return referer
}
. . .
最初に強調されたコメントは、下のコードを変更しないように警告しています。なぜなら、それはHTTPプロトコルの公式仕様であるRFCに準拠して意図的に書かれているからです。2番目に強調されたコメントは、下のコードが理想的でないことを認め、メンテナが改善を試みる方法を示唆し、その危険性について警告しています。
こういったコメントは欠かせません。開発者が無意識にバグや他の問題を導入するのを防ぎ、同時に新しいアイデアの実装を促すこともできますが、注意が必要です。
関数の宣言の前のコメントも役立ちますが、異なる方法で役に立ちます。次はそのようなコメントを探ってみましょう。
ドキュメントコメント
上位レベルの(インデントされていない)宣言(例: package、func、const、var、type)の直上に表示されるコメントは、ドキュメントコメントと呼ばれます。それらは公式のパッケージドキュメントおよびそのパッケージ内のすべてのエクスポートされた名前を表すために使用されます。
Note
ドキュメントのコメントは、何をどのように説明しているかを説明しています。
普通のコメントとは異なり、ドキュメントコメントは通常、コードの動作やその方法について説明します。それはパッケージのメンテナンス担当者ではなく、通常はコードを読んだりコードに貢献したりしたくないユーザー向けに作成されているためです。
一般的にユーザーはコメントを3つの場所のいずれかで読むことがあります。
-
- Goの個別のソースファイルやディレクトリに対して、ローカルターミナルでgo docを実行することで。
-
- 公開されているあらゆるGoパッケージの公式ドキュメントにアクセスするためのpkg.go.devで。
- godocツールを使用して、チームがホストするプライベートなウェブサーバー上で。このツールを使うことで、チームは独自のリファレンスポータルを作成することができます。
Goのパッケージを開発する際には、すべての公開されている名前に対してドキュメントコメントを書くべきです。(非公開の名前にも時折書くことがあります。)ここに、Silicon Cloud APIのGoクライアントライブラリであるgodoでの1行のドキュメントコメントの例があります。
// Client manages communication with Silicon Cloud V2 API.
type Client struct {
こうしたシンプルなドキュメントコメントは必要なさそうに思えるかもしれませんが、忘れずに他のドキュメントコメントと共に表示され、パッケージのすべての使用可能なコンポーネントを包括的にドキュメント化することを覚えておいてください。
こちらはパッケージからのより長いドキュメントコメントです。
// Do sends an API request and returns the API response. The API response is JSON decoded and stored in the value
// pointed to by v, or returned as an error if an API error has occurred. If v implements the io.Writer interface,
// the raw response will be written to v, without attempting to decode it.
func (c *Client) Do(ctx context.Context, req *http.Request, v interface{}) (*Response, error) {
. . .
}
関数のドックコメントは、引数の予想される形式(明らかでない場合)と関数が返すデータの形式を明確に指定するべきです。また、関数の動作を要約することもあります。
関数Doのドキュメントコメントと関数内のコメントを比較してください。
// Ensure the response body is fully read and closed
// before we reconnect, so that we reuse the same TCPConnection.
// Close the previous response's body. But read at least some of
// the body so if it's small the underlying TCP connection will be
// re-used. No need to check for errors: if it fails, the Transport
// won't reuse it anyway.
これは、私たちがnet/httpパッケージで見たコメントに似ています。このコメントの下にあるコードを読んでいるメンテナーは、「なぜエラーチェックをしないのか?」と疑問に思い、それからエラーチェックを追加するかもしれません。しかし、コメントはなぜそれをする必要がないのかを説明しています。これは、ドキュメントコメントと同じく、高レベルの「何」や「どのように」ではありません。
非常に高いレベルのドキュメントコメントはパッケージコメントです。各パッケージには、パッケージの概要と使用方法、コードやコマンドの例を提供するための1つのパッケージコメントが含まれるべきです。パッケージコメントは任意のソースファイル内(かつそのファイルのみ)に現れることができます。しばしば、パッケージコメントはdoc.goという独自のファイルに現れます。
他のすべてのコメントとは異なり、パッケージのコメントは/*と*/を使用することがよくあります。なぜなら、パッケージのコメントは非常に長いことがあるからです。以下はgofmtのパッケージコメントの冒頭です。
/*
Gofmt formats Go programs.
It uses tabs for indentation and blanks for alignment.
Alignment assumes that an editor is using a fixed-width font.
Without an explicit path, it processes the standard input. Given a file,
it operates on that file; given a directory, it operates on all .go files in
that directory, recursively. (Files starting with a period are ignored.)
By default, gofmt prints the reformatted sources to standard output.
Usage:
gofmt [flags] [path ...]
The flags are:
-d
Do not print reformatted sources to standard output.
If a file's formatting is different than gofmt's, print diffs
to standard output.
. . .
*/
package main
では、ドキュメントコメントの形式はどうなりますか?どのような構造を持つことができるのでしょうか(または持たなければならないのでしょうか)?
ドキュメントのコメントには形式があります。
ゴーのクリエイターによる古いブログ記事によると、
Godocは概念的にPythonのDocstringやJavaのJavadocに関連していますが、そのデザインはよりシンプルです。Godocが読み込むコメントは、Docstringのような言語構造ではなく、独自の機械可読な構文を持つ必要もありません(Javadocのように)。Godocのコメントは、単に良いコメントであり、Godocが存在しなくても読みたいようなものです。
ドキュメントコメントは必須のフォーマットはありませんが、Goのドキュメントで詳細に説明されているMarkdownの「簡略化されたサブセット」のフォーマットを使用することもできます。ドキュメントコメントでは、段落やリストで書き、インデントされたブロックで例示コードやコマンドを示し、参照先へのリンクなども記載します。このフォーマットに沿って、ドキュメントコメントが適切に構造化されていると、美しいウェブページにレンダリングされることがあります。
ゴー言語でのはじめてのプログラム「greeting.go」に追加されたコメントを以下に示します。
// This is a doc comment for greeting.go.
// - prompt user for name.
// - wait for name
// - print name.
// This is the second paragraph of this doc comment.
// `gofmt` (and `go doc`) will insert a blank line before it.
package main
import (
"fmt"
"strings"
)
func main() {
// This is not a doc comment. Gofmt will NOT format it.
// - prompt user for name
// - wait for name
// - print name
// This is not a "second paragraph" because this is not a doc comment.
// It's just more lines to this non-doc comment.
fmt.Println("Please enter your name.")
var name string
fmt.Scanln(&name)
name = strings.TrimSpace(name)
fmt.Printf("Hi, %s! I'm Go!", name)
}
上記のpackage mainのコメントはドキュメントコメントです。このコメントは、段落やリストの形式を使おうとしていますが、完全に正しくありません。gofmtツールを使用すると、それを適切な形式に整形します。gofmt greeting.goを実行すると、次のように表示されます。
// This is a doc comment for greeting.go.
// - prompt user for name.
// - wait for name.
// - print name.
//
// This is the second paragraph of this doc comment.
// `gofmt` (and `go doc`) will insert a blank line before it.
package main
import (
"fmt"
"strings"
)
func main() {
// This is not a doc comment. `gofmt` will NOT format it.
// - prompt user for name
// - wait for name
// - print name
// This is not a "second paragraph" because this is not a doc comment.
// It's just more lines to this non-doc comment.
fmt.Println("Please enter your name.")
var name string
fmt.Scanln(&name)
name = strings.TrimSpace(name)
fmt.Printf("Hi, %s! I'm Go!", name)
}
以下を日本語で述べ替えるときに留意してください:
-
- ドキュメントコメントの最初の段落にリストされたアイテムが現在整列されています。
-
- 最初と2番目の段落の間には空行が挿入されました。
- main()内のコメントは、gofmtがドキュメントコメントではないと認識したため、フォーマットされませんでした。(しかし、前述のように、gofmtはすべてのコメントをコードと同じインデントに整列させます。)
greeting.goを実行すると、ドキュメントコメントを整形して表示しますが、main()内のコメントは表示されません。
This is a doc comment for greeting.go.
- prompt user for name.
- wait for name.
- print name.
This is the second paragraph of this doc comment. `gofmt` (and `go doc`) will
insert a blank line before it.
このドキュメントコメントの書式を一貫して適切に使用すれば、パッケージのユーザーは読みやすいドキュメンテーションに感謝します。
ドキュメントコメントの公式参照ページを読んで、それらをうまく書く方法についてすべてを学んでください。
コードを素早く無効にする
アプリケーションの動作を遅くしたり、最悪の場合は全ての機能を壊してしまいましたか?そのような場合には、Cスタイルの /* と */ タグを使用することができます。問題のあるコードの前に /* を一つ、後ろに */ を一つ書くことで、素早くそのコードを無効化することができます。その問題が解決されたら、そのコードを再度有効化するためにタグを取り除いてください。
. . .
func main() {
x := initializeStuff()
/* This code is causing problems, so we're going to comment it out for now
someProblematicCode(x)
*/
fmt.Println("This code is still running!")
}
長いコードの塊では、問題のあるコードの各行の最初に「//」を追加するよりも、これらのタグを使用する方が便利です。慣例として、通常のコメントやコードに永続的に残るドキュメントコメントには「//」を使用してください。「/*」と「*/」のタグは、テスト中に一時的に使用するか、前述したようにパッケージコメントでのみ使用してください。コメントアウトされたコードの一部を長期間プログラムに残さないでください。
結論
すべてのGoプログラムに表現豊かなコメントを書くことで、あなたは以下のことを実現しています:
-
- 以下の内容を日本語で同義に書き換えます。オプションは1つだけです:
1. コラボレーターが問題を起こさないようにする。
2. コードがなぜ最初にそのように書かれたのかを時折忘れてしまう自分の未来のためにサポートする。
3. 自分のコードに深入りせずに、パッケージのユーザーに参照として提供する。