Go言語チートシート

# 概要

goを書いていてよく使う割によく忘れる処理まとめ
思い出したら順次書き足します

実際のファイルはGitHubにまとめてあります。
こちらに書き足していって、ひとまとまりになったらこちらに書き足します

https://github.com/nc30/golang_examples


## Context

https://pkg.go.dev/context

コンテクストは環境やスコープ、タイムアウトなどをツリー式に管理する構造体。
goを書く上で非常に重要な概念です。

~~~go
import (
    "context"
)

ctx := context.Background()

ctx, cancel := context.WithCancel(ctx)
defer cancel()

~~~

### store
~~~go
package main

import "context"
import "log"

func main() {
	ctx := context.Background()
	ctx = context.WithValue(ctx, "silly", "work")

	value := ctx.Value("silly")
	// 返り値はinterface{}なので値のキャストが必要
	str, ok := value.(string)
	if !ok {
		log.Println("value not found")
	}

	log.Printf("value found: '%s'", str)

	// 上の省略形がこれ
	str, ok = ctx.Value("silly").(string)
	log.Printf("re value found: '%s'", str)
}
~~~

### タイムアウト処理

~~~go
package main

import (
	"context"
	"time"
	"log"
)

func main(){
	ctx := context.Background()
	ctx, cancel := context.WithTimeout(ctx, time.Second * 1)
	defer cancel()

	go func(){
		// too many time function
		time.Sleep(time.Second * 10)
	}()

	<-ctx.Done()

	log.Println("done")
}
~~~

## 環境変数

参照する変数が少数なら普通に`os.Getenv()`を使えばいい。

~~~go
import "os"

debug := os.Getenv("DEBUG")
~~~

DB設定のように、デフォルト値や必須値が欲しい場合はhttps://github.com/caarlos0/env/ を使うと便利

~~~go
package main

import (
	"log"

	"github.com/caarlos0/env/v6"
)

type DatabaseConfig struct {
	UserName string `env:"DB_USER,required"`
	Password string `env:"DB_PASSWORD,required"`
	Host     string `env:"DB_HOST" envDefault:"localhost"`
	Port     string `env:"DB_PORT" envDefault:"3306"`
	DBName   string `env:"DB_NAME"`
}

func main() {
	cfg := new(DatabaseConfig)

	if err := env.Parse(cfg); err != nil {
		log.Printf("%+v\n", err)
	}

	log.Printf("%+v\n", cfg)
}
~~~

## JSON

~~~go
package main

import "encoding/json"

type Valiable struct {
	Key   string `json:"key"`
	Value string `json:"value"`
}

func load(obj []byte) (*Valiable, error) {
	valiable := new(Valiable)
	err := json.Unmarshal(obj, valiable)

	return valiable, err
}

func dump(valiable *Valiable) ([]byte, error) {
	return json.Marshal(valiable)
}

func main() {
	object := []byte(`{"key": "egg", "value": "spam"}`)

	// to struct
	valiable, _ := load(object)

	// to []byte
	obj, _ := dump(valiable)

	_ = obj
}

~~~

## io

### ファイル読み出し
~~~go
package main

import (
	"os"
	"log"
	"io/ioutil"
)

func main() {
	file, err := os.Open("test.bin")
	if err != nil {
		// no such file やら permission errorやら
		// このハンドリングを省略するとロクなことがない
		log.Fatal(err)
	}
	// このタイミングでクローズのdeferを書いておく
	defer file.Close()

	// すべてを[]byteで読み出す荒業
	b, err := ioutil.ReadAll(file)
	if err != nil {
		log.Fatal(err)
	}
	log.Println(b)


	/// open からReadallまでの流れを全部やってくれるすごいやつ
	content, err := ioutil.ReadFile("test.bin")
	if err != nil {
		log.Fatal(err)
	}
	log.Println(content)
}
~~~

## CSV

### Read

~~~go
/*
	document: https://pkg.go.dev/encoding/csv
*/
package main

import (
	"log"
	"io/ioutil"

	"encoding/csv"
	"io"
	"os"
)

var csvBody = []byte(`ID,名前,ふりがな
1,松本清張,まつもとせいちょう
2,高野和明,たかのかずあき
3,"桜庭 一樹","さくらだ
かずき"
4,貴志祐介,きしゆうすけ
`)

var f *os.File
var err error

// 実験環境構築
func init() {
	defer func() {
		if recover() != nil && f != nil { os.Remove(f.Name()) }
	}()
	if f, err = ioutil.TempFile(os.TempDir(), ""); err != nil {panic(err) }
	defer f.Close()
	if _, err = f.Write(csvBody); err != nil { panic(err) }
}

func main() {
	defer os.Remove(f.Name())


	file, err := os.Open(f.Name())
	if err != nil {
		panic(err)
	}
	reader := csv.NewReader(file)

	var record []string

	for {
		record, err = reader.Read()
		if err == io.EOF {
			log.Println("EOFの例外処理を入れないと無限ループになる")
			log.Println(record)
			log.Println("EOFの時はレコードは空")
			break
		}
		if err != nil {panic(err) }

		log.Println(record)
	}

	record, err = reader.Read()
	if err != nil {
		if err == io.EOF {
			log.Println("終了まで読み出してなおRead()をするとio.EOFが返ってくる")
			log.Println(record)
			log.Println("recordは空")
		}
	}


	file.Seek(0, os.SEEK_SET)
	record, err = reader.Read()
	log.Println(record)
	log.Println(err)
	log.Println("読み込み対象がio.ReadSeekerならseekして任意の場所から読み直せる")


	/*
	2021/11/11 14:21:36 [ID 名前 ふりがな]
	2021/11/11 14:21:36 [1 松本清張 まつもとせいちょう]
	2021/11/11 14:21:36 [2 高野和明 たかのかずあき]
	2021/11/11 14:21:36 [3 桜庭 一樹 さくらだ
	かずき]
	2021/11/11 14:21:36 [4 貴志祐介 きしゆうすけ]
	2021/11/11 14:21:36 EOFの例外処理を入れないと無限ループになる
	2021/11/11 14:21:36 []
	2021/11/11 14:21:36 EOFの時はレコードは空
	2021/11/11 14:21:36 終了まで読み出してなおRead()をするとio.EOFが返ってくる
	2021/11/11 14:21:36 []
	2021/11/11 14:21:36 recordは空
	2021/11/11 14:21:36 [ID 名前 ふりがな]
	2021/11/11 14:21:36 <nil>
	2021/11/11 14:21:36 読み込み対象がio.ReadSeekerならseekして任意の場所から読み直せる
	*/
}

~~~


## http

### client

~~~go
/*
	- https://pkg.go.dev/net/http
*/
package main

import (
	"bytes"
	"io/ioutil"
	"log"
	"net/http"
)

func get() {
	r, err := http.Get("https://httpbin.org/get")
	if err != nil {
		// この場合のエラーはL6辺りでの通信障害の場合で
		// 404や500などのエラーはここでは拾えない。
		// 試しにLANを引っこ抜いて試してみよう
		log.Fatal(err)
	}

	log.Println(r.StatusCode) // 200
	for key, value := range r.Header {
		log.Printf("%s: %s", key, value)
	}
	/*
		Access-Control-Allow-Credentials: [true]
		Date: [Mon, 15 Nov 2021 09:56:22 GMT]
		Content-Type: [application/json]
		Content-Length: [272]
		Server: [gunicorn/19.9.0]
		Access-Control-Allow-Origin: [*]
	*/

	// レスポンスはio.ReadCloserで返ってくる
	defer r.Body.Close()
	data, err := ioutil.ReadAll(r.Body)
	if err != nil {
		log.Fatal(err)
	}

	log.Println(string(data))
	/*
		2021/11/15 18:53:24 {
		  "args": {},
		  "headers": {
		    "Accept-Encoding": "gzip",
		    "Host": "httpbin.org",
		    "User-Agent": "Go-http-client/2.0",
		    "X-Amzn-Trace-Id": "Root=1-61922e14-3971ae515f0ac14f6a7cd8db"
		  },
		  "origin": "***.***.***.***",
		  "url": "https://httpbin.org/get"
		}
	*/
}

func post() {
	payload := []byte(`{"id": 12, "name": "東方仗助"}`)
	body := bytes.NewBuffer(payload)

	// bodyはio.Readerで渡す
	r, err := http.Post("https://httpbin.org/post", "application/json; charset=utf-8", body)
	if err != nil {
		// この場合のエラーはL6辺りでの通信障害の場合で、
		// 404や500などのエラーはここでは拾えない
		// 試しにLANを引っこ抜いて試してみよう
		log.Fatal(err)
	}

	log.Println(r.StatusCode) // 200
	for key, value := range r.Header {
		log.Printf("%s: %s", key, value)
	}
	/*
		Date: [Mon, 15 Nov 2021 10:05:45 GMT]
		Content-Type: [application/json]
		Content-Length: [528]
		Server: [gunicorn/19.9.0]
		Access-Control-Allow-Origin: [*]
		Access-Control-Allow-Credentials: [true]
	*/

	// レスポンスはio.ReadCloserで返ってくる
	defer r.Body.Close()
	data, err := ioutil.ReadAll(r.Body)
	if err != nil {
		log.Fatal(err)
	}

	log.Println(string(data))
	/*
		{
		  "args": {},
		  "data": "{\"id\": 12, \"name\": \"\u6771\u65b9\u4ed7\u52a9\"}",
		  "files": {},
		  "form": {},
		  "headers": {
		    "Accept-Encoding": "gzip",
		    "Content-Length": "34",
		    "Content-Type": "application/json; charset=utf-8",
		    "Host": "httpbin.org",
		    "User-Agent": "Go-http-client/2.0",
		    "X-Amzn-Trace-Id": "Root=1-619230f9-330019b61045709e1ccf8686"
		  },
		  "json": {
		    "id": 12,
		    "name": "\u6771\u65b9\u4ed7\u52a9"
		  },
		  "origin": "***.***.***.***",
		  "url": "https://httpbin.org/post"
		}
	*/
}

func main() {
	get()
	post()
}

~~~


## log

https://pkg.go.dev/log
https://pkg.go.dev/fmt

標準出力はfmtを使う例が多いが、スレッドセーフではないためgoroutinを多用すると出力が混ざる可能性がある。
実運用する場面ではlogを使ったほうが無難ではある

~~~go
/*
   log package Sample

   document:
    - https://pkg.go.dev/log
    - https://pkg.go.dev/fmt
*/
package main

import (
	"errors"
	"log"
	"os"
)

// logパッケージのサンプル
// Goの技術書などではよくfmt.Println()を使っているが
// あれはただ単純に書き出しているだけのため、
// 並列処理中に同時に書き込んだ場合にそれぞれが混ざり合ってしまう
// logは一つのハンドルに対してはスレッドセーフとなっているためこっちを使ったほうが安心
// あと時間が表示されるのはデバッグ中にとても助かる
func basic() {
	log.Println("spam")                     // 2021/01/19 18:47:35 spam
	log.Println("egg", "bacon", "and spam") // 2021/01/19 18:51:04 egg bacon and spam

	// Printfはfmtと同じ
	log.Printf("I'm a %s It's Ok", "Lumberjack") // 2021/01/19 19:53:54 I'm a Lumberjack It's Ok
	log.Printf("int: %d", 253)                   // 2021/01/19 19:51:39 int: 253
	log.Printf("hex: 0x%x", 253)                 // 2021/01/19 19:51:39 hex: 0xfd
	log.Printf("oct: 0o%o", 253)                 // 2021/01/19 19:51:39 oct: 0o375
	log.Printf("bin: 0b%b", 253)                 // 2021/01/19 19:51:39 bin: 0b11111101

	s := struct {
		ID   int
		Name string
	}{123, "Graham"}

	// 構造体のダンプ時に便利
	log.Printf("%+v", s) // 2021/01/19 19:50:00 {ID:123 Name:Graham}

	log.SetPrefix("[log] ")
	log.Println("プレフィックスをつける") // [log] 2021/01/19 18:50:07 プレフィックスをつける
	log.SetPrefix("")

	log.SetFlags(log.Flags() | log.LUTC)
	log.Println("時刻タイムゾーンはデフォルトを使用するが、フラグを追加することによりUTCにできる") // 2021/01/19 09:57:09 時刻タイムゾーンはデフォルトを使用するが、フラグを追加することによりUTCにできる

	log.SetFlags(0)
	log.Println("フラグをすべて外すと時間出力をオフにできる") // フラグを設定すると時間表示をオフにできる

	log.SetFlags(log.Ldate | log.Lmicroseconds)
	log.Println("マイクロ秒まで表記") // 2021/01/19 18:57:09.086480 マイクロ秒まで表記

	// このロガーのデフォルト出力先は標準エラー出力(os.Stderr)である。
	// この関数で出力先を変える。
	// 引数はio.Writerならなんでもいい
	log.SetOutput(os.Stdout)

	var err error = nil
	if err != nil {
		// エラー内容を出力してstatus code 1で終了する。
		// 正直使う機会はない
		log.Fatal(errors.New("何かしらのエラー"))
	}
}

// pythonで言うところのNullHandler的な
// https://docs.python.org/ja/3/library/logging.handlers.html#logging.NullHandler
type NoneWriter struct{}

func (n *NoneWriter) Write(o []byte) (int, error) { return len(o), nil }

var DEBUG = false

// 独自のハンドラを作成する。
// 上で呼び出していたのはlogパッケージが自動で作成したデフォルトのロガーである
// 新たにハンドラを作成することにより、例えばエラーレベルによってハンドラを変えたり、
// フラグを全てオフにしてファイル書き込みに使ったりと大抵のアウトプット処理はなんとかなる
func handlers() {
	logger := log.New(os.Stdout, "[stdout] ", log.LstdFlags)

	logger.Println("I sleep all night and I work all day") // [stdout] 2021/11/11 11:42:38 I sleep all night and I work all day
	log.Println("I cut down trees, I skip and jump")       // 2021/11/11 11:42:38.997646 I cut down trees, I skip and jump

	// デバッグ用のハンドラを予め分けておいて、
	// 運用モード時は出力先を変えるようにすればいちいちデバッグコードを消さなくて済む
	var debug *log.Logger
	if DEBUG {
		debug = log.New(os.Stderr, "[DEBUG] ", log.LstdFlags)
	} else {
		debug = log.New(&NoneWriter{}, "[DEBUG] ", log.LstdFlags)
	}
	debug.Println("何かしらのデバッグ文言")
}

func main() {
	basic()
	handlers()
}
~~~

## panic

~~~go
/*
   panic basic sample
*/
package main

import (
	"errors"
	"log"
)

// Panicは他言語で言うところの例外ではないが、一応panicを拾うことはできる。
func basicPanic() {
	defer func() {
		// defer内でrecover()を呼ぶことによってpanicを握りつぶすことができる。
		e := recover()
		if e != nil {
			str, _ := e.(string)
			log.Println("panic:", str) // 2021/11/11 12:00:55 panic: example panic
		}
	}()
	defer func() {
		log.Println("panicが起こってもdeferは動く")
	}()

	panic("example panic")

	log.Println("panicが起こった時点でdeferの消化が始まり、recoverで拾われない限りその後の処理は行われない")

	/*
	2021/11/11 12:27:17 *****basicPanic()*****
	2021/11/11 12:27:17 panicが起こってもdeferは動く
	2021/11/11 12:27:17 panic: example panic
	*/
}

func panicTiming() {
	defer func() {
		e := recover()
		if e != nil {
		} else {
			log.Println("一度recoverされるともうrecoverされない")
		}
	}()

	defer func() {
		e := recover()
		if e != nil {
			log.Println("呼び出した関数内でのpanicも受け取れる")
		}
	}()

	func() {
		panic("panic!")
	}()

	/*
	2021/11/11 12:30:37 *****panicTiming()*****
	2021/11/11 12:30:37 呼び出した関数内でのpanicも受け取れる
	2021/11/11 12:30:37 一度recoverされるともうrecoverされない
	*/
}

// recoverで戻ってくるものはpanicの引数によって変わる
func panicValue() {
	re := func() {
		e := recover()
		if e != nil {
			switch typ := e.(type) {
			case error:
				log.Println("type: error,", typ)
			case int:
				log.Println("type: int,", typ)
			case string:
				log.Println("type: string,", typ)
			default:
				log.Printf("unknown type of, %+v", typ)
			}
		}
	}

	func() {
		defer re()
		panic("some Error")
	}()

	func() {
		defer re()
		panic(errors.New("some Error"))
	}()

	func() {
		defer re()
		panic(12345)
	}()

	func() {
		defer re()

		var someValues []string
		_ = someValues[1]
	}()

	/*
	2021/11/11 12:27:17 *****panicValue()*****
	2021/11/11 12:27:17 type: string, some Error
	2021/11/11 12:27:17 type: error, some Error
	2021/11/11 12:27:17 type: int, 12345
	2021/11/11 12:27:17 type: error, runtime error: index out of range [1] with length 0
	*/
}

func main() {
	log.Println("*****basicPanic()*****")
	basicPanic()
	log.Println("*****panicTiming()*****")
	panicTiming()
	log.Println("*****panicValue()*****")
	panicValue()
}

~~~

## time

### 基本形

~~~go
/*
	timeパッケージを使った現在時刻の取得・タイムゾーン処理

	doc:
	  - https://pkg.go.dev/time
	  - https://xn--go-hh0g6u.com/pkg/time/
*/

package main

import (
	"time"

	"log"
)

// 現在日時の取得
func getNow() {
	var now time.Time

	log.Println("--- Get now")

	// Timezoneのデフォルトは/etc/localtime つまり動作環境のローカルタイムゾーンとなる
	// TZ=Australia/Sydney go run time/basic.go のようにタイムゾーンを指定して試してみよう
	now = time.Now()

	log.Println(now) // 2022-01-25 12:25:58.039466266 +0900 JST m=+0.000108249

	// UTCが欲しい場合はUTC()を使う
	log.Println(now.UTC()) // 2022-01-25 03:25:58.039466266 +0000 UTC

	// ローカルタイムゾーンが必要な場合はLocal()を使う
	// (別の方法でtime.Timeを作った場合など)
	log.Println(now.Local()) // 2022-01-25 12:25:58.039466266 +0900 JST m=+0.000108249

	// UnixTimeの取得 (int64であることに注意)
	log.Println(now.Unix()) // 1643085657

	// 年の取得
	log.Println(now.Year()) // 2022

	// 月の取得
	month := now.Month()
	log.Println(month, int(month)) // January 1

	// 日の取得
	log.Println(now.Day()) // 25

	// 時の取得
	log.Println(now.Hour()) // 12

	// 秒の取得 (not unixtime)
	log.Println(now.Second()) // 25
}

// タイムゾーンの取得
func getTimeZone() {
	var timezone *time.Location
	var err error

	log.Println("--- Get Timezone")
	// タイムゾーン(*time.Locale)を取得するにはtime.LoadLocation()を使用する
	// タイムゾーンは"UTC"でUTC、"Local"でローカルタイムゾーン
	// そしてIANAデータベースのものを引っ張ってくる。
	// ここにあるものなら取れるんじゃないでしょうか
	// https://en.wikipedia.org/wiki/List_of_tz_database_time_zones

	// ドキュメントを読む限りだと/usr/share/zoneinfo/にオリジナルのタイムゾーンを入れると
	// それも取れそうではありますが、基本的に悪手です

	timezone, err = time.LoadLocation("Asia/Tokyo")
	if err != nil {
		log.Fatal(err)
	}

	log.Println(timezone) // Asia/Tokyo

	timezone, err = time.LoadLocation("Egypt")
	if err != nil {
		log.Fatal(err)
	}

	log.Println(timezone) // Egypt

	// timeパッケージのデフォルトタイムゾーンはtime.Localで変更できる
	// 影響範囲が大きいため使用する場合は要注意
	tz, _ := time.LoadLocation("MST")
	time.Local = tz

	// MST -25200
	log.Println(time.Now().Zone())
}

func main() {
	// 現在日時の取得
	getNow()
	// タイムゾーンの取得
	getTimeZone()
}
~~~