コーディング規約を追加

This commit is contained in:
ry.yamafuji 2025-12-11 00:11:35 +09:00
parent a4a53616ba
commit 372ba41406
4 changed files with 583 additions and 2 deletions

View File

@ -2,7 +2,6 @@
Go言語の共通モジュールを作成する
## Go言語の特徴
* シンプルで学習コストが低い構造
@ -12,3 +11,9 @@ Go言語の共通モジュールを作成する
* 静的型付け
* クラスを持たず、構造体とメソッドでオブジェクト指向的な書き方ができる
* 標準ライブラリが強力で小さなバイナリにまとまる
## Go言語を実行する
```sh
go run src/hello.go
```

188
examples/api_client.go Normal file
View File

@ -0,0 +1,188 @@
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"time"
)
// APIClient はHTTPリクエストを送信するクライアント
type APIClient struct {
BaseURL string
HTTPClient *http.Client
}
// NewAPIClient は新しいAPIクライアントを作成
func NewAPIClient(baseURL string) *APIClient {
return &APIClient{
BaseURL: baseURL,
HTTPClient: &http.Client{
Timeout: 30 * time.Second,
},
}
}
// Get はGETリクエストを送信
func (c *APIClient) Get(endpoint string) ([]byte, error) {
url := c.BaseURL + endpoint
resp, err := c.HTTPClient.Get(url)
if err != nil {
return nil, fmt.Errorf("GETリクエスト失敗: %w", err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("レスポンス読み込み失敗: %w", err)
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("ステータスコード: %d, レスポンス: %s", resp.StatusCode, string(body))
}
return body, nil
}
// Post はPOSTリクエストを送信
func (c *APIClient) Post(endpoint string, data interface{}) ([]byte, error) {
url := c.BaseURL + endpoint
jsonData, err := json.Marshal(data)
if err != nil {
return nil, fmt.Errorf("JSONエンコード失敗: %w", err)
}
resp, err := c.HTTPClient.Post(url, "application/json", bytes.NewBuffer(jsonData))
if err != nil {
return nil, fmt.Errorf("POSTリクエスト失敗: %w", err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("レスポンス読み込み失敗: %w", err)
}
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated {
return nil, fmt.Errorf("ステータスコード: %d, レスポンス: %s", resp.StatusCode, string(body))
}
return body, nil
}
// Put はPUTリクエストを送信
func (c *APIClient) Put(endpoint string, data interface{}) ([]byte, error) {
url := c.BaseURL + endpoint
jsonData, err := json.Marshal(data)
if err != nil {
return nil, fmt.Errorf("JSONエンコード失敗: %w", err)
}
req, err := http.NewRequest(http.MethodPut, url, bytes.NewBuffer(jsonData))
if err != nil {
return nil, fmt.Errorf("リクエスト作成失敗: %w", err)
}
req.Header.Set("Content-Type", "application/json")
resp, err := c.HTTPClient.Do(req)
if err != nil {
return nil, fmt.Errorf("PUTリクエスト失敗: %w", err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("レスポンス読み込み失敗: %w", err)
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("ステータスコード: %d, レスポンス: %s", resp.StatusCode, string(body))
}
return body, nil
}
// Delete はDELETEリクエストを送信
func (c *APIClient) Delete(endpoint string) ([]byte, error) {
url := c.BaseURL + endpoint
req, err := http.NewRequest(http.MethodDelete, url, nil)
if err != nil {
return nil, fmt.Errorf("リクエスト作成失敗: %w", err)
}
resp, err := c.HTTPClient.Do(req)
if err != nil {
return nil, fmt.Errorf("DELETEリクエスト失敗: %w", err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("レスポンス読み込み失敗: %w", err)
}
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusNoContent {
return nil, fmt.Errorf("ステータスコード: %d, レスポンス: %s", resp.StatusCode, string(body))
}
return body, nil
}
// 使用例
func main() {
// APIクライアントの作成
client := NewAPIClient("https://jsonplaceholder.typicode.com")
// GETリクエストの例
fmt.Println("=== GETリクエスト ===")
getData, err := client.Get("/posts/1")
if err != nil {
fmt.Printf("エラー: %v\n", err)
} else {
fmt.Printf("レスポンス: %s\n\n", string(getData))
}
// POSTリクエストの例
fmt.Println("=== POSTリクエスト ===")
postData := map[string]interface{}{
"title": "新しい投稿",
"body": "これはテスト投稿です",
"userId": 1,
}
postResp, err := client.Post("/posts", postData)
if err != nil {
fmt.Printf("エラー: %v\n", err)
} else {
fmt.Printf("レスポンス: %s\n\n", string(postResp))
}
// PUTリクエストの例
fmt.Println("=== PUTリクエスト ===")
putData := map[string]interface{}{
"id": 1,
"title": "更新された投稿",
"body": "これは更新されたテスト投稿です",
"userId": 1,
}
putResp, err := client.Put("/posts/1", putData)
if err != nil {
fmt.Printf("エラー: %v\n", err)
} else {
fmt.Printf("レスポンス: %s\n\n", string(putResp))
}
// DELETEリクエストの例
fmt.Println("=== DELETEリクエスト ===")
deleteResp, err := client.Delete("/posts/1")
if err != nil {
fmt.Printf("エラー: %v\n", err)
} else {
fmt.Printf("レスポンス: %s\n", string(deleteResp))
}
}

11
examples/hello.go Normal file
View File

@ -0,0 +1,11 @@
// src/hello.go
// A simple Go program that prints "Hello, World!" to the console.
// go run src/hello.go
package main
import "fmt"
func main() {
fmt.Println("Hello, World!")
}

View File

@ -0,0 +1,377 @@
# 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
}
```