Сайт Данила

Структура аргументів

Кількість слів: ~504

Zig часто використовує підхід який називають “Options bag pattern” (якщо його взагалі так хтось називає). Останнім часом я почав використовувати його в Go.

Go структура з аргументами

package fediverse

type NewClientArgs struct{
	HTTPClient *http.Client
	UseV2      *bool
	FluentOpts []fluent.Opts
}

func (nca NewClientArgs) WithDefaults() NewClientArgs {
	if nca.HTTPClient == nil {
		nca.HTTPClient = http.DefaultClient
	}
	if nca.UseV2 == nil{
		nca.UseV2 = new(viper.GetBool("some_env_var"))
	}
	if *nca.UseV2{
		nca.FluentOpts = append(nca.FluentOpts, fluent.WithSomethingCool, fluent.WithNewGen)
	}
	return nca
}

type Client struct{}

func NewClient(url *url.URL, args NewClientArgs) *Client {
	args = args.WithDefaults()
	return &Client{
		// set fields
		// client: args.HTTPClient, // etc.
	}
}

Чому мені це більше подобається? Усі опції в одному місці, їх легко додати як поле для логування, опції можуть залежати одна від одної бо порядковість ініціалізації контролюється імплементацією.

Думаю що назва NewClientOpts може бути більш вдалим у даному випадку, коли всі поля структури є опціональними, я ще не вирішив як краще називати цю структуру в коді. Також є випадки коли обов’язкові поля додаються в таку структуру, тоді назва ~Args краща.

Мутуючі функції

У Go мені здається переважає патерн коли опція це інтерфейс або функція, що приймає об’єкт для налаштування, і модифікує його.

type WithOpt func(*privateStruct)

func WithInsanity(level int) WithOpt
func WithPrice(price uint) WithOpt

type privateStruct struct{
	// state
}

func New(opts ...WithOpt) *privateStruct {
	v := &privateStruct{}
	for _, opt := range opts {
		opt(v)
	}
	return v
}

Мені цей підхід не подобається. Ми втратили контроль поряку застосування опцій, спосіб легко логувати параметри й потенційно ці функції можуть бути в різних файлах.

Валідація

У випадку обов’язкових полів можливо додати метод Validate.

type Args struct{
	IsWater      *bool
	IsLiquid     *bool
	CanCatchFire *bool
}

func (a Args) Validate() error {
	var errs []error

	if a.IsWater != nil && a.CanCatchFire != nil{
		errs = append(errs, errors.New("water is not compatible with fire"))
	}

	return errors.Join(errs...)
}
Ґікс (Guix)