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

目次
- 名前付き返り値
- 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
}

package
-
Goのプログラムはパッケージで構成される
-
プログラムは「main」から開始される
-
fmtなどは importする
exported Names
- 参照できるパッケージは、大文字から始まる
import "math"
fmt.Println(math.Pi)

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 に引数を指定しないでも返却できる

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" => 変数の定義とデータの「ゼロ値」で初期化
- var 変数名 データ型
データ型
- 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}]

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

ポインタ
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] // 代入か

メソッド
- レシーバー
- クラスのイメージ
- Vertex構造体
- フィールドに頂点を持つ
- メソッド
- フィールド・引数を利用して、ロジックを実行
- 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())
}

interfaces
-
インターフェース
- 「メソッドシグニチャの集まり」
- 実体ではない
- 「メソッドシグニチャの集まり」
-
interface使い方
- interface型で変数を定義
2. メソッドを定義
3. 「実務では構造体のフィールドにinterfaceを定義することが多い」 - 実体の代入
4. interfaceのメソッドを持ったレシーバーを
- 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 ポインター」でエラーになる
- 下記はエラー
-
- 下記はエラー
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関数を持つ
- interface Calcer
// 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
}

ゴルーチン
- 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になったら解除される
- Add関数
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

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

gorm
目次
- 構造体の定義
- [] アソシエーションも含めて
- データの取得
- Find
- Take
- [] データの作成・更新・削除
- [] トランザクション
- エラーハンドリング
- Errorメソッド
- Takeは not found
- Findは配列
- Errorメソッド
タグ
- 下記のシリアライズタグというらしい
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
}
規約
Take Findの違い
- Takeは単一レコード
- レコードがないとnot found になる
- https://gorm.io/ja_JP/docs/query.html
- 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
- メモ
- Timeooutできたり、データを渡したりできる感じみたい
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
db.WithContext(ctx).Find(&users)
エラーハンドリング
- 基本
if err := db.Where("name = ?", "jinzhu").First(&user).Error; err != nil {
// Handle error...
}
- First, Last, Takeは、レコードがなければ ErrRecordNotFound
- FIndはレコードがなくても、空配列か?
アソシエーション
-
通常gormはモデルの主キーをリレーションの外部キーの値と使用する
-
has many
- https://gorm.io/ja_JP/docs/has_many.html
- UserIDで紐付けてそう
// User は複数の CreditCards を持ちます。UserID は外部キーとなります。
type User struct {
gorm.Model
CreditCards []CreditCard
}
type CreditCard struct {
gorm.Model
Number string
UserID uint
}
- belongs to
外部キー制約
- 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
}

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

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)
}
}
}

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)
}
}

time
根本理解
- https://zenn.dev/hsaki/articles/go-time-cheatsheet
- UTCと日本時間で対比して書かれている
基本 操作
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))
}

- 他にも
下記補足
- オプションパターン
- クロージャーの理解が大事
- デコレーターパターンvsストラテジーパターン
- デコレーターパターン
- 機能の追加と拡張
- ストラテジー
- アルゴリズムの切り替え
- デコレーターパターン
実装パターン go
Go言語の主な実装パターン一覧
1. Functional Options Pattern (関数型オプションパターン)
####ポイント
-
- クロージャーを利用してる(クロージャーは変数のライフサイクルを引きばす仕組み)
- s := Serverがライフサイクルを伸ばす構造体
- クロージャーに巻き込みけど、WIthPortなどは、ラップした関数で少し複雑
-
- 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 などで広く採用されています。
- NewServerの引数にWIthPortを引数に入れているのは
クロージャー関数
-
仕組み
- 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メソッド
- 特定の新しい振る舞いや機能を追加
- コンポーネント(interface)
- 本質
- 継承ではなく、合成を利用して、機能を拡張・追加すること
- コンパイル時にはなく、動的に組み合わせがあできる
- コードを変更しないで
- 継承ではなく、合成を利用して、機能を拡張・追加すること
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()
}
参考記事
- 目的: オブジェクトの振る舞いを動的に追加または変更します。元のインターフェースを変更せずに機能を追加できるため、既存のコードを拡張するのに役立ちます。
-
特徴:
- 同じインターフェースを実装するラッパー構造体を作成し、元のオブジェクトを内包する。
- ラッパーのメソッド内で、元のオブジェクトのメソッドを呼び出しつつ、追加のロジック(ログ、認証、キャッシュなど)を実行する。
-
例:
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言語の設計哲学(シンプルさ、明示性、コンポジションの重視)に合わせて調整されて使われることが多いです。インターフェースや構造体の埋め込み、関数型プログラミングの要素(高階関数、クロージャ)などがパターンの実装に貢献します。

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])
}

errors Isと errors As
- Is
- 同じErrorか
- As
- 代入できるか
実際に使う場合
- エラーを複数のintefaceに分ける
- AppError interface
- appError struct 具体のエラー
- ValidateError interface
- validateError struct 具体のエラー
- AppError interface
- エラー生成
- 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)
}
}

データ構造
- データ構造
- ゼロ値がnil以外
- string・int
- ゼロ値がnil
- スライス・map・chan・struct
- ゼロ値がnil以外
- スライス・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)