l0w.dev

GoCon で喋りました & godoc の限界について


GoCon で喋りました

先日開催された Go Conference 2019 Autumnmsgraph.go のお話をさせていただきました。 だいぶニッチなトピックだったので、はたしてどれだけの方が興味を持ってくれるだろうと不安だったのですが、 思ったより多くの方に来ていただけたのでほっとしました。来場いただいたみなさま、ありがとうございました。

本番ではあいにくネットワークの調子が悪く、YouTube に置いたデモ動画がまともに再生できないというトラブルがありました。 ちょっといやな予感はしていたのですが、それが的中したようです。オフラインでも支障がないよう対策をしていくべきでした。

あとはマイクに対する喋り方がへたくそでだいぶ聞き苦しい感じになってしまったんではと思います。 あのマイクはスタンドにつけたままにせず手に持って話せばよかったですね。 次回からは気をつけていきたいと思います。

godoc の限界について

さて、今回のお話は Microsoft Graph というトピックこそニッチではありましたが、 Go 言語でコード生成したり API を叩くライブラリを作ったりされている方には いろいろと共感いただける内容だったのではないかと勝手に思っています。

しかしながら 20 分という短いセッションではいくつかの要点を手短に話すだけで終わりになってしまいましたので、 これから何回かにわけて、それらを補足する記事をお送りしたいと思います。初回はドキュメントの生成についてです。

セッションでも触れていますが、msgraph.go は膨大な API 仕様を元に Go のコードを自動生成するため、 ひとつのパッケージのフォルダに大量のソースファイルが生成されます。

生成したソースファイルが格納されているのは次のフォルダになりますが、 ファイルの数が多すぎるため GitHub でもすべては表示できない状況です。

これらフォルダのパッケージのドキュメントは、 オンラインドキュメント生成サービスとして有名な godoc.org では次のページで参照できます。

両者とも一見正しく生成されているようなページが表示されますが、 よく調べてみるとだいぶドキュメントが欠落していることがわかります。 たとえば API エンドポイントの根っこになる GraphServiceRequestBuilder という struct の定義があるはずなのですが、 ページ内検索してみても見つかりません。

そこで godoc.org のソースコードを https://github.com/golang/gddo で見てみると、 godoc.org は Git リポジトリをクローンするのではなく、 GitHub の API で個々のソースファイルを HTTP で取得していることがわかります。 そして github.go で GitHub の Contents API というのを呼んでおり、 これがまず 1000 個までしかファイルリストを返せない仕様なので、 それできっと不完全な結果になってしまっているのだろうと推測しました。

もうひとつ有名なオンラインドキュメント生成サービス gowalker.org で msgraph.go のドキュメントを生成してみると、こんどは socket: too many open files というあやしげなエラーメッセージが表示されます (DDoS になりそうなのでリンクは書きません)。

https://github.com/unknwon/gowalker からたどってコードを調べてみたところ、 こちらは GitHub の Tree API というのを使っており、 無事に msgraph.go リポジトリ内の全ファイルのリストを取得できているようでしたが、 実際のファイルをダウンロードするところは次のようになっていました。

https://github.com/unknwon/com/blob/757f69c95f3e6e3ef823a57bfc3b4ca50804523e/http.go#L158-L178

// FetchFiles fetches files specified by the rawURL field in parallel.
func FetchFiles(client *http.Client, files []RawFile, header http.Header) error {
	ch := make(chan error, len(files))
	for i := range files {
		go func(i int) {
			p, err := HttpGetBytes(client, files[i].RawUrl(), nil)
			if err != nil {
				ch <- err
				return
			}
			files[i].SetData(p)
			ch <- nil
		}(i)
	}
	for _ = range files {
		if err := <-ch; err != nil {
			return err
		}
	}
	return nil
}

引数 files は取得すべきファイルの URL が入った配列です。 1000 個以上の URL が渡されて 1000 以上の goroutine が一斉に走って GitHub からファイルをダウンロードしようとします。 ふつうの Linux だと fd の ulimit は 1024 とかなので、あ、こりゃだめそうだということがわかりますね。 まともに動かすには goroutine 数を制限したスレッドプールにするべきところでしょうか。

そもそも godoc.org も gowalker.org も、正しく動作するようになったらこんどは GitHub API の rate limit にすぐにひっかかるでしょうから、 両サイトで msgraph.go のサイズのドキュメントを生成してもらうのは現実的ではなさそうだということがわかりました。

代替案の検討

このような状況で msgraph.go のまともなドキュメントを生成するための現実的な対応としては、次のようなものが考えられます。

  1. ソースファイルの数を減らす
  2. パッケージを分割する
  3. ローカルでドキュメントを生成してどこかにアップロードして公開する

まず 1. ですが、これはあまりやりたくない手です。 GitHub 上で個々の型や定数の定義部分が参照しやすく、また更新の差分もわかりやすくなるように維持したいという要望があります。 まず思いつく Enum/Model/Request/Action のような大雑把な分割では それぞれが数万行のソースファイルとなって GitHub でまともに表示できなくなりますし、 もう少し細分化しようにも、納得感のあるうまい分割基準が思いつきません。

2. は msgraph パッケージを enum/model/request くらいに分割することです。 実はこれはいちど試してみたのですが、アプリを書くときにパッケージの区別が必要になって微妙だったのと、 beta 版の model がファイル数 1684 ですでに限界突破していたので、解決策にはなりませんでした。

3. がいちばん好ましいと思っているのですが、 問題は静的な HTML のドキュメントを生成できるまともな Go のツールが現状存在しないことです。 標準ツールの godoc でできることは builtin のライブラリのドキュメントもソースコードもすべて抱え込んだ web サーバなので、 それからひとつのパッケージのドキュメントだけ抜き出すとリンク切れだらけの使いにくいものができます。 Go のドキュメントは godoc.org がやはりいちばん慣れて見やすいので、 そちらと組み合わせて使えるドキュメントが生成できればよいのにと思います。

ということで妙案が思いつかないまま今に至ります。 godoc に対しては昔から issue もあげられており、

  1. のような静的 HTML ドキュメント生成手段は要望が多いと思うのですが、 あまりそのような方向の進化は考えられていないようにも見えます。

理想的にはコマンドラインから起動できて GitHub のリポジトリをクローンしてそこから 静的な HTML ファイルを吐き出すことができる godoc.org があればよいのでしょうか。 案外簡単な改造で実現できたりしないかなあとぼんやり考えています。

まとめ

GoCon のフォローアップ記事として、 巨大な Go パッケージのドキュメントについて悩んでいることを書きました。 次回は Microsoft Graph で使う JSON の扱いについて書きたいと思います。