初めてのGo言語
第3章 合成型
配列
配列の初期化は以下のようにする。
x := [3]int{10, 20, 30} //配列の長さを指定する方法
y := [...]int{10, 20, 30} //配列リテラルを使う方法
fmt.Println(x == y) //true
7章 メソッド
### メソッドの定義
メソッドは以下のように定義する
// 型Personを定義
type Person struct {
LastName string
FirstName string
Age int
}
// Stringメソッドを定義
func (p Person) String() string {
return fmt.Sprintf("%s %s: 年齢%d歳", p.LastName, p.firstName, p.Age)
}
型アサーション
type MyInt int
func main() {
var i any
var mine MyInt = 20
i = mine
i2 := i.(MyInt) // iをMyInt型と仮定して、その値(20)を取得
fmt.Println(i2) // 20
}
ポインタ型レシーバと値型レシーバの使い分け
- メソッドがレシーバを変更する・・・ ポインタレシーバ
- メソッドがnilを扱う必要がある・・・ポインタレシーバ
- メソッドがレシーバを変更しない・・・値レシーバ
第8章 エラー処理
8.5 エラーのラップ
エラーのラップとは
エラーのラップとは、エラーを保ったまま付加的な情報を追加すること。例として、エラーにコンテキストを付け加えることがある。
ラップ方法
以下のように、fmt.Errorf
を用いる。
fmt.Errorf("エラー内容: %w", err)
アンラップ方法
errors.Unwrap
を用いる
// オリジナルのエラーを作成
originalError := errors.New("original error")
// オリジナルのエラーをラップ
wrappedError := fmt.Errorf("an error occurred: %w", originalError)
// ラップされたエラーを出力
fmt.Println(wrappedError)
// ラップされたエラーをアンラップしてオリジナルのエラーを取り出す
unwrappedError := errors.Unwrap(wrappedError)
// アンラップしたエラーを出力
fmt.Println(unwrappedError)
Is
とAs
8.6 -
Is
errors.Is
は==
を使うと、ラップされた各エラーと指定されたエラーを比較できる。
func fileChecker(name string) error {
f, err := os.Open(name)
if err != nil {
return fmt.Errorf("in fileChecker: %w", err)
}
f.Close()
return nil
}
func main() {
// ラップされたエラー
err := fileChecker("not_here.txt")
if err != nil {
// true
if errors.Is(err, os.ErrNotExist) {
fmt.Println("File not found")
}
}
}
ゴルーチン
用語
- プロセス ・・・具体的なプログラムがコンピュータのOSによって実行されているもの
- スレッド・・・実行の単位。OSから決められた時間が与えられて実行される
チャネル
作り方
ゴルーチンでは情報のやり取りにチャネルを使う。作り方は以下の通り。
ch := make(chan int) // make(chan 型)
読み込み、書き込み、バッファリング
チャネルとやり取りする際には「<-」を使う。チャネルからの読み込みにはチャネル変数の左側に<-を書き、チャネルに書き込むにはチャネル変数の右側に<-を書く
a := <-ch //チャンネルからの読み込み。チャンネル変数chから値を読み込み、aに代入する
ch <- b //チャンネルへの書き込み。bの値をチャンネル変数の右側に<-を書く
デフォルトでは、チャネルはバッファリングされない。オープンされてバッファリングされていないチャネルへの書き込みが行われるとき、書き込み側のゴルーチンは同じチャネルから他のゴルーチンが読み込みを行うまでポーズする。つまり、読み込みが終わるまで書き込みされない。
同様に、オープンされバッファリングされていないチャネルからの読み込みを行っているゴルーチンは同じチャネルへ他のゴルーチンが書き込みを行うまで停止する。
バッファリングされるチャネルの生成は以下のようにする。
ch := make(chan int, 10) // バッファのキャパシティは10
for-range とチャネル
以下のようにfor-range
を使ってチャネルからの読み込みができる。
for v := range ch { // chがクローズされるまでループ
fmt.Println(v)
}
チャネルのクローズ
close(ch)
バックプレッシャ
特定のコンポーネントの仕事量を制限して、システム全体が効率よく動作するよう、バッファ付きのチャネルとselect
文を使って同時リクエストの数を制限する。
package main
import (
"fmt"
"time"
)
func main() {
taskCh := make(chan int, 5) // キャパシティを5に設定
doneCh := make(chan bool)
// タスク生成
go func() {
for i := 0; i < 4; i++ {
select {
case taskCh <- i:
fmt.Println("タスクを生成します:", i)
case <-doneCh:
return
}
time.Sleep(1 * time.Second) // タスク生成に時間がかかると仮定
}
close(taskCh)
}()
// タスク処理
go func() {
for task := range taskCh {
fmt.Println("タスクを処理します:", task)
time.Sleep(2 * time.Second) // タスクの処理に時間がかかると仮定
}
doneCh <- true
}()
<-doneCh
}
上のコードでは、taskCh
のキャパシティが満たされると新たなタスクの送信がブロックされバックプレッシャとなる。
WaitGroup
の使い方
-
Add
・・・終了を待つゴルーチン数のカウンタを指定した数だけ増やす -
Done
・・・カウンタをデクリメントする。処理が終了したときに呼ばれる -
Wait
・・・カウンタが0になるまで、それを含むゴルーチンをポーズする
以下具体例。
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
wg.Add(3)
go func() {
defer wg.Done() // ゴルーチンがパニックになっても実行されるようdeferをつける
fmt.Println("Goroutine 1")
}()
go func() {
defer wg.Done()
fmt.Println("Goroutine 2")
}()
go func() {
defer wg.Done()
fmt.Println("Goroutine 3")
}()
wg.Wait()
fmt.Println("All Goroutines Finished")
}
sync.Once
の使い方
package main
import (
"fmt"
"sync"
)
var once sync.Once // sync.Onceのインスタンスの宣言は通常関数外で行う
func doOnce() {
fmt.Println("一度だけ実行")
}
func main() {
for i := 0; i < 5; i++ {
once.Do(doOnce)
}
}
// 出力
// 一度だけ実行
Jsonの取り扱い
Goのデータ型 -> Json (アンマーシャリング)
package main
import (
"encoding/json"
"fmt"
)
var f = struct {
Name string // キー名は必ず大文字から!
Age int
}{}
func main() {
// 第2引数には必ずfのポインタを渡す!
err := json.Unmarshal([]byte(`{"Name":"John", "Age":30}`), &f)
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("%+v\n", f)
}
Json -> Goのデータ型 (マーシャリング)
以下Jsonのサンプル。
{
"users": [
{
"id": 1,
"name": "山田 太郎",
"email": "taro.yamada@example.com",
"roles": ["admin", "user"]
},
{
"id": 2,
"name": "佐藤 花子",
"email": "hanako.sato@example.com",
"roles": ["user"]
}
]
}
以下、JsonをGoのデータ型に変更するコード。
import (
"encoding/json"
"fmt"
"io"
"os"
)
// 型の次に`json:タグ名`を指定する
// 各フィールドのキーは大文字から!
type User struct {
ID int `json:"id"`
Name string `json:name`
Email string `json:email`
Roles []string `json:roles`
}
type Users struct {
Users []User `json:users`
}
func main() {
jsonFile, err := os.Open("test.json")
if err != nil {
fmt.Println(err)
}
fmt.Println("Jsonファイルを正常に開きました")
defer jsonFile.Close()
byteFile, _ := io.ReadAll(jsonFile)
var users Users
json.Unmarshal(byteFile, &users)
for i := 0; i < len(users.Users); i++ {
fmt.Println("User ID: ", users.Users[i].ID)
fmt.Println("User Name: ", users.Users[i].Name)
fmt.Println("User Email: ", users.Users[i].Email)
fmt.Println("User Roles: ", users.Users[i].Roles)
}
}
もっと効率よくJSONを読み書きする方法
以下のようにjson.Encode
, json.Decode
を使う
import (
"encoding/json"
"fmt"
"os"
)
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
}
func main() {
dataToFile := Person{
Name: "フレッド",
Age: 20,
}
tmpFile, err := os.CreateTemp(os.TempDir(), "sample-")
defer os.Remove(tmpFile.Name())
err = json.NewEncoder(tmpFile).Encode(dataToFile)
if err != nil {
panic(err)
}
err = tmpFile.Close()
if err != nil {
panic(err)
}
tmpFile2, err := os.Open(tmpFile.Name())
if err != nil {
panic(err)
}
var dataFromFile Person
err = json.NewDecoder(tmpFile2).Decode(&dataFromFile)
if err != nil {
panic(err)
}
err = tmpFile2.Close()
if err != nil {
panic(err)
}
fmt.Println("読み込んだデータ")
fmt.Printf("%+v\n", dataFromFile)
}
テストコード
テストファイル名
テストコードを書くにはファイル名を_test.go
で終わるようにする。foo.go
というファイルを作った場合はfoo_test.go
とする。
テストコード
// adder.go
func addNumbers(x, y int) int {
return x + y
}
// adder_test.go
func Test_addnumbers(t, *testing.T) {
result := addNumbers(2, 3)
if result != 5 {
t.Error("結果が違う: 期待する結果 5, 得られた結果 result")
}
}
テスト前の設定の適用と解除
テストを実行する前に特定の状態を設定して置き、テスト終了後にそれを解除する場合は以下のようにする。
package adder
import (
"testing"
"fmt"
"time"
"os"
)
var testTime time.Time
func TestMain(m *testing.M) {
fmt.Println("テストの準備")
testTime = time.Now()
exitVal := m.Run()
fmt.Println("テストの後片付け")
os.Exit(exitVal)
}
func TestFirst(t *testing.T) {
fmt.Println("TestFirstではMainTestで設定されたものを使う", testTime)
}
func TestSecond(t *testing.T) {
fmt.Println("TestSecondでもMainTestで設定されたものを使う", testTime)
}