12 KiB
12 KiB
| description | applyTo |
|---|---|
| 慣用的なGoプラクティスとコミュニティ標準に従ったGoコード記述の指針 | **/*.go,**/go.mod,**/go.sum |
Go開発指針
Goコードを書く際は慣用的なGoプラクティスとコミュニティ標準に従ってください。これらの指針はEffective Go、Go Code Review Comments、GoogleのGoスタイルガイドに基づいています。
全般指針
- シンプルで明確、慣用的なGoコードを記述
- 巧妙さよりも明確性とシンプルさを優先
- 最小驚きの原則に従う
- ハッピーパスを左寄せにする(インデントを最小化)
- ネストを減らすため早期リターンを使用
- ゼロ値を有用にする
- エクスポートされた型、関数、メソッド、パッケージを文書化
- 依存関係管理にはGoモジュールを使用
命名規則
パッケージ
- 小文字の単語でパッケージ名を使用
- アンダースコア、ハイフン、mixedCapsを避ける
- パッケージが何を含むかではなく、何を提供するかを説明する名前を選択
util、common、baseなどの汎用的な名前を避ける- パッケージ名は複数形ではなく単数形にする
変数と関数
- アンダースコアではなくmixedCapsまたはMixedCaps(camelCase)を使用
- 短いが説明的な名前を維持
- 単文字変数は非常に短いスコープ(ループインデックスなど)でのみ使用
- エクスポートされた名前は大文字で始まる
- エクスポートされていない名前は小文字で始まる
- 重複を避ける(例:
http.HTTPServerを避け、http.Serverを使用)
インターフェース
- 可能な場合はインターフェース名に-er接尾辞を使用(例:
Reader、Writer、Formatter) - 単一メソッドインターフェースはメソッド名で命名(例:
Read→Reader) - インターフェースは小さく焦点を絞って維持
定数
- エクスポートされた定数にはMixedCapsを使用
- エクスポートされていない定数にはmixedCapsを使用
- 関連する定数は
constブロックでグループ化 - より良い型安全性のため型付き定数を検討
コードスタイルとフォーマット
フォーマット
- コードのフォーマットには常に
gofmtを使用 - インポートの自動管理には
goimportsを使用 - 行の長さを適度に保つ(厳格な制限はないが、可読性を考慮)
- 論理的なコードグループを分離するため空行を追加
コメント
- 完全な文でコメントを記述
- 説明される対象の名前で文を開始
- パッケージコメントは「Package [name]」で開始
- ほとんどのコメントには行コメント(
//)を使用 - ブロックコメント(
/* */)は控えめに使用、主にパッケージ文書化用 - 何をするかではなく、なぜするかを文書化(何が複雑でない限り)
エラーハンドリング
- 関数呼び出しの直後にエラーをチェック
- 良い理由がない限り
_を使ってエラーを無視しない(理由を文書化) fmt.Errorfと%w動詞を使用してコンテキストでエラーをラップ- 特定のエラーをチェックする必要がある場合はカスタムエラー型を作成
- エラー戻り値を最後の戻り値として配置
- エラー変数は
errと命名 - エラーメッセージは小文字で、句読点で終わらない
アーキテクチャとプロジェクト構造
パッケージ構成
- 標準Go プロジェクトレイアウト規則に従う
mainパッケージはcmd/ディレクトリに保持- 再利用可能なパッケージは
pkg/またはinternal/に配置 - 外部プロジェクトにインポートされるべきでないパッケージには
internal/を使用 - 関連機能をパッケージにグループ化
- 循環依存関係を避ける
依存関係管理
- Goモジュール(
go.modとgo.sum)を使用 - 依存関係を最小限に保つ
- セキュリティパッチのため定期的に依存関係を更新
go mod tidyを使用して未使用の依存関係をクリーンアップ- 必要な場合のみ依存関係をベンダー化
型安全性と言語機能
型定義
- 意味と型安全性を追加するため型を定義
- JSON、XML、データベースマッピングには構造体タグを使用
- 明示的な型変換を優先
- 型アサーションは慎重に使用し、第二戻り値をチェック
ポインターvs値
- 大きな構造体またはレシーバーを変更する必要がある場合はポインターを使用
- 小さな構造体とイミュータビリティが望ましい場合は値を使用
- 型のメソッドセット内で一貫性を保つ
- ポインターvs値レシーバーを選択する際はゼロ値を考慮
インターフェースと合成
- インターフェースを受け入れ、具象型を返す
- インターフェースを小さく保つ(1〜3メソッドが理想的)
- 合成には埋め込みを使用
- 実装される場所ではなく使用される場所の近くでインターフェースを定義
- 必要でない限りインターフェースをエクスポートしない
並行性
ゴルーチン
- ライブラリでゴルーチンを作成しない;呼び出し元に並行性を制御させる
- ゴルーチンがどのように終了するかを常に知る
- ゴルーチンを待つために
sync.WaitGroupまたはチャネルを使用 - クリーンアップを確実に行うことでゴルーチンリークを避ける
チャネル
- ゴルーチン間の通信にはチャネルを使用
- メモリを共有して通信するのではなく、通信してメモリを共有する
- 受信側ではなく送信側からチャネルを閉じる
- 容量が分かっている場合はバッファードチャネルを使用
- 非ブロッキング操作には
selectを使用
同期
- 共有状態の保護には
sync.Mutexを使用 - クリティカルセクションを小さく保つ
- 多くの読み手がいる場合は
sync.RWMutexを使用 - 可能な場合はミューテックスよりもチャネルを優先
- 一度だけの初期化には
sync.Onceを使用
エラーハンドリングパターン
エラー作成
- シンプルな静的エラーには
errors.Newを使用 - 動的エラーには
fmt.Errorfを使用 - ドメイン固有のエラーにはカスタムエラー型を作成
- センチネルエラーにはエラー変数をエクスポート
- エラーチェックには
errors.Isとerrors.Asを使用
エラー伝播
- スタックを上に伝播する際にコンテキストを追加
- エラーをログして返す(どちらか一方を選択)
- 適切なレベルでエラーを処理
- より良いデバッグのため構造化エラーの使用を検討
API設計
HTTPハンドラー
- シンプルなハンドラーには
http.HandlerFuncを使用 - 状態が必要なハンドラーには
http.Handlerを実装 - 横断的関心事にはミドルウェアを使用
- 適切なステータスコードとヘッダーを設定
- エラーを適切に処理し、適切なエラーレスポンスを返す
JSON API
- JSONマーシャリングを制御するため構造体タグを使用
- 入力データを検証
- オプションフィールドにはポインターを使用
- 遅延解析のため
json.RawMessageの使用を検討 - JSONエラーを適切に処理
パフォーマンス最適化
メモリ管理
- ホットパスでのアロケーションを最小化
- 可能な場合はオブジェクトを再利用(
sync.Poolを検討) - 小さな構造体には値レシーバーを使用
- サイズが分かっている場合はスライスを事前割り当て
- 不要な文字列変換を避ける
プロファイリング
- 組み込みプロファイリングツール(
pprof)を使用 - 重要なコードパスをベンチマーク
- 最適化前にプロファイル
- まずアルゴリズムの改善に焦点を当てる
- ベンチマークには
testing.Bの使用を検討
テスト
テスト構成
- テストは同じパッケージに保持(ホワイトボックステスト)
- ブラックボックステストには
_testパッケージ接尾辞を使用 - テストファイルは
_test.go接尾辞で命名 - テストファイルはテスト対象コードの隣に配置
テスト記述
- 複数のテストケースにはテーブル駆動テストを使用
Test_functionName_scenarioを使って説明的にテストを命名- より良い構成のため
t.Runでサブテストを使用 - 成功ケースとエラーケースの両方をテスト
testifyや類似のライブラリは控えめに使用
テストヘルパー
- ヘルパー関数に
t.Helper()をマーク - 複雑なセットアップにはテストフィクスチャを作成
- テストとベンチマークで使用される関数には
testing.TBインターフェースを使用 t.Cleanup()を使ってリソースをクリーンアップ
セキュリティベストプラクティス
入力検証
- すべての外部入力を検証
- 無効な状態を防ぐため強い型付けを使用
- SQLクエリで使用する前にデータをサニタイズ
- ユーザー入力からのファイルパスに注意
- 異なるコンテキスト(HTML、SQL、シェル)のためデータを検証・エスケープ
暗号化
- 標準ライブラリのcryptoパッケージを使用
- 独自の暗号化を実装しない
- 乱数生成にはcrypto/randを使用
- bcryptまたは類似を使用してパスワードを保存
- ネットワーク通信にはTLSを使用
文書化
コード文書化
- すべてのエクスポートされたシンボルを文書化
- シンボル名で文書化を開始
- 有用な場合は文書化に例を使用
- 文書をコードの近くに保持
- コード変更時に文書を更新
READMEと文書化ファイル
- 明確なセットアップ指示を含める
- 依存関係と要件を文書化
- 使用例を提供
- 設定オプションを文書化
- トラブルシューティングセクションを含める
ツールと開発ワークフロー
必須ツール
go fmt: コードフォーマットgo vet: 疑わしい構造を発見golintまたはgolangci-lint: 追加リンティングgo test: テスト実行go mod: 依存関係管理go generate: コード生成
開発プラクティス
- コミット前にテストを実行
- フォーマットとリンティングにプリコミットフックを使用
- コミットを焦点を絞ってアトミックに保つ
- 意味のあるコミットメッセージを記述
- コミット前に差分をレビュー
避けるべき一般的な落とし穴
- エラーをチェックしない
- 競合状態を無視する
- ゴルーチンリークを作成
- クリーンアップにdeferを使用しない
- マップを同時変更
- nilインターフェースvsポインターを理解していない
- リソース(ファイル、接続)を閉じるのを忘れる
- グローバル変数を不必要に使用
- 空インターフェース(
interface{})を過度に使用 - 型のゼロ値を考慮しない