最近の記事

構造体フィールドへのアクセスはゲッターで行いセッターは避ける

Contents

Access struct fields through getter methods; avoid setters. (構造体のフィールドへのアクセスはゲッターメソッドを通して行い、セッターは避ける)

解説

セッターメソッドを提供すると、オブジェクトの状態がいつでも変更可能になり、不変性が損なわれます。ゲッターのみを提供することで、オブジェクトの読み取り専用アクセスを実現し、予期しない状態変更を防げます。状態の変更が必要な場合は、ビジネスロジックを含むドメインメソッドとして実装すべきです。これにより、状態変更に対する制御が強化され、バグの混入を防ぎます。

具体例

// 悪い例(セッターが存在)
type Account struct {
    balance float64
}

func (a *Account) GetBalance() float64 {
    return a.balance
}

func (a *Account) SetBalance(amount float64) {
    a.balance = amount  // 検証なしで変更可能
}

func main() {
    acc := &Account{balance: 1000}
    acc.SetBalance(-500)  // 不正な状態を作成可能
}

// 良い例(ゲッターのみ、状態変更はドメインメソッド)
type Account struct {
    balance float64
}

func NewAccount(initialBalance float64) (*Account, error) {
    if initialBalance < 0 {
        return nil, errors.New("initial balance must be positive")
    }
    return &Account{balance: initialBalance}, nil
}

func (a *Account) Balance() float64 {
    return a.balance
}

func (a *Account) Deposit(amount float64) error {
    if amount <= 0 {
        return errors.New("deposit amount must be positive")
    }
    a.balance += amount
    return nil
}

func (a *Account) Withdraw(amount float64) error {
    if amount <= 0 {
        return errors.New("withdraw amount must be positive")
    }
    if a.balance < amount {
        return errors.New("insufficient balance")
    }
    a.balance -= amount
    return nil
}

参考リンク

core coding-standard

各関数の目的を1行コメントで要約する

Contents

Write a one-line comment summarizing the purpose of each function. Argument explanations are unnecessary except in special cases. (各関数の目的を要約する1行コメントを書く。引数の説明は特別な場合を除いて不要)

解説

関数の目的を簡潔に説明するコメントは、コードの理解を助ける重要な要素です。1行のコメントで「何をする関数か」を明確にすることで、コードレビューや保守作業が効率化されます。ただし、引数や戻り値の詳細な説明は、型や名前から自明な場合は不要です。コメントは実装の詳細ではなく、関数の意図や目的を伝えることに焦点を当てるべきです。

具体例

// 悪い例(コメントなし、または冗長)
func ProcessOrder(order Order) error {
    // この関数はOrderを受け取り、それを処理し、エラーがあればエラーを返します
    // 引数: order - 処理するOrder型の変数
    // 戻り値: error - エラーがあればerror、なければnil
    // ...
}

// 良い例
// ProcessOrder は注文を検証して保存し、確認メールを送信する
func ProcessOrder(order Order) error {
    // ...
}

// CalculateTax は商品価格に対する消費税額を計算する
func CalculateTax(price float64) float64 {
    return price * 0.1
}

// FetchActiveUsers はアクティブなユーザー一覧をデータベースから取得する
func FetchActiveUsers() ([]User, error) {
    // ...
}

// GenerateInvoicePDF は注文データからPDF形式の請求書を生成する
// pdfOptions: マージン、フォントサイズなど細かい制御が必要な場合のみコメント
func GenerateInvoicePDF(order Order, pdfOptions PDFOptions) ([]byte, error) {
    // ...
}

参考リンク

core coding-standard

依存性注入ではインターフェースに依存する

Contents

For dependency injection, depend on interfaces, not concrete implementations. (依存性注入では、具体的な実装ではなくインターフェースに依存する)

解説

依存性注入において、具体的な実装ではなくインターフェースに依存することで、疎結合な設計が実現されます。インターフェースに依存することで、実装の差し替えが容易になり、テスト時にモックやスタブを注入できます。この原則は依存性逆転の原則(DIP)の実践であり、変更に強く拡張しやすいコードベースを構築するための基盤となります。

具体例

// 悪い例(具体的な実装に依存)
type MySQLDatabase struct{}

func (db *MySQLDatabase) Query(sql string) []Row {
    // MySQL固有の実装
    return nil
}

type UserService struct {
    db *MySQLDatabase  // 具体的な実装に依存
}

// 良い例(インターフェースに依存)
type Database interface {
    Query(sql string) []Row
}

type MySQLDatabase struct{}

func (db *MySQLDatabase) Query(sql string) []Row {
    return nil
}

type UserService struct {
    db Database  // インターフェースに依存
}

func NewUserService(db Database) *UserService {
    return &UserService{db: db}
}

参考リンク

core coding-standard

ドメインの責務をユーティリティパッケージに入れない

Contents

Keep domain responsibilities out of utility packages. (ドメインの責務をユーティリティパッケージに入れない)

解説

ユーティリティパッケージは、文字列操作や日付計算など、ドメイン非依存の汎用的な機能を提供すべきです。ドメイン固有のビジネスロジックをユーティリティに配置すると、ドメイン層の概念が分散し、ビジネスロジックの理解が困難になります。ドメインロジックは、適切なエンティティやドメインサービスに配置することで、コードの意図が明確になり、保守性が向上します。

具体例

// 悪い例(ドメインロジックがutilに配置)
package util

func CalculateOrderTotal(items []Item) float64 {
    total := 0.0
    for _, item := range items {
        total += item.Price * float64(item.Quantity)
    }
    // 1万円以上で送料無料(ドメインルール)
    if total < 10000 {
        total += 500  // 送料
    }
    return total
}

// 良い例(ドメインロジックはドメイン層に配置)
package domain

type Order struct {
    items []Item
}

func (o *Order) CalculateTotal() float64 {
    subtotal := 0.0
    for _, item := range o.items {
        subtotal += item.Price * float64(item.Quantity)
    }

    shippingFee := o.calculateShippingFee(subtotal)
    return subtotal + shippingFee
}

func (o *Order) calculateShippingFee(subtotal float64) float64 {
    const freeShippingThreshold = 10000
    const standardShippingFee = 500

    if subtotal >= freeShippingThreshold {
        return 0
    }
    return standardShippingFee
}

// utilパッケージは汎用機能のみ
package util

func FormatCurrency(amount float64) string {
    return fmt.Sprintf("¥%.0f", amount)
}

参考リンク

core coding-standard

クラスはすべての依存関係を明示的に受け取る

Contents

A class must: 1. Explicitly receive all dependencies (via DI) — it should not secretly rely on any helper. 2. Maintain a consistent responsibility. (クラスは次を満たす必要がある:1. すべての依存関係を明示的に受け取る(DI経由)— ヘルパーに密かに依存してはいけない。2. 一貫した責任を維持する)

解説

依存関係が暗黙的だと、テストやデバッグが困難になり、コードの理解に時間がかかります。すべての依存関係をコンストラクタで明示的に注入することで、クラスが必要とするものが一目でわかり、モックやスタブを使ったテストが容易になります。また、一貫した責任を維持することで、クラスの目的が明確になり、変更の影響範囲が限定されます。これは保守性の高いコード設計の基本原則です。

具体例

// 悪い例(暗黙的な依存)
type OrderService struct {
    // 依存関係が明示されていない
}

func (s *OrderService) CreateOrder(items []Item) error {
    // 内部でグローバルなhelperを使用
    db := GetGlobalDB()  // 暗黙的な依存
    logger := GetGlobalLogger()  // 暗黙的な依存

    total := calculateTotal(items)
    if err := db.Insert(total); err != nil {
        logger.Error(err)
        return err
    }
    return nil
}

// 良い例(明示的な依存注入)
type OrderService struct {
    db     Database
    logger Logger
    pricer PriceCalculator
}

func NewOrderService(db Database, logger Logger, pricer PriceCalculator) *OrderService {
    return &OrderService{
        db:     db,
        logger: logger,
        pricer: pricer,
    }
}

func (s *OrderService) CreateOrder(items []Item) error {
    total := s.pricer.CalculateTotal(items)

    if err := s.db.Insert(total); err != nil {
        s.logger.Error(err)
        return err
    }

    return nil
}

参考リンク

core coding-standard

エンティティのドメイン検証は内部メソッドで実行する

Contents

When an Entity requires domain-specific validation, perform it inside the Entity’s own methods. (エンティティがドメイン固有の検証を必要とする場合、エンティティ自身のメソッド内で実行する)

解説

ドメイン層のエンティティは、自身のビジネスルールを理解し、検証する責任を持つべきです。検証ロジックをエンティティ外部に配置すると、ビジネスルールが分散し、変更時の整合性維持が困難になります。エンティティ内部にドメイン検証を配置することで、ビジネスロジックがカプセル化され、ルールの変更が局所化されます。これは、ドメイン駆動設計における重要な原則です。

具体例

// 悪い例(検証がエンティティ外部)
type Order struct {
    Amount float64
    Status string
}

func ValidateOrder(order Order) error {
    if order.Amount < 0 {
        return errors.New("amount must be positive")
    }
    if order.Status != "pending" && order.Status != "completed" {
        return errors.New("invalid status")
    }
    return nil
}

// 良い例(検証がエンティティ内部)
type Order struct {
    amount float64
    status string
}

func NewOrder(amount float64) (*Order, error) {
    o := &Order{amount: amount, status: "pending"}
    if err := o.validate(); err != nil {
        return nil, err
    }
    return o, nil
}

func (o *Order) validate() error {
    if o.amount < 0 {
        return errors.New("amount must be positive")
    }
    return nil
}

func (o *Order) Complete() error {
    if o.status != "pending" {
        return errors.New("only pending orders can be completed")
    }
    o.status = "completed"
    return nil
}

参考リンク

core coding-standard

インフラストラクチャはTerraformを使用して構築および運用する

ルール

Infrastructure is built and operated using Terraform

(インフラストラクチャはTerraformを使用して構築および運用する)

解説

このプロジェクトでは、インフラをIaCツールのTerraformで管理します。 コードでインフラを管理することは、生成AIによるインフラ構築を容易にするばかりか、GitとGithubを使ったトレーサビリティの確保の道も開きます。 今や手作業だけでAWSの管理をすることは想像もし難いことです。

サンプルコード

参考リンク

Infrastructure 開発ガイドライン Project Information

クラウドプラットフォームとしてAWSを使用する

ルール

AWS is used as the cloud platform

(クラウドプラットフォームとしてAWSを使用する)

解説

このプロジェクトでは、AWSを標準のクラウドプラットフォームとして採用しています。 AWSは手厚いサポートと情報提供、優秀なサービスラインナップ、安定したエコシステムを持ち、多くの企業での採用実績があります。

サンプルコード

参考リンク

Infrastructure 開発ガイドライン Project Information

providers.tfとversions.tfはルートディレクトリのみに配置する

ルール

Place providers.tf and versions.tf only in the root directory

(providers.tfとversions.tfはルートディレクトリのみに配置する)

解説

プロバイダーはルートモジュール(terraform apply を実行する場所。development/など。)で一元管理します。言い方を変えると、サブモジュールでプロバイダを設定してはいけません。

サンプルコード

# ./production/providers.tf (ルートモジュール)
provider "aws" {
  region = "ap-northeast-1"

  default_tags {
    tags = {
      Environment = "production"
      ManagedBy   = "Terraform"
    }
  }
}

# ./production/versions.tf (ルートモジュール)
terraform {
  required_version = "= 1.9.8"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "= 5.75.1"
    }
  }
}

参考リンク

Infrastructure 開発ガイドライン Repository Structure

全てのモジュールにoutputs.tfを配置する

ルール

Place outputs.tf in every module. Empty file is acceptable

(全てのモジュールにoutputs.tfを配置する)

解説

全てのモジュールにoutputs.tfを配置します。もちろんoutputブロックは例外なくここに記載します。たとえ空ファイルであってもかまいません。

すべてのoutputブロックは必ずこのファイルを見れば把握できるようになることで、可読性が向上します。

サンプルコード

# ./modules/networking/outputs.tf

output "vpc_id" {
  description = "作成されたVPCのID"
  value       = aws_vpc.main.id
}

参考リンク

Infrastructure 開発ガイドライン Repository Structure