Open27
Golangを学ぶ
とりあえずチュートリアルをやる。
ローカルで実行。
ポインタレシーバはレシーバが指す変数を変更することができる。またメソッド呼出ごとにレシーバそのもののコピーをする必要がない。
一般的には値レシーバ or ポインタレシーバのどちらかに統一する必要がある。
インターフェースは肩が持つべきメソッドを定義する。
empty interface, nil interface
変数やエラー内容の出力形式を独自設定できる。
初学者向けの資料が掲載されている。
ポインタ、構造体、メソッド、インターフェースについておさらい。
並行処理について学ぶ。
- 並行処理の定義を勘違いしていた
・並行処理: ある時間の範囲において、複数のタスクを扱うこと
・並列処理: ある時間の点において、複数のタスクを扱うこと - Go の使用ではメインゴールーチンが終わったら、他のゴールーチンの終了を待たずにプログラム全体が終わる
-
WaitGroup
: 待ちタスク数をカウンタとして持たせる、wg.Wait()
でカウンタが0になるまで待つ -
Channel
: 特定の型の値を送信・受信することで (異なるゴールーチンで) 並行に実行している関数がやり取りする機構 (実行同期機能も併せ持つ) - そのゴールーチンよりも広いスコープを持つ変数は参照しない方が無難
-
Do not communicate by sharing memory; instead, share memory by communicating.
: 複数のゴールーチン上で何かデータを共同で使ったり、やり取りをしたい際には、排他制御しながらデータを共有するよりかはチャネルの利用を推奨 - 並行処理の応用
-
拘束
: 受信専用チャネルを返り値として返す関数を定義 -
select文
: 送受信を実行できるチャネルの中からどれかを選択し実行します - バッファありチャネルはセマフォの役割 (実行数制御?)
- メインルーチンからサブルーチンを停止させる
-
FanIn
: 複数個あるチャネルから受信した値を、1つの受信用チャネルの中にまとめる方法 - タイムアウト、定期実行の実装:
time.After, time.NewTimer
-
さらに深く学ぶ。
- 基本構文
- 型なしの定数を活用することで煩雑な型変換を避けることができる
-
iota
: ConstSpec のインデックス -
switch
によるif-else
の簡略化
- 関数と型
- 配列と構造体のゼロ値はフィールド/要素が全てゼロ値、スライスとマップのゼロ値は
nil
-
empty struct
: 存在はしてほしいがデータ容量を使いたくない場合に使用、マップのキーだけが重要な場合 - x/exp/slicesパッケージ:
slice
に関する便利な操作がまとまっている - x/exp/mapsパッケージ: マップに関する便利な操作がまとまっている
- 値の代入はコピーが発生する
- パッケージ
-
import
時に名前をつけることができる (インポートパスの左側に記載) - 先頭を大文字にした識別子がエクスポートされる
-
go get
: ライブラリの取得 - TODO: モジュールについて理解深める
- コマンドラインツール
- 実行引数は
os.Args
で受け取れる -
flag
パッケージはフラグを扱うパッケージ、フラグは事前に変数を定義しておきflag.Parse()
を行う -
os
パッケージとfmt.Fprintln
関数と組み合わせて出力先を指定することができる -
os
パッケージはプログラムの終了やファイル操作も可能 -
log.Fatal
は標準エラー出力とプログラムの異常終了 (終了コードを指定できないので注意) -
bufio.Scanner
は標準入力を1行ずつ読み込みやエラー処理が便利 -
path/filepath
パッケージはファイルパス操作に便利 (OSに依存しない)
- 抽象化
- 型が持つメソッドをインターフェースとして定義することで抽象化を行う
-
value, ok := インタフェース.(型)
: 型アサーション - インターフェースのメソッドセットは小さく -> 実装が楽になる
- TODO: 「構造体 / インターフェースの埋め込み」についての理解
- 単純なインターフェースから複雑なインターフェースを作成できる
- エラー処理
- エラー処理では
nil
と比較してエラーが発生したかをチェックする- エラーの使い回しを防ぐために代入付き
if
を用いることが多い
- エラーの使い回しを防ぐために代入付き
- 文字列ベース:
errors.New
、fmt.Errorf
- 型ベース: Errorメソッドを実装している型を定義する
- エラー処理をまとめるのも便利 (参考:buffio.Scanner)
-
fmt.Errorf with %w
でエラーに文脈を持たせつつ、必要に応じてエラー内容をアンラップできる -
errors.Is
でエラーを値によって分岐できる - パニック: 回復不能だと判断された実行時のエラーを発生させる機構
- リカバー: 発生したパニックを取得し、エラー処理を行う (
recover
関数をdefer
で呼び出された関数内で実行する、関数単位で実行)
- テストとテスタビリティ
-
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: インターフェースや埋め込みを活用した手スタビリティの高いコード
公式チュートリアル。
CLI 作ってみる。
CLI 作成に便利なパッケージを紹介している。
初期設定。
$ cd $HOME/code
$ mkdir goscraper
$ cd goscraper
$ go mod init github.com/nomnomnonono/goscraper
$ cobra-cli init
// 必要に応じて実行
$ cobra-cli add hoge
モジュールの追加は、
- コードに
import
文を書く -
go mod tidy
を実行
複雑な機能は必要としないため、goquery
を採用。
標準出力の色を変える。
json
-
sync.WaitGroup
を関数に渡すときに、ポインタにしないと内部でコピーされることでエラーが発生する - semaphore による
goroutine
の実行数制御:semaphore package
の利用、buffer あり channel
の利用 -
errgroup
:sync.WaitGroup
+error
型を返せるgoroutine
(一番最初に発生したエラーのみ取得) -
goroutine
のリークに気をつける (context
やselect
の活用) - TODO:
context
の理解 - Go におけるテストの種類:
Test
,Benchmark
,Fuzz
,Example
(プレフィックスで指定する) - Table Driven Test の推奨: テストケースの入出力の1つの
struct
にまとめ、全てのテストケースをfor
を用いて実行する
context
の理解を深める。
- 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 もキャンセルされる
-
context.Canceled
とcontext.DeadlineExceeded
の2種類のエラー変数があり、ctx.Err()
の値によって context がキャンセルかタイムアウトどちらで終わったのかを確かめることができる -
context.WithValue(ctx, key, value)
で context に値を持たせることができる- 取り出すときは
ctx.Value(key).(type)
- key と value は context を介した時点で全て
interface{}
型になる - key の衝突を防ぐ: パッケージごとに独自の非公開 key 型を導入
- value はリクエストスコープな変数 (一つのリクエストが処理されている間に共有される) を与えるべき
- 取り出すときは
ハンズオンに取り組む。
Go による Web アプリケーション開発。