最近の記事

参照されなくなったデッドコードは削除する

Contents

Remove or comment out dead code that is no longer referenced. (参照されなくなったデッドコードは削除またはコメントアウトする)

解説

使用されていないコードは、コードベースを不必要に複雑にし、メンテナンスコストを増加させます。デッドコードは開発者を混乱させ、誤って呼び出される可能性もあります。バージョン管理システムを使用していれば、削除したコードは履歴から復元できるため、コメントアウトして残す必要はありません。定期的にデッドコードを検出・削除することで、コードベースの品質を維持できます。

具体例

// 悪い例
type UserService struct {
    db Database
}

// もう使われていない古い実装
// func (s *UserService) CreateUserOld(name string) error {
//     return s.db.InsertOld(name)
// }

func (s *UserService) CreateUser(name string) error {
    return s.db.Insert(name)
}

// 誰も呼んでいない関数
func (s *UserService) DeprecatedMethod() {
    // ...
}

// 良い例
type UserService struct {
    db Database
}

// 使用されている関数のみ存在
func (s *UserService) CreateUser(name string) error {
    return s.db.Insert(name)
}

参考リンク

core coding-standard

古いまたは誤ったコメントを削除する

Contents

Remove obsolete or incorrect comments. (古いまたは誤ったコメントを削除する)

解説

コードが変更されたにもかかわらずコメントが更新されないと、コメントが嘘の情報源となり、開発者を誤った方向に導きます。古いコメントは、コードの理解を助けるどころか混乱を招き、バグの原因となります。コードを変更する際は、関連するコメントも同時に更新するか、不要になったコメントは削除すべきです。正確なコメントのみが価値を持ちます。

具体例

// 悪い例(古いコメントが残っている)
type UserService struct {
    db Database
}

// CreateUser creates a new user and sends a welcome email
// この関数はemailとpasswordを受け取ります
func (s *UserService) CreateUser(name string) error {
    // 実際にはnameしか受け取っていない
    // メール送信機能も削除済み
    return s.db.Insert(name)
}

// ValidatePassword checks if password meets requirements
// パスワードは8文字以上必要です
func ValidatePassword(password string) bool {
    // 実際には12文字以上に変更された
    return len(password) >= 12
}

// 良い例(正確なコメント)
type UserService struct {
    db Database
}

// CreateUser creates a new user with the given name
func (s *UserService) CreateUser(name string) error {
    return s.db.Insert(name)
}

// ValidatePassword checks if password is at least 12 characters
func ValidatePassword(password string) bool {
    return len(password) >= 12
}

参考リンク

core coding-standard

副作用よりも結果を返す純粋関数を優先する

Contents

Prefer pure functions that return results rather than causing side effects. Use pointers only when necessary (e.g., large slices or shared transactions). (副作用を起こすのではなく、結果を返す純粋関数を優先する。ポインタは必要な場合のみ使用する(例:大きなスライスや共有トランザクション))

解説

純粋関数は、同じ入力に対して常に同じ出力を返し、外部状態を変更しません。これにより、関数の振る舞いが予測可能になり、テストが容易になります。副作用を持つ関数は、呼び出し順序や状態に依存するため、デバッグが困難です。ポインタを多用すると、意図しない状態変更が発生しやすくなるため、必要な場合のみ使用すべきです。純粋関数は並行処理でも安全です。

具体例

// 悪い例(副作用を持つ関数)
var globalCounter int

func ProcessItem(item *Item) {
    item.Price *= 1.1  // 引数を直接変更(副作用)
    globalCounter++    // グローバル状態を変更(副作用)
}

func CalculateTotal(items []*Item) float64 {
    total := 0.0
    for _, item := range items {
        ProcessItem(item)  // 元のデータが変更される
        total += item.Price
    }
    return total
}

// 良い例(純粋関数)
func ApplyTax(price float64) float64 {
    return price * 1.1  // 新しい値を返す
}

func CalculateTaxedPrice(item Item) Item {
    // 元のitemを変更せず、新しいItemを返す
    return Item{
        Name:  item.Name,
        Price: ApplyTax(item.Price),
    }
}

func CalculateTotal(items []Item) float64 {
    total := 0.0
    for _, item := range items {
        taxedPrice := ApplyTax(item.Price)
        total += taxedPrice
        // 元のitemsは変更されない
    }
    return total
}

// ポインタが必要な例(大きなデータ構造の効率的な処理)
type LargeDataSet struct {
    Data []byte // 数MBのデータ
}

func ProcessLargeData(data *LargeDataSet) error {
    // 大きなデータのコピーを避けるためポインタを使用
    return processBatch(data.Data)
}

// トランザクション内での共有が必要な例
func UpdateWithTransaction(tx *sql.Tx, user User) error {
    // トランザクションは共有する必要がある
    return tx.Exec("UPDATE users SET name = ?", user.Name)
}

参考リンク

core coding-standard

深いネストよりもブロック文を優先する

Contents

Prefer block statements over deep nesting where possible. (可能な限り、深いネストよりもブロック文を優先する)

解説

深いネストは、コードの可読性を著しく低下させ、ロジックの理解を困難にします。早期リターン(ガード節)を使用することで、ネストを浅く保ち、正常系のフローを明確にできます。ネストが深くなるほど、エラー処理や条件分岐が複雑になり、バグの混入リスクが高まります。フラットなコード構造は、コードレビューやデバッグを容易にします。

具体例

// 悪い例(深いネスト)
func ProcessOrder(order Order) error {
    if order.ID != "" {
        if order.Amount > 0 {
            if order.Status == "pending" {
                if err := validateOrder(order); err == nil {
                    if err := saveOrder(order); err == nil {
                        if err := sendConfirmation(order); err == nil {
                            return nil
                        } else {
                            return err
                        }
                    } else {
                        return err
                    }
                } else {
                    return err
                }
            } else {
                return errors.New("invalid status")
            }
        } else {
            return errors.New("invalid amount")
        }
    } else {
        return errors.New("invalid ID")
    }
}

// 良い例(早期リターンでフラット化)
func ProcessOrder(order Order) error {
    if order.ID == "" {
        return errors.New("invalid ID")
    }

    if order.Amount <= 0 {
        return errors.New("invalid amount")
    }

    if order.Status != "pending" {
        return errors.New("invalid status")
    }

    if err := validateOrder(order); err != nil {
        return err
    }

    if err := saveOrder(order); err != nil {
        return err
    }

    if err := sendConfirmation(order); err != nil {
        return err
    }

    return nil
}

参考リンク

core coding-standard

同一スコープ内で変数を再利用しない

Contents

Do not reuse a variable within the same scope. (同一スコープ内で変数を再利用しない)

解説

同一スコープ内で変数を異なる目的に再利用すると、変数の意味が曖昧になり、バグの原因となります。変数の値が途中で別の意味を持つようになると、コードを読む人が混乱し、意図しない値の参照が発生する可能性があります。各変数は一つの明確な目的を持つべきです。MUST項目として、変数のライフサイクルを明確にし、コードの予測可能性を保証します。

具体例

// 悪い例
func processData() {
    result := fetchUserCount() // ユーザー数を取得
    fmt.Println(result)

    result = calculateTotal()   // 合計金額を計算(同じ変数を再利用)
    fmt.Println(result)
}

// 良い例
func processData() {
    userCount := fetchUserCount()
    fmt.Println(userCount)

    totalAmount := calculateTotal()
    fmt.Println(totalAmount)
}

参考リンク

core coding-standard

switch/if-elseif分岐をインターフェースとポリモーフィズムで置き換える

Contents

Replace switch/if-elseif branching with interfaces and polymorphism when appropriate. (適切な場合、switch/if-elseif分岐をインターフェースとポリモーフィズムで置き換える)

解説

型や状態による分岐が多数存在すると、新しい型を追加する際にすべての分岐を更新する必要があり、変更が困難になります。インターフェースとポリモーフィズムを使用することで、Open-Closed原則に従った拡張可能な設計が実現されます。新しい型を追加する際、既存のコードを変更せずに済むため、バグの混入リスクが減少します。ただし、単純な分岐まで無理に抽象化する必要はありません。

具体例

// 悪い例(型による分岐が多数)
func CalculateShipping(orderType string, weight float64) float64 {
    if orderType == "standard" {
        return weight * 10
    } else if orderType == "express" {
        return weight * 20
    } else if orderType == "overnight" {
        return weight * 30
    } else if orderType == "international" {
        return weight * 50
    }
    return 0
}

func GetDeliveryDays(orderType string) int {
    if orderType == "standard" {
        return 5
    } else if orderType == "express" {
        return 2
    } else if orderType == "overnight" {
        return 1
    } else if orderType == "international" {
        return 10
    }
    return 0
}

// 良い例(ポリモーフィズム使用)
type ShippingMethod interface {
    CalculateShipping(weight float64) float64
    GetDeliveryDays() int
}

type StandardShipping struct{}

func (s StandardShipping) CalculateShipping(weight float64) float64 {
    return weight * 10
}

func (s StandardShipping) GetDeliveryDays() int {
    return 5
}

type ExpressShipping struct{}

func (e ExpressShipping) CalculateShipping(weight float64) float64 {
    return weight * 20
}

func (e ExpressShipping) GetDeliveryDays() int {
    return 2
}

type OvernightShipping struct{}

func (o OvernightShipping) CalculateShipping(weight float64) float64 {
    return weight * 30
}

func (o OvernightShipping) GetDeliveryDays() int {
    return 1
}

// 新しいタイプを追加しても既存コードは変更不要
type InternationalShipping struct{}

func (i InternationalShipping) CalculateShipping(weight float64) float64 {
    return weight * 50
}

func (i InternationalShipping) GetDeliveryDays() int {
    return 10
}

// 使用例
func ProcessOrder(method ShippingMethod, weight float64) {
    cost := method.CalculateShipping(weight)
    days := method.GetDeliveryDays()
    fmt.Printf("Cost: %.2f, Days: %d\n", cost, days)
}

参考リンク

core coding-standard

1ファイルに複数のクラスを配置しない

Contents

Even if a file exceeds 200 lines, do not place multiple classes (structs) in it. If you have multiple classes in a file over 200 lines, split them into separate files. (ファイルが200行を超えても、複数のクラス(構造体)を配置しない。200行を超えるファイルに複数のクラスがある場合は、別々のファイルに分割する)

解説

1ファイル1クラスの原則に従うことで、ファイルの責任が明確になり、目的のクラスを素早く見つけられます。複数のクラスが1ファイルにあると、ファイルが肥大化し、コードレビューやマージコンフリクトの解決が困難になります。特に200行を超える大きなファイルでは、クラスを分離することで可読性と保守性が大幅に向上します。ファイル名とクラス名を一致させることも推奨されます。

具体例

// 悪い例(1ファイルに複数のクラス)
// user_service.go (300行)
package service

type UserService struct {
    db Database
}

func (s *UserService) CreateUser(name string) error {
    // ... 100行の実装
}

type OrderService struct {
    db Database
}

func (s *OrderService) CreateOrder(items []Item) error {
    // ... 100行の実装
}

type PaymentService struct {
    gateway PaymentGateway
}

func (s *PaymentService) ProcessPayment(amount float64) error {
    // ... 100行の実装
}

// 良い例(クラスごとに分離)
// user_service.go (100行)
package service

type UserService struct {
    db Database
}

func (s *UserService) CreateUser(name string) error {
    // ... 実装
}

// order_service.go (100行)
package service

type OrderService struct {
    db Database
}

func (s *OrderService) CreateOrder(items []Item) error {
    // ... 実装
}

// payment_service.go (100行)
package service

type PaymentService struct {
    gateway PaymentGateway
}

func (s *PaymentService) ProcessPayment(amount float64) error {
    // ... 実装
}

参考リンク

core coding-standard

不必要にゲッターを使わない

Contents

Don’t use getters unnecessarily. (不必要にゲッターを使わない)

解説

すべてのフィールドに機械的にゲッターを用意することは、カプセル化の本質を見失った形式的な実装です。ゲッターは、外部に公開する必要がある情報に対してのみ提供すべきです。不必要なゲッターは、内部実装の詳細を露出させ、クラスの責任を不明確にします。本当に必要な場合のみゲッターを作成することで、適切なカプセル化が実現され、変更の影響範囲が限定されます。

具体例

// 悪い例(すべてのフィールドにゲッターを用意)
type Order struct {
    id          string
    customerID  string
    items       []Item
    subtotal    float64
    tax         float64
    shipping    float64
    createdAt   time.Time
    updatedAt   time.Time
}

// すべてのフィールドに機械的にゲッターを用意
func (o *Order) ID() string           { return o.id }
func (o *Order) CustomerID() string   { return o.customerID }
func (o *Order) Items() []Item        { return o.items }
func (o *Order) Subtotal() float64    { return o.subtotal }
func (o *Order) Tax() float64         { return o.tax }
func (o *Order) Shipping() float64    { return o.shipping }
func (o *Order) CreatedAt() time.Time { return o.createdAt }
func (o *Order) UpdatedAt() time.Time { return o.updatedAt }

// 良い例(本当に必要なゲッターのみ提供)
type Order struct {
    id         string
    customerID string
    items      []Item
    subtotal   float64
    tax        float64
    shipping   float64
    createdAt  time.Time
    updatedAt  time.Time
}

// 外部に公開が必要な情報のみゲッターを提供
func (o *Order) ID() string {
    return o.id
}

func (o *Order) CustomerID() string {
    return o.customerID
}

// 計算されたtotalのみ公開(内訳は公開しない)
func (o *Order) Total() float64 {
    return o.subtotal + o.tax + o.shipping
}

// ItemsのゲッターではなくItemの数のみ公開
func (o *Order) ItemCount() int {
    return len(o.items)
}

// さらに良い例(ドメインメソッドとして提供)
func (o *Order) IsExpired() bool {
    return time.Since(o.createdAt) > 30*24*time.Hour
}

func (o *Order) CanBeCancelled() bool {
    return time.Since(o.createdAt) < 24*time.Hour
}

// 内部計算フィールドは完全に隠蔽
func (o *Order) calculateSubtotal() float64 {
    total := 0.0
    for _, item := range o.items {
        total += item.Price * float64(item.Quantity)
    }
    return total
}

func (o *Order) calculateTax() float64 {
    return o.subtotal * 0.1
}

参考リンク

core coding-standard

TODOや未実装コードを残さない

Contents

Leave no TODOs or partially implemented code. (TODOや部分的に実装されたコードを残さない)

解説

TODOコメントや未実装のコードは、不完全な状態でコードがマージされていることを示します。これらは時間とともに忘れられ、技術的負債として蓄積します。コードレビュー時にもノイズとなり、本当に重要な問題を見逃す原因となります。すべての機能は完全に実装され、テストが通った状態でコミットされるべきです。やむを得ず後回しにする場合は、課題管理システムに登録し、コード内には残しません。

具体例

// 悪い例
func ProcessOrder(order Order) error {
    // TODO: バリデーション追加

    if err := saveOrder(order); err != nil {
        return err
    }

    // TODO: メール送信実装
    // sendConfirmationEmail(order.Email)

    return nil
}

// 良い例
func ProcessOrder(order Order) error {
    if err := validateOrder(order); err != nil {
        return err
    }

    if err := saveOrder(order); err != nil {
        return err
    }

    if err := sendConfirmationEmail(order.Email); err != nil {
        return err
    }

    return nil
}

func validateOrder(order Order) error {
    if order.Amount <= 0 {
        return errors.New("invalid amount")
    }
    return nil
}

func sendConfirmationEmail(email string) error {
    // 完全に実装済み
    return emailClient.Send(email, "Order Confirmed")
}

参考リンク

core coding-standard

機密情報をプレーンテキストで保存しない

Contents

Never store sensitive information (e.g., API keys) in plain text within the project. Fetch them at runtime from environment variables or external services such as SSM Parameter Store. Encrypted storage is acceptable, but do not bundle the decryption key in the project. (機密情報(APIキーなど)をプロジェクト内にプレーンテキストで保存しない。実行時に環境変数やSSM Parameter Storeなどの外部サービスから取得する。暗号化ストレージは許容されるが、復号化キーをプロジェクトに含めてはいけない)

解説

APIキーやパスワードなどの機密情報をソースコードに直接記述することは、重大なセキュリティリスクです。コードリポジトリが漏洩した場合、即座に悪用される可能性があります。環境変数や外部の秘密管理サービスを使用することで、機密情報とコードを分離し、アクセス制御が可能になります。暗号化する場合も、復号化キーを同じリポジトリに含めては意味がありません。

具体例

// 悪い例
const APIKey = "sk_live_1234567890abcdef"  // ハードコード

func callExternalAPI() {
    client := NewAPIClient(APIKey)
    client.Request()
}

// 良い例
func callExternalAPI() {
    apiKey := os.Getenv("API_KEY")  // 環境変数から取得
    if apiKey == "" {
        log.Fatal("API_KEY not set")
    }
    client := NewAPIClient(apiKey)
    client.Request()
}

// さらに良い例(AWS SSM使用)
func getAPIKey() (string, error) {
    sess := session.Must(session.NewSession())
    svc := ssm.New(sess)

    param, err := svc.GetParameter(&ssm.GetParameterInput{
        Name:           aws.String("/myapp/api_key"),
        WithDecryption: aws.Bool(true),
    })
    if err != nil {
        return "", err
    }
    return *param.Parameter.Value, nil
}

参考リンク

core coding-standard