フューチャー技術ブログ

Goの標準ライブラリのコードリーディングのすすめ

はじめに

2019年のアドベントカレンダーではGo言語がその7まで続いて 3 、盛り上がりを見せたのは記憶に新しいです。当社でもGoを扱う案件が増えてきて、社内でもいろいろなメンバが趣味や業務でGoを書いています。日々新しいGopherが生まれています。

さて、GoをはじめようとA Tour of Goをやってみたが、Goらしいコードがわからない、Go言語をより深く学びたい、という方も多いのではないでしょうか。Go言語は一部を除いて言語自体がGoで書かれており、標準パッケージはGoらしい書き方のお手本とも言われたりします。

標準パッケージのコードリーディングをしてみたいという方も多いのではないでしょうか?しかし社内で標準パッケージのコードリーディング会を取り組んでいきたいけど難しそう、以下のような疑問があるかもしれません。

  • 進め方がわからない
  • どのパッケージを読んだらいいかわからない
  • 1回あたりの粒度感はどれくらいが妥当なのかわからない

そこで本記事では社内で取り組んでいるGoの標準コードリーディング会の取り組みを紹介しながら、上記のような質問に答えていきたいと思います。

なおコードリーディング自体のテクニックはインターネット上でもたくさん紹介されていると思いますので、本記事では省略します。

目的

そもそもこのGoの標準ライブラリのコードリーディング会をどういった目的で実施しているか説明します。

  • 標準パッケージの使い方を知る
    • GoDocを読んで仕様を知る
    • テストコードを読んで仕様を知る
  • Goらしいコードの書き方を知る
    • コードを読んでGoの標準ライブラリのコードの書き方を知る
  • Goのテストの書き方を知る
    • テストコードを読んでGoのテストの書き方を知る

コードリーディング会では、コードを読むだけではなく、GoDocを読んだり、テストを読んだりして、そもそものパッケージの仕様や使い方をおさらいしています。例えばコマンドラインでのフラグを扱う flag パッケージは -myflag=x などとしてフラグ名を引数で指定することが多いと思います。ところがテストを見ると --myflag=x という -- という形式でも引数を受け取けられることが分かります。仕様を確認した上で実装を読むことで、仕様を満たすためにGoの標準ライブラリはどのように実装しているのか、という実装そのものに着目することができます。

また、参加しているメンバは初心者からベテランまで幅広く参加しています。最大公約数的な形をとって、なるべく多くのメンバにとって役に立つようにしています。

読んでいるパッケージ

まず社内で読んでいるパッケージは以下です。全部で 12 回実施する予定です。現在(2020/03/02)のところ path パッケージまで実施しました。

# パッケージ名
1 io
2 errors
3 hash/maphash
4 context
5 flag
6 path
7 testing
8 iotest
9 sort
10 net/http (client)
11 net/http (server)
12 database/sql

パッケージの選定

そもそも、どのパッケージを読んだらいいんだろう?という疑問があると思います。上記のパッケージを選定した方法を紹介したいと思います。

読むパッケージの選定は有志のメンバ数名で一緒に決めました。

まずパッケージのステップ数を見てスクリーニングしています。gocloc というツールを使うと以下のフォーマットで簡単にステップ数を確認することができます。

$ gocloc archive
-------------------------------------------------------------------------------
Language files blank comment code
-------------------------------------------------------------------------------
Go 21 782 1511 9099
Plain Text 2 0 0 2
-------------------------------------------------------------------------------
TOTAL 23 782 1511 9101
-------------------------------------------------------------------------------

...

$ gocloc io
-------------------------------------------------------------------------------
Language files blank comment code
-------------------------------------------------------------------------------
Go 12 353 480 2045
-------------------------------------------------------------------------------
TOTAL 12 353 480 2045
-------------------------------------------------------------------------------

...

$ gocloc runtime
-------------------------------------------------------------------------------
Language files blank comment code
-------------------------------------------------------------------------------
Go 577 12182 21862 75117
Assembly 177 5102 7567 34766
C 47 599 498 2273
Python 1 135 103 366
Markdown 1 71 0 240
C Header 8 50 156 217
BASH 1 1 3 11
Makefile 1 1 3 1
-------------------------------------------------------------------------------
TOTAL 813 18141 30192 112991
-------------------------------------------------------------------------------

runtime パッケージは極端な例ですが、コードの読む前に明らかにやばい、、、ということがわかるわけです。このような要領でまずは $GOROOT/src/ 配下にあるパッケージの一覧に対して gocloc を実施し、パッケージの全体感を把握しました。 2

12回やるっ!というのは決め打ちです。標準パッケージだけでも200近くあって 1、もちろん全部を取り上げて読むことは難しいです。かといって目標を決めないと達成感を得られにくいと考えています。Futureでは技術書の社内輪読会を実施しています 4 が、技術書の場合は一冊を技術書を読了することで達成感を得ることができます。しかし、標準パッケージのコードリーディングの場合はどの範囲をとりあげるかは任意です。

そのため今回は予め12回は実施すると決め打ちしてパッケージを選んでいきました。キリの良い10回を考えていたのですが、読みたいパッケージがあり、最終的に少し増やして12回になりました。

次に具体的に読みたいパッケージを選びます。先程の gocloc で得たステップ数を踏まえながら、みんなで議論しています。例えば io パッケージはGoらしいインターフェースの使い方を学べるからおすすめ、とか hash/maphash は Go1.14 で新しく追加されるパッケージだからみんなで確認しておこう、やっぱり net/http のHTTPサーバ、クライアントの実装は読んでおきたいよね、じゃあダメ押しで database/sql もやりましょう、みたいな感じです。

標準パッケージ以外のパッケージ(たとえば golang.org/x などです)も検討したのですが、まずは標準パッケージのみに絞って実施することにしています。

運営方針

開催頻度/時間

発表するメンバと参加するメンバも負担にならないように、開催頻度と時間は以下のようにしています。

  • 開催頻度
    • 週1回
    • 毎週水曜日の夕方
  • 時間
    • 1回あたり45分程度

進め方

  • 発表
    • 予め決めた2名がまとめる
  • 資料
    • 非公開のQiita記事やGitHub Gist、Google slideにまとめる

各パッケージごとに発表するメンバを2名決めました。io パッケージなら A さんと B さん、errors は C さん、D さん、が内容をまとめて発表する要領です。事前に決めた2人が宿題形式でコードを読んで資料にまとめています。まとめた資料とGoのソースコードをもとに40分程度(1人あたり20分程度)で発表しています。パッケージのコードすべてを取り上げるのではなく、コアな部分や興味がある部分を取り上げて発表しています。2人で取り組むと、同じコードを読んでいても気づかなかったり、面白かったポイントも異なり、補完できるのでオススメです。

とはいえ進め方はやっぱり難しいと感じています。いろいろなバックグラウンドを持ったメンバが参加しているので、参加したメンバが満足いくような会になっているかは悩みポイントです。

このあたりは勉強会の運営に長けているメンバがアンケートを取ってくださり、フィードバックをもらいました。アンケートの一部を抜粋します。

コードリーディング会の雰囲気

参加しているメンバは、はじめてGoをさわったメンバ、A Tour of Goはとりあえずやってみたメンバ、業務でGoをガンガン書いているメンバ、趣味でGoを書いているメンバ、Goに関する本を出版しているメンバなど様々です。

また参加方法も、会議室から参加しているメンバ、リモートから参加しているメンバなど様々です。

発表内容を聞きながら、気になることや良くわからなかったこと、疑問に思ったことなどわいわい議論しています。

やってみて良かったこと

Goのテクニックを学べる/議論できる

以下は参加している/発表しているメンバが紹介していた内容の一部です。実用的なテクニックや、ライブラリ独特なテクニックまでいろいろあります。

  • インターフェースがコンパイル時に型を満たしているかチェックする方法
  • [0]func() のような比較不能な型を構造体のフィールドにもたせると構造体が == 演算子で比較ができなくなる
  • //go:linkname ... とすると外部パッケージのプライベートな関数が呼び出せる
  • クローズされているチャネルを生成するために init 関数でチャネルをクローズしている
  • for range 構文はチャネルにも使える
  • 組み込み関数の close でチャネルをクローズすると、複数のゴルーチンが一斉に同じチャネルを Read することができる
  • context パッケージでのインターフェースを部分実装方法
  • contextWithDeadline 関数で time.AfterFunc 関数で使われている
  • map に値が存在するかのチェック、第二引数 ok じゃないこともある
  • flag パッケージや path パッケージでパース処理は丁寧に場合分けしたり、一文字ずつ文字列を解析したり細やか
  • テストで異常系と正常系を分けたほうが、テストコードがシンプルになりそう
  • 異常系のテストの命名で bad という変数名を使っていてエラーになることが命名から明らか
  • 標準出力を使うパッケージ bytes.Buffer でキャプチャして buf.String() で結果を取得してテストしている
  • テストで標準出力が不要であれば ioutil.Discard で捨てれば良い
  • ioutil.Discard/dev/null っぽいけどGoのコード上でのアナロジー
  • iota のイディオムよく見る

Go以外のテクニックも学べる/議論できる

  • bytes/buffer.go の Read では最小のバッファサイズが 512 バイトなんだけど、これって何?
    • HDD の 1 セクタの容量 512 バイトにあわせているのではないか
  • flagExitOnError のとき os.Exit(2) を呼び出しているけど、2 って何?
    • Bash の 終了ステータスビルトインコマンドの誤用 のことを指しているのではないか

まとめ

社内で取り組んでいるコードリーディング会の内容と運営方法を紹介しました。Goの標準ライブラリのコードリーディング会は標準ライブラリを学べ、またGoらしいコードを学べる良い会です。本記事がみなさんの会社でコードリーディング会を実施するときの参考になれば幸いです。

Goに関連した連載企画がありますので宣伝です。