実用 Go言語 を読んでみる
![tkg216](https://res.cloudinary.com/zenn/image/fetch/s--K0SV-r6F--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/399867d3f9.jpeg)
1章 「Goらしさに触れる」
パッケージ名
パッケージ名は小文字で構成される1つの単語、bytes, http, listなどとすることが慣例。
変数名
短い名前が好まれる、ただし宣言している箇所と利用箇所が離れすぎていない場合。
contextをcとしたり、短いfor文の中だと1文字の変数が使われている印象なので、納得。
panic()は使わない
基本的にerror値のチェックでエラーハンドリングする。
httpサーバーを起動するのに失敗するときはpanicを使ったりしている印象(ポートが空いていなくて処理を続けても仕方ないからだという認識)
使えるならvarよりも短縮形式:=
関数のオプション引数
すっと頭に入ってこなかったので、別記事を当たった。https://qiita.com/k-penguin-sato/items/e2791d7a57e96f6144e5)
が丁寧に書かれていて良かった。他言語の例としてTypeScriptの記事の紹介も良かった(- 使われそうなパターン分のファクトリー関数を作る
- →ユースケースが限られているなら有用
- オプション構造体を用意する
- →ポインタを取るためのUtility関数が必要
- ビルダーを利用する
- →zerologなどのロガーライブラリなどでよく見る(らしい)
- Functional Optionパターン
- →
func NewCoffee(opts ...CoffeeOptionFunc) *Coffee {}
のように任意の数関数を渡せるようにする
- →
![tkg216](https://res.cloudinary.com/zenn/image/fetch/s--K0SV-r6F--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/399867d3f9.jpeg)
付録A 駆け足で学ぶGoの基礎
- 20ページくらいで要点がまとまっていて良かった、ここから読むといいかも
![tkg216](https://res.cloudinary.com/zenn/image/fetch/s--K0SV-r6F--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/399867d3f9.jpeg)
付録B 情報源
- ドキュメント
- Effective Go: https://go.dev/doc/effective_go
- The Go Blog: https://go.dev/blog/
- Go Wiki: https://go.dev/wiki/
Effective Goはコードも多そうで良さそう。
- 教材
- A Tour of Go: https://go-tour-jp.appspot.com/welcome/1
- Go by Example: https://oohira.github.io/gobyexample-jp/
- 解説付きでコードを読めて良さそう、日本語(自動翻訳?)
- プログラミング言語Go完全入門: https://engineering.mercari.com/blog/entry/goforbeginners/
- Gopher道場のやつ
![tkg216](https://res.cloudinary.com/zenn/image/fetch/s--K0SV-r6F--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/399867d3f9.jpeg)
2章 定義型
- Goでは「型」であれば何でもメソッドを持てる
アプリケーションを作成する作業を分割していくと、次の要素に分けられる
- 提供されている基本的なツール(標準ライブラリ、文法)を組み合わせる
- ライブラリやフレームワークを使うためのグルー(糊付け)コードを書く
Goは前者に比重があるらしい。標準ライブラリのインセンティブが高く、この辺りがコードの書きっぷりにばらつきが少なく学習コストが低い、ことにつながっているのかもしれない。
オブジェクト指向言語のようにコンストラクタが用意されていないので、「この型・構造体の機能」どうすればいいのか」を判断することが難しいことがある。
→シンプルで良いので、値を定義した型のインスタンスを返すファクトリー関数を提供しておくといい。
参考:
// ユーザー情報を格納する構造体
type User struct {
Name string
Age int
}
// ファクトリー関数
func NewUser(name string, age int) (*User, error) {
if name == "" {
return nil, errors.New("name is required")
}
if age < 0 {
return nil, errors.New("age must be positive")
}
return &User{Name: name, Age: age}, nil
}
![tkg216](https://res.cloudinary.com/zenn/image/fetch/s--K0SV-r6F--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/399867d3f9.jpeg)
スライスへの型定義
type Cunsumers []Consumer
func (c Cunsumers) ActiveCunsumer() Cosumers {}
レシーバーで実装すると、実装箇所を極小化でき、テストも書きやすくなる!!
戻り値を元の型に揃えておくとメソッドチェーンで記述できるので良い感じ。
※配列とスライスを言葉上混同しているケースもある気がするけど、違うもの
![tkg216](https://res.cloudinary.com/zenn/image/fetch/s--K0SV-r6F--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/399867d3f9.jpeg)
構造体への型定義
構造体を関数にそのまま渡すならレシーバーにしたほうがいい、テストもしやすくなり、再利用性も高まるらしい。なるべくレシーバーで実装することがGoらしいプログラミングの方法。
そうなのか?
ChatGPTに聞いてみるとわかりやすかった。
Goらしいスタイル:
Go言語では、メソッドを構造体(またはインターフェース)のレシーバーとして定義することが一般的です。これにより、その構造体に関連する機能がメソッドとしてまとまり、コードの可読性と整合性が高まります。
テストのしやすさ:
レシーバーメソッドは、構造体のインスタンスに対して直接呼び出すことができるため、テストの際にモックやスタブを使いやすくなります。特定の構造体のメソッドをテストすることで、関連するデータと処理を一元管理しやすくなります。
再利用性の向上:
構造体のメソッドとして定義することで、そのメソッドは構造体の一部として再利用されやすくなります。複数の箇所で同じ機能を使う際にも、一度定義したメソッドを使い回せるため、コードの重複を避けることができます。
![tkg216](https://res.cloudinary.com/zenn/image/fetch/s--K0SV-r6F--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/399867d3f9.jpeg)
Stringer, GoStringerの2つのインターフェースを満たすことで機密情報のマスキングができる、なるほど。
![tkg216](https://res.cloudinary.com/zenn/image/fetch/s--K0SV-r6F--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/399867d3f9.jpeg)
12章 ログとオブザーバビリティ
ちょっとSREっぽい話題に寄り道。
- データ分析を前提に、構造かログが用いられるようになった
- ログの収集方法もFluentdなどのエージェントを経由したり多様化
- LambdaなどのサーバーレスだとCloudWatch Logsで標準出力・エラー出力を自動で集められる
- コンテナ・マイクロサービスではログを追いかけるのが大変なのでTraceIDなどセッションで一意に決まるIDを使った共有分散トレーシングが出てくる
Goのパッケージ:
- logパッケージ: デバッグの補助で情報を出力 (多分これ https://pkg.go.dev/log)
- zap: データ分析で必要な構造化ログ (https://pkg.go.dev/go.uber.org/zap)
- 分散トレーシング: (https://pkg.go.dev/go.opencensus.io, https://pkg.go.dev/go.opentelemetry.io)
観点:
- 必要な情報
- セキュリティ(監査、マスキング)
- クラウド費用
その他:
- log.SetFlags(LstdFlags) で日時の出力
![tkg216](https://res.cloudinary.com/zenn/image/fetch/s--K0SV-r6F--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/399867d3f9.jpeg)
構造化ログ
先述のzapが有名らしい。
zerologという速度を売りにしたものもある。
import (
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
)
log.Printf("Hello")
だけで標準パッケージのlogの代わりになるし、構造化ログになる。良さそう。
![tkg216](https://res.cloudinary.com/zenn/image/fetch/s--K0SV-r6F--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/399867d3f9.jpeg)
log.Fatalとpanic()は終了の仕方が大きく違う。
とはいえ使うのは多くなくて以下のシーンくらい
- main()関数
- init()関数 = main()の前に呼ばれる
- Must接頭辞がついた関数 = errorが返ってきた時用のラップ関数
- その他の初期化処理 (OSSで公開するならmainに返すべきとあるのであまり使わないかも)
Must知らなかったな、panicにするからあまり使うことないかもしれないけど
![tkg216](https://res.cloudinary.com/zenn/image/fetch/s--K0SV-r6F--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/399867d3f9.jpeg)
14章 クラウドとGo
慣れ親しんだ話が多くて安心感がある章。
- ベースイメージの選択の表が最初わからなかったが、ビルド用とデプロイ用で最適化できる意味だった
- →マルチステージビルドでビルドをgolang:1.x-bullseye, デプロイをgolang:1.x-bullseye-slimなどのように組み合わせられる
![tkg216](https://res.cloudinary.com/zenn/image/fetch/s--K0SV-r6F--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/399867d3f9.jpeg)
15章 クラウドのストレージ
- Go CDKっていうAWS CDKでないCDKがある
- S3やDynamoDBの話
![tkg216](https://res.cloudinary.com/zenn/image/fetch/s--K0SV-r6F--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/399867d3f9.jpeg)
13章 テスト
基礎
- ファイル名: xx_test.go
- 関数: Testから始まる名前、引数は t *testing.T
- 検証
- 失敗を知らせる: t.Error()やt.Errorf()
Table Driven Test
この辺りがわかりやすかった。 https://zenn.dev/urakawa_jinsei/articles/8de654bb2c1649
時間がかかるテスト
- デフォルトではGOMAXPROCS値(Go1.15以降はCPUコア数が設定)だけ並列に動かせる
testify
- sliceを含む構造体の比較に==を使うとコンパイルエラーになる
- reflect.DeepEqual()で比較できる
- どこに差分があるかわかりにくい→stretchr/testify
- assert.Equal()などが使える
- https://zenn.dev/aiiro/articles/cfc7c2e9a8e53a
構造体の比較にgo-cmp
- フィールドの除外などができるのが便利そう