読者です 読者をやめる 読者になる 読者になる

Go言語感想文

最近、敵情視察を兼ねた仕事ととしてGoでアプリケーションを書いていた。このアプリケーションがどんなものかはそのうちid:tagomorisさんがどこかで話すと思うけれど、このコンポーネントOSS化される予定はいまのところないので、そこで得た知見をここにまとめておくことにする。

GoroutineとChannel

さて、GoといえばGoroutineとChannelですね。

Goroutineはようするにスレッドなんですが、文法と実装の支援でより気軽に使えるのが他の言語との違いでしょうか。なので、Goroutineをどれだけほいほい使うべきかというコスト感覚を身につけることがとても大事な気がします。Rubyなどとは気持ちを切り替えていく必要があるでしょう。ぼくはまだ切り替えきれていません。

もう一つがChannelですね。これは端的にはメッセージキューです。 Goは前述の通り同時に動くマルチスレッド (Simultaneous Multithreading; SMT) を気軽に扱えるわけですが、七つの人類悪の一つであるマルチスレッドに丸腰で立ち向かっても無残な死が見えています。 そのための道具の一つがキューで、適切にロックを用いて作られた通信路を経由してデータをコピーすることで、わかりやすくかつ比較的効率よくスレッドをまたいでデータをやりとりすることができるわけです。(ちなみにRubyにもThread::Queueってのがあったりしますが、CRubyだとGVLがあるのでわざわざ使うことは少ないかも) とは言ってもそれなりにコストかかるんでしょう?って人はchansendの実装を眺めるとチャンネルごとのロックを取ってmemcpyしてるだけってわかるので、頑張って並列ハッシュとか使わなくていいんだって気分になれます。送受信にかかる時間は10~100ナノ秒くらいかな? とすると、話はいかに書きたいアプリケーションをGoroutineに分割し、間をChannelでつなぐかという話になります。そして、様々なデータは基本的にそれぞれのGoroutine内に閉じ込め、他のGoroutineはChannel経由でアクセスする。この構図、どこかで見たことがありますね?そうマイクロサービスの話と同じです。 Goでアプリケーション書くのは、このようなマイクロサービス的なコンポーネント分割というマクロな楽しみと、Cのコードを触っている時のような細かなデータの取り回しというミクロな楽しみが隣り合わせにあって、独特の感覚を覚えます。

テストについて

Goの標準パッケージにはtestingってのがあります。このパッケージの思想はGo の Test に対する考え方などで解説されています。「まぁ、一理はあるけど……」という感想の方が多いのではないでしょうか。「いやダメでしょきつすぎ」って反射的に反応したくなりますが、敬意を表して実際にtestingでやってみました。 いや、ダメでしょこれ。結局テストのメッセージなんて”A is expected but B”が9割で、これのメッセージをいちいち考えるとか決断力の無駄遣いですよ。日本人が英語でメッセージを綴るリソースが有限だという悲しい現実を考慮していない。ぼくは5個くらいassertionを書いたところで嫌になってあとはひたすら”A is expected but B”をコピペしまくりました。 まぁでも、それで致命的につらいかというと真面目にやるのを放棄してコピペすれば良いし、Goのfmt.Printfは%vと%#vが便利なので、なんとかなりはします。assert_equalの類以外に、タイムアウトをつけてチャンネルを読んだりするassertionメソッドが欲しくなって作りたくなるかもしれませんが、そういうときはruntime.Callerで呼び出し元の行数が取れるので、うまいことやるとよいです。 まとめると、正直testingはダメだと思うけど、とはいえなんとかはなるのでtestingで依存先を減らすのはありかも。

その他

  • log.Fatalは使わない カバレッジ上げづらくなる fatalに追い込まれるようなエラーはもっと上流で捌くべきなんだと思う
  • テストでlogが邪魔なときはlog.SetOutput(ioutil.Discard)で黙らせる ** 並列実行でうまくいかないのでloggerを持たせて個別にやった方がたぶんよい
  • 困ったときはgoto ← おまえほんとうにそれでいいのか
  • switchべんり ← おまえほんとうにそれでいいのか ** selectやswitchの中でbreakすると外側のforまで届かないのでbreak
  • Printf(“%#v”, v)べんり
  • error伝播させたいときは、pkg/errorsのcause使う
  • とすると、エラー用構造体は作りまくった方がよい ← 本当に?
  • sync/atomicとかあるけど、new(expvar.Int)とかでmutex付き変数が簡単に作れる、便利
  • Goはnull安全ではない←構造体のポインタを扱い始めると気になってくる

まとめ

えーっと、この記事の趣旨は「Goは21世紀のRubyistが触っておくべき言語なのか?」でしたっけ。Elixirとかをやっていないのなら、Guildの予習として一度Goで何か作ってみるとよいと思いました。作ると言ってもこういう悪い例じゃなくてchannelを使ったアプリを作ってみると、従来のスレッドがうぇいうぇい暴れながら大事な共有リソースをロックで守るみたいなのとは違う設計が体に染みいります。みなさんもGoをいじってRuby 3.0を準備万端で迎えられるようにしましょう!