💭

Goを学ぶときに参照した学習リソースまとめ

2021/12/19に公開

皆さん、新しいプログラミング言語を学ぶ時、どのように学習しているでしょうか?
私は4月に新卒でエンジニアになり、業務でGoを使うことになりました。その際、とりあえず公式チュートリアルであるTour of Goをやりましたが、その後にどうやって学習を進めれば良いか迷ってしまいました。

考えてみると、新しい言語を学ぶ際、毎回学習方法に困っている気がします。ネットでサンプルを探す、動画を見る、書籍を読む、などさまざまな学習方法があると思いますが、私は手を動かしながらいろいろなパターンを学んでいくのが好きです。

そこで今回Goを学ぶ際も、手を動かしてさまざまなコーディングのパターンを学習するために、ネットや書籍でサンプルを探して実践しました。
この学習方法は私にとっては楽しみながら続けることができて、他の言語を学ぶ際も今回実装したサンプルを使って学習しようと考えています!
そこで自分と同じ様な悩みを持っている人や未来の自分のために、手を動かしながら新しい言語(今回はGo)を学ぶ際に、自分が取り組んだ方法をまとめてみます!

動画

sudoku

https://www.youtube.com/watch?v=IyPujQ381YY

概要

1つ目は、本記事で紹介する中で唯一の動画資料です。これは、2020年の技育祭で@suzu_vさんが発表してくれたライブコーディング動画です。この動画では数独を解くプログラムをテストを書きながら開発していきます。Goのテストの書き方も学べ、1時間で書ける量のプログラムなので難易度もTour of Goを終えた次に取り組むのに丁度良いと感じました。プログラムを書いているときの思考を言葉に出して聞かせてくれるので、見ていてとても面白い動画です!

仕様

int配列を数独のボードに見立てて、与えられたボードの状態から全てのマスを数字で埋めるプログラムを実装します。

type Board [9][9]int

以下の仕様を満たすメソッドを順に実装していきます。

Boardをプリントするprettyを実装する

Boardを受け取って、標準出力にフォーマット化した数独ボードの文字列を出力します

func pretty(b Board) string {
  // 実装
}

以下のような出力が得られれば完成です。

+---+---+---+
|050|083|017|
|000|100|400|
|304|005|608|
+---+---+---+
|000|030|009|
|090|824|500|
|006|000|070|
+---+---+---+
|009|000|050|
|007|290|086|
|103|607|204|
+---+---+---+

与えられたボードが正しい状態であることをチェックするverifyを実装する

ボードに数字を1つ埋める→正しいかチェック→次の数字を埋める→正しいかチェック→...の順序で数独を解いていきます。ここでは、ボードが正しい状態であるかをチェックするメソッドを実装します。

func verify(b Board) bool {
  // 実装
}

数独のルールを知らない方はこちらをご覧ください。
ここでは、テストを書きながら実装を進めていきます。数独のルールから、どんなボードの状態をテストすれば良いか考えてテストコードを書いてみましょう。

ボードに数字を埋めていくbacktraceを実装する

verify()が完成したら、ボードに順々に数字を埋めていくロジックを実装します。verify()の返り値がtrueだったら次のマスを埋める、falseだったら今のマスに違う数字を入れる、を繰り返せば実装できます。

func backtrace(b *Board) bool {
  // 実装
}

学べる事

  • Tour of Goの復習
    • Tour of Goで学んだ構文(for文やstruct、メソッドなど)を活用して形あるものを実装することができます。
  • テストの書き方
    • 業務ではテストを書きながら開発することが多いと思います。新しい言語を学ぶときに、その言語でのテストの書き方を知っておくことは後の開発に役立つでしょう。

OSS

hhatto/gocloc

https://github.com/hhatto/gocloc

概要

goclocはあるディレクトリ配下にあるプログラムの言語毎のステップ数を出力してくれるツールです。コードリーディングを始める前に、プログラムの規模感を知りたくてよく使っています。

gocloc ~/src/github.com/golang/go/src/fmt
-------------------------------------------------------------------------------
Language                     files          blank        comment           code
-------------------------------------------------------------------------------
Go                              13            616           1175           5426
-------------------------------------------------------------------------------
TOTAL                           13            616           1175           5426
-------------------------------------------------------------------------------

仕様

goclocの元の実装は1,800行程度あり、練習がてら実装するには少し大きいです。なので今回は仕様を絞って実装を進めていきます。こちらが元のリポジトリからforkして仕様を削ったgoclocです。
https://github.com/ytakaya/gocloc
元の実装から以下の仕様を削りました。

  • 解析対象ファイル
    • Goのファイルのみ解析対象となるようにしました。
  • オプション
    • --match-d以外のオプションを省きました。
  • ブロックコメント
    • ブロックコメントを計測するには、考慮しなければいけない点が意外と多いため今回は省きました。

以上の仕様を削ることで、300行のコードになり実装の練習として気軽に使えるくらいの規模感になりました。それでは簡単な仕様を説明していきます。

go-flagsパッケージを使ってターミナルからの入力を受け取る

goclocではjessevdk/go-flagsというサードパーティパッケージを使って、ターミナルからの入力を受け取ります。Goには標準でflagパッケージもあるので、こちらを使っても実装できるでしょう。他にもたくさんのサードパーティパッケージがあるので比較してみても面白いかもしれません。

指定されたディレクトリ配下の全てのファイルを取得するgetAllFilesを実装する

filepath.Walkを使って指定されたパス配下のファイルを探索していきます。--match-dが指定されていれば、この時に正規表現に一致しないファイルを省きます。

ファイルのステップ数を計測する

ステップ数を計測してる実態はAnalyzeReader()です。このメソッド内ではbufio.Scannerを使ってファイルを読み込み、ステップ数をカウントします。この時に空白行のスキップやコメント行数のカウントも行います。私のリポジトリではブロックコメントをカウントする処理を省いていますが、追加で実装してみるのも良いでしょう。

GoのI/O処理について、もっと深く知りたければ以下の記事で丁寧に解説されているのでオススメです。
https://zenn.dev/hsaki/books/golang-io-package/viewer/intro

学べる事

  • CLIツールの作り方
    • GoでCLIツールを作れば、バイナリを配布するだけで他の人も実行可能になるので便利です。作り方を覚えておくと、いつか役に立つでしょう。
  • ファイルの取扱い
    • プログラムを書くときにファイルを扱わなければいけない場面は多いと思います。新しく覚えた言語でファイルをどう扱うのかは知ってくと便利です。

他にもCLIツールを作りたい方には、メモ管理ツールであるmattn/memoなどがコード量も少なくオススメです。
https://github.com/mattn/memo

google/go-github

https://github.com/google/go-github

概要

エンジニアにとっては馴染み深いGitHub APIのGoクライアントです。作るものについての背景知識を覚える必要がないので、練習にとっては良い題材だと思っています。しかし、Github APIはエンドポイントが多いこともあり、コード量が多いです。何か1つのエンドポイントを叩けることを目標に開発してみるのが良いでしょう(例えば、リポジトリ一覧を取得するエンドポイント)。

仕様

ここでは、実際にHTTPリクエストを送る*github.Client.Do()の中でどんな処理をしてるのかを見てみます。どのエンドポイントを叩く際も、この*github.Client.Do()メソッドでリクエストを送っています。

APIレート制限の確認

if err := c.checkRateLimitBeforeDo(req, rateLimitCategory); err != nil {
	return &Response{
		Response: err.Response,
		Rate:     err.Rate,
	}, err
}

*github.Client.Do()内で実行される*github.Client.BareDo()で最初にAPIレート制限に達していないかを確認します。この処理は*github.Client.checkRateLimitBeforeDo()で行われており、レート制限に達している場合はHTTPリクエストを送る前にエラーを返しています。レート制限の確認は、GitHub APIからのレスポンスに付随するX-RateLimt-xxxxヘッダーを元に計算されています。
https://docs.github.com/ja/rest/overview/resources-in-the-rest-api#rate-limiting

HTTPリクエストの送信

resp, err := c.client.Do(req)

go-githubの内部でHTTPリクエストを送信しているのは、Goの標準パッケージnet/http*http.Client.Do()メソッドです。*http.Client*github.Clientの初期化時に引数として渡しています。

client := github.NewClient(&http.Client{})

golang.org/x/oauth2パッケージの*http.Client実装を使うことで、OAuth認証を通じてprivateリポジトリのデータ取得などが可能になります。

ts := oauth2.StaticTokenSource(
	&oauth2.Token{AccessToken: "GITHUB_ACCESS_TOKEN"},
)
tc := oauth2.NewClient(oauth2.NoContext, ts)
client := github.NewClient(tc)

HTTPリクエストのエラーハンドリング

if e, ok := err.(*url.Error); ok {
	if url, err := url.Parse(e.URL); err == nil {
		e.URL = sanitizeURL(url).String()
		return nil, e
	}
}

HTTPリクエストのエラーが*url.Errorだったらe.URLのシークレット情報をダミーにすり替える処理(sanitizeURL())をしています。
エラータイプによって複数の分岐を作りたい時は、switchを使うと良いでしょう。

レスポンスの確認

CheckResponse()の中でレスポンスの確認をしていきます。
HTTPリクエストを送った際に、レスポンスのステータスが4xxでも5xxでもHTTPリクエストとしては成功しているため、*http.Client.Doからエラーは返ってきません。そのためステータスコードを見て、適切にエラーハンドリングする必要があります。
go-githubではswitchと使ってエラーハンドリングをしています。

switch {
case r.StatusCode == http.StatusForbidden && r.Header.Get(headerRateRemaining) == "0":
	return &RateLimitError{
		Rate:     parseRate(r),
		Response: errorResponse.Response,
		Message:  errorResponse.Message,
	}
// ...
}

jsonのデコード

json.NewDecoder(resp.Body).Decode(v)

最後に*github.Client.Do()に戻り、レスポンス結果のjsonを構造体にデコードします。デコード先の構造体は*github.Client.Do()を呼び出す際に引数として渡しています。

Goに限ったことではないですが、HTTPクライアントをしっかり実装しようと思うと意外と考慮点が多いことに気がつきます。新しい言語を学んだ際は、一度HTTPクライアントを作ってみるのも良い練習になると思います。また、HTTPクライアントの実装に関しては2020年Go Conferenceのこちらの発表が参考になります。

学べる事

  • HTTP Clientの実装

書籍

最後に紹介するのは、Go関連で読んで良かった書籍です!Goの書籍は良書が多くとても勉強になっています。今回紹介する書籍は、サンプルコードが掲載されており、手を動かしながら学べる書籍です。

みんなのGo言語

https://www.amazon.co.jp/dp/4297107279
本書はサブタイトルにもある様に、現場で使える実戦テクニックがまとめられた書籍です。開発ツールやテスト、ベンチマーク、データベースの取り扱いなどに触れられています。個人的にはテストに関する章がとても参考になりました。

Go言語でつくるインタプリタ

https://www.amazon.co.jp/dp/4873118220
Goを使ってMonkeyというインタプリタ型言語のREPLをフルスクラッチで実装する書籍です。Lexer、Parser、ASTなどの説明から実装まで盛り込まれており、インタプリタ型言語がどの様に動いているのかを理解できてとても面白いです。実装についてもGoの標準パッケージしか用いておらず、テスト駆動で開発を進めていきます。単純にGoの勉強にもなるのでオススメです。

自分は読めてませんが、本書の続編としてMonkeyのコンパイラを開発する書籍もある様です。こちらはまだ翻訳されていない様でした。
https://www.amazon.com/dp/398201610X

Goならわかるシステムプログラミング

https://www.amazon.co.jp/dp/4908686033
大学などでシステムプログラミングを学ぶ際はC/C++を使うのが一般的だと思いますが、それをGoを使って解説している書籍です。普段使い慣れているGoで解説されることで、とても理解しやすかったです。コンピュータサイエンスの勉強にもなりますが、io.Writeio.Readerの取り扱い、ファイル処理、並列処理など実践でも役立つ知識が詰め込まれているのでオススメです。

まとめ

以上、Goを学ぶために使った資料を紹介してきました。優良な資料やOSSが多く、学びやすいのもGoの良さだと思いました。

Discussion