Open27

Golangを学ぶ

nomnomnonononomnomnonono

https://go-tour-jp.appspot.com/list

とりあえずチュートリアルをやる。

nomnomnonononomnomnonono

https://go-tour-jp.appspot.com/methods/4

ポインタレシーバはレシーバが指す変数を変更することができる。またメソッド呼出ごとにレシーバそのもののコピーをする必要がない。
一般的には値レシーバ or ポインタレシーバのどちらかに統一する必要がある。

nomnomnonononomnomnonono

https://zenn.dev/hsaki/books/golang-concurrency

並行処理について学ぶ。

nomnomnonononomnomnonono
  • 並行処理の定義を勘違いしていた

    ・並行処理: ある時間の範囲において、複数のタスクを扱うこと
    ・並列処理: ある時間の点において、複数のタスクを扱うこと

  • Go の使用ではメインゴールーチンが終わったら、他のゴールーチンの終了を待たずにプログラム全体が終わる
  • WaitGroup: 待ちタスク数をカウンタとして持たせる、wg.Wait()でカウンタが0になるまで待つ
  • Channel: 特定の型の値を送信・受信することで (異なるゴールーチンで) 並行に実行している関数がやり取りする機構 (実行同期機能も併せ持つ)
  • そのゴールーチンよりも広いスコープを持つ変数は参照しない方が無難
  • Do not communicate by sharing memory; instead, share memory by communicating.: 複数のゴールーチン上で何かデータを共同で使ったり、やり取りをしたい際には、排他制御しながらデータを共有するよりかはチャネルの利用を推奨
  • 並行処理の応用
    • 拘束: 受信専用チャネルを返り値として返す関数を定義
    • select文: 送受信を実行できるチャネルの中からどれかを選択し実行します
    • バッファありチャネルはセマフォの役割 (実行数制御?)
    • メインルーチンからサブルーチンを停止させる
    • FanIn: 複数個あるチャネルから受信した値を、1つの受信用チャネルの中にまとめる方法
    • タイムアウト、定期実行の実装: time.After, time.NewTimer
nomnomnonononomnomnonono

https://tenn.in/go

さらに深く学ぶ。

nomnomnonononomnomnonono
  1. 基本構文
  • 型なしの定数を活用することで煩雑な型変換を避けることができる
  • iota: ConstSpec のインデックス
  • switchによるif-elseの簡略化
  1. 関数と型
  • 配列と構造体のゼロ値はフィールド/要素が全てゼロ値、スライスとマップのゼロ値はnil
  • empty struct: 存在はしてほしいがデータ容量を使いたくない場合に使用、マップのキーだけが重要な場合
  • x/exp/slicesパッケージ: sliceに関する便利な操作がまとまっている
  • x/exp/mapsパッケージ: マップに関する便利な操作がまとまっている
  • 値の代入はコピーが発生する
  1. パッケージ
  • import時に名前をつけることができる (インポートパスの左側に記載)
  • 先頭を大文字にした識別子がエクスポートされる
  • go get: ライブラリの取得
  • TODO: モジュールについて理解深める
nomnomnonononomnomnonono
  1. コマンドラインツール
  • 実行引数はos.Argsで受け取れる
  • flagパッケージはフラグを扱うパッケージ、フラグは事前に変数を定義しておきflag.Parse()を行う
  • osパッケージとfmt.Fprintln関数と組み合わせて出力先を指定することができる
  • osパッケージはプログラムの終了やファイル操作も可能
  • log.Fatalは標準エラー出力とプログラムの異常終了 (終了コードを指定できないので注意)
  • bufio.Scannerは標準入力を1行ずつ読み込みやエラー処理が便利
  • path/filepathパッケージはファイルパス操作に便利 (OSに依存しない)
  1. 抽象化
  • 型が持つメソッドをインターフェースとして定義することで抽象化を行う
  • value, ok := インタフェース.(型): 型アサーション
  • インターフェースのメソッドセットは小さく -> 実装が楽になる
  • TODO: 「構造体 / インターフェースの埋め込み」についての理解
    • 単純なインターフェースから複雑なインターフェースを作成できる
  1. エラー処理
  • エラー処理ではnilと比較してエラーが発生したかをチェックする
    • エラーの使い回しを防ぐために代入付きifを用いることが多い
  • 文字列ベース: errors.Newfmt.Errorf
  • 型ベース: Errorメソッドを実装している型を定義する
  • エラー処理をまとめるのも便利 (参考:buffio.Scanner)
  • fmt.Errorf with %wでエラーに文脈を持たせつつ、必要に応じてエラー内容をアンラップできる
  • errors.Isでエラーを値によって分岐できる
  • パニック: 回復不能だと判断された実行時のエラーを発生させる機構
  • リカバー: 発生したパニックを取得し、エラー処理を行う (recover関数をdeferで呼び出された関数内で実行する、関数単位で実行)
  1. テストとテスタビリティ
  • go test: 単体テストを行うためコマンド (_test.goという名のついたファイル)
    • パッケージ名も{テストしたいパッケージ名}_testとする?
  • t.Cleanup: テスト終了時に行う関数を登録
  • t.TempDir: テスト終了時に消える一時ディレクトリを作成
  • Exampleテスト: Exampleで始まる関数を書くと、Go Docにサンプルとして表示される
  • t.Parallel: テストの並列実行
  • t.Helper(): テスト用のヘルパー関数の定義に使用
  • テストのカバレッジ分析: go test -coverprofile=cover.out {package_name}
  • カバレッジの可視化: go tool cover -html=cover.out
  • tenntenn/golden: ゴールデンファイルテストを行うライブラリ
  • TODO: インターフェースや埋め込みを活用した手スタビリティの高いコード
nomnomnonononomnomnonono
nomnomnonononomnomnonono

初期設定。

$ cd $HOME/code
$ mkdir goscraper
$ cd goscraper
$ go mod init github.com/nomnomnonono/goscraper
$ cobra-cli init
// 必要に応じて実行
$ cobra-cli add hoge
nomnomnonononomnomnonono

モジュールの追加は、

  1. コードにimport文を書く
  2. go mod tidyを実行
nomnomnonononomnomnonono
nomnomnonononomnomnonono
  • json
  • sync.WaitGroupを関数に渡すときに、ポインタにしないと内部でコピーされることでエラーが発生する
  • semaphore による goroutine の実行数制御: semaphore package の利用、buffer あり channel の利用
  • errgroup: sync.WaitGroup + error型を返せる goroutine (一番最初に発生したエラーのみ取得)
  • goroutineのリークに気をつける (contextselectの活用)
  • TODO: contextの理解
  • Go におけるテストの種類: Test, Benchmark, Fuzz, Example (プレフィックスで指定する)
  • Table Driven Test の推奨: テストケースの入出力の1つの struct にまとめ、全てのテストケースをforを用いて実行する
nomnomnonononomnomnonono

https://zenn.dev/hsaki/books/golang-context

contextの理解を深める。

nomnomnonononomnomnonono
  • context の役割: 「処理の締め切りを伝達」、「キャンセル信号の伝播」、「リクエストスコープ値の伝達」
    • context が役に立つのは1つの処理が複数のゴールーチンにまたがるとき
    • Go ではライブラリの仕様上複数のゴールーチン上に処理がまたがり、いくつものゴールーチンが木構造的に積み上がっていくが珍しくない
    • 複数ゴールーチン間で安全・簡単に情報伝達を行うことを、チャネルによる伝達だけ実現することが難しいため、context が活用されている
  • context 実行方法
    • context の初期化: context.Background()
    • context にキャンセル機能を追加: context.WithCancel(context.Background())
    • context に自動タイムアウト機能の追加: context.WithDeadline(context.Background(), time.Second())
  • context の挙動
    • 同じ context を使い回る場合、直列・並列に関わらず、キャンセルは同期される
    • 親 context がキャンセルされると子 context もキャンセルされる
  • context.Canceledcontext.DeadlineExceededの2種類のエラー変数があり、ctx.Err()の値によって context がキャンセルかタイムアウトどちらで終わったのかを確かめることができる
  • context.WithValue(ctx, key, value)で context に値を持たせることができる
    • 取り出すときはctx.Value(key).(type)
    • key と value は context を介した時点で全てinterface{}型になる
    • key の衝突を防ぐ: パッケージごとに独自の非公開 key 型を導入
    • value はリクエストスコープな変数 (一つのリクエストが処理されている間に共有される) を与えるべき