最近の記事

値をハードコーディングしない

Contents

Avoid hard-coding values. Place frequently changing values in configuration; rarely changing values may be declared as package-level constants. (値をハードコーディングしない。頻繁に変わる値は設定に配置し、稀にしか変わらない値はパッケージレベルの定数として宣言してもよい)

解説

ハードコードされた値は、環境ごとの違いや将来的な変更に対応できず、コードの修正とデプロイを強制します。頻繁に変更される値は設定ファイルや環境変数に外部化し、変更時にコードの再コンパイルを不要にすべきです。定数として宣言すべき値と設定として外部化すべき値を適切に判断することで、保守性の高いコードベースが実現されます。

具体例

// 悪い例(ハードコーディング)
func ProcessPayment(amount float64) error {
    tax := amount * 0.1  // 税率がハードコード
    total := amount + tax

    if total > 10000 {  // 閾値がハードコード
        return errors.New("amount too large")
    }

    apiURL := "https://api.payment.com/v1/charge"  // URLがハードコード
    return callAPI(apiURL, total)
}

// 良い例(定数と設定の適切な使用)
const (
    TaxRate = 0.1  // 法律で定められた税率(稀に変更)
)

type PaymentConfig struct {
    MaxAmount float64
    APIBaseURL string
}

type PaymentService struct {
    config PaymentConfig
}

func NewPaymentService(config PaymentConfig) *PaymentService {
    return &PaymentService{config: config}
}

func (s *PaymentService) ProcessPayment(amount float64) error {
    tax := amount * TaxRate
    total := amount + tax

    if total > s.config.MaxAmount {
        return errors.New("amount too large")
    }

    apiURL := s.config.APIBaseURL + "/v1/charge"
    return callAPI(apiURL, total)
}

参考リンク

core coding-standard

フラグパラメータで振る舞いを切り替えるのではなく関数を分割する

Contents

Instead of flag parameters that toggle behavior inside a function, split the function. (関数内部で振る舞いを切り替えるフラグパラメータを使う代わりに、関数を分割する)

解説

フラグパラメータを持つ関数は、実質的に2つ以上の異なる処理を1つの関数に詰め込んでおり、単一責任原則に違反します。フラグによって振る舞いが変わる関数は、呼び出し側でフラグの意味を理解する必要があり、可読性が低下します。関数を分割することで、各関数の意図が明確になり、使用方法が直感的になります。これにより、コードの保守性と理解しやすさが向上します。

具体例

// 悪い例(フラグパラメータで振る舞いを変更)
func ProcessUser(user User, sendEmail bool) error {
    if err := validateUser(user); err != nil {
        return err
    }

    if err := saveUser(user); err != nil {
        return err
    }

    if sendEmail {
        // メール送信処理
        if err := sendWelcomeEmail(user.Email); err != nil {
            return err
        }
    }

    return nil
}

func main() {
    ProcessUser(newUser, true)   // trueは何を意味する?
    ProcessUser(existingUser, false)  // falseは何を意味する?
}

// 良い例(関数を分割)
func RegisterNewUser(user User) error {
    if err := validateUser(user); err != nil {
        return err
    }

    if err := saveUser(user); err != nil {
        return err
    }

    if err := sendWelcomeEmail(user.Email); err != nil {
        return err
    }

    return nil
}

func UpdateExistingUser(user User) error {
    if err := validateUser(user); err != nil {
        return err
    }

    if err := saveUser(user); err != nil {
        return err
    }

    // メール送信なし

    return nil
}

func main() {
    RegisterNewUser(newUser)       // 意図が明確
    UpdateExistingUser(existingUser)  // 意図が明確
}

参考リンク

core coding-standard

変数と関数には意味のある名前を使う

Contents

Use meaningful names for variables and functions (e.g., good: getUser() / bad: method1()). (変数と関数には意味のある名前を使う(例: 良い getUser() / 悪い method1()))

解説

変数や関数に意味のある名前を付けることは、コードの可読性と保守性を高める基本原則です。名前から目的や役割が明確に伝わることで、他の開発者がコードを理解しやすくなります。抽象的な名前や番号付きの名前は、コードレビューやデバッグ時に混乱を招き、バグの温床となります。MUST項目として、すべての変数と関数に対して例外なく適用されるべきルールです。

具体例

// 悪い例
func method1() User {
    var a = "john@example.com"
    var b = fetchData(a)
    return b
}

// 良い例
func getUser() User {
    userEmail := "john@example.com"
    user := fetchUserByEmail(userEmail)
    return user
}

参考リンク

core coding-standard

関数のパラメータは5つ以内にする

Contents

A function should have no more than five parameters. More than five suggests too many responsibilities or insufficient aggregation. (関数のパラメータは5つ以内にすべき。5つを超える場合は、責任が多すぎるか、集約が不足していることを示す)

解説

パラメータが多い関数は、理解と使用が困難であり、単一責任原則に違反している可能性が高いです。パラメータが5つを超える場合、関連するパラメータをオブジェクトにまとめるか、関数の責任を分割すべきです。パラメータを集約することで、関連する値が明確になり、関数シグネチャがシンプルになります。これにより、コードの可読性と保守性が向上します。

具体例

// 悪い例(パラメータが多すぎる)
func CreateUser(
    firstName string,
    lastName string,
    email string,
    phone string,
    address string,
    city string,
    zipCode string,
    country string,
) error {
    // パラメータの順序を覚えるのが困難
    // ...
}

func main() {
    CreateUser("John", "Doe", "john@example.com", "123-456",
               "123 Main St", "Tokyo", "100-0001", "Japan")
}

// 良い例(パラメータをオブジェクトに集約)
type UserProfile struct {
    FirstName string
    LastName  string
    Email     string
    Phone     string
}

type Address struct {
    Street  string
    City    string
    ZipCode string
    Country string
}

func CreateUser(profile UserProfile, address Address) error {
    // 関連するデータがグループ化され、理解しやすい
    // ...
}

func main() {
    profile := UserProfile{
        FirstName: "John",
        LastName:  "Doe",
        Email:     "john@example.com",
        Phone:     "123-456",
    }

    address := Address{
        Street:  "123 Main St",
        City:    "Tokyo",
        ZipCode: "100-0001",
        Country: "Japan",
    }

    CreateUser(profile, address)
}

// さらに良い例(責任を分割)
func CreateUser(profile UserProfile) (UserID, error) {
    // ユーザー作成のみに集中
    // ...
}

func SetUserAddress(userID UserID, address Address) error {
    // 住所設定を別関数に分離
    // ...
}

参考リンク

core coding-standard

外部から呼ばれる必要がない関数は公開しない

Contents

Do not expose a function externally unless it truly needs to be called from outside. A public function must represent an external interface. (外部から呼ばれる必要がない限り、関数を外部に公開しない。公開関数は外部インターフェースを表現する必要がある)

解説

関数の可視性を必要最小限に抑えることは、カプセル化の原則に基づく重要な設計です。不必要に公開された関数は、意図しない外部からの呼び出しを許し、内部実装の変更を困難にします。公開関数は契約として機能するため、変更時には後方互換性を考慮する必要があります。内部実装の詳細は非公開にすることで、リファクタリングの自由度が高まります。

具体例

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

// 内部処理なのに公開されている
func (s *UserService) ValidateUserInput(name string) bool {
    return len(name) > 0
}

func (s *UserService) CreateUser(name string) error {
    if s.ValidateUserInput(name) {
        return s.db.Insert(name)
    }
    return errors.New("invalid input")
}

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

// 内部処理は非公開
func (s *UserService) validateUserInput(name string) bool {
    return len(name) > 0
}

// 外部インターフェースのみ公開
func (s *UserService) CreateUser(name string) error {
    if s.validateUserInput(name) {
        return s.db.Insert(name)
    }
    return errors.New("invalid input")
}

参考リンク

core coding-standard

各関数は100行以内に収める

Contents

Keep each function to 100 lines or fewer (logical lines; wrapped lines count as one). (各関数は100行以内に収める(論理行;折り返された行は1行とカウント))

解説

長い関数は、複数の責任を持っている可能性が高く、理解とテストが困難です。100行を超える関数は、処理を小さな関数に分割することで、可読性と再利用性が向上します。短い関数は、単一の責任を持ち、名前から目的が明確になります。論理行でカウントするため、コメントや空行、折り返しは複数行とはみなされません。適切な粒度の関数は、バグの発見と修正を容易にします。

具体例

// 悪い例(長すぎる関数 - 200行以上)
func ProcessOrder(order Order) error {
    // バリデーション(30行)
    if order.ID == "" {
        return errors.New("order ID is required")
    }
    if order.CustomerID == "" {
        return errors.New("customer ID is required")
    }
    // ... さらに20項目のバリデーション

    // 在庫チェック(40行)
    for _, item := range order.Items {
        stock, err := db.GetStock(item.ProductID)
        if err != nil {
            return err
        }
        // ... 複雑な在庫ロジック
    }

    // 価格計算(40行)
    subtotal := 0.0
    for _, item := range order.Items {
        subtotal += item.Price * float64(item.Quantity)
    }
    // ... 割引、税金、送料の計算

    // 決済処理(40行)
    // ... 決済ゲートウェイとの通信

    // メール送信(30行)
    // ... メール作成と送信

    // データベース保存(20行)
    // ... 複数テーブルへの保存

    return nil
}

// 良い例(適切に分割された関数)
func ProcessOrder(order Order) error {
    if err := validateOrder(order); err != nil {
        return fmt.Errorf("validation failed: %w", err)
    }

    if err := checkInventory(order.Items); err != nil {
        return fmt.Errorf("inventory check failed: %w", err)
    }

    total, err := calculateOrderTotal(order)
    if err != nil {
        return fmt.Errorf("calculation failed: %w", err)
    }

    if err := processPayment(order.CustomerID, total); err != nil {
        return fmt.Errorf("payment failed: %w", err)
    }

    if err := saveOrder(order); err != nil {
        return fmt.Errorf("save failed: %w", err)
    }

    if err := sendConfirmationEmail(order); err != nil {
        log.Printf("email failed: %v", err)
        // メール失敗は注文処理を失敗させない
    }

    return nil
}

// 各関数は単一の責任を持ち、100行以内
func validateOrder(order Order) error {
    if order.ID == "" {
        return errors.New("order ID is required")
    }
    if order.CustomerID == "" {
        return errors.New("customer ID is required")
    }
    // ... その他のバリデーション
    return nil
}

func checkInventory(items []Item) error {
    for _, item := range items {
        if err := verifyStockAvailability(item); err != nil {
            return err
        }
    }
    return nil
}

func calculateOrderTotal(order Order) (float64, error) {
    subtotal := calculateSubtotal(order.Items)
    discount := calculateDiscount(order, subtotal)
    tax := calculateTax(subtotal - discount)
    shipping := calculateShipping(order)
    return subtotal - discount + tax + shipping, nil
}

参考リンク

core coding-standard

密接に関連するデータと振る舞いはクラスにまとめる

Contents

Keep closely related data and behavior together in a class for high cohesion and clear separation of concerns. Conversely, do not split strongly related elements across separate functions. (密接に関連するデータと振る舞いをクラスにまとめて高い凝集度と明確な関心の分離を実現する。逆に、強く関連する要素を別々の関数に分割しない)

解説

高い凝集度は、関連するデータと操作を1箇所にまとめることで実現されます。データとそれを操作する振る舞いが離れていると、変更時に複数箇所を修正する必要が生じ、整合性を保つことが困難になります。クラス(構造体)内で関連する要素を集約することで、コードの理解が容易になり、変更の影響範囲が明確になります。

具体例

// 悪い例
type User struct {
    Name  string
    Email string
}

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

func formatUserName(name string) string {
    return strings.Title(name)
}

// 良い例
type User struct {
    Name  string
    Email string
}

func (u *User) ValidateEmail() bool {
    return strings.Contains(u.Email, "@")
}

func (u *User) FormatName() string {
    return strings.Title(u.Name)
}

参考リンク

core coding-standard

仕様に記載されたすべての詳細を実装する

Contents

Implement every detail stated in the specification. (仕様に記載されたすべての詳細を実装する)

解説

仕様書に記載された要件は、ステークホルダーとの合意事項であり、完全に実装される必要があります。仕様の一部を省略したり、勝手に変更したりすることは、期待される機能が提供されないことを意味し、プロジェクトの失敗につながります。不明点や実装困難な要件がある場合は、実装前にステークホルダーと協議し、仕様を明確にする必要があります。実装後は、仕様との整合性を確認するテストが必須です。

具体例

// 仕様:
// - ユーザー登録時にメールアドレスの形式を検証する
// - パスワードは8文字以上必要
// - 登録完了メールを送信する

// 悪い例(仕様の一部のみ実装)
func RegisterUser(email, password string) error {
    if !strings.Contains(email, "@") {
        return errors.New("invalid email")
    }

    return saveUser(email, password)
    // パスワード長チェック未実装
    // メール送信未実装
}

// 良い例(仕様をすべて実装)
func RegisterUser(email, password string) error {
    if !strings.Contains(email, "@") {
        return errors.New("invalid email format")
    }

    if len(password) < 8 {
        return errors.New("password must be at least 8 characters")
    }

    if err := saveUser(email, password); err != nil {
        return err
    }

    if err := sendWelcomeEmail(email); err != nil {
        log.Printf("Failed to send email: %v", err)
        // メール送信失敗は登録処理を失敗させない
    }

    return nil
}

参考リンク

core coding-standard

各クラスは独立して動作するようにする

Contents

Each class (struct + methods) should work independently. (各クラス(構造体+メソッド)は独立して動作するようにする)

解説

クラスが他のクラスやグローバル状態に暗黙的に依存していると、テストが困難になり、再利用性が低下します。各クラスは、必要な依存関係を明示的に受け取り、独立して動作すべきです。これにより、単体テストでモックやスタブを容易に注入でき、クラスの振る舞いを分離して検証できます。独立性の高いクラスは、異なるコンテキストでの再利用も容易です。

具体例

// 悪い例(グローバル状態に依存)
var globalDB *Database  // グローバル変数

type UserService struct{}

func (s *UserService) GetUser(id string) (User, error) {
    return globalDB.Query(id)  // 暗黙的な依存
}

// 良い例(依存関係を明示的に注入)
type UserService struct {
    db Database
}

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

func (s *UserService) GetUser(id string) (User, error) {
    return s.db.Query(id)  // 明示的な依存
}

// テストでのモック使用
type MockDatabase struct{}

func (m *MockDatabase) Query(id string) (User, error) {
    return User{ID: id, Name: "Test User"}, nil
}

func TestGetUser(t *testing.T) {
    mockDB := &MockDatabase{}
    service := NewUserService(mockDB)

    user, err := service.GetUser("123")
    if err != nil {
        t.Fatal(err)
    }
    if user.Name != "Test User" {
        t.Errorf("unexpected user name: %s", user.Name)
    }
}

参考リンク

core coding-standard

略語ではなく完全な単語を使う

Contents

Use full words rather than abbreviations (e.g., compensatingTransaction instead of ct). Commonly accepted abbreviations like db are fine. (略語ではなく完全な単語を使う(例:ctではなくcompensatingTransaction)。dbのように一般的に受け入れられている略語は問題ない)

解説

略語は、書いた本人以外には意味が不明確で、コードの理解を妨げます。完全な単語を使用することで、コードの意図が明確になり、新しいメンバーでも容易に理解できます。現代のエディタには自動補完機能があるため、長い名前でも入力の手間は問題になりません。ただし、広く認知されている略語(例:db、api、html、url)は使用しても問題ありません。

具体例

// 悪い例(独自の略語を使用)
func ProcUsrData(usr *Usr) error {
    // usr, Procは何の略か不明
    res := CalcTtl(usr.Itms)
    // res, Ttl, Itmsは何の略か不明

    if err := SaveToDb(res); err != nil {
        return err
    }

    return nil
}

type Usr struct {
    Nm   string  // Nmは何の略?
    Em   string  // Emは何の略?
    Ph   string  // Phは何の略?
    Itms []Item
}

func CalcTtl(itms []Item) float64 {
    ttl := 0.0
    for _, itm := range itms {
        ttl += itm.Prc
    }
    return ttl
}

// 良い例(完全な単語を使用)
func ProcessUserData(user *User) error {
    total := CalculateTotal(user.Items)

    if err := SaveToDatabase(total); err != nil {
        return err
    }

    return nil
}

type User struct {
    Name  string
    Email string
    Phone string
    Items []Item
}

func CalculateTotal(items []Item) float64 {
    total := 0.0
    for _, item := range items {
        total += item.Price
    }
    return total
}

// 一般的な略語は使用可(良い例)
type UserRepository struct {
    db  *sql.DB     // dbは一般的
    api APIClient   // apiは一般的
}

func FetchFromAPI(url string) ([]byte, error) {
    // url, apiは一般的
    resp, err := http.Get(url)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()

    return io.ReadAll(resp.Body)
}

func GenerateHTML(data TemplateData) string {
    // htmlは一般的
    return renderTemplate(data)
}

参考リンク

core coding-standard