🦅

Enablement Bootcamp for Gopherizing を受講して学んだこと

2023/10/11に公開

初めに

Enablement Bootcamp for Gopherizingの受講から学べたこと、またそれをきっかけに取り組んだことなどを一つのアウトプットとしてこの記事にまとめました(講義の内容だけでなく、自分の解釈や追加情報も入れていたりします)

Enablement Bootcamp for Gopherizing とは?

ナレッジワークさん主催のGo実務未経験者向けのブートキャンプです。
以下に詳しい内容が掲載されています。
https://note.com/knowledgework/n/n4d7b97ff802c

4日間+発表会というスケジュールでした。
Day1:Goらしいコードの書き方
Day2:テスタブルなコードの書き方
Day3:実践的なエラー処理
Day4:gRPCを使ったAPIサーバの作成
発表会(これから):このブートキャンプで学んだこと、これきっかけで取り組んだことを発表し合う

受講のきっかけ

  • プライベートでGoを学習はしていたが、講義の内容的に非常に実践的で自分だけではカバーのしにくい新たな視点を得られそうだったから
  • 講義してくださる方が、tenntennさんとmayahさんという日本でトップレベルのエンジニアだったから
  • ナレッジワークにも少し興味があったから

個人的に学びになったポイント

以下で得られた学びを講義ごとにまとめてみました。

Day1: Goらしいコードの書き方

Go Style Guide に掲載の原則を守れば、自然とGoらしいコードになる

https://google.github.io/styleguide/go/guide#style-principles

愚直なコードが好まれる

誰が書いても大体同じになるので、結果的に全体の可読性が向上し保守しやすくなる。
逆に1行で完結するようなクレバーなコードは微妙。
tenntennさん曰く、月末や年末の疲れている時でも読めるのがGoらしい(笑)

標準機能で実装することを重要視している理由は、新たに覚えることが少なくなること

サードパーティーで有名なものがあるのとデフォでで備わっているのとでは全然違うらしい。
標準で備わっていれば基本的にエンジニアは皆同じものを使う。その結果、エンジニアはその度に新たな学習を強いられないで済む。

パッケージの分け方に銀の弾丸は存在しない

ベストプラクティスなど鵜呑みにせず、以下の方向性で自分で考えて分けていくことが大切。

  • 学習コスト
  • 高い可読性
  • 成長とリファクタリング

設計や実装の悪さは名前に出る

リーダブルコードなどから命名が大切なのは知っていたが、設計や実装の良し悪しが表出するといった観点は自分にあまりなかった。

パターンマッチングから深い理解へ

パターンマッチング的なやり方で書き方(How)をその都度覚えるのではなく、言語仕様やライブラリ、文化や背景など(Why)を深く理解していくことが重要。そうして初めて、その言語を高いレベルで扱えるようになる。最初はパターンマッチングでも仕方ないので、少しずつHowからWhyへ転換していけばよい。

Day2: テスタブルなコードの書き方

testingパッケージは失敗だけ目立つように作られている

-vオプションつけなければログすら出ない。
巨大なコードの場合、成功やログまで一々アナウンスしていたら、一番重要な「失敗」が埋もれてしまうため。

テスティングフレームワークを使うことが一般的でない理由

ミニ言語などは作らずにテストなども純正Goで書くことで、誰が読んでも分かる&新たな学習を強いられない状態にしたいから。

テーブル駆動の目的はテストケースとロジックを分けること

ケースとロジックを分離することで、以下のようなメリットを享受できる。

  • テストケースを追加しやすい(ロジックを変更しなくて良い)
  • テストケースを過不足を確認しやすい
  • 全体的に可読性が高まる

テストケースの注意事項

  • テストケースはなるべく1行で書く
  • 目的に関係のないテストケースは含ませない

Day3: 実践的なエラー処理

エラーには対処に「必要な情報」と「必要十分な原因」を含める

以下がそれにあたる。

  • どこで?
  • 誰の原因?
  • エラー再現に必要な情報

エラー発生経路を提示する

上記の「どこで?」にあたる。

方法1:エラーを常にラップする

  • メリット
    • 特別なライブラリが不要
  • デメリット
    • 非常に面倒で、しかも忘れやすい
    • エラーメッセージが冗長になりがち
    • 独自型と一緒に使うのが少し大変

方法2:スタックトレースをつける

  • メリット
    • 発生時にのみ処理すればいい
  • デメリット
    • ライブラリを使わないと面倒
    • 定番のライブラリが不在
    • スタックトレースの付与ミスや多重付与

誰の原因かを提示する

誰の原因かは、大きく以下の3つに分類でき、対処の仕方が少しずつ違う

1. 利用者
例:期待していないnilや入力範囲外の値を関数に渡した
対応方法:契約違反を利用者に通知し、利用者に対処を委ねる

2. 開発者
例:配列の範囲外アクセスをした、メモリが足りなくなった
対応方法:何が起こったのかを利用者に報告する、それもできない場合は実行を停止する

3. 第三者
例:外部サービスを正当に呼び出したのに失敗した
対応方法:利用者に対処を委ねる

*ここで言う利用者は、プログラムを実行したユーザーだけでなく、ライブラリの利用者、関数の利用者(呼び出し元)のことも指している

エラー処理の委ね方

errors.Iserrors.As などでエラーのより詳しい情報を得て処理を分岐できるようにしておくと、呼び出し側がエラー内容をもとに処理を決定できる(呼び出し側にうまく処理してもらうにはエラーを値や型で予め定義しておくべし)

fmt.Errorf("error: %v", err) のように作ると、エラー内容での分岐が難しくなるが、エラーの存在・非存在および表示にのみに興味がある場合はこれでも良い

実行の停止方法
panic によって実行を停止する。
一応 recover() によって panic によって停止された実行を復活できる。

エラーの表示方法

見る人によって表示するべきエラーの内容は異なる。
特にウェブアプリケーションの場合、利用者と開発者の両方がエラーを見る可能性があるのでエラーを出し分ける必要がある。

利用者
利用者orシステムの何が悪かったのかがわかれば良い(特に入力の何が間違っているのか)。
スタックトレースを表示されても利用者は困る場合がある。

開発者
スタックトレースなど間違っている箇所や経路の情報が重要。

Goと他言語のエラーの違い

他言語の場合は例外を用いてエラー処理を表現するが、Go言語では返り値にエラーを含ませ、明示的にエラー処理をする。
ちなみにGoがそのような選択をした理由は、例外でエラー処理をするのは難しいから。

以下のコードで例外が起こり得るのはどこか?

struct S { ... };

S f() {
   S x = g() + h();
   return x;
}

例えば、以下が考えられる。

  • g() の呼び出し
  • h() の呼び出し
  • g() + h() の足し算
  • S x = g() + h() で構造体 S のコンストラクタが呼ばれたとき
  • g() や h() が一時オブジェクトを返しこのデストラクタが呼ばれたとき
  • return x で x が返り値としてコピーされるとき
  • return x のあと x のデストラクタが呼ばれるとき

まとめると、例外がどこで発生するのか理解するのが非常に難しいので try-catch で囲うのも難しいし、囲んだとしてもどの例外が発生するのかも全くわからない。
逆に Go のように明示的にエラー処理すると、むしろ正しくエラー処理できる(という思想が近年の言語では多いようである)

Day4: gRPCを使ったAPIサーバの作成

Connetについて

gRPC互換のHTTP APIを構築するためのフレームワークで、以下の特徴を持つ。

  • Connect, gRPC, gRPC-Webの3つのプロトコルをサポート
  • net/httpパッケージのエコシステムが使える(connect-go)
  • bufの開発元が作っているためエコシステムの完成度が高い

https://connectrpc.com/

Evansについて

gRPCをコマンドラインから確認できるツールで、以下の特徴をもつ。

  • Server Reflectionに対応(-rオプション)
    • Connectで利用するには、connectrpc.com/grpcreflectパッケージを利用する
  • REPLモードがある
  • 補完が効く

https://github.com/ktr0731/evans

senarigoについて

シナリオテストを行うためのライブラリで、以下の特徴をもつ

  • gRPCとREST APIに対応している
    • Connectには非対応
  • プラグイン機構が使える
  • YAMLでシナリオテストを記述

https://github.com/zoncoen/scenarigo

このブートキャンプをきっかけに自分で学んでみたこと

Goの文化や言語仕様の深掘り

講義の中でこれらの重要性が説かれていたので、以下のGoDocを熟読中(まだ未完了)。。。
https://go.dev/talks/2012/splash.article

Goという言語がなぜ生まれたか、Goが大切にしている思想、ビルドがなぜこんなにも早いのか、単純明快な言語にするために行った工夫、継承ではなくコンポジションである理由、などなど事細かく書かれていた。
このドキュメントを読むことで、GoのバックボーンやGoと他言語との間にある差異やその理由など、普段曖昧にしてしまいがちな内容を学べると思う。

ほぼゼロからgRPC周りの知識を吸収

day4がgRPCに関する講義だったので、予習としてgRPCやその周辺の技術情報を学んだ。
正直gRPCに関してほとんど知識を持っていなかったので、そもそもRPCとは何か?IDLとは何か?HTTP/2とは何か?からスタートした。
これらの学習はこのZenn記事にアウトプットとしてまとめた。

gRPCを用いて簡易アプリを作ってみる

gRPCという技術が個人的にめちゃ面白くて、実際に使ってみたくなった。
そこで、かなり簡易ではあるがクイズアプリを作ってみた。
https://github.com/wada1355/quiz-app_grpc

以下アプリの概要。

  • CLI上で完結するインタラクティブな簡易クイズアプリ
  • gRPCの双方向ストリーミングで出題と回答のやり取りを実現している
  • 中学生レベルの日本史クイズを50問ほど用意

以下クイズの流れ。

  1. 「何問出題しますか?」と聞かれるので、自分が回答したい問題数を送信
  2. 1問ずつ出題され、その度に回答を入力していく
  3. 全ての問題に回答し終えたら、最終結果が送信される

Discussion