Zenn
📘

Goの関数引数設計:バラバラに渡す vs structでまとめる

2025/03/26に公開

Goでは関数に引数を「バラバラに渡す」か「構造体にまとめて渡す」かの選択が設計上よくあります。よく開発で議論に上がるので、自分なりの考えをまとめておきます。

方法①:引数をバラバラに渡す

func SendEmail(to string, subject string, body string) error {
    // 処理
}

メリット

  • 明示的でわかりやすい
  • IDEの補完が効きやすい
  • 構造体を作らない分、オーバーヘッドが少ない

デメリット

  • 引数が多くなると可読性が下がる
  • 順序に注意が必要(間違えてもコンパイルエラーにならない)

方法②:構造体にまとめて渡す

type EmailParams struct {
    To      string
    Subject string
    Body    string
}

func SendEmail(params EmailParams) error {
    // 処理
}

メリット

  • 引数が多くても整理されて見やすい

  • 名前付き引数のように渡せる(同じ型が複数ある場合のミス防止に有効)

    SendEmail(EmailParams{
        To: "user@example.com",
        Subject: "Hello",
        Body: "This is a test.",
    })
    
  • 将来の拡張に強い(フィールド追加が容易)

デメリット

  • struct を都度定義・生成する手間
  • 単純なケースでは冗長になる可能性あり
  • 大きな struct に依存しすぎると凝集性が下がる
  • 必須値の未設定に気づきにくい

以下のように、入力が必須の項目を与えなくても、コンパイルが失敗することがないため、不具合の原因となり得ます。

package main

import (
    "fmt"
)

type EmailParams struct {
    To      string
    Subject string
    Body    string
}

func SendEmail(params EmailParams) error {
    if params.To == "" {
        return fmt.Errorf("宛先(To)が設定されていません")
    }

    fmt.Println("To:", params.To)
    fmt.Println("Subject:", params.Subject)
    fmt.Println("Body:", params.Body)
    return nil
}

func main() {
    // 宛先を渡し忘れている(しかしコンパイルは通る)
    err := SendEmail(EmailParams{
        Subject: "No Recipient",
        Body:    "This email has no recipient address.",
    })
    if err != nil {
        fmt.Println("Error:", err)
    }
}

自分なりの判断基準

条件 推奨される方法
引数が2〜3個以下 バラバラに渡す
引数が多い struct にまとめる
同じ型の引数が複数ある struct にまとめる
拡張を見越している struct にまとめる

最後の「拡張を見越してる」に関しては、そもそもどんどん引数が増える拡張を、その関数だけで吸収すべきなのか(別の関数を作成すべきではないのか)、というのも論点になってくるので、その関数だけで判断するのは難しいところですね。
自分で設計する場合は、まずはバラバラに書いてみて、保守しづらくなってきたら構造体にまとめるということが多いかなと思います。

Discussion

ログインするとコメントできます