Open7

Golang Tips

snamiki1212snamiki1212

Go 使ってるけど開発効率を上げたいのでそのメモ

snamiki1212snamiki1212

Q. Go で関数の返り値として return nil, nil でもOK?

A. No. Go の慣習に違反していて、ダメな理由はNull-pointer になる可能性があるので。関数を呼び出す側の期待値としては、「エラーではないということはデータが入ってる」と思って処理するのに実際はnilなのでnull-pointerになる可能性がある。


調べた内容

  • Go言語で「なかった」の返し方 #Go - Qiita

    • ダメ絶対のスタンス。自分も最初にこれを思った
    • できればgoの言語仕様で違反としてかけないようにしてほしい
    • もしかしてlint とかある? →Goは標準のやつでしかもCustom不可なはずなので、確認できないか。。
  • Goとエラーハンドリング慣習について

    • 慣習でNGとのこと
    • 慣習調べても見つからん
  • Attention Required! | Cloudflare

    • 意訳として: 厳格なNGではなく、 返り値の組み合わせは意味のあるもの に従う。もし、nil, nil が意味のあるものならOKだけど、あんまりそんなケースはないしハンドリングはやはり大変になる
snamiki1212snamiki1212

デザパタ: Strategy

Goで実装を書いてる時に Strategy にしたいことがあった。
久しぶりに23個のデザパタを全部復習しててリファクタリング Guru を眺めていたら下記の記述を見つけた。

多くの近代的なプログラミング言語は関数型をサポートしており、 アルゴリズムの異種を匿名関数の集合中で実装可能。 余計なクラスやインターフェースでプログラムを膨らませることなく、 ストラテジー・オブジェクトとまったく同じようにこれらの関数が使用可能。 -- https://refactoring.guru/ja/design-patterns/strategy

実際にどのようなコードで特徴があるかを試してみた。

そもそもストラテジーパターンとは

TODO:

構造体ストラテジー

  • 一般的なストラテジーパターン。戦略ごとに構造体を用意する。
  • 複数の命令を戦略別に実行したいとき、interface + struct を拡張すればいいだけ。Newでストラテジーの構造体の注入では変更がない
package main

import (
	"fmt"
)

func main() {
	ga := NewAuth(GoogleAuthStrategy{})
	fmt.Println(ga.Ready())
	fmt.Println(ga.Verify())

	ma := NewAuth(MailAuthStrategy{})
	fmt.Println(ma.Ready())
	fmt.Println(ma.Verify())
}
// > google auth is done!
// > google auth is ready...
// > mail auth is done!
// > mail auth is ready...


type Auth struct {
	strategy AuthStrategy
}

func NewAuth(strategy AuthStrategy) Auth {
	return Auth{strategy}
}

func (a Auth) Verify() string {
	return a.strategy.Verify()
}

func (a Auth) Ready() string {
	return a.strategy.Ready()
}

type AuthStrategy interface {
	Ready() string
	Verify() string
}

type GoogleAuthStrategy struct{}

func (r GoogleAuthStrategy) Verify() string {
	// ここにgoogle認証処理を書く
	return "google auth is done!"
}

func (r GoogleAuthStrategy) Ready() string {
	return "google auth is ready..."
}

type MailAuthStrategy struct{}

func (r MailAuthStrategy) Verify() string {
	// ここにMail認証処理を書く
	return "Mail auth is done!"
}

func (r MailAuthStrategy) Ready() string {
	return "mail auth is ready..."
}

関数ストラテジーケース

  • 関数を使ったストラテジーパターン。戦略ごとに構造体ではなく関数を用意してあげる必要がある
  • 複数の命令を戦略別に実行したいとき、interfaceを拡張して対応する関数を作成し、Newでストラテジー関数の注入を追加しないといけない。
  • 所感
    • 1つの関数を呼び出したいかつ拡張する予定がない時は構造体ストラテジーよりも薄く作れる
    • 複数の戦略を簡単に直交できる。
      • 例示したコードで説明すると 例えば mixedAuth := NewAuth(verifyWithGoogle, readyWithMail) のように関数の組み合わせを自由に変えられる
package main

import (
	"fmt"
)

func main() {
	ga := NewAuth(verifyWithGoogle, readyWithGoogle)
	fmt.Println(ga.Ready())
    fmt.Println(ga.Verify())

	ma := NewAuth(verifyWithMail, readyWithMail)
    fmt.Println(ma.Ready())
	fmt.Println(ma.Verify())
}
// > google auth is done!
// > google auth is ready...
// > mail auth is done!
// > mail auth is ready...

type Auth struct {
	verifier func() string
	ready    func() string
}

func NewAuth(verifier func() string, ready func() string) Auth {
	return Auth{verifier, ready}
}

func (a Auth) Verify() string {
	return a.verifier()
}

func (a Auth) Ready() string {
	return a.ready()
}

func verifyWithGoogle() string {
	// ここにgoogle認証処理を書く
	return "google auth is done!"
}

func readyWithGoogle() string {
	return "google auth is ready..."
}

func verifyWithMail() string {
	// ここにMail認証処理を書く
	return "mail auth is done!"
}

func readyWithMail() string {
	return "mail auth is ready..."
}

snamiki1212snamiki1212

Enum の実装パターン

TODO: まだメリデ目について調べてる途中
https://www.google.com/search?q=golang+enum+type+interface&oq=golang+enum+type+interface&gs_lcrp=EgZjaHJvbWUyBggAEEUYOTIGCAEQLhhA0gEINzAzOGowajGoAgCwAgA&sourceid=chrome&ie=UTF-8

type + const

Go は言語標準で Enum がないため下記のようなやり方が一般的。

  • シンプルに描ける
  • enum に対して型があるのでMapが作れる(map[AuthType]Auth )
type Auth struct {
	Type AuthType
}
type AuthType string

const (
	GoogleAuthType AuthType = "google"
	MailAuthType   AuthType = "mail"
)

type + const を interface でラップする

  • type+const よりも冗長。
  • type に応じて、構造体が異なるときに使う。
  • どの type なのかを知りたいときは、Getterとなるメソッドinterface で関数を定義した上で構造体に追加する(Keyメソッド)
type Auth struct {
	Type AuthType
}
type AuthType interface {
	Key() AuthTypeKey
}

type GoogleAuthType struct {
	GoogleAccountID string
}

func (g GoogleAuthType) Key() AuthTypeKey { return AuthTypeKeyGoogle }

type MailAuthType struct {
	Email string
}

func (m MailAuthType) Key() AuthTypeKey { return AuthTypeKeyMail }

type AuthTypeKey string

const (
	AuthTypeKeyGoogle AuthTypeKey = "google"
	AuthTypeKeyMail   AuthTypeKey = "mail"
)
snamiki1212snamiki1212

Slice を格納したInterface

これが動いてくれない。ハマってる。なんでや。

https://goplay.tools/snippet/VL667gKBT37

// error
cannot use rs (variable of type Rules) as IRules value in argument to do: Rules does not implement IRules (wrong type for method Data)
		have Data() []RuleData
		want Data() []IRulecompilerInvalidIfaceAssign

RuleData は IRule というInterface を満たしているので動いてくれると思ったんだが。

過去に調べた時に Data() []IRule について []IRuleIRule というInterface へのポインタを格納したSlice となってしまうから、みたいなのをみたような気がしてきたけど、じゃあどうすればいいんかわからん。

Go言語のInterfaceの考え方、Accept interfaces,return structs #Go - Qiita

Data() の返り値が Interface なのが悪さをしてそう。ここの時点で構造体にしないといけない。あれ、でもそれだとインターフェース側が実装側のことを知らないといけないのでは?

package main

import (
	"fmt"
)

func main() {
	fmt.Println("Hello, World!")
	rs := Rules{}
	do(rs)
}

func do(rules IRules) {
	for _, d := range rules.Data() {
		fmt.Println(d.Next())
	}
}

// Interface
type IRules interface {
	Data() []IRule
}
type IRule interface {
	Next() string
}

// Impl
type Rules struct {
	data []RuleData
}

func (rs Rules) Data() []RuleData { return rs.data }

type RuleData struct{}

func (r RuleData) Next() string { return "this is next" }