引数と戻り値にはできるだけ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)
}
参考リンク
引数と戻り値にはできるだけValue Objectを使う https://www.tricrow.com/core/coding-standard/value-objects-in-signature.html

