本節では、より簡潔で明確なコードを書く方法について説明しました。各言語には独自の特性と独自のコーディング規範があり、Go においては、どのようなパフォーマンス最適化手段や便利なツールがあるかも紹介しました。
高品質なコードは、正確で信頼性があり、簡潔で明確な特性を持つ必要があります。
- 正確性:さまざまな境界条件が考慮されているか、誤った呼び出しが処理できるか
- 信頼性:異常事態やエラー処理が明確で、依存するサービスの異常が迅速に処理できるか
- 簡潔さ:ロジックが単純で、後続の機能追加が迅速にサポートできるか
- 明確さ:他の人がコードを読み理解する際に明確で、リファクタリング時に予期しない事態を心配しないか
これにはコーディング規範が必要です。
コーディング規範#
フォーマットツール#
コーディング規範を語る上で、コードフォーマットツールについて触れざるを得ません。Go 公式が提供するフォーマットツール gofmt
の使用を推奨します。Goland にはその機能が内蔵されており、一般的な IDE でも簡単に設定できます。
- もう一つのツールは
goimports
で、これはgofmt
に依存パッケージの管理機能を加えたもので、自動的に依存パッケージを追加または削除します。
js にも似たようなフォーマットツール
Prettier
があり、ESLint と組み合わせてコードフォーマットを行うことができます。
コメント規範#
良いコメントは以下を必要とします。
-
コードの役割を説明する
-
複雑で明確でないロジックを説明する
-
コード実装の理由を説明する(これらの要因は文脈から外れると理解が難しい)
-
コードがどのような状況でエラーになるかを説明する(制限条件を説明する)
-
公共シンボルのコメントを説明する(パッケージ内で宣言された各公共シンボル:変数、定数、関数、構造体など)
- 例外:インターフェースの実装メソッドにはコメントを付ける必要はありません
Google スタイルガイドには 2 つのルールがあります:
- 明白でもなく短くもない 公共機能 はコメントを付ける必要があります。
- 長さや複雑さに関わらず、ライブラリ内の任意の関数にはコメントを付ける必要があります。
避けるべき状況は以下の通りです:
- 可視名知義の関数に冗長なコメントを付ける
- 明らかなプロセスを直接翻訳する
要するに、コードが最良のコメントです。
- コメントは コードが表現していない文脈情報を提供するべきです。
- 簡潔で明確なコードはプロセスコメントの要求がありませんが、なぜそうするのか、コードの関連背景などはコメントで補足し、有効な情報を提供できます。
命名規範#
変数名#
-
簡潔さは冗長さに勝る
i
とindex
の作用範囲では、index
の余分な冗長さは必要ありません。
// Bad
for index := 0; index < len(s); index++ {
// do something
}
// Good
for i := 0; i < len(s); i++ {
// do something
}
-
略語は全て大文字ですが、変数の先頭にあり、エクスポートする必要がない場合は、全て小文字を使用します。
- 例えば
ServeHTTP
を使用し、ServeHttp
ではなく XMLHTTPRequest
またはxmlHTTPRequest
を使用します。
- 例えば
-
変数名が使用される場所から遠くなるほど、より多くの文脈情報を持つ必要があります。
- 例えば、グローバル変数では、その名前により多くの文脈情報が必要で、異なる場所でその意味を容易に識別できるようにします。
// Bad
func (c *Client) send(req *Request, t time.Time)
// Good
func (c *Client) send(req *Request, deadline time.Time)
関数命名#
-
関数名は パッケージ名の文脈情報を持たない、なぜならパッケージ名と関数名は常にペアで現れるからです。
- 例えば、http パッケージ内でサービスを作成する関数は、
Serve
よりもServeHTTP
です。なぜなら呼び出すときは常にhttp.Serve
だからです。
- 例えば、http パッケージ内でサービスを作成する関数は、
-
関数名は できるだけ短く。
-
foo
というパッケージのある関数が戻り値の型T
を返す場合(T
がFoo
でない場合)、関数名に戻り値の型情報を追加できます。Foo
型を返す場合は、省略しても誤解を招きません。
パッケージ名#
- 小文字のみで構成される。大文字やアンダースコアなどの文字を含まない。
- 短く、一定の文脈情報を含む。例えば
schema
、task
など。 - 標準ライブラリと同名にしない。例えば
sync
やstrings
を使用しない。以下の規則をできるだけ満たすこと。標準ライブラリのパッケージ名を例に: - 一般的な変数名をパッケージ名として使用しない。例えば
bufio
を使用し、buf
ではなく。 - 複数形ではなく単数形を使用する。例えば
encoding
を使用し、encodings
ではなく。 - 略語の使用には注意する。例えば、文脈を壊さない範囲で
fmt
を使用することはformat
よりも短くなります。
全体的に、良い命名はコードの理解コストを下げ、主なプロセスに焦点を当て、プログラムの機能を明確に理解できるようにし、頻繁にブランチの詳細に切り替える必要がなくなります。
制御フロー#
-
ネストを避け、通常のフローを明確に読みやすく保つ。
- エラー状況や特殊な状況を優先的に処理し、早期に戻ったりループを続けたりしてネストを減らします。
// Bad
if foo {
return x
} else {
return nil
}
// Good
if foo {
return x
}
return nil
- 通常のコードパスを最小のインデントに保ち、ネストを減らす。
// Bad
func OneFunc() error {
err := doSomething()
if err == nil {
err := doAnotherThing()
if err == nil {
return nil // normal case
}
return err
}
return err
}
// Good
func OneFunc() error {
if err := doSomething(); err != nil {
return err
}
if err := doSomething(); err != nil {
return err
}
return nil // normal case
}
要するに、プログラム内のフロー処理はできるだけ直線的にし、複雑なネスト分岐を避け、通常のフローコードを画面に沿って下に移動させます。これにより、コードの保守性と可読性が向上します。なぜなら、故障問題は大抵複雑な条件文やループ文に現れるからです。
エラー処理#
-
簡単なエラー
- 簡単なエラーは 一度だけ発生する エラーであり、他の場所で そのエラーを捕捉する必要がない。
- 優先的に
errors.New
を使用して匿名変数を作成し、簡単なエラーを直接表現します。 - フォーマットが必要な場合は、
fmt.Errorf
を使用します。
func defaultCheckRedirect(req *Request, via []*Request) error {
if len(via) >= 10 {
return errors.New("10回のリダイレクト後に停止しました")
}
return nil
}
-
複雑なエラー:エラーの
Wrap
とUnwrap
を使用します。- エラーの
Wrap
は、実際にはerror
を別のerror
にネストする能力を提供し、エラーの追跡チェーンを生成します。 fmt.Errorf
で%w
キーワードを使用して、エラーをエラーのチェーンに関連付けます。errors.Is
を使用して、エラーが特定のエラーであるかどうかを判定し、エラーのチェーン上のすべてのエラーを判定できます(go/wrap_test.go · golang/go)。errors.As
を使用して、エラーのチェーン上で特定の種類のエラーを取得し、エラーを定義された変数に割り当てます(go/wrap_test.go · golang/go)。
- エラーの
Go において、エラーよりも深刻なのは panic
であり、その発生はプログラムが正常に動作できないことを示します。
-
ビジネスコード内での panic の使用は推奨されません。
panic
が発生すると、呼び出しスタックの頂上に向かって伝播します。- 呼び出し関数が全て
recover
を含まない場合、プログラム全体がクラッシュします。 - 問題が隠蔽または解決できる場合は、
panic
の代わりにerror
を使用することを推奨します。
-
プログラムの起動段階で不可逆的なエラーが発生した場合は、
init
またはmain
関数内でpanic
を使用できます(sarama/main.go · Shopify/sarama)。
panic
があれば、自然と recover
にも言及されます。他のライブラリのbug
が原因でpanic
が発生し、自身のロジックに影響を与える場合は、recover が必要です。
recover
はdefer
された関数内でのみ使用でき、ネストは効かず、現在の goroutine 内でのみ有効です(github.com/golang/go/b…)。- defer の文は後入れ先出しです。
- さらなる文脈情報が必要な場合は、recover 後にログに現在の呼び出しスタックを記録できます(github.com/golang/webs…)。
小結#
error
は可能な限り簡潔な文脈情報チェーンを提供し、問題の特定を容易にします。panic
は本当に異常な状況で使用されます。recover
の効果範囲は、現在の goroutine のdefer
された関数内で有効です。
パフォーマンス最適化の提案#
- 前提:正確で信頼性があり、簡潔で明確な品質要因を満たした上で、プログラムの効率をできるだけ高める。
- 妥協:時には時間効率と空間効率が対立することがあり、重要度を分析して適切に妥協する必要があります。
Go 言語の特性に基づいて、授業では多くの Go 関連のパフォーマンス最適化提案が紹介されました。
メモリの事前割り当て#
make () を使用してスライスを初期化する際は、できるだけ容量情報を提供します。
func PreAlloc(size int) {
data := make([]int, 0, size)
for k := 0; k < size; k++ {
data = append(data, k)
}
}
これは、スライスが本質的に配列の断片の記述であり、配列ポインタ、断片の長さ、断片の容量(メモリ割り当てを変更しない場合の最大長)を含むためです。
- スライス操作はスライスが指す要素をコピーしません。
- 新しいスライスを作成すると、元のスライスの基底配列を再利用します。したがって、事前に容量の値を設定することで、追加のメモリ割り当てを回避し、より良いパフォーマンスを得ることができます。
文字列処理の最適化#
strings.Builder
を使用した一般的な文字列結合方法
-
+
で結合する(最も遅い) -
strings.Builder
(最も速い) -
bytes.Buffer
原理:文字列は Go 言語において不変型であり、占有するメモリサイズは固定です。 -
+
で結合する際、新しい文字列を生成し、新しい空間を開放します。この新しい空間のサイズは元の 2 つの文字列のサイズの合計です。 -
strings.Builder
とbytes.Buffer
のメモリは倍数で要求されます。 -
strings.Builder
とbytes.Buffer
の基底はどちらも[]byte
配列です。bytes.Buffer
は文字列に変換する際に新しい空間を再度要求し、生成された文字列変数を格納します。strings.Builder
は基底の[]byte
を直接文字列型に変換して返します。
func PreStrBuilder(n int, str string) string {
var builder strings.Builder
builder.Grow(n * len(str))
for i := 0; i < n; i++ {
builder.WriteString(str)
}
return builder.String()
}
空の構造体#
-
空の構造体
struct
インスタンスはメモリ空間を占有しません。 -
様々なシーンでのプレースホルダーとして使用できます。
- メモリ空間を節約
- 空の構造体自体は非常に強い意味を持ち、ここに値は必要なく、プレースホルダーとしてのみ使用されます。
-
例えば、Set を実装する際に、map のキーを利用し、値を空の構造体に設定します。(golang-set/threadunsafe...)
関連リンク#
- 《golang pprof 実戦》コード実験用例: github.com/wolfogre/go…
- test コマンドを使用して、簡単なテストを作成して実行する go.dev/doc/tutoria…
- -bench パラメータを使用して、作成した関数のパフォーマンステストを行う、pkg.go.dev/testing#hdr…
- Go コードレビューの提案 github.com/golang/go/w…
- Uber の Go コーディング規範、github.com/uber-go/gui…
まとめと感想#
本節では、Go や他の言語における一般的なコーディング規範を紹介し、Go 言語に関連するパフォーマンス最適化の提案を行いました。今後は、pprof ツールを使用したパフォーマンス最適化の実践演習も行いました。
ノートの内容は、第三回青訓キャンプの張雷先生の講義「高品質プログラミングとパフォーマンス調整実戦」に基づいています。
講義資料:【Go 言語原理と実践学習資料(上)】第三回バイトダンス青訓キャンプ - バックエンド専用