go-common-code/readme/coding-conventions.md

378 lines
8.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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
}
```