はじめに
この記事は未完成です。Golangらしいコードというのは文化のようなものだと考えているので、様々な方の意見を取り込みながら、この記事を充実させていきたいと考えています。よろしくお願いいたします。
Goらしいコードの前に
全言語共通のルールについてまとめます。きれいなコードを学びたい方へのおすすめはリーダブルコードです。というよりかは、エンジニアなら絶対に読んでほしい本です。
可読性の高いコード
読むときに覚えておくことが少ない
- シンプルに作られている
- 変数のスコープはできるだけ小さく
- ネストは浅く、関数は短く
意図が理解しやすい
- 適切なコメントが添えてある
- 設計の背景などがドキュメントにまとまっている
再現性がとりやすい
- ライブラリのバージョン管理がきっちりされている
- ビルドやテストがMakefileやスクリプトで再現できる
- 適度に抽象化されていてテストが外部依存してない
Goらしいコード
静的解析ツール
基本的に静的解析ツールは導入しないとプルリクは通りません。
静的解析ツール | 役割 |
---|---|
gofmt, goimports | コードフォーマッター |
go vet, golint | コードチェッカー, リンター |
gocode | コード補完 |
errcheck | エラー処理チェッカー |
コメントアウト
コメントは、記述されているものの名前で始まり、ピリオドで終わる必要があります。
// Request represents a request to run a command.
type Request struct { ...
// Encode writes the JSON encoding of req to w.
func Encode(w io.Writer, req *Request) { ...
Context
APIおよびプロセスの一連の流れで保持しておく必要のある値をコンテキストで管理します。具体的にはセキュリティ資格情報、トレース情報、期限、キャンセルのシグナルなどが挙げられます。
Contextを利用するほとんどの関数で一番最初の引数にしています。
func F(ctx context.Context, /* other arguments */) {}
スライス
var t []string
t := []string{}
上はnilスライスを返し、下は非nilスライスを返します。どちらもcap,lenともにゼロですが、上のnilスライスが優先スタイルです。しかしJSONオブジェクトをエンコードするときは非nilスライスが優先されます。
また、インターフェイスを設計するときは、ちょっとしたプログラミングエラーにつながる可能性があるので、nilスライスと非nil、長さゼロのスライスを区別しないでください。
math/rand
基本的にこのパッケージはキーを生成するときには利用しないでください。シードなしでは予測可能な値が出てしまいます。代わりに、crypto/randのリーダーを使用し、テキストが必要な場合は、16進数またはbase64で印刷します。
import (
"crypto/rand"
// "encoding/base64"
// "encoding/hex"
"fmt"
)
func Key() string {
buf := make([]byte, 16)
_, err := rand.Read(buf)
if err != nil {
panic(err) // out of randomness, should never happen
}
return fmt.Sprintf("%x", buf)
// or hex.EncodeToString(buf)
// or base64.StdEncoding.EncodeToString(buf)
}
panicは使わない
通常のエラー処理にpaincを使用しないでください。エラーと複数の戻り値を使用します。
エラーの文字列について
エラー文字列は、通常、他のコンテキストに従って出力されるため、大文字にしないでください。ただし、固有名詞や頭文字で始まる場合は大文字にしてください。
//これはよくない
fmt.Errorf("Something bad")
//通常はこのようにフォーマットされて出力されます。
fmt.Errorf("something bad")
log.Printf("Reading %s: %v", filename, err)
エラーハンドリングについて
関数の戻り値としてエラーが返ってくるときに、絶対に受け取り、処理をする必要があります。本当に例外の時だけpanicを利用して下さい。また、エラーが発生したときは先にエラーハンドリングを書きます。
if文があるときのエラーハンドリングについて。
//これはよくない例
if x, err := f(); err != nil {
// error handling
return
} else {
// use x
}
//こっちがよい例
x, err := f()
if err != nil {
// error handling
return
}
// use x
importについて
名前の衝突を避けるため以外は、インポートの名前を変更しないでください。衝突を避ける場合には、プロジェクト固有の名前にしてください。
変数や関数について
IDやHTTP、URLなど一貫して大文字の名前を付けることが一般的です。例えば「URL」はUrlとは命名せず、URLとします。ユーザIDに関してもuserIDのように命名します。また、golangはキャメルケースで書きます。
また、Golangは他の言語とは違い、長い名前を好まず、ある程度省略が許されています。
さらに、golangはスコープに厳しいので、変数を使う直前で宣言してください。
interface
使用する予定がない関数はinterfaceで定義しないでください。
値を渡す場合
大きな構造体や大きくなる見込みのあるスライス以外は、引数はポインタ型にしないでください。
メソッドのレシーバ
一貫性を保ち、短い名前、そのアイデンティティを反映していれば十分です。clientならば「c」や「cl」で十分です。メソッドの引数は説明的要素を含まなければなりませんが、レシーバは役割が明確なので、簡潔さが一番好まれます。
また、メソッドに特別な意味を与えるオブジェクト指向言語の典型的な識別子である「me」、「this」、「self」などの一般的な名前は使用しないようにしてください。
レシーバのタイプ
ポインタレシーバを使うか否かの判断基準
- レシーバがmap、func、またはchanの場合、それらへのポインターを使用しないでください。レシーバーがスライスであり、メソッドがスライスの再スライスまたは再割り当てを行わない場合、そのポインターを使用しないでください。
- メソッドがレシーバーを変更する必要がある場合、レシーバーはポインターでなければなりません。
- レシーバがsync.Mutexまたは同様の同期フィールドを含む構造体である場合、受信者はコピーを避けるためにポインターでなければなりません。
- レシーバーが大きな構造体または配列の場合、ポインターレシーバーの方が効率的です。
- レシーバーが構造体、配列、またはスライスであり、その要素のいずれかが変化している可能性のある場合は、ポインターレシーバーを優先します。
- 受信者が小さな配列または構造体の場合や可変フィールドとポインターがない場合、またはintやstringなどの単純な基本型である場合、ポインタレシーバは必要ないです。
- 疑わしい場合はポインタレシーバを使います。
Getter
GetXxxではなくXxxと命名しましょう。