# Go コーディング規約 ## 1. 一般的なガイドライン 1. コードフォーマットは **必ず `gofmt`(または `goimports`)に従う**。手で整形しない。 2. インデントは **タブ**(`\t`)を使用する(`gofmt`が自動で設定する)。 3. 1行の長さは厳密な制限はないが、**できれば 100 文字前後**を目安にする。 4. 1ファイルは 300〜400 行以内を目安とし、長くなりすぎる場合はファイル分割を検討する。 5. パッケージ単位で責務を分割し、**小さく・単機能な関数**を心がける。 6. エクスポート(大文字始まり)するものは最小限にし、原則として **パッケージ内で完結する API を設計**する。 ## 2. コメント/ドキュメント Go では **godoc** 形式のコメントが重要です。 1. **パッケージコメント**: `package` 宣言の直前に、そのパッケージの説明を書く。 ```go // Package user provides user management functionalities such as // registration, authentication, and profile updates. package user ``` 2. **エクスポートされた関数/メソッド**には、**シグネチャ名から始まるコメント**を書く。 ```go // CreateUser creates a new user and stores it into the repository. func CreateUser(name string, age int) (*User, error) { // 実装 } ``` 3. **エクスポートされた型やフィールド**にもコメントを書く。 ```go // User represents a user entity in the system. type User struct { // ID is a unique identifier of the user. ID int64 // Name is the display name of the user. Name string } ``` 4. 非エクスポート(小文字始まり)のものでも、複雑な処理や分岐には**行コメント**で意図や前提条件を書いておく。 ```go // cache にヒットした場合は DB に問い合わせない if v, ok := cache[id]; ok { return v, nil } ``` ## 3. 命名規則 Go は **CamelCase / mixedCaps** を使います(snake_case をあまり使わない)。 1. **パッケージ名**: * すべて小文字、`_` や数字を極力避ける。 * 短く意味のある名前にする(`users` より `user` のように単数形が好まれがち)。 ```go package user // 良い例 ``` 2. **変数・関数名(非エクスポート)**: 先頭小文字の `mixedCaps`。 ```go func loadUser() (*User, error) { userCount := 0 _ = userCount // ... return nil, nil } ``` 3. **エクスポート関数・構造体**: 先頭大文字の `MixedCaps`。 ```go type UserService struct { repo Repository } func NewUserService(repo Repository) *UserService { return &UserService{repo: repo} } ``` 4. **定数**: * エクスポートする定数は `CamelCase`。 * パッケージ内専用であれば先頭小文字。 ```go const DefaultTimeout = 5 * time.Second const maxRetryCount = 3 ``` 5. **レシーバ名**: * 型名の1〜2文字の略を使う。 * `UserService` → `us`、`Server` → `s` など。 ```go func (s *Server) Start() error { // ... return nil } ``` ## 4. インポート/パッケージ構成 1. インポートは **自動整形ツール(goimports)にまかせる**。 2. グループ順は以下が一般的: 1. 標準ライブラリ 2. サードパーティ 3. 自プロジェクト内パッケージ グループ間は空行で区切る。 ```go import ( "context" "fmt" "net/http" "github.com/julienschmidt/httprouter" "example.com/project/internal/user" "example.com/project/pkg/logger" ) ``` 3. **循環参照**が起きないようにパッケージを分割する。 * `cmd/`… エントリポイント(`main` パッケージ) * `internal/`… アプリ内部でのみ使うパッケージ * `pkg/`… 外部にも公開可能なパッケージ ## 5. エラーハンドリング 1. Go のエラーは **戻り値の `error`** で扱う。 ```go user, err := repo.FindByID(ctx, id) if err != nil { return nil, fmt.Errorf("failed to find user: %w", err) } ``` 2. エラーはできるだけ **早めに return** し、ネストを浅く保つ。 ```go if err != nil { return nil, err } // ここから成功時の処理 ``` 3. `panic` は **プログラミングミスなど「復旧不能」なケースのみ**で使用し、通常のエラー処理には使わない。 4. `errors.Is` / `errors.As` を使ってエラー種別を判定する。 ```go if errors.Is(err, sql.ErrNoRows) { // not found の扱い } ``` ## 6. 複数ファイル間の分割時の書き方 ### 6.1 同じパッケージ内での分割 Go では **同じディレクトリ配下の `.go` ファイルは同じ `package` 名**であれば、1つのパッケージとして扱われます。 ```text user/ user.go service.go repository.go ``` すべてのファイルで `package user` と書きます。 ```go // user/user.go package user type User struct { ID int64 Name string } ``` ```go // user/service.go package user // Service handles user-related usecases. type Service struct { repo Repository } func NewService(repo Repository) *Service { return &Service{repo: repo} } ``` ```go // user/repository.go package user type Repository interface { FindByID(id int64) (*User, error) Save(user *User) error } ``` 同一パッケージ内であれば、**インポートなしですべてのシンボルを参照**できます。 ### 6.2 `main` パッケージと内部パッケージの分割 ```text cmd/app/main.go internal/user/service.go internal/user/repository.go ``` ```go // cmd/app/main.go package main import ( "log" "example.com/project/internal/user" ) func main() { repo := user.NewInMemoryRepository() svc := user.NewService(repo) if err := svc.Run(); err != nil { log.Fatal(err) } } ``` ```go // internal/user/service.go package user type Service struct { repo Repository } func NewService(repo Repository) *Service { return &Service{repo: repo} } func (s *Service) Run() error { // 実装 return nil } ``` ```go // internal/user/repository.go package user type Repository interface { // ... } type inMemoryRepository struct { // ... } func NewInMemoryRepository() Repository { return &inMemoryRepository{} } ``` ポイント: * **ディレクトリとパッケージ名を一致させる**と分かりやすい。 * `cmd/app/` の `main.go` はできるだけ薄くし、構造体やビジネスロジックは `internal/` や `pkg/` に置く。 ### 6.3 テストファイルの分割 テストコードは基本的に同じパッケージか `package xxx_test` で書きます。 ```go // user/service_test.go package user_test import ( "testing" "example.com/project/internal/user" ) func TestService_Run(t *testing.T) { repo := user.NewInMemoryRepository() svc := user.NewService(repo) if err := svc.Run(); err != nil { t.Fatalf("Run() error = %v", err) } } ``` ## 7. gofmt / Lint / VSCode の設定 ### 7.1 フォーマット・リンター 1. **必須ツール** * `gofmt`(標準) * `goimports`(インポートも自動整形) 2. **推奨リンター** * `golangci-lint`(多数のリンターを統合したツール) プロジェクトルートに `.golangci.yml` を置く例: ```yaml run: timeout: 3m tests: true linters: enable: - govet - gofmt - gosimple - staticcheck - unused - errcheck issues: exclude-use-default: false ``` ### 7.2 VSCode の設定例 (`.vscode/settings.json`) ```json { "go.useLanguageServer": true, "go.formatTool": "goimports", "gopls": { "staticcheck": true }, "editor.formatOnSave": true, "[go]": { "editor.codeActionsOnSave": { "source.organizeImports": true } }, "go.lintTool": "golangci-lint", "go.lintOnSave": "file", "go.lintFlags": [ "run", "--fast" ], "files.trimTrailingWhitespace": true, "files.insertFinalNewline": true } ```