最近の記事

仕様に必要のない変数や関数を追加しない

Contents

Do not add variables or functions that the specification does not require (YAGNI principle). (仕様に必要のない変数や関数を追加しない(YAGNI原則))

解説

YAGNI(You Aren’t Gonna Need It)原則は、現時点で必要のない機能を実装しないという考え方です。将来必要になるかもしれないと予測して実装した機能の多くは、実際には使われず、コードの複雑性を増すだけです。未使用のコードはメンテナンスコストを増加させ、バグの温床となります。仕様に明記された要件のみを実装することで、シンプルで保守しやすいコードベースを維持できます。

具体例

// 悪い例(仕様にない機能を実装)
type User struct {
    Name  string
    Email string
    Age   int           // 仕様に不要
    Phone string        // 仕様に不要
}

func (u *User) GetFullProfile() map[string]interface{} {
    // 仕様に不要な複雑な処理
    return map[string]interface{}{
        "name":  u.Name,
        "email": u.Email,
        "age":   u.Age,
        "phone": u.Phone,
    }
}

// 良い例(仕様に必要な要素のみ)
type User struct {
    Name  string
    Email string
}

func (u *User) GetEmail() string {
    return u.Email
}

参考リンク

core coding-standard

関数名は動詞または動詞+目的語で命名する

Contents

Name functions with a verb or verb + object. (関数名は動詞または動詞+目的語で命名する)

解説

関数は動作を表すため、動詞で始まる名前が自然で理解しやすくなります。動詞+目的語の形式は、関数が何を行うかを明確に伝え、コードの可読性を向上させます。名詞だけの関数名は、その関数が何をするのか不明確であり、混乱を招きます。一貫した命名規則に従うことで、チーム全体でのコードの理解が容易になります。

具体例

// 悪い例(名詞のみ、または不明瞭)
func User(id string) User {
    // Userは名詞のみで、何をする関数か不明
}

func Data() []byte {
    // 何のデータを取得するのか不明
}

func Process() {
    // 何を処理するのか不明
}

func Order(items []Item) {
    // 注文を作成?取得?
}

// 良い例(動詞+目的語)
func GetUser(id string) User {
    // ユーザーを取得することが明確
}

func FetchUserData() []byte {
    // ユーザーデータを取得することが明確
}

func ProcessOrder(order Order) error {
    // 注文を処理することが明確
}

func CreateOrder(items []Item) (Order, error) {
    // 注文を作成することが明確
}

func ValidateEmail(email string) bool {
    // メールアドレスを検証することが明確
}

func CalculateTotalPrice(items []Item) float64 {
    // 合計金額を計算することが明確
}

func SendNotification(user User, message string) error {
    // 通知を送信することが明確
}

func UpdateUserProfile(userID string, profile Profile) error {
    // ユーザープロフィールを更新することが明確
}

func DeleteExpiredSessions() error {
    // 期限切れセッションを削除することが明確
}

参考リンク

core coding-standard

引数と戻り値にはできるだけValue Objectを使う

Contents

Use Value Objects for both arguments and return values when possible. (可能な限り、引数と戻り値の両方にValue Objectを使用する)

解説

関数のシグネチャでValue Objectを使用することで、型安全性が向上し、無効な値の受け渡しを防げます。プリミティブ型では、引数の順序を間違えたり、無効な値を渡したりするミスが発生しやすくなります。Value Objectを使用することで、コンパイル時に型チェックが働き、ドメインルール違反を早期に検出できます。これにより、実行時エラーではなくコンパイルエラーとして問題を発見できます。

具体例

// 悪い例(プリミティブ型の引数)
func TransferMoney(fromAccount, toAccount string, amount float64) error {
    // 引数の順序ミスが起きやすい
    // 負の金額が渡される可能性がある
    if amount <= 0 {
        return errors.New("invalid amount")
    }
    // ...
}

func main() {
    // 引数の順序を間違える可能性
    TransferMoney("ACC002", "ACC001", 100.0)
    // 負の値を渡せてしまう
    TransferMoney("ACC001", "ACC002", -50.0)
}

// 良い例(Value Objectの引数と戻り値)
type AccountID struct {
    value string
}

func NewAccountID(id string) (AccountID, error) {
    if id == "" || !strings.HasPrefix(id, "ACC") {
        return AccountID{}, errors.New("invalid account ID")
    }
    return AccountID{value: id}, nil
}

type Amount struct {
    value float64
}

func NewAmount(amount float64) (Amount, error) {
    if amount <= 0 {
        return Amount{}, errors.New("amount must be positive")
    }
    return Amount{value: amount}, nil
}

func (a Amount) Value() float64 {
    return a.value
}

type TransferResult struct {
    TransactionID string
    CompletedAt   time.Time
}

func TransferMoney(from, to AccountID, amount Amount) (TransferResult, error) {
    // Value Objectは既に検証済み
    // 型が異なるため引数の順序ミスも防げる
    result := TransferResult{
        TransactionID: generateID(),
        CompletedAt:   time.Now(),
    }
    return result, nil
}

func main() {
    from, _ := NewAccountID("ACC001")
    to, _ := NewAccountID("ACC002")
    amount, _ := NewAmount(100.0)

    // 型安全性により、引数の順序ミスはコンパイルエラーになる
    result, err := TransferMoney(from, to, amount)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Transfer completed: %s\n", result.TransactionID)
}

参考リンク

core coding-standard

重要なドメイン概念にはValue Objectを活用する

Contents

Actively use Value Objects for variables representing important domain concepts. (重要なドメイン概念を表す変数には、Value Objectを積極的に使用する)

解説

プリミティブ型(文字列、数値など)だけでドメイン概念を表現すると、型安全性が失われ、無効な値の混入リスクが高まります。Value Objectを使用することで、ドメインルールをカプセル化し、型システムの力でバグを防止できます。重要なドメイン概念(メールアドレス、金額、ユーザーIDなど)をValue Objectとして定義することで、コードの表現力と安全性が向上します。

具体例

// 悪い例(プリミティブ型のみ使用)
type User struct {
    ID    string
    Email string
}

func CreateUser(id, email string) (*User, error) {
    // バリデーションが散在
    if id == "" || email == "" {
        return nil, errors.New("invalid input")
    }
    return &User{ID: id, Email: email}, nil
}

func SendEmail(email string) error {
    // emailの妥当性チェックが毎回必要
    if !strings.Contains(email, "@") {
        return errors.New("invalid email")
    }
    return emailClient.Send(email, "Hello")
}

// 良い例(Value Object使用)
type UserID struct {
    value string
}

func NewUserID(id string) (UserID, error) {
    if id == "" {
        return UserID{}, errors.New("user ID cannot be empty")
    }
    return UserID{value: id}, nil
}

func (u UserID) String() string {
    return u.value
}

type Email struct {
    value string
}

func NewEmail(email string) (Email, error) {
    if !strings.Contains(email, "@") {
        return Email{}, errors.New("invalid email format")
    }
    return Email{value: email}, nil
}

func (e Email) String() string {
    return e.value
}

type User struct {
    ID    UserID
    Email Email
}

func CreateUser(id UserID, email Email) *User {
    // Value Objectは既に検証済みなので追加チェック不要
    return &User{ID: id, Email: email}
}

func SendEmail(email Email) error {
    // Emailは既に妥当性が保証されている
    return emailClient.Send(email.String(), "Hello")
}

参考リンク

core coding-standard

構造体のフィールドは非公開にしコンストラクタで初期化する

Contents

Struct fields should be unexported (lowercase) and initialized only via a constructor function. (構造体のフィールドは非公開(小文字)にし、コンストラクタ関数経由でのみ初期化する)

解説

構造体のフィールドを非公開にすることで、不正な状態での初期化を防ぎ、データの整合性を保証できます。コンストラクタ関数を使用することで、必須フィールドの初期化漏れや不正な値の設定を防止し、常に有効な状態のオブジェクトを作成できます。これは、カプセル化の原則に基づく重要な設計パターンであり、バグの混入を防ぎます。

具体例

// 悪い例(フィールドが公開されている)
type User struct {
    Name  string
    Email string
    Age   int
}

func main() {
    user := User{Name: "John"}  // Emailが空で不正な状態
    // ...
}

// 良い例(フィールドが非公開、コンストラクタ使用)
type User struct {
    name  string
    email string
    age   int
}

func NewUser(name, email string, age int) (*User, error) {
    if name == "" {
        return nil, errors.New("name is required")
    }
    if !strings.Contains(email, "@") {
        return nil, errors.New("invalid email")
    }
    if age < 0 {
        return nil, errors.New("age must be positive")
    }

    return &User{
        name:  name,
        email: email,
        age:   age,
    }, nil
}

func (u *User) Name() string {
    return u.name
}

func (u *User) Email() string {
    return u.email
}

参考リンク

core coding-standard

すべての関数にユニットテストを提供する

Contents

Provide unit tests for every function, and ensure all tests pass. (すべての関数にユニットテストを提供し、すべてのテストが合格することを保証する)

解説

ユニットテストは、コードの品質を保証し、リファクタリング時の安全性を提供する重要な要素です。すべての関数にテストがあることで、変更時に既存機能が破壊されていないことを迅速に検証できます。テストが存在しないコードは、変更のリスクが高く、技術的負債となります。MUST項目として、例外なくすべての関数にテストを記述し、継続的に実行して合格状態を維持する必要があります。

具体例

// 実装
func Add(a, b int) int {
    return a + b
}

func ValidateEmail(email string) bool {
    return strings.Contains(email, "@")
}

// テスト
func TestAdd(t *testing.T) {
    tests := []struct {
        name string
        a, b int
        want int
    }{
        {"positive numbers", 2, 3, 5},
        {"negative numbers", -1, -2, -3},
        {"zero", 0, 0, 0},
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            got := Add(tt.a, tt.b)
            if got != tt.want {
                t.Errorf("Add(%d, %d) = %d, want %d", tt.a, tt.b, got, tt.want)
            }
        })
    }
}

func TestValidateEmail(t *testing.T) {
    if !ValidateEmail("test@example.com") {
        t.Error("Valid email should pass")
    }
    if ValidateEmail("invalid") {
        t.Error("Invalid email should fail")
    }
}

参考リンク

core coding-standard

目的が異なる場合は同じ処理でも関数を分ける

Contents

Even if two functions perform the same process, separate them when their purposes differ. (2つの関数が同じ処理を実行する場合でも、目的が異なる場合は分離する)

解説

同じ処理内容であっても、目的やコンテキストが異なる場合は別々の関数として定義すべきです。これにより、将来的に要件が変わり処理内容が分岐した際に、影響範囲を限定できます。共通化を優先しすぎると、異なる目的の処理が密結合になり、一方の変更が他方に意図しない影響を与えるリスクが生じます。目的に応じた関数分離は、変更に強い設計の基本です。

具体例

// 悪い例(目的が異なるのに共通化)
func calculateDiscount(price float64) float64 {
    return price * 0.9  // 10%割引
}

func processNewUserOrder(price float64) float64 {
    return calculateDiscount(price)  // 新規ユーザー割引
}

func processSeasonalSale(price float64) float64 {
    return calculateDiscount(price)  // シーズンセール割引
}

// 良い例(目的ごとに分離)
func calculateNewUserDiscount(price float64) float64 {
    return price * 0.9  // 新規ユーザー割引
}

func calculateSeasonalDiscount(price float64) float64 {
    return price * 0.9  // シーズンセール割引(将来変更可能性あり)
}

func processNewUserOrder(price float64) float64 {
    return calculateNewUserDiscount(price)
}

func processSeasonalSale(price float64) float64 {
    return calculateSeasonalDiscount(price)
}

参考リンク

core coding-standard

1つの関数には1つの責任のみを割り当てる

Contents

Assign one responsibility to one function. Do not pack multiple responsibilities into a single function. The same applies to classes (or “struct + methods” in Go) — follow the Single Responsibility Principle. (1つの関数には1つの責任のみを割り当てる。複数の責任を1つの関数に詰め込んではいけない。クラス(またはGoの「構造体+メソッド」)にも同様に単一責任原則を適用する)

解説

単一責任原則は、ソフトウェア設計の基本原則の一つです。1つの関数が複数の責任を持つと、変更の影響範囲が広がり、テストが困難になります。責任が明確に分離されていれば、変更時に影響を受ける箇所が限定され、バグの混入リスクが減少します。関数やクラスは「変更する理由が1つだけ」であるべきです。

具体例

// 悪い例
func processUser(id string) {
    user := fetchUserFromDB(id)
    validateUser(user)
    sendEmail(user.Email, "Welcome")
    logActivity("User processed")
}

// 良い例
func processUser(id string) User {
    user := fetchUserFromDB(id)
    validateUser(user)
    return user
}

func notifyUser(user User) {
    sendEmail(user.Email, "Welcome")
}

func logUserActivity(message string) {
    logActivity(message)
}

参考リンク

core coding-standard

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

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