概要
goを書いていてよく使う割によく忘れる処理まとめ
思い出したら順次書き足します
実際のファイルはGitHubにまとめてあります。
こちらに書き足していって、ひとまとまりになったらこちらに書き足します
Context
コンテクストは環境やスコープ、タイムアウトなどをツリー式に管理する構造体。
goを書く上で非常に重要な概念です。
import (
"context"
)
ctx := context.Background()
ctx, cancel := context.WithCancel(ctx)
defer cancel()
store
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)
}
タイムアウト処理
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()
を使えばいい。
import "os"
debug := os.Getenv("DEBUG")
DB設定のように、デフォルト値や必須値が欲しい場合はhttps://github.com/caarlos0/env/ を使うと便利
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
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
ファイル読み出し
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
/*
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
/*
- 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を使ったほうが無難ではある
/*
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
/*
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
基本形
/*
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()
}