378 lines
8.2 KiB
Markdown
378 lines
8.2 KiB
Markdown
# 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
|
||
}
|
||
```
|