New関数の慣習
不正な状態を作れなくする
構造体はどんな値でも入れられます。別の題材として、制限時間を持つ Session の生成関数を考えます。
Go
type Session struct {
Minutes int
}
session := Session{Minutes: -5} // 作れてしまう制限時間が負のセッションができました。作成そのものはエラーになりません。この Session が後の処理に流れていくと、不正な時間を使ってしまいます。
作る時点で止めます。 そのための関数を用意するのが Go の慣習です。
生成関数で検証します。errors.New を使うには import "errors" が必要です。
Go
func NewSession(minutes int) (*Session, error) {
if minutes <= 0 {
return nil, errors.New("時間は正にしてください")
}
return &Session{Minutes: minutes}, nil
}第4章でやった検証が、生成と一体になりました。
New という名前
Go には constructor という仕組みがありません。ただの関数です。ただし名前の付け方に決まりがあります。
NewItem Item を作る
NewUser User を作る
New パッケージの主役の型を作るNew で始めるだけで、読む人には「これは生成関数だ」と伝わります。言語の機能ではなく、名前の習慣で揃えているのが Go らしいところです。
ポインタで返す
Go
return &Session{Minutes: minutes}, nil& は「その居場所」を意味します。&Session{...} は「作った Session の居場所」を返します。前のレッスンの *Item と同じ考え方で、戻り値の型は *Session になります。
*Session 居場所を表す型
&session 居場所を取り出す生成関数がポインタを返すと、呼び出し側とメソッドが同じ場所の値を扱えます。ただし、値で返しても、変数に受け取ればその変数は書き換えられます。ポインタレシーバの呼び出しでも、Go が変数のアドレスを自動で補います。
Go
func (s *Session) Extend() {
s.Minutes += 5
}
session := Session{Minutes: 10} // ポインタではなく値の変数
session.Extend()
// session.Minutes は 15値レシーバが変更するのはコピーです。生成関数の戻り値が値かポインタかと、メソッドが値レシーバかポインタレシーバかは、別の話です。
失敗したときは nil
Go
return nil, errors.New("時間は正にしてください")ポインタのゼロ値は nil です。第4章で (int, error) を返したときの 0 にあたるものが、ポインタでは nil になります。
受け取る側は、いつもの形で確かめます。
Go
session, err := NewSession(0)
if err != nil {
return err.Error()
}
// ここから先、session は nil ではないと信用してよい生成に失敗したのに err を確かめずに item を使うと panic します。 nil のポインタからフィールドを読もうとするからです。第4章で「エラーを _ で捨てるな」と書いた理由が、ここでは実行時の停止という形で現れます。
演習で担当する部分
NewItem の商品名と価格が0以下の検証は用意済みです。その後に上限超えの検証を足し、成功したときだけ Item を作ってポインタと nil を返します。createItem では、生成関数が返した err を先に確認します。err.Error() でエラーの理由を文字列として取り出せます。
表示用の createdMessage は用意済みです。未完成のひな形でも動くように、nil を受け取った場合は未完成の表示を返します。完成後は、エラーを先に返したうえで、この関数へ商品を渡してください。
要件
NewItemは(*Item, error)を返す- 検証の順番は 商品名が空 → 価格が0以下 → 価格が上限超え
- 成功時は
&Item{...}とnilを返す - 失敗時は
nilとエラーを返す createItemは成功なら商品名 価格円 を作成しました、失敗ならエラーの理由を返す