Open19

go 【文法・パッケージ・gorm】

engineer rebornengineer reborn

目次

  • 名前付き返り値
  • package
    • exported Names
  • Functions 関数
  • Variables
  • データ型
  • 型変換
  • interface型
  • 型のチェック
    • reflection
  • Constants 定数
  • 制御系
    • For
    • while
    • Forever 無限ループ
    • if
    • switch
    • Defer
  • 配列系
    • ポインタ
    • 構造体
    • 配列
    • スライス
    • map
  • メソッド
    • レシーバー
    • ポインタレシーバー
  • interface
    • 空のinterface
    • Typeエイリアス
      • s, ok := i.(string)
  • ゴルーチン
    • チャンネル
    • バッファーチャンネル
    • Select
    • Mutex
  • go get vs go install
  • gorm
  • echo
  • validator
  • json
  • time
  • 実装パターン
  • csv
  • errors.Is
    • Unwrapなど
  • データ構造

名前付き返り値

返り値に名前をつければ、returnに引数を持たせなくても、返却される
=> 実務で使うかは微妙な感じするな。使ってはいけないなどのコーディング規約ありそうだ

func Fprintf(w io.Writer, format string, a ...any) (n int, err error) {
	p := newPrinter()
	p.doPrintf(format, a)
	n, err = w.Write(p.buf)
	p.free()
	return
}
engineer rebornengineer reborn

package

exported Names

  • 参照できるパッケージは、大文字から始まる
import "math"

fmt.Println(math.Pi)
engineer rebornengineer reborn

Functions 関数

package main

import "fmt"

func add(x int, y int) int {
	return x + y
}

func main() {
	fmt.Println(add(42, 13))
}
  • 引数
// before
x int, y int

// after

x, y int 
  • 複数の返り値
  • 名前付きの返り値
    • return に引数を指定しないでも返却できる
engineer rebornengineer reborn

Variables

  • 変数
    • var 変数名 データ型
      • var i int
      • var hoge string
    • 変数と初期化
      • 初期化はプリミティブと構造体で変わる
        • var num int = 1
        • var hoge string = "hoge"
        • var todo Todo{ Title: "title"}
          • type Todo { Title string }
    • 省略記法
      • hoge := "string" => 変数の定義とデータの「ゼロ値」で初期化

データ型

  • bool
  • string
  • int
    • intはたくさんある
    • 基本はint
  • byte
  • rune
    • int32
    • Unicode
    • 文字列とかで
  • float32
  • complex64
  • ゼロ値
    • 数値: 0
    • bool: false
    • string: 空文字

型変換

  • 類似の型であれば変換できる
    • int をfloatにするなど

interface型

  • 明示的な型を支持せずに変数を定義する場合

型のチェック

  • %T
package main

import "fmt"

func main() {
	hoge := "string"
	num := 2
	fmt.Printf("type of %T\n", hoge)
	fmt.Printf("type of %T", num)
}
package main

import (
	"fmt"
	"reflect"
)

func main() {
	// いろんな型の値を定義

	v := "aaa"
	t := reflect.TypeOf(v)

	fmt.Printf("値: %-10v | 型: %-15T | reflect.Type: %-20v | Kind: %v\n", v, v, t, t.Kind())
}
  • reflectのTypeOf
func TypeOf(i any) Type {
	return toType(abi.TypeOf(i))
}
type Type interface {
  Kind() Kind
}

enum

  • 定数を定義
  • 文字配列にする。[]string
  • 定数の数字で出力する
// You can edit this code!
// Click here and start typing.
package main

import "fmt"

type Kind uint

const (
	Invalid       Kind = iota // 0
	Bool                      // 1
	Int                       // 2
	Int8                      // 3
	Int16                     // 4
	Int32                     // 5
	Int64                     // 6
	Uint                      // 7
	Uint8                     // 8
	Uint16                    // 9
	Uint32                    // 10
	Uint64                    // 11
	Uintptr                   // 12
	Float32                   // 13
	Float64                   // 14
	Complex64                 // 15
	Complex128                // 16
	Array                     // 17
	Chan                      // 18
	Func                      // 19
	Interface                 // 20
	Map                       // 21
	Ptr                       // 22
	Slice                     // 23
	String                    // 24  ← これ!
	Struct                    // 25
	UnsafePointer             // 26
)

var names = []string{
		"Invalid", "Bool", "Int", "Int8", "Int16", "Int32", "Int64",
		"Uint", "Uint8", "Uint16", "Uint32", "Uint64", "Uintptr",
		"Float32", "Float64", "Complex64", "Complex128",
		"Array", "Chan", "Func", "Interface", "Map", "Ptr", "Slice",
		"String", "Struct", "UnsafePointer",
	}


func (k Kind) String() string {
	if int(k) < len(names) {
		return names[k]
	}
	return "Unknown"
}

func main() {

	var a Kind

	a = 18

	fmt.Println(a.String())
}
  • String() string をメソッドに持っていれば fmt.Printlnは出力してくれる
import (
	"fmt"
)

type Kind uint

const (
	Invalid   Kind = iota // 0
	Bool                  // 1
	Int                   // 2
	Chan                  // 18
	Func                  // 19
	Interface             // 20
	Map                   // 21

)

var names = []string{"Invalid", "Bool", "Int", "Chan", "Func", "Interface", "Map"}

func (k Kind) String() string {
	return names[k]
}

func main() {

	var k Kind
	k = 4

	fmt.Printf("t %s", k)

}

Constants 定数

  • 省略宣言はできない
    • :=
const Pi = 3.14

// You can edit this code!
// Click here and start typing.
package main

import "fmt"

// 複数も設定できる
const (
	Pi = 3.14
)

func main() {
	fmt.Println("hhoge", Pi)
}

make

  • bbは初期化されていないので、lenもcapも共に 0ではあった
    • appendをすると len もcap
package main

import "fmt"

func main() {
	aa := make([]int, 1)
	bb := []int{}

	fmt.Println("Hello, 世界", len(aa), cap(aa), len(bb), cap(bb))
	bb = append(bb, 1)
	fmt.Println(len(bb), cap(bb))
}

Hello, 世界 1 1 0 0
1 1
  • makeで要素を作ると初期値が設定される
    • aa[0] = 1 で代入しないと [1,2]にはならない
package main

import "fmt"

func add(x int, y int) int {
	return x * y
}

func main() {

	aa := make([]int, 1)

	aa[0] = 1
	aa = append(aa, 2)

	fmt.Println("Hello, 世界", aa)
}

// 結果
Hello, 世界 [0 2]

make 追加

  • インデックス追加
// You can edit this code!
// Click here and start typing.
package main

import "fmt"

type Todo struct {
	Title  string
	Status int
}

var datas = []string{"todo1", "todo2", "todo3"}

func main() {

	fmt.Println(len(datas))
	todos := make([]Todo, len(datas))
	todo2 := make([]Todo, len(datas))

	for i, d := range datas {
		todos[i] = Todo{Title: d, Status: 0}
	}

	fmt.Printf("%v", todos)
	fmt.Printf("%v", todo2)
}

### 出力
// todo2は 構造体のゼロ値が設定されて3つ要素がある
[{todo1 0} {todo2 0} {todo3 0}][{ 0} { 0} { 0}]

  • append追加
// You can edit this code!
// Click here and start typing.
package main

import "fmt"

type Todo struct {
	Title  string
	Status int
}

var datas = []string{"todo1", "todo2", "todo3"}

func main() {

	fmt.Println(len(datas))
	todos := make([]Todo, len(datas))

	for _, d := range datas {
		todos = append(todos, Todo{Title: d, Status: 0})
	}

	fmt.Printf("%v", todos)
}

### 出力
// makeで初期化された要素の後に、追加した要素がくる
[{ 0} { 0} { 0} {todo1 0} {todo2 0} {todo3 0}]
engineer rebornengineer reborn

For

package main

import "fmt"

func main() {
	sum := 0
	for i := 0; i < 10; i++ {
		sum += i
	}
	fmt.Println(sum)
}

while

package main

import "fmt"

func main() {
	sum := 1
	for sum < 1000 {
		sum += sum
	}
	fmt.Println(sum)
}

Forever 無限ループ

  • LOOPはラベル
package main

import "fmt"

func main() {

	i := 1

LOOP:
	for {
		fmt.Println(i)
		i++

		if i == 100 {
			break LOOP
		}
	}

}

if

  • ()は省略可能
  • {}は省略はできない
  • ifの省略記法
    • 身長が170未満なら身長が低い
package main

import "fmt"

func main() {

	if v := 160; v < 170 {
		fmt.Println("身長が低い")
	}

}
  • if and else
    -サンプルロジック
    - 身長が170以上なら高い
    - 身長が170未満なら低い
package main

import "fmt"

func main() {

	if v := 171; v < 170 {
		fmt.Println("身長が低い")
	} else {
		fmt.Println("身長が高い")
	}

}

switch

  • 挨拶のロジック
  • if elseのステートメントを短く書いていく
package main

import "fmt"

func main() {
	switch country := "Japan"; country {
	case "Japan":
		fmt.Println("こんにちわ")
	case "US":
		fmt.Println("Hello world")
	default:
		fmt.Println("Hello world")
	}

}
  • これはイメージしやすい
    • switchに引数はない
package main

import "fmt"

func main() {
	country := "Japan"
	switch country {
	case "Japan":
		fmt.Println("こんにちわ")
	case "US":
		fmt.Println("Hello world")
	default:
		fmt.Println("Hello world")
	}

}

Defer

  • 処理が終わったてから何かする感じ
package main

import "fmt"

func main() {

	defer fmt.Println("完了")

	for i := 0; i < 100; i++ {
		fmt.Println(i)
	}

}
  • deferはスタック
package main

import "fmt"

func main() {
	fmt.Println("counting")

	for i := 0; i < 10; i++ {
		defer fmt.Println(i)
	}

	fmt.Println("done")
}

counting
done
9
8
7
6
5
4
3
2
1
0
engineer rebornengineer reborn

ポインタ

package main

import "fmt"

func main() {
	var p *int
	var sp *string // ここ *intだとエラーになる
	i := 42
	p = &i

	s := "Hello"
	sp = &s
	fmt.Println(p, sp)
}

構造体

  • フィールドの集まり
    • 大文字にしないと呼び出せない
package main

import "fmt"

type Vertex struct {
	X int
	Y int
}

func main() {
	v := Vertex{X: 1, Y: 3}
	fmt.Println(v.X, v.Y)
}

  • 初期化
    • varを利用する場合は、=を利用する
package main

import "fmt"

type Vertex struct {
	X int
	Y int
}

// var num int = 1
func main() {
    // 明示的な書き方
	var v Vertex = Vertex{X: 1, Y: 3}
	// v := Vertex{X: 1, Y: 3}
	fmt.Println(v.X, v.Y)
}

構造体とポインタ (実戦で使う)

  • ポインタでデータを渡しても、フィールドは普通に参照できる
package main

import "fmt"

type Vertex struct {
	X int
	Y int
}

func main() {
	v := Vertex{X: 1, Y: 3}
	pv := &v
	fmt.Println(pv.X, pv.Y)
}
1 3
  • 構造体のゼロ値
    • intでフィールドを定義したら、0が入る
package main

import "fmt"

type Vertex struct {
	X, Y int
}

var (
	v3 = Vertex{}      // X:0 and Y:0
)

func main() {
	fmt.Println(v1, p, v2, v3)
}

配列

  • 配列の要素を指定する
    • インデックスで指定できる

Slices

  • インデックスで要素を指定できる
    • a[low:high]
  • スライスは配列への参照のようなもの
  • スライスの長さとキャパシティ
    • 長さ: len(s)
    • キャパシティ: cap(s)
  • nilスライス
package main

import "fmt"

func main() {
	var num []int

	fmt.Println(len(num), cap(num))

	fmt.Println(num)
}

0 0
[]

## 代入もするケース
package main

import "fmt"

func main() {
	var num = []int{1}

	fmt.Println(len(num), cap(num))

	fmt.Println(num)
}
1 1
[1]
  • len, cap
package main

import "fmt"


func main() {

	aa := make([]int, 1)
	bb := []int{}
	aa[0] = 1
	fmt.Println("aa", len(aa), cap(aa))
	fmt.Println("bb", len(bb), cap(bb))
}
aa 1 1
bb 0 0
  • sliceへの追加
    • appendで追加
package main

import "fmt"

func main() {
	// len cap
	a := make([]int, 5, 10)
	fmt.Println(len(a), cap(a))
	a = append(a, 1)
	fmt.Println(len(a), cap(a))
}
  • range
    • i, v で インデックスと 値が取得できる

map

  • ゼロ値

    • nil
    • nilだから nilポインタでpanicになる
  • a := make(map[string]int)

    • 空のmapで存在してないキーにアクセスすると、「バリュー」のゼロ値が返却される
    • キーは intにしてたらintで指定しないとエラー
package main

## makeで初期化
import "fmt"

func main() {
  // 長さは設定していない
	a := make(map[string]int)
	fmt.Println(a["aa"])
}

## {}で初期化


func main() {
	a := map[string]int{}
	fmt.Println(a["aa"])
}
0 が返却される
  • マップの操作
// 参照

m[key]

hoge := m[key]  // 変数に初期値を入れたり

// 削除

elem = m[key] // 削除済みのmapを返却

// キーの存在判定

elem, ok = m[key] // 代入か
engineer rebornengineer reborn

メソッド

  • レシーバー
  • クラスのイメージ
    • Vertex構造体
      • フィールドに頂点を持つ
      • メソッド
        • フィールド・引数を利用して、ロジックを実行
package main

import "fmt"

type Vertex struct {
	X int
	Y int
}

func (v Vertex) Calc() int {

	return 3*v.X + v.Y
}

func main() {
	v := Vertex{X: 2, Y: 3}
	fmt.Println(v.Calc())
}

ポインターレシーバー

  • レシーバーをポインタにする
    • フィールドを変更する

Methods and pointer indirection

  • ポインタが間接的にメソッドを呼ぶ
    • メソッドを、レシーバーをでリファレンスしないで実行できる
var v Vertex
v.Scale(5)  // OK
p := &v
p.Scale(10) // pはポインタだけどでリファレンスしないで、メソッドを実行できる
// (*p).Scale(10)

Choosing a value or pointer receiver (値レシーバ・ポインタレシーバ)

  • Scaleはフィールドを更新するからポインタレシーバは理解できる
  • Absメソッドに関しては、「コピーを避けるために」、ポインタレシーバで呼ぶ
    • フィールドがバイト以上ならポインタレシーバーの方が良いなどがある
package main

import (
	"fmt"
	"math"
)

type Vertex struct {
	X, Y float64
}

func (v *Vertex) Scale(f float64) {
	v.X = v.X * f
	v.Y = v.Y * f
}

func (v *Vertex) Abs() float64 {
	return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func main() {
	v := &Vertex{3, 4}
	fmt.Printf("Before scaling: %+v, Abs: %v\n", v, v.Abs())
	v.Scale(5)
	fmt.Printf("After scaling: %+v, Abs: %v\n", v, v.Abs())
}

engineer rebornengineer reborn

interfaces

  • インターフェース

    • 「メソッドシグニチャの集まり」
      • 実体ではない
  • interface使い方

    1. interface型で変数を定義
      2. メソッドを定義
      3. 「実務では構造体のフィールドにinterfaceを定義することが多い」
    2. 実体の代入
      4. interfaceのメソッドを持ったレシーバーを
package main

import "fmt"

type Calcer interface {
	Calc() int
}

type Vertex struct {
	X int
	Y int
}

func (v Vertex) Calc() int {

	return 3*v.X + v.Y
}

func main() {
	var v Calcer

	v = Vertex{X: 2, Y: 3}
	fmt.Println(v.Calc())
}

interface with nil ⭐️

  • interfaceの変数に何も代入しない場合は、「nilをレシーバーとして呼び出す」
  • 「実務でnull ポインター」でエラーになる
    • 下記はエラー
      • nil ポインターデリファレンスでエラーになる

panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x491470]

goroutine 1 [running]:
main.main()
	/tmp/sandbox4231968961/prog.go:23 +0x10
package main

import "fmt"

type Calcer interface {
	Calc() int
}

type Vertex struct {
	X int
	Y int
}

func (v Vertex) Calc() int {

	return 3*v.X + v.Y
}

func main() {
	var v Calcer

	// v = Vertex{X: 2, Y: 3}
	fmt.Println(v.Calc())
}

空のinteface {} == any

  • 他の方を代入できる
package main

import "fmt"

func main() {
	var num interface{}

	num = 4

	fmt.Printf("val %d typeof %T ", num, num)
}

Type エイリアス

  • 型アサーション
    • 具体的な値を利用する手段を提供する
package main

import "fmt"

func main() {
	var i interface{} = "hello"

	// fmt.Printf("val %d typeof %T ", num, num)
	// fmt.Printf("val %string typeof %T ", i, i)
	s, ok := i.(string)
	if ok {
		fmt.Println("yahoo", s)
	} else {
		fmt.Println("ng")
	}
}

検証 interface Calcer

  • 実装
    • interface Calcer
      • Calc関数
    • 実態のVertext,HogeがCalc関数を持つ

// You can edit this code!
// Click here and start typing.
package main

import "fmt"

type Calcer interface {
	Calc() int
}

type Vertex struct {
	X int
	Y int
}

type Hoge struct {
	X int
	Y int
}

func (v Vertex) Calc() int {
	return 3*v.X + v.Y
}

func (v Hoge) Calc() int {
	return 3*v.X + v.Y
}

func main() {
	var v Calcer
	v = Vertex{X: 2, Y: 3}

	_, ok := v.(Hoge)

	if ok {
		fmt.Println("OKに入った")
	} else {
		fmt.Println("OKに入ってない")

	}
	fmt.Println("Hello, 世界", v.Calc())

}

型の引き継ぎ

  • 代入をしたり、引数がinterfaceでも typeはしっかり取れる
// You can edit this code!
// Click here and start typing.
package main

import "fmt"

type Calcer interface {
	Calc() int
}

type Vertex struct {
	X int
	Y int
}

type Hoge struct {
	X int
	Y int
}

func (v Vertex) Calc() int {
	return v.X + v.Y
}

func (h Hoge) Calc() int {
	return h.X + h.Y*2
}

func Print(v Calcer) {
	fmt.Printf("type inner %T\n", v)

}

func main() {

	var v Calcer

	v = Vertex{X: 2, Y: 3}

	fmt.Printf("outer %T\n", v)
	Print(v)

}

### 出力
outer main.Vertex
type inner main.Vertex

実際に使うとなると下記みたいな感じっぽい

// エラーとかで 型アサーション使ってるイメージ
// 持っているメソッドは同じだけど、ログの出力の仕方が違うとか

type Calcer interface {
	Calc() int
}

type Vertex struct{ X, Y int }
type Hoge struct{ X, Y int }

func (v Vertex) Calc() int { return v.X + v.Y }
func (h Hoge) Calc() int   { return h.X - h.Y }

func DoSomething(c Calcer) {
	switch v := c.(type) {
	case Vertex:
		fmt.Println("Vertex:", v.Calc())
	case Hoge:
		fmt.Println("Hoge:", v.Calc())
	default:
		fmt.Println("Unknown type")
	}
}

type switches

type Calcer interface {
	Calc() int
}

type Vertex struct{ X, Y int }
type Hoge struct{ X, Y int }

func (v Vertex) Calc() int { return v.X + v.Y }
func (h Hoge) Calc() int   { return h.X - h.Y }

func DoSomething(c Calcer) {
	switch v := c.(type) {
	case Vertex:
		fmt.Println("Vertex:", v.Calc())
	case Hoge:
		fmt.Println("Hoge:", v.Calc())
	default:
		fmt.Println("Unknown type")
	}
}

Stringers

  • fmt.Printlnの引数にはStringerを指定
type Stringer interface {
  String() string
}
  • Type
    • goのパッケージはStringerと同じ構造
func TypeOf(i any) Type {
	return toType(abi.TypeOf(i))
}

type Type interface {
  ... 
  Kind() Kind
}
engineer rebornengineer reborn

ゴルーチン

  • goを実行してる段階でメインスレッドはゴルーチン

サンプル

  • userの出力を並行処理する
    • 出力順番は前後する
    • closeをすると v, ok := <- chのokで検知できる
      • これはselectパターンとrangeパターンがある
    • rangeパターンの方が効率的 【closeするまでgoroutineを待ち受ける】とした場合
### range パターン
package main

import "fmt"

type User struct {
	Name string
}

func main() {
	c := make(chan User)

	go func() {
		for i := 0; i < 10; i++ {
			c <- User{Name: fmt.Sprintf("Name %d", i)}
		}
		close(c)
	}()

    // closeされるまでループし続ける
	for v := range c {
		fmt.Println(v)
	}
}

### select パターン

package main

import "fmt"

type User struct {
	Name string
}

func main() {
	c := make(chan User)

	go func() {
		for i := 0; i < 10; i++ {
			c <- User{Name: fmt.Sprintf("Name %d", i)}
		}
		close(c)
	}()

Loop:
	for {
		select {
		case v, ok := <-c:
			if ok {
				fmt.Println(v)
			} else {
				// チャネルが閉じられたのでループ終了
				break Loop
			}
		}
	}

	fmt.Println("done!")
}

syncを使う場合

  • syncパッケージ
    • Add関数
      • カウント数を追加する
    • Done関数
      • DoneをするとAddで追加カウントを1減らす
    • Wait関数
      • Addしたカウントが0になったら解除される
package main

import (
	"fmt"
	"sync"
	"time"
)

type User struct {
	Name string
}

type RecommentItem struct {
	Name string
}

type Response struct {
	User           *User
	RecommendItems []*RecommentItem
}

func fetchUserData() *User {
	time.Sleep(2 * time.Second) // 擬似的な遅延
	return &User{Name: "User Name"}
}

func fetchRecommendedItems() []*RecommentItem {
	time.Sleep(1 * time.Second) // 擬似的な遅延

	var res []*RecommentItem
	res = append(res, &RecommentItem{Name: "Recommend Item"})
	return res
}

func main() {
	var wg sync.WaitGroup
	var userData *User
	var items []*RecommentItem

	wg.Add(2)

	go func() {
		defer wg.Done()
		userData = fetchUserData()
	}()

	go func() {
		defer wg.Done()
		items = fetchRecommendedItems()
	}()

	wg.Wait()

	fmt.Printf("Fetched: %v\n", userData)
	fmt.Printf("Fetched: %v\n", items)
	res := Response{User: userData, RecommendItems: items}
	fmt.Printf("All done!: response is %+v", res)
}

error group

  • errorグループの挙動
    • 最初のエラーを返す
  • 実務
    • 複数データを取得して全てを取得できないなあエラーを返すみたいな実装はある
package main

import (
	"fmt"
	"time"

	"golang.org/x/sync/errgroup"
)

type User struct {
	Name string
}

type RecommentItem struct {
	Name string
}

type Response struct {
	User           *User
	RecommendItems []*RecommentItem
}

func fetchUserData() (*User, error) {
	time.Sleep(2 * time.Second) // 擬似的な遅延
	return &User{Name: "User Name"}, nil
}

func fetchRecommendedItems() ([]*RecommentItem, error) {
	time.Sleep(1 * time.Second) // 擬似的な遅延

	var res []*RecommentItem
	res = append(res, &RecommentItem{Name: "Recommend Item"})
	return res, nil
}

func main() {
	var userData *User
	var items []*RecommentItem

	g := new(errgroup.Group)

	// ユーザーデータ取得
	g.Go(func() error {
		u, err := fetchUserData()
		if err != nil {
			return err
		}
		userData = u
		return nil
	})

	// レコメンドアイテム取得
	g.Go(func() error {
		i, err := fetchRecommendedItems()
		if err != nil {
			return err
		}
		items = i
		return nil
	})

	// 全てのgoroutineが完了、またはエラーが発生
	if err := g.Wait(); err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("Fetched: %v\n", userData)
	fmt.Printf("Fetched: %v\n", items)
	res := Response{User: userData, RecommendItems: items}
	fmt.Printf("All done!: response is %+v\n", res)
}

  • api echo
package main

import (
	"net/http"
	"time"

	"github.com/labstack/echo/v4"
	"golang.org/x/sync/errgroup"
)

type User struct {
	Name string
}

type RecommendItem struct {
	Name string
}

type Response struct {
	User           *User            `json:"user"`
	RecommendItems []*RecommendItem `json:"recommend_items"`
}

func fetchUserData() (*User, error) {
	time.Sleep(2 * time.Second) // 擬似的な遅延
	return &User{Name: "User Name"}, nil
}

func fetchRecommendedItems() ([]*RecommendItem, error) {
	time.Sleep(1 * time.Second) // 擬似的な遅延
	items := []*RecommendItem{{Name: "Recommend Item"}}
	return items, nil
}

func handler(c echo.Context) error {
	var userData *User
	var items []*RecommendItem

	g := new(errgroup.Group)

	// ユーザーデータ取得
	g.Go(func() error {
		u, err := fetchUserData()
		if err != nil {
			return err
		}
		userData = u
		return nil
	})

	// レコメンドアイテム取得
	g.Go(func() error {
		i, err := fetchRecommendedItems()
		if err != nil {
			return err
		}
		items = i
		return nil
	})

	// 全ての並行処理が終了
	if err := g.Wait(); err != nil {
		// エラーがあれば 400 Bad Request を返却
		return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
	}

	// 正常レスポンス
	res := Response{
		User:           userData,
		RecommendItems: items,
	}
	return c.JSON(http.StatusOK, res)
}

func main() {
	e := echo.New()

	e.GET("/api", handler)

	e.Start(":8080")
}

channel

  • ContextもChannelでできている
  • go routineの同期に使う
  • ⭐️送信と受信の両方が準備されるまで、ブロックされる

channel 最小

  • 入力を非同期で先にやらないとブロックされる
package main

import (
	"fmt"
)

func main() {
	c := make(chan int)

	go func() {
		c <- 1
	}()

	fmt.Println(<-c)

}

channel フロー

  • 定義は同期

  • 送信の実行は非同期

  • 受信は同期

  • 下記の処理

    • 合計処理を並列実行にしてる
    • 処理が独立しているので並列処理を行える
## 自作

package main

import (
	"fmt"
)

// 引数の 加算してる
// 入力する処理
func sum(s []int, c chan int) {
	sum := 0

	for _, v := range s {
		sum += v
	}
	c <- sum
}

func main() {

	c := make(chan int)
	s := []int{1, 2, 3, 3, 4, 3, 2, 2}
	e := []int{4, 2, 3, 3, 4, 3, 2, 2}

	go sum(s, c)
	go sum(e, c)

	// 送信2つは非同期だから、受信2つは待機 して4つが揃う時がある
	x, y := <-c, <-c

	fmt.Println(x, y)

}
  • tutorialのやつ
package main

import "fmt"

// 第一引数のスライスの合計の数字を送信してる
func sum(s []int, c chan int) {
	sum := 0
	for _, v := range s {
		sum += v
	}
	c <- sum // send sum to c
}

func main() {
	s := []int{7, 2, 8, -9, 4, 0}

	c := make(chan int)
	go sum(s[:len(s)/2], c)
	go sum(s[len(s)/2:], c)
    // 受信してる
	x, y := <-c, <-c // receive from c

	fmt.Println(x, y, x+y)
}

バッファーチャネル

  • ブロックしない
    • ⭐️送信するときは受信側がいないと送信できないので、「ブロック」される
package main

import "fmt"

func main() {
	ch := make(chan int, 2)
    // 送信を非同期で
	ch <- 1
	ch <- 2
	fmt.Println(<-ch)
	fmt.Println(<-ch)
}
  • 実務で使われるシーン
    • 処理
      • for w := 1; w <= 3; w++の箇所でworker(ジョブの何かしら処理)を待機状態
      • for j := 1; j <= numJobs; j++でジョブの起動処理
      • for a := 1; a <= numJobs; a++ 結果を受け取る処理
    • ポイント
      • 「バッファ付きチャネル」でも待機は存在する! ブロックはされない
        • for w := 1; w <= 3; w++ではブロックはされずに、待機状態に
        • for j := 1; j <= numJobs; j++でブロックはされずに、送信状態に
      • この処理の意味
        • 逐次処理に見えるけど、go worker(w, jobs, results)にすることで、ジョブの処理を並行処理できるので早くなる。順番は担保され得ないので
        • 「考察」go worker(w, jobs, results)を同じテーブルに対して行いたい場合は、offsetとlimitを利用して実行できるはず、論理上はできるけどバットプラクティス
package main

import (
	"fmt"
	"time"
)

func worker(id int, jobs <-chan int, results chan<- int) {
	for j := range jobs {
		fmt.Printf("worker %d started job %d\n", id, j)
		time.Sleep(time.Second) // 擬似的に処理中を表現
		fmt.Printf("worker %d finished job %d\n", id, j)
		results <- j * 2
	}
}

func main() {
	const numJobs = 5
	jobs := make(chan int, numJobs) // バッファ付きチャネルを利用
	results := make(chan int, numJobs)

	// ワーカーを3つ起動
	for w := 1; w <= 3; w++ {
		go worker(w, jobs, results)
	}

	// ジョブを送信
	for j := 1; j <= numJobs; j++ {
		jobs <- j
	}
	close(jobs)

	// 結果を受信
	for a := 1; a <= numJobs; a++ {
		fmt.Println("result:", <-results)
	}
}

Range and Close

  • channelの理解が必要
    • channelはFIFOでデータを管理 + ステータスがある
    • close されると初期値で受信できる
      • ステータスの確認
        • v, ok := <-ch
package main

import "fmt"

type User struct {
	Name string
}

func main() {
	c := make(chan User)

	go func() {
		for i := 0; i < 10; i++ {
			c <- User{Name: fmt.Sprintf("Name %d", i)}
		}
		close(c)
	}()

    // closeされるまでループし続ける
	for v := range c {
		fmt.Println(v)
	}
}

Select

  • 複数のgo routineを並行処理できる
package main

import "fmt"

type User struct {
	Name string
}

func main() {
	c := make(chan User)

	go func() {
		for i := 0; i < 10; i++ {
			c <- User{Name: fmt.Sprintf("Name %d", i)}
		}
		close(c)
	}()

Loop:
	for {
		select {
		case v, ok := <-c:
			if ok {
				fmt.Println(v)
			} else {
				// チャネルが閉じられたのでループ終了
				break Loop
			}
		}
	}

	fmt.Println("done!")
}


Mutex

https://go-tour-jp.appspot.com/concurrency/9

engineer rebornengineer reborn

go get vs go installの違い

  • go get はコードをdownloadしただけ
    • go get して go run main.go
      • go run main.goは一時的にビルドして、実行してくれている
  • go install はビルドして実行可能な状態らしい
    • cli とかで使う air を利用するときに必要だった
  • Dockerfileを利用する時に
engineer rebornengineer reborn

gorm

目次

  • 構造体の定義
    • [] アソシエーションも含めて
  • データの取得
    • Find
    • Take
  • [] データの作成・更新・削除
  • [] トランザクション
  • エラーハンドリング
    • Errorメソッド
      • Takeは not found
      • Findは配列

タグ

https://gorm.io/ja_JP/docs/serializer.html

  • 下記のシリアライズタグというらしい
type User struct {
    Name        []byte                 `gorm:"serializer:json"`
    Roles       Roles                  `gorm:"serializer:json"`
    Contracts   map[string]interface{} `gorm:"serializer:json"`
    JobInfo     Job                    `gorm:"type:bytes;serializer:gob"`
    CreatedTime int64                  `gorm:"serializer:unixtime;type:time"` // store int as datetime into database
}

規約

https://gorm.io/ja_JP/docs/conventions.html

Take Findの違い

gorm.Model

  • 下記は便利
  • 組み込みでIDからcreatedが作成できる
// gorm.Modelの定義
type Model struct {
  ID        uint           `gorm:"primaryKey"`
  CreatedAt time.Time
  UpdatedAt time.Time
  DeletedAt gorm.DeletedAt `gorm:"index"`
}

Context

https://gorm.io/ja_JP/docs/context.html

  • メモ
    • Timeooutできたり、データを渡したりできる感じみたい
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()

db.WithContext(ctx).Find(&users)

エラーハンドリング

https://gorm.io/ja_JP/docs/error_handling.html

  • 基本
if err := db.Where("name = ?", "jinzhu").First(&user).Error; err != nil {
  // Handle error...
}
  • First, Last, Takeは、レコードがなければ ErrRecordNotFound
  • FIndはレコードがなくても、空配列か?

アソシエーション

// User は複数の CreditCards を持ちます。UserID は外部キーとなります。
type User struct {
  gorm.Model
  CreditCards []CreditCard
}

type CreditCard struct {
  gorm.Model
  Number string
  UserID uint
}
  • belongs to

https://gorm.io/ja_JP/docs/belongs_to.html

外部キー制約

  • constraintタグで OnUpdate, OnDeleteの設定ができるらしい
type User struct {
  gorm.Model
  CreditCards []CreditCard `gorm:"constraint:OnUpdate:CASCADE,OnDelete:SET NULL;"`
}

type CreditCard struct {
  gorm.Model
  Number string
  UserID uint
}
  • 参照のフィールドを上書き
    • 外部キーは主キーを基本に
type User struct {
  gorm.Model
  MemberNumber string
  CreditCards  []CreditCard `gorm:"foreignKey:UserNumber;references:MemberNumber"`
}

type CreditCard struct {
  gorm.Model
  Number     string
  UserNumber string
}
  • has one

返り値

返り値が予約になっている場合
var result *T みたいな事前宣言は不要になる

func FetchSingleRecord[T any](ctx context.Context, db *gorm.DB, buildQuery func(*gorm.DB) *gorm.DB) (result *T) {
	if err := buildQuery(db).Take(&result).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil
		}
		panic(err) // 必要に応じて適切なエラー処理に置き換えてください
	}
	return
}
engineer rebornengineer reborn

validator

  • 仕組み
    • 構造体 && タグの利用
    • validateの初期化
    • バリデーション実行
      • err := validate.Struct(user)
    • validator.ValidationErrors
      • []FieldErrorを格納
        • Tagなどのメソッドを保持している
package main

import (
	"errors"
	"fmt"

	"github.com/go-playground/validator/v10"
)

type User struct {
	FirstName string     `validate:"required"`
	LastName  string     `validate:"required"`
	Age       uint8      `validate:"gte=0,lte=130"` // ここ 0~130才?
	Email     string     `validate:"required,email"`
	Gender    string     `validate:"oneof=male female prefer_not_to"`
	Addresses []*Address `validate:"required,dive,required"`
}

type Address struct {
	Street string `validate:"required"`
	Cipt   string `validate:"required"`
	Planet string `validate:"required"`
	Phone  string `validate:"required"`
}

// 構造体が入る宣言か
var validate *validator.Validate

func main() {

	// validator はpackageから撮ってきてる。package validatorと宣言がある
	validate = validator.New(validator.WithRequiredStructEnabled())

	// fmt.Println("%v", validate)

	addres := &Address{
		Street: "Eavesdown Docks",
		Planet: "Persphon",
		Phone:  "none",
	}

	user := &User{
		FirstName: "Badder",
		LastName:  "Smith",
		Age:       134,
		Gender:    "male",
		Email:     "aaaa@gmail.com",
		Addresses: []*Address{addres},
	}

	err := validate.Struct(user)

	if err != nil {
		var invalidValidationError *validator.InvalidValidationError
		if errors.As(err, &invalidValidationError) {
			fmt.Println(err)
			return
		}

		// FieldErrorが複数
		var validateErrs validator.ValidationErrors
		if errors.As(err, &validateErrs) {
			for _, e := range validateErrs {
				fmt.Println(e.Namespace())
				fmt.Println(e.Field())
				fmt.Println(e.StructNamespace())
				fmt.Println(e.StructField())
				fmt.Println(e.Tag())
				fmt.Println(e.ActualTag())
				fmt.Println(e.Kind())
				fmt.Println(e.Type())
				fmt.Println(e.Value())
				fmt.Println(e.Param())
				fmt.Println()
			}
		}

		// from here you can create your own error messages in whatever language you wish
		return

	}

}

engineer rebornengineer reborn

echo

ミドルウェア入れたい時

  • MiddlrewareFuncを引数に入れられる
    • HandlerFuncを持っている
  • 実装するときは、echo.MiddlewareFuncなどで型の定義
func (g *Group) POST(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
	return g.Add(http.MethodPost, path, h, m...)
}

type MiddlewareFunc func(next HandlerFunc) HandlerFunc

// HandlerFunc defines a function to serve HTTP requests.
type HandlerFunc func(c Context) error

## 実装するとき

func (e *EchoValidator) MiddlewareFunc() echo.MiddlewareFunc {
	return func(next echo.HandlerFunc) echo.HandlerFunc {
		return func(c echo.Context) error {
			// リクエストボディを validateType にバインド(deep copyが必要)
			req := cloneEmpty(e.validateType)
			if err := c.Bind(req); err != nil {
				return echo.NewHTTPError(400, fmt.Sprintf("Failed to bind request: %v", err))
			}

			if err := e.validate.Struct(req); err != nil {
				return echo.NewHTTPError(400, fmt.Sprintf("Validation failed: %v", err))
			}

			// c.Set で次の handler に渡すことも可能
			c.Set("validated", req)

			return next(c)
		}
	}
}
engineer rebornengineer reborn

json

  • json
    • c.JSON(xxx,xxx) とするとクライアントのレスポンスボディは、json

シンプル

  • jsonは []byteで扱うみたい
// You can edit this code!
// Click here and start typing.
package main

import (
	"encoding/json"
	"fmt"
)

type Book struct {
	Title string
}

func main() {
	b := []byte(`{"title": "リーダブルコード"}`)
	var book Book
	if err := json.Unmarshal(b, &book); err != nil {
		fmt.Println(err)
	}

	fmt.Printf("Hello, 世界 %+v", book)

	b2 := Book{Title: "jsonにされた"}

	json, err := json.Marshal(&b2)

	if err == nil {
		fmt.Printf("Hello, 世界 v2 %+v", json)

	}

}

jsonのレスポンス

  • json デコーダーで、レスポンスにあったjsonを構造体に移す

package main

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
)

type TickerResponse struct {
	Last      float64 `json:"last"`
	Bid       float64 `json:"bid"`
	Ask       float64 `json:"ask"`
	High      float64 `json:"high"`
	Low       float64 `json:"low"`
	Volume    float64 `json:"volume"`
	Timestamp int64   `json:"timestamp"`
}

type CoinCheckClient struct{}

func (c *CoinCheckClient) GetCoin(pair string) (*TickerResponse, error) {

	url := fmt.Sprintf("https://coincheck.com/api/ticker?pair=%s", pair)

	resp, err := http.Get(url)
	if err != nil {
		log.Fatal(err)
	}
	defer resp.Body.Close()

	var ticker TickerResponse
	if err := json.NewDecoder(resp.Body).Decode(&ticker); err != nil {
		log.Fatal(err)
	}
	fmt.Printf("ETH/JPY: Last=%.2f, High=%.2f, Low=%.2f\n", ticker.Last, ticker.High, ticker.Low)

	return &ticker, nil

}

func main() {

	client := CoinCheckClient{}

	if res, err := client.GetCoin("eth_jpy"); err != nil {
		panic(err)
	} else {
		fmt.Printf("res%v", res)
	}
}

engineer rebornengineer reborn

time

根本理解

基本 操作

https://qiita.com/tm6093kt/items/0914355bb11bbd93b0e2

package main

import (
	"fmt"
	"time"
)

func main() {
	// ここにコードを追加してください
	// 例: fmt.Println("Hello, World!")

	today := time.Now().UTC().Truncate(24 * time.Hour)

	fmt.Println("今日の日付:", today)
	fmt.Println("今日の日付:", today.Add(-30*24*time.Hour))

}

engineer rebornengineer reborn
  • 他にも

https://chatgpt.com/c/686d36cc-0b50-8004-95c9-46e6a90fde1a

下記補足

  • オプションパターン
    • クロージャーの理解が大事
  • デコレーターパターンvsストラテジーパターン
    • デコレーターパターン
      • 機能の追加と拡張
    • ストラテジー
      • アルゴリズムの切り替え

実装パターン go


Go言語の主な実装パターン一覧

1. Functional Options Pattern (関数型オプションパターン)

####ポイント

    1. クロージャーを利用してる(クロージャーは変数のライフサイクルを引きばす仕組み)
    • s := Serverがライフサイクルを伸ばす構造体
    • クロージャーに巻き込みけど、WIthPortなどは、ラップした関数で少し複雑
    1. NewServerの引数にWIthPortを引数に入れているのはクロージャー関数を展開して渡している
    • 目的: 構造体やクライアントの初期化時に、柔軟かつ拡張可能な方法でオプション設定を提供します。引数の順序に依存せず、必要なオプションだけを指定できます。
    • 特徴:
      • 初期化関数(コンストラクタ)が、設定を変更する関数の可変長引数 (...func(*Options)) を受け取る。
      • これらのオプション関数は、内部で設定用の構造体 (*Options など) を変更する。
    • :
      type Server struct {
          port int
          timeout time.Duration
          // ...
      }
      
      type ServerOption func(*Server)
      
      func WithPort(port int) ServerOption {
          return func(s *Server) {
              s.port = port
          }
      }
      
      func WithTimeout(timeout time.Duration) ServerOption {
          return func(s *Server) {
              s.timeout = timeout
          }
      }
      
      func NewServer(opts ...ServerOption) *Server {
          s := &Server{ // デフォルト値
              port:    8080,
              timeout: 30 * time.Second,
          }
         // optsは`func(*Server) { s.port = 8000 }` と`func(*Server) { s.timeout = 10 * time.Second }` 
          for _, opt := range opts {
              opt(s) // オプション関数を適用して設定を上書き
          }
          return s
      }
      
      // 使用例:
      // WithPortとWithTimeoutはラッパー関数 で中身の無名関数を渡す
      // s := NewServer(WithPort(8000), WithTimeout(10*time.Second))
      
    • 用途: クライアントライブラリ、サーバー設定、複雑なオブジェクトの初期化。AWS SDK V2 や gRPC などで広く採用されています。
クロージャー関数
  • 仕組み

    • newCounter内で変数を定義する
    • returnで 更新する関数を返却し、変数に保存することで再実行可能にする
    • main関数が実行完了することで全てのメモリが解放される
  • クロージャー

    • 「匿名関数」がその関数が定義された外側のスコープの変数を記憶している関数
      • 外部の変数を閉じ込めることで、その変数のライフサイクルを伸ばす
        • 通常、変数を定義した関数を実行したらその変数のメモリは解放される
          • クロージャー関数に変数を引き渡すことで更新を可能にする
            • countを func() int { count++} をcount1の変数に入れて寿命を引き延ばしている
package main

import "fmt"

// newCounter は、呼び出されるたびに値を1つ増やすクロージャを返します。
func newCounter() func() int {
	count := 0 // この変数はクロージャによって「記憶」される

	return func() int {
		count++ // 記憶された count 変数を変更する
		return count
	}
}

func main() {
	// counter1 は新しいクロージャのインスタンス
	counter1 := newCounter()

	fmt.Println("--- counter1 ---")
	fmt.Println(counter1()) // 1
	fmt.Println(counter1()) // 2
	fmt.Println(counter1()) // 3

	// counter2 は別の独立したクロージャのインスタンス
	counter2 := newCounter()

	fmt.Println("\n--- counter2 ---")
	fmt.Println(counter2()) // 1
	fmt.Println(counter2()) // 2

	// counter1 は以前の状態を記憶している
	fmt.Println("\n--- counter1 (再び) ---")
	fmt.Println(counter1()) // 4
}

2. Builder Pattern (ビルダーパターン)

  • 目的: 複雑なオブジェクトを段階的に構築し、その構築プロセスと表現を分離します。特に、多数のオプション引数がある場合や、オブジェクトの検証が必要な場合に有効です。
  • 特徴:
    • オブジェクトのフィールドを設定するためのメソッドチェーンを持つビルダー構造体を作成する。
    • 最後に Build() メソッドなどを呼び出して、目的のオブジェクトを生成する。
  • :
    type Query struct {
        selects []string
        from string
        wheres []string
    }
    
    type QueryBuilder struct {
        query Query
    }
    
    func NewQueryBuilder() *QueryBuilder {
        return &QueryBuilder{}
    }
    
    func (qb *QueryBuilder) Select(cols ...string) *QueryBuilder {
        qb.query.selects = append(qb.query.selects, cols...)
        return qb
    }
    
    func (qb *QueryBuilder) From(table string) *QueryBuilder {
        qb.query.from = table
        return qb
    }
    
    func (qb *QueryBuilder) Where(condition string) *QueryBuilder {
        qb.query.wheres = append(qb.query.wheres, condition)
        return qb
    }
    
    func (qb *QueryBuilder) Build() (Query, error) {
        // 検証ロジックなど
        if qb.query.from == "" {
            return Query{}, fmt.Errorf("FROM clause is required")
        }
        return qb.query, nil
    }
    
    // 使用例:
    // q, err := NewQueryBuilder().
    //     Select("id", "name").
    //     From("users").
    //     Where("age > 20").
    //     Build()
    
  • 用途: SQL クエリビルダー、設定ファイルパーサー、複雑なリクエストオブジェクトの生成。

3. Decorator Pattern (デコレーターパターン)

  • 継承と合成(ラップ)の違い && 最大の利点
    • 継承はどこで使うにしても、継承した機能が必須になってしまうら、削るをしたい場合に難しい
    • 合成は都度追加できるので、便利
    • レコレーターパターンは「合成」
  • 用語
    • コンポーネント(interface)
      • 基盤となるインターフェースで共通で持つべき振る舞い
      • ex)type Printer interface { Print() }
    • コンクリートコンポーネント
      • デコレーションされていない、最も基本的な機能や振る舞いを提供するオブジェクト
        • type MyPrinter struct{}
    • デコレーター(goは明示的な抽象クラスがないの不要)
      • Printerのコードでは存在しない
    • コンクリートデコレーター (Concrete Decorator)
      • 特定の新しい振る舞いや機能を追加
        • ex) DecoratedPrinterのPrintメソッド
  • 本質
    • 継承ではなく、合成を利用して、機能を拡張・追加すること
      • コンパイル時にはなく、動的に組み合わせがあできる
      • コードを変更しないで
package main

import "fmt"

// Printerインタフェースの定義
type Printer interface {
	Print()
}

// プリンターの実装
type MyPrinter struct{}

func (mp MyPrinter) Print() {
	fmt.Println("hogehoge")
}

// Printerを属性として持つ構造体
type DecoratedPrinter struct {
	wrapped Printer
}

func (dp DecoratedPrinter) Print() {
	fmt.Println("start")
	dp.wrapped.Print()
	fmt.Println("end")
}

// Printerインタフェースをラップする
func Decorate(p Printer) Printer {
	return DecoratedPrinter{wrapped: p}
}

func main() {
	printer := Decorate(MyPrinter{})
	printer.Print()
}

参考記事
https://qiita.com/kogamochiduki/items/44a233312b70a1876817

  • 目的: オブジェクトの振る舞いを動的に追加または変更します。元のインターフェースを変更せずに機能を追加できるため、既存のコードを拡張するのに役立ちます。
  • 特徴:
    • 同じインターフェースを実装するラッパー構造体を作成し、元のオブジェクトを内包する。
    • ラッパーのメソッド内で、元のオブジェクトのメソッドを呼び出しつつ、追加のロジック(ログ、認証、キャッシュなど)を実行する。
  • :
    type Greeter interface {
        Greet(name string) string
    }
    
    type SimpleGreeter struct{}
    func (s *SimpleGreeter) Greet(name string) string {
        return "Hello, " + name + "!"
    }
    
    // LoggingDecorator
    type LoggingGreeter struct {
        Greeter Greeter
    }
    
    func (l *LoggingGreeter) Greet(name string) string {
        fmt.Printf("LOG: Greet method called with name: %s\n", name)
        return l.Greeter.Greet(name) // 元のメソッドを呼び出す
    }
    
    // 使用例:
    // simple := &SimpleGreeter{}
    // logged := &LoggingGreeter{Greeter: simple}
    // fmt.Println(logged.Greet("Alice")) // "LOG:..." が出力され、その後 "Hello, Alice!" が返る
    
  • 用途: ロギング、認証、キャッシング、メトリクス収集など、横断的な関心を分離して追加する際。HTTP ミドルウェアもこの一種。

4. Strategy Pattern (ストラテジーパターン)

  • ポイント

    • これは合成ではない
    • NewShoppingCartで ShoppingCartのフィールドをinterfaceにすることで、PayPal クレジットの支払いの内部ロジックを隠蔽できる
      • 返り値などは合わせる
    • 目的: アルゴリズムのファミリーを定義し、それぞれを独立したインターフェースの実装としてカプセル化します。クライアントが実行時に使用するアルゴリズムを選択できるようにします。
    • 特徴:
      • 共通のインターフェースを定義し、異なるアルゴリズムがそのインターフェースを実装する。
      • コンテキストとなる構造体が、このインターフェースのインスタンスを保持し、それを介してアルゴリズムを実行する。
    • :
      type PaymentStrategy interface {
          Pay(amount float64) error
      }
      
      type CreditCardPayment struct {
          cardNumber string
      }
      func (c *CreditCardPayment) Pay(amount float64) error {
          fmt.Printf("Paid %.2f using Credit Card: %s\n", amount, c.cardNumber)
          return nil
      }
      
      type PayPalPayment struct {
          email string
      }
      func (p *PayPalPayment) Pay(amount float64) error {
          fmt.Printf("Paid %.2f using PayPal: %s\n", amount, p.email)
          return nil
      }
      
      type ShoppingCart struct {
          amount float64
          strategy PaymentStrategy
      }
      
      func NewShoppingCart(amount float64) *ShoppingCart {
          return &ShoppingCart{amount: amount}
      }
      
      func (sc *ShoppingCart) SetPaymentStrategy(strategy PaymentStrategy) {
          sc.strategy = strategy
      }
      
      func (sc *ShoppingCart) Checkout() error {
          if sc.strategy == nil {
              return fmt.Errorf("payment strategy not set")
          }
          return sc.strategy.Pay(sc.amount)
      }
      
      // 使用例:
      // cart := NewShoppingCart(100.0)
      // cart.SetPaymentStrategy(&CreditCardPayment{cardNumber: "1234-5678-9012-3456"})
      // cart.Checkout()
      //
      // cart.SetPaymentStrategy(&PayPalPayment{email: "user@example.com"})
      // cart.Checkout()
      
    • 用途: ソートアルゴリズム、データ検証、支払い処理、異なるファイルフォーマットの処理。

5. Factory Pattern (ファクトリーパターン)

  • 目的: オブジェクトの生成ロジックをカプセル化し、クライアントがインスタンス化の詳細を知らずにオブジェクトを作成できるようにします。
  • 特徴:
    • オブジェクト生成のための専用のファクトリ関数やファクトリ構造体を提供する。
    • クライアントはファクトリに要求を渡し、ファクトリが適切なオブジェクトを生成して返す。
  • :
    type Product interface {
        Name() string
    }
    
    type ProductA struct{}
    func (p *ProductA) Name() string { return "Product A" }
    
    type ProductB struct{}
    func (p *ProductB) Name() string { return "Product B" }
    
    // ファクトリ関数
    func CreateProduct(productType string) (Product, error) {
        switch productType {
        case "A":
            return &ProductA{}, nil
        case "B":
            return &ProductB{}, nil
        default:
            return nil, fmt.Errorf("unknown product type: %s", productType)
        }
    }
    
    // 使用例:
    // pA, _ := CreateProduct("A")
    // fmt.Println(pA.Name()) // Product A
    
  • 用途: データベースドライバの選択、異なるタイプのコネクタの生成、設定に基づいて異なるオブジェクトを返す場合。

6. Singleton Pattern (シングルトンパターン)

  • 目的: あるクラスのインスタンスが一つしか存在しないことを保証し、そのインスタンスへのグローバルなアクセスポイントを提供します。
  • 特徴:
    • パッケージレベルのプライベート変数でインスタンスを保持。
    • インスタンスを返す公開関数(通常は GetInstance など)を提供し、初めて呼び出されたときにインスタンスを初期化し、それ以降は既存のインスタンスを返す。
    • 並行処理環境での競合状態を避けるために sync.Once を使用するのがGoでの一般的な方法。
  • :
    import (
        "fmt"
        "sync"
    )
    
    type Logger struct {
        // ...
    }
    
    func (l *Logger) Log(msg string) {
        fmt.Println("LOG:", msg)
    }
    
    var (
        loggerInstance *Logger
        once           sync.Once
    )
    
    func GetLogger() *Logger {
        once.Do(func() {
            loggerInstance = &Logger{}
            fmt.Println("Logger instance created (only once).")
        })
        return loggerInstance
    }
    
    // 使用例:
    // log1 := GetLogger()
    // log1.Log("First message")
    //
    // log2 := GetLogger() // 同じインスタンスが返される
    // log2.Log("Second message")
    
  • 用途: グローバルな設定マネージャー、単一のデータベース接続プール、ロガー。

7. Observer Pattern (オブザーバーパターン)

  • 目的: オブジェクト間の多対一の依存関係を定義し、あるオブジェクトの状態が変化したときに、それに依存するすべてのオブジェクトに自動的に通知して更新されるようにします。
  • 特徴:
    • Subject (発行者): 自身にオブザーバーを登録・解除するメソッドと、オブザーバーに通知するメソッドを持つ。
    • Observer (購読者): Subject から通知を受け取るためのメソッド(例: Update)を持つインターフェースを実装する。
  • : イベントシステムや pub/sub (Publish/Subscribe) モデル。Goではチャンネル (chan) を使うことが多い。
  • 用途: UI イベント処理、株価情報更新、RSS フィード。

8. Middleware Pattern (ミドルウェアパターン)

  • 目的: HTTP リクエスト処理のような一連の処理パイプラインにおいて、共通のロジック(認証、ロギング、エラーハンドリングなど)を再利用可能な形で挿入します。
  • 特徴:
    • HTTP ハンドラや関数を引数に取り、新しいハンドラや関数を返す関数(クロージャ)として実装されることが多い。
    • チェーン状に連結して適用される。
  • : Goの標準ライブラリ net/http のHTTPハンドラでよく使われる。
    type Middleware func(http.Handler) http.Handler
    
    func LoggingMiddleware(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            start := time.Now()
            next.ServeHTTP(w, r)
            fmt.Printf("Request: %s %s took %v\n", r.Method, r.URL.Path, time.Since(start))
        })
    }
    
    // 使用例:
    // http.Handle("/", LoggingMiddleware(http.HandlerFunc(myHandler)))
    
  • 用途: Web アプリケーションの認証、ロギング、圧縮、キャッシュ、レート制限。

これらのパターンは、Go言語の設計哲学(シンプルさ、明示性、コンポジションの重視)に合わせて調整されて使われることが多いです。インターフェースや構造体の埋め込み、関数型プログラミングの要素(高階関数、クロージャ)などがパターンの実装に貢献します。

engineer rebornengineer reborn

csv

  • ポイント
    • openでファイルを読み込む
    • Readメソッドで []stringで1行ずつ返却を取得できる
      • インデックスで指定をすることで、フィールドを取得できる
package main

import (
	"encoding/csv"
	"fmt"
	"os"
)

func main() {

	file, err := os.Open("test.csv")

	if err != nil {
		panic(err)
	}
	defer file.Close()

	r := csv.NewReader(file)

	rec, err := r.Read()

	if err != nil {
		panic(err)
	}

	fmt.Println(rec)

	rec2, err2 := r.Read()

	if err2 != nil {
		panic(err2)
	}
	fmt.Printf("data %+v %T\n", rec2, rec2)
	fmt.Printf("data index 0 %+v\n", rec2[0])
	fmt.Printf("data index 1%+v\n", rec2[1])

}

engineer rebornengineer reborn

errors Isと errors As

  • Is
    • 同じErrorか
  • As
    • 代入できるか

実際に使う場合

  • エラーを複数のintefaceに分ける
    • AppError interface
      • appError struct 具体のエラー
    • ValidateError interface
      • validateError struct 具体のエラー
  • エラー生成
    • appError{}
    • validateError{}
  • 判定
// nilで初期化する必要があるらしい
var ae AppError
var ve ValidateError

a := appError{}
v  := valudateError{}

if errors.As(a, &ae) {
  // アプリケーションエアー
}

if errors.As(v, &ve) {
  // バリデーションケーションエアー
}

// もっと実践的
switch {
case errors.As(err, &ve):
    fmt.Println("ValidateError:", ve.FieldErrors())
case errors.As(err, &ae):
    fmt.Println("AppError:", ae.Code())
default:
    fmt.Println("unknown:", err)
}

errors Is

  • Unwrapがある必要がある
  • e.Err
package main

import (
	"errors"
	"fmt"
)

var ErrNotFound = errors.New("not found")
var ErrInternal = errors.New("internal")

type AppError struct {
	Err  error
	Info string
}

func (e *AppError) Error() string {
	return fmt.Sprintf("%s info: %s", e.Err.Error(), e.Info)
}

func (e *AppError) Unwrap() error {
	return e.Err
}

type Error struct {
	Code string
	Msg  string
}

func (e *Error) Error() string {
	return fmt.Sprintf("code: %s msg: %s", e.Code, e.Msg)
}

func main() {
	a := &AppError{Err: &Error{Code: "aaa", Msg: "aaa"}, Info: "補足"}

	if errors.Is(a, ErrInternal) {
		fmt.Println("Hello, 世界")

	} else {
		fmt.Println("not")
	}
}

errors.As

  • 第一引数にエラー(具体), 第二引数に interfacaseのnilポインタを渡す必要がある
package main

import (
	"errors"
	"fmt"
)

type CustomerErrorA interface {
	Error() string
	Hoge() string
}

type customerErrorA struct {
	Code string
	Msg  string
}

func (e *customerErrorA) Error() string {
	return fmt.Sprintf("code: %s msg: %s", e.Code, e.Msg)
}

func (e *customerErrorA) Hoge() string {
	return fmt.Sprintf("hoge code: %s msg: %s", e.Code, e.Msg)
}

type CustomerErrorB interface {
	Error() string
	Hoge() string
}

type customerErrorB struct {
	Code string
	Msg  string
}

func (e *customerErrorB) Error() string {
	return fmt.Sprintf("code: %s msg: %s", e.Code, e.Msg)
}

func (e *customerErrorB) Hoge() string {
	return fmt.Sprintf("hoge code: %s msg: %s", e.Code, e.Msg)
}

type CustomerErrorC interface {
	Error() string
	Ahoo() string
}

func main() {

	b := &customerErrorB{Code: "code", Msg: "msg"}

	var ca CustomerErrorA

	if errors.As(b, &ca) {
		fmt.Println("ok")
	} else {
		fmt.Println("ng")
	}

}

サンプル echo errors

  • constで not_foundなどを定義
  • HTTPStatus
    • Codeと http status codeをマッピング
  • ErrorがApp
package apperr

import (
	"errors"
	"fmt"
	"net/http"
)

type Code string

const (
	CodeNotFound        Code = "not_found"
	CodeInvalidArgument Code = "invalid_argument"
	CodeUnauthenticated Code = "unauthenticated"
	CodePermission      Code = "permission_denied"
	CodeInternal        Code = "internal"
)

func (c Code) HTTPStatus() int {
	switch c {
	case CodeNotFound:
		return http.StatusNotFound
	case CodeInvalidArgument:
		return http.StatusBadRequest
	case CodeUnauthenticated:
		return http.StatusUnauthorized
	case CodePermission:
		return http.StatusForbidden
	default:
		return http.StatusInternalServerError
	}
}

var (
	ErrNotFound        = errors.New(string(CodeNotFound))
	ErrInvalidArgument = errors.New(string(CodeInvalidArgument))
	ErrUnauthenticated = errors.New(string(CodeUnauthenticated))
	ErrPermission      = errors.New(string(CodePermission))
	ErrInternal        = errors.New(string(CodeInternal))
)

// ===== AppError インターフェース =====
type AppError interface {
	error
	AppCode() Code
	HTTPStatus() int
}

// ===== 実装本体 =====
type Error struct {
	Code Code
	Msg  string
	err  error // cause/sentinel
}

func (e *Error) Error() string {
	if e.Msg != "" {
		return fmt.Sprintf("%s: %s", e.Code, e.Msg)
	}
	return string(e.Code)
}
func (e *Error) Unwrap() error   { return e.err }
func (e *Error) AppCode() Code   { return e.Code }
func (e *Error) HTTPStatus() int { return e.Code.HTTPStatus() }

func New(code Code, msg string) *Error                   { return &Error{Code: code, Msg: msg} }
func Wrap(code Code, msg string, cause error) *Error     { return &Error{Code: code, Msg: msg, err: cause} }

// ===== バリデーション系 =====
type ValidationError struct {
	Field   string
	Message string
}
func (v *ValidationError) Error() string { return fmt.Sprintf("%s: %s", v.Field, v.Message) }

type ValidationErrors struct {
	Errors []*ValidationError
}
func (ve *ValidationErrors) Error() string { return "validation failed" }

  • サービス層
// 見つからない
return apperr.Wrap(apperr.CodeNotFound, "store not found", apperr.ErrNotFound)

// バリデーション
ve := &apperr.ValidationErrors{
  Errors: []*apperr.ValidationError{
    {Field: "email", Message: "invalid format"},
    {Field: "age", Message: "must be >= 18"},
  },
}
  • errorhandler
package handler

import (
	"errors"
	"net/http"

	"your/module/apperr" // ← 実パスに合わせて修正
	"github.com/labstack/echo/v4"
	"github.com/rs/zerolog/log"
)

type errorResponse struct {
	Code    string            `json:"code"`
	Message string            `json:"message"`
	Detail  map[string]string `json:"detail,omitempty"`
}

func ErrorHandler(err error, c echo.Context) {
	// 既定値(予期しないエラー)
	status := http.StatusInternalServerError
	resp := errorResponse{
		Code:    string(apperr.CodeInternal),
		Message: http.StatusText(status),
	}

	var (
		app apperr.AppError
		ves *apperr.ValidationErrors
	)

	switch {
	// 1) アプリ共通エラー(HTTP ステータスやアプリコードをここで決定)
	case errors.As(err, &app):
		status = app.HTTPStatus()
		resp.Code = string(app.AppCode())
		resp.Message = app.Error()

		// Validation の詳細が含まれていれば詰める
		if errors.As(err, &ves) && len(ves.Errors) > 0 {
			resp.Detail = make(map[string]string, len(ves.Errors))
			for _, v := range ves.Errors {
				resp.Detail[v.Field] = v.Message
			}
		}

		// 代表的センチネルごとに追加ログやマスクを入れたい場合
		switch {
		case errors.Is(err, apperr.ErrUnauthenticated):
			// 例: 追加タグ/監査ログ
		case errors.Is(err, apperr.ErrPermission):
			// 例: 監査用に principal を付けるなど
		case errors.Is(err, apperr.ErrNotFound):
			// 例: 詳細をマスク
		}

		log.Error().Stack().Err(err).
			Int("status", status).
			Str("code", resp.Code).
			Msg("request failed")

	// 2) AppError ではないが ValidationErrors が投げられた(想定外ルートの保険)
	case errors.As(err, &ves):
		status = http.StatusBadRequest
		resp.Code = string(apperr.CodeInvalidArgument)
		resp.Message = "invalid request"
		if len(ves.Errors) > 0 {
			resp.Detail = make(map[string]string, len(ves.Errors))
			for _, v := range ves.Errors {
				resp.Detail[v.Field] = v.Message
			}
		}
		log.Error().Stack().Err(err).Int("status", status).Str("code", resp.Code).Msg("validation failed")

	// 3) それ以外(本当に想定外)
	default:
		log.Error().Stack().Err(err).Int("status", status).Str("code", resp.Code).Msg("unexpected error")
	}

	if !c.Response().Committed {
		_ = c.JSON(status, resp)
	}
}


engineer rebornengineer reborn

データ構造

  • データ構造
    • ゼロ値がnil以外
      • string・int
    • ゼロ値がnil
      • スライス・map・chan・struct
  • スライス・map・interfaceの未初期化はnil
  • nilが使われるのは基本的に返り値のとき

スライス

  • 未初期化 nil
var hoge []int
fmt.Println(hoge == nil) // true
fmt.Println(hoge)        // []

empty := []int{}
fmt.Println(empty == nil) // false
fmt.Println(empty)        // []
  • スライス(chan チャネル)は3つの要素がある
    • ポインタ
      • 未初期化 []int
        • nilなので参照してない
      • 初期化 []int{}
        • 0長配列を指してる
    • 長さ: 0 <= 長さなどは 0で保持してる
    • 容量: 0
  • map
    • ポインタ
    • 長さはあるけど、容量はない

返り値指定

func loadConfig(path string) map[string]string {
    if !fileExists(path) {
        return nil // ← 設定ファイルが無い
    }
    // 実際に読み込んで map に詰める
}

conf := loadConfig("config.json")
if conf == nil {
    fmt.Println("設定ファイルが存在しない")
} else {
    fmt.Println("ポート番号:", conf["port"])
}

struct・interface と nilや初期値 => ここむずい

  • typeで定義をすることが多かったけど stringなどと同じか
  • var hoge string
  • var hoge struct

ゼロ値

- フィールド毎にゼロ値を持つ
- 長さなどは持たない
type User struct {
    ID   int
    Name string
    Tags []string
}

func main() {
    var u User // ← ゼロ値で初期化
    fmt.Printf("%#v\n", u)
}
  • 名前有無で全く違う
type User struct {
    ID   int
    Name string
}

var a User        // = User{ID:0, Name:""}
b := User{}       // 同じくゼロ値

c := struct{}{}   // フィールド無しの struct(空 struct)
package main

import "fmt"

func main() {
	var a struct{}

	fmt.Println("Hello, 世界", a)
}

interface

  • type Hoge interface{} とも使うし interface{} anyとも使う
### 名前なし
var x interface{}
x = 42
x = "hello"
x = []int{1, 2, 3}

### 名前つき
type Any interface{} // これも「なんでも入る型」

var x Any = "hoge"
fmt.Println(x)