実用Go言語: 読書メモ

1章
「Goらしさ」にふれる
iotaで列挙型を実現
ユニークな値を持つ定数
type CarType int
const (
Sedan CarType = iota + 1
Hatchback
SUV
)
iotaの初期値は0。このまま利用すると初期化されていない状態の0なのか、最初の要素である0なのか区別がつかない。なのでiota + 1
として1をオリジンにするのが一般的
組み合わせてフラグとして利用する定数
ファイルを開くモードのように、それぞれの定数を組み合わせて宣言し、組み合わせを表現するフラグとしてiotaを使う場合
import "fmt"
type CarOption int
const (
GPS CarOption = iota << 1 // 1 (0001 in binary)
AWD // 2 (0010 in binary)
Sunroof // 4 (0100 in binary)
HeatedSeats // 8 (1000 in binary)
)
func main() {
var o CarOption = Sunroof | HeatedSeats
o = Sunroof | HeatedSeats
if o&Sunroof != 0 {
fmt.Println("Has Sunroof")
}
}
if o&Sunroof != 0
の箇所はビット積(AND)で計算した結果で存在確認をしている。
より具体的なサンプルだとGo言語 ビット演算でのフラグ管理が参考になる
文字列として出力可能にする
iotaはintベースの列挙型なので、ログ出力時に元の整数値が出力される。その対応方法として
iotaによる手法+go-generateで解決するのが良い。
具体的には次の2種類がある。詳細は上記のリンクを参考
- iota + Stringer
- iota + enumer
関数のオプション引数
関数やメソッドを実装する時に必要に応じてパラメーターを増減させたいことがある。Goでの実現方法について説明する
著者の記事ではないが、Goで関数のオプション引数を実装するパターン集に同様の内容が書かれている。
- 構造体の利用
Goの標準ライブラリでよく見られる。例:http.Client
の初期化 - ビルダーを利用したオプション引数
APIやロガー周り、コマンドライン引数パーサで見かける - Functional Optionパターン
一番凝っている方法
著者おすすめは、まずは構造体パターンを実装して提供する。必要になってからビルダー形式やFunctional Optionパターンを提供する
メモリ起因のパフォーマンス低下を解消する方法
Goでパフォーマンスに差が現れがちなポイントはスライス
とマップ
のメモリ確保である。パフォーマンス問題にあたると、まず調べるのがメモリ確保の頻度
スライスのメモリ確保を高速化
- 予め必要な要素数が分かっているなら
-
s1 := make([]int, 1000)
で実際のサイズを確保
-
- 最大量の見込みがつく場合
-
s2 := make([]int, 0, 1000)
でキャパシティを確保
-
マップのメモリ確保を高速化
予め必要な要素が分かっているなら
m := make(map[string]string, 1000)
でキャパシティを確保
deferの落とし穴1
deferは関数ブロックを抜けたときに、そのキーワードの後ろにあった文を実行する。deferはリソースの明示的な開放を行うために主に利用され、リソース確保の直後に書く。一方で関数を抜けるまで終了処理が呼ばれないため、forループと一緒に使うと、リソースの消費量が線形に増えていくので、明示的に開放処理を呼ぶ必要がある。
for _, fname := range files {
f, err := os.Open(fname)
if err != nil {
return err
}
defer f.Close()
}
for _, fname := range files {
f, err := os.Open(fname)
if err != nil {
fmt.Println("Error opening file:", err)
}
err = f.Close()
if err != nil {
fmt.Println("Error closing file:", err)
}
}
deferの落とし穴2
Close()
など、エラーを返す可能性がある場合、普通にdefer
を呼ぶだけでは、エラーを取りこぼす。無名関数でくくってそのエラーを名前付きの返り値に代入することでエラーを呼び出し元に返す
func deferReturnSample(fname string) (err error) {
var f *os.File
f, err = os.Open(fname)
if err != nil {
return fmt.Errorf("os.Open: %w", err)
}
defer func() {
// Closeのエラーを拾って名前付き返り値に代入
err = f.Close()
}()
io.WriteString(f, "deferのエラーを拾うサンプル")
return
}
日時の取り扱い
- 日時のフォーマットはJSとの変換を考慮するとtime.RFC3390Nanoがおすすめ
- 翌月など日数を自分で
+
や-
で計算しているロジックを見たら警戒する