過度に長い行を避ける
横にスクロールが必要ないし文書が過度に回転するようなコード行は避けてください。
99文字を上限とすることを推奨いたします。作者は、この制限を超えないようにコードを分割することをお勧めしますが、これは硬いルールではありません。コードがこの制限を超えても許容されます。
一貫性
このドキュメントで概説された規格のいくつかは、主観的な判断、シナリオ、または文脈に基づいています。しかし、最も重要なのは、一貫性を維持することです。
一貫性のあるコードは、メンテナンスしやすく、より合理的であり、学習コストが低く、新しい規約が出現するかエラーが発生した際に移行、更新、エラー修正がしやすいです。
逆に、コードベースに異なる完全に異なるまたは競合する複数のコードスタイルを含めることは、メンテナンスコストの増加、不確実性、そして認知バイアスを引き起こします。これらはすべて、速度の低下、苦痛なコードレビュー、およびバグの数の増加と直結しています。
これらの規格をコードベースに適用する際には、パッケージ(またはそれ以上)レベルで変更することをお勧めします。サブパッケージレベルで複数のスタイルを適用することは、上記の懸念を侵害します。
類似の宣言をグループ化する
Go言語では、似たような宣言をグループ化することがサポートされています。
推奨されない:
import "a"
import "b"
推奨される:
import (
"a"
"b"
)
これは、定数、変数、および型の宣言にも適用されます。
推奨されない:
const a = 1
const b = 2
var a = 1
var b = 2
type Area float64
type Volume float64
推奨される:
const (
a = 1
b = 2
)
var (
a = 1
b = 2
)
type (
Area float64
Volume float64
)
関連する宣言は一緒にグループ化し、関連のない宣言をグループ化するのを避けてください。
推奨されない:
type Operation int
const (
Add Operation = iota + 1
Subtract
Multiply
EnvVar = "MY_ENV"
)
推奨される:
type Operation int
const (
Add Operation = iota + 1
Subtract
Multiply
)
const EnvVar = "MY_ENV"
グループ化を使用する場所に制約はありません。たとえば、関数の中でそれらを使用することができます。
推奨されない:
func f() string {
red := color.New(0xff0000)
green := color.New(0x00ff00)
blue := color.New(0x0000ff)
...
}
推奨される:
func f() string {
var (
red = color.New(0xff0000)
green = color.New(0x00ff00)
blue = color.New(0x0000ff)
)
...
}
例外:変数の宣言が他の変数に隣接している場合、特に関数内の宣言であっても、それらを一緒にグループ化する必要があります。関連のない変数が一緒に宣言されている場合も同様です。
推奨されない:
func (c *client) request() {
caller := c.name
format := "json"
timeout := 5*time.Second
var err error
// ...
}
推奨される:
func (c *client) request() {
var (
caller = c.name
format = "json"
timeout = 5*time.Second
err error
)
// ...
}
Importのグルーピング
Importは次の二つのカテゴリにグループ化されるべきです:
- 標準ライブラリ
- その他のライブラリ
デフォルトでは、これはgoimportsによって適用されるグループ化です。
推奨されない:
import (
"fmt"
"os"
"go.uber.org/atomic"
"golang.org/x/sync/errgroup"
)
推奨される:
import (
"fmt"
"os"
"go.uber.org/atomic"
"golang.org/x/sync/errgroup"
)
パッケージ名
パッケージ名を付けるときは、以下のルールに従ってください:
- すべて小文字、大文字やアンダースコアは使用しないこと。
- 多くの場合、インポート時に名前を変更する必要はありません。
- 短く簡潔にすること。名前は使用される場所全てで完全修飾されることを忘れないでください。
- 複数形を避けること。例えば、
net/url
の代わりにnet/urls
を使用しないこと。 - "common," "util," "shared," または "lib"の使用は避けること。これらは十分な情報提供ができないためです。
関数の命名
関数名には、Goコミュニティの慣例であるMixedCapsを使用します。関連するテストケースをグループ化する場合は、関数名にアンダースコアを含めることがあります。例:TestMyFunction_WhatIsBeingTested
。
インポートエイリアス
パッケージ名がインポートパスの最後の要素と一致しない場合は、インポートエイリアスを使用する必要があります。
import (
"net/http"
client "example.com/client-go"
trace "example.com/trace/v2"
)
それ以外の場合は、直接的なインポートの衝突がない限り、インポートエイリアスは避けるべきです。 推奨されない書き方:
import (
"fmt"
"os"
nettrace "golang.net/x/trace"
)
推奨される書き方:
import (
"fmt"
"os"
"runtime/trace"
nettrace "golang.net/x/trace"
)
関数のグループ化と順序
- 関数は大まかに呼び出される順序で並べるべきです。
- 同じファイル内の関数は、レシーバによってグループ化する必要があります。
したがって、エクスポートされた関数はファイルの先頭に配置され、struct
、const
、var
の定義の後に配置されます。
newXYZ()
/NewXYZ()
は、タイプ定義の後に現れることがありますが、残りのレシーバのメソッドの前に配置されます。
関数が受信者によってグループ化されるため、一般的なユーティリティ関数はファイルの最後に配置する必要があります。 推奨されない書き方:
func (s *something) Cost() {
return calcCost(s.weights)
}
type something struct{ ... }
func calcCost(n []int) int {...}
func (s *something) Stop() {...}
func newSomething() *something {
return &something{}
}
推奨される書き方:
type something struct{ ... }
func newSomething() *something {
return &something{}
}
func (s *something) Cost() {
return calcCost(s.weights)
}
func (s *something) Stop() {...}
func calcCost(n []int) int {...}
入れ子を減らす
コードはエラー/特殊なケースを可能な限り早く処理し、ループを終了または継続することで、入れ子を減らすべきです。入れ子を減らすことは、複数のレベルでのコードの量を減らします。
推奨されない書き方:
for _, v := range data {
if v.F1 == 1 {
v = process(v)
if err := v.Call(); err == nil {
v.Send()
} else {
return err
}
} else {
log.Printf("Invalid v: %v", v)
}
}
推奨される書き方:
for _, v := range data {
if v.F1 != 1 {
log.Printf("Invalid v: %v", v)
continue
}
v = process(v)
if err := v.Call(); err != nil {
return err
}
v.Send()
}
不要なelse
変数がifの両方のブランチで設定されている場合、単一のif文で置き換えることができます。
推奨されない書き方:
var a int
if b {
a = 100
} else {
a = 10
}
推奨される書き方:
a := 10
if b {
a = 100
}
トップレベルの変数宣言
トップレベルでは、標準のvar
キーワードを使用します。式の型が異なる場合を除いて、型を明示しないでください。
推奨されない書き方:
var _s string = F()
func F() string { return "A" }
推奨される書き方:
var _s = F()
// Fは明示的に文字列型を返すため、_sの型を明示的に指定する必要はありません
func F() string { return "A" }
式の型が必要な場合は、それが式で必要な型と完全に一致しない場合に型を指定します。
type myError struct{}
func (myError) Error() string { return "error" }
func F() myError { return myError{} }
var _e error = F()
// FはmyError型のインスタンスを返しますが、error型が必要です
未公開のトップレベル定数および変数の接頭辞として'_'を使用する
未公開のトップレベルvars
およびconsts
には、使用される際にそのグローバルな性質を明示するために、アンダースコア _
を接頭辞として付けます。
基本的な理由:トップレベルの変数と定数はパッケージレベルのスコープを持っています。一般的な名前を使用すると、他のファイルで間違って値を使用する可能性があります。
推奨されない:
// foo.go
const (
defaultPort = 8080
defaultUser = "user"
)
// bar.go
func Bar() {
defaultPort := 9090
...
fmt.Println("Default port", defaultPort)
// Bar()の最初の行が削除された場合、コンパイルエラーは発生しません。
}
推奨:
// foo.go
const (
_defaultPort = 8080
_defaultUser = "user"
)
例外:未公開のエラー値は、アンダースコアなしでerr
接頭辞を使用することができます。エラーの命名を参照してください。
構造体への埋め込み
埋め込まれた型(mutexなど)は、構造体内のフィールドリストの先頭に配置し、埋め込まれたフィールドと通常のフィールドを区切るために空行を置く必要があります。
推奨されない:
type Client struct {
version int
http.Client
}
推奨:
type Client struct {
http.Client
version int
}
埋め込みは、セマンティックに適切な方法で機能を追加または強化するなど、具体的な利点を提供するべきです。ユーザに悪影響を与えることなく使用されるべきです。(また参照:公開する構造体での型の埋め込みを避ける)
例外:非公開の型でも、Mutexは埋め込まれたフィールドとして使用するべきではありません。また参照:ゼロ値のMutexは有効です。
埋め込みはしないべきこと:
- 美的または便宜のために存在するだけである。
- 外部タイプの構築や使用をより困難にする。
- 外部タイプのゼロ値に影響を与える。
- 埋め込まれた内部タイプから関連しない関数やフィールドを公開する副作用を持つ。
- 非公開の型を公開する。
- 外部タイプのクローニングフォームに影響を与える。
- 外部タイプのAPIまたはタイプの意味論を変更する。
- 内部タイプを標準形式で埋め込む。
- 外部タイプの実装の詳細を公開する。
- ユーザが内部のタイプを観察または制御できるようにする。
- ユーザを驚かせる可能性のある方法で内部関数の一般的な動作を変更する。
要するに、埋め込みは意識的かつ目的をもって行うべきです。良い基準は、「内部の型から直接外部の型に追加されるそれらの公開メソッド/フィールドですか?」というテストです。もし答えがいくつか
またはいいえ
であるならば、内部の型を埋め込まずにフィールドを使用してください。
推奨されない:
type A struct {
// 悪い: A.Lock() と A.Unlock() が利用可能になりました
// 機能上の利点を提供せず、ユーザにAの内部詳細を制御する機会を与えます。
sync.Mutex
}
推奨:
type countingWriteCloser struct {
// 良い: Write()は特定の目的で外部で提供され、内部のWrite()に作業が委譲されます。
io.WriteCloser
count int
}
func (w *countingWriteCloser) Write(bs []byte) (int, error) {
w.count += len(bs)
return w.WriteCloser.Write(bs)
}
ローカル変数の宣言
変数が明示的に値を設定されている場合、短い変数宣言形式(:=
)を使用すべきです。
推奨されない:
var s = "foo"
推奨:
s := "foo"
ただし、一部の場合ではデフォルトの値に対してvar
キーワードを使用する方がわかりやすいことがあります。
推奨されない:
func f(list []int) {
filtered := []int{}
for _, v := range list {
if v > 10 {
filtered = append(filtered, v)
}
}
}
推奨:
func f(list []int) {
var filtered []int
for _, v := range list {
if v > 10 {
filtered = append(filtered, v)
}
}
}
nilは有効なスライスです
nil
は長さ0の有効なスライスです。これは次のことを意味します:
- 長さ0のスライスを明示的に返すべきではありません。代わりに
nil
を返してください。
非推奨:
if x == "" {
return []int{}
}
推奨:
if x == "" {
return nil
}
- スライスが空かどうかを確認する場合は、常に
len(s) == 0
を使用してください。nil
を使用しないでください。
非推奨:
func isEmpty(s []string) bool {
return s == nil
}
推奨:
func isEmpty(s []string) bool {
return len(s) == 0
}
- ゼロ値スライス(
var
で宣言されたスライス)は、make()
を呼び出さずに直接使用することができます。
非推奨:
nums := []int{}
// or, nums := make([]int)
if add1 {
nums = append(nums, 1)
}
if add2 {
nums = append(nums, 2)
}
推奨:
var nums []int
if add1 {
nums = append(nums, 1)
}
if add2 {
nums = append(nums, 2)
}
nilのスライスは有効なスライスですが、長さ0のスライスと等しくはないことに注意してください(一方はnilであり、他方はそうではありません)。そして、異なる状況で異なる扱いをされる可能性があるため(例:シリアル化)、それらを異なるものとして取り扱う可能性があります。
変数のスコープを狭める
可能であれば、変数のスコープを狭めるようにしてください。ただし、ネストの削減のルールと競合しない限りです。
非推奨:
err := os.WriteFile(name, data, 0644)
if err != nil {
return err
}
推奨:
if err := os.WriteFile(name, data, 0644); err != nil {
return err
}
if文の外で関数呼び出しの結果を使用する必要がある場合は、スコープを狭めることを試みないでください。
非推奨:
if data, err := os.ReadFile(name); err == nil {
err = cfg.Decode(data)
if err != nil {
return err
}
fmt.Println(cfg)
return nil
} else {
return err
}
推奨:
data, err := os.ReadFile(name)
if err != nil {
return err
}
if err := cfg.Decode(data); err != nil {
return err
}
fmt.Println(cfg)
return nil
裸のパラメータを避ける
関数呼び出しで意味が不明確なパラメータは可読性に害を及ぼす可能性があります。パラメータの意味が明らかでない場合は、Cスタイルのコメント(/* ... */
)をパラメータに追加してください。
非推奨:
// func printInfo(name string, isLocal, done bool)
printInfo("foo", true, true)
推奨:
// func printInfo(name string, isLocal, done bool)
printInfo("foo", true /* isLocal */, true /* done */)
上記の例では、より良いアプローチとして、bool
型の代わりにカスタム型を使用することができます。これにより、将来的に2つだけでなく他の状態(true/false)もサポートできる可能性があります。
type Region int
const (
UnknownRegion Region = iota
Local
)
type Status int
const (
StatusReady Status= iota + 1
StatusDone
// 将来的にStatusInProgressが追加されるかもしれません。
)
func printInfo(name string, region Region, status Status)
エスケープを避けるために生の文字列リテラルを使用する
Goでは生の文字列リテラルを使用できます。生文字列は " ` " で表され、手作業でエスケープするよりも読みやすいソースコードを実現できます。
これらの文字列は複数行にまたがり、引用符を含めることができます。これらの文字列を使用することで、手作業でエスケープするよりも読みやすくなります。
非推奨:
wantError := "unknown name:\"test\""
推奨:
wantError := `unknown error:"test"`
フィールド名を使用して構造体を初期化する
構造体を初期化する際は、フィールド名をほとんど常に指定する必要があります。これは現在go vet
によって強制されています。
推奨されない方法:
k := User{"John", "Doe", true}
推奨される方法:
k := User{
FirstName: "John",
LastName: "Doe",
Admin: true,
}
例外: フィールドが3つ以下の場合、テストテーブル内のフィールド名を省略することができます。
tests := []struct{
op Operation
want string
}{
{Add, "add"},
{Subtract, "subtract"},
}
構造体内のゼロ値フィールドを省略する
フィールド名付きで構造体を初期化する際は、意味のある文脈が提供されていない限り、ゼロ値のフィールドは無視します。つまり、これらを自動的にゼロ値に設定します。
推奨されない方法:
user := User{
FirstName: "John",
LastName: "Doe",
MiddleName: "",
Admin: false,
}
推奨される方法:
user := User{
FirstName: "John",
LastName: "Doe",
}
これにより、コンテキスト内のデフォルト値を省略することで読み取り障壁が低減されます。意味のある値のみを指定してください。
フィールド名が意味のある文脈を提供する場合は、ゼロ値を含めてください。例えば、テーブル駆動型テストのテストケースでは、フィールドを名前付きにすると有益です。
tests := []struct{
give string
want int
}{
{give: "0", want: 0},
// ...
}
ゼロ値の構造体にvar
を使用する
構造体のすべてのフィールドが宣言で省略されている場合は、構造体を宣言する際にvar
を使用してください。
推奨されない方法:
user := User{}
推奨される方法:
var user User
これにより、ゼロ値の構造体とゼロ値でないフィールドを持つ構造体が区別されます。これは、空のスライスを宣言する際の好ましい方法と似ています。
構造体の参照を初期化する
構造体の参照を初期化する際は、new(T)
ではなく&T{}
を使用して、構造体の初期化を一貫させてください。
推奨されない方法:
sval := T{Name: "foo"}
// 一貫していない
sptr := new(T)
sptr.Name = "bar"
推奨される方法:
sval := T{Name: "foo"}
sptr := &T{Name: "bar"}
マップの初期化
空のマップを初期化する場合は、make(..)
を使用して初期化し、マップをプログラムで埋めるようにします。これにより、初期化が宣言と見た目で異なるだけでなく、make
を使用することでサイズのヒントを追加することも便利です。
推奨されない方法:
var (
// m1は読み取り書き込み安全です;
// m2は書き込むとパニックします
m1 = map[T1]T2{}
m2 map[T1]T2
)
推奨される方法:
var (
// m1は読み取り書き込み安全です;
// m2は書き込むとパニックします
m1 = make(map[T1]T2)
m2 map[T1]T2
)
| 宣言と初期化は非常に似ています。 | 宣言と初期化は非常に異なります。 |
可能な場合は、初期化時にマップの容量を指定し、詳細については「マップ容量の指定」を参照してください。
さらに、マップが固定された要素のリストを含む場合は、マップリテラルを使用してマップを初期化してください。
推奨されない方法:
m := make(map[T1]T2, 3)
m[k1] = v1
m[k2] = v2
m[k3] = v3
推奨される方法:
m := map[T1]T2{
k1: v1,
k2: v2,
k3: v3,
}
基本的なガイドラインは、初期化時に固定された要素セットを追加する場合はマップリテラルを使用することです。それ以外の場合はmake
を使用してください(可能であれば、マップの容量を指定してください)。
Printfスタイル関数のための文字列フォーマット
Printf
スタイルの関数のフォーマット文字列を関数の外で宣言する場合は、const
定数として設定してください。
これにより、go vet
がフォーマット文字列に対して静的解析を実行できるようになります。
推奨されない方法:
msg := "unexpected values %v, %v\n"
fmt.Printf(msg, 1, 2)
推奨される方法:
const msg = "unexpected values %v, %v\n"
fmt.Printf(msg, 1, 2)
Printf形式の関数の命名
Printf
形式の関数を宣言する際は、go vet
がフォーマット文字列を検出およびチェックできるようにしてください。
これは、可能な限り事前定義されたPrintf
形式の関数名を使用する必要があることを意味します。go vet
はこれらをデフォルトでチェックします。詳細については、Printfファミリを参照してください。
事前に定義された名前が使用できない場合は、選択した名前をf
で終了させてください: Wrap
の代わりにWrapf
。go vet
は特定のPrintf形式の名前をチェックするよう要求できますが、その名前はf
で終わらなければなりません。
go vet -printfuncs=wrapf,statusf