Open7
詳解Go言語Webアプリケーション開発

詳解Go言語Webアプリケーション開発を読んで気になった事をメモ。

CHAPTER01 Goのコーディングで意識しておきたいこと
Goの誕生理由
Googleの課題
- ビルド時間が長い
- 同じ機能を実現するための表現方法がプログラム間でばらつきがある
- 自動化ツールの作成が困難
- バージョン管理やバージョンアップのコストが大きい
- マルチコアプロセッサ、ネットワークシステム、大規模計算クラスタやWebプログラミングモデルで開発する際に生じる問題
Goが目指したこと
- 動的型付けインタプリタ言語が持つプログラミングのしやすさ
- 静的型付けコンパイル言語が持つ効率性と型安全性
- ネットワークプログラミング・マルチコアプログラミングを容易にする並行処理の書きやすさ
- 大規模システムや大規模開発チームにおける効率的なプログラミング
- シンプルな言語機能
迷ったらシンプルを選ぶ
- Goで開発する場合「シンプルであるか」を判断基準に置くとよい

CHAPTER02 contextパッケージ
アンチパターン
- 構造体のフィールドに
context.Context
の値は含めない- その構造体のスコープが曖昧になるため
- 関数の追加引数となる値を含めない
- 筆者は「関数・メソッドのロジックに関わる値はcontextで渡すのではなく、その値を関数・メソッドに渡すべき」と考えている
- また、contextに関数・メソッドに必要な情報を突っ込むと、シグネチャから関数・メソッドが必要な情報が判断できなくなる
- ただし、リクエスト元のIPアドレスなどの情報はエラーログ出力などに有用 & 処理ロジックそのものに影響はない事が多いので、 contextで引き回す設計もあり
context.Context型の情報はサーバー間で伝わるか?
- 結論: (基本的に)伝わらない
- 理由: context.ContextはGo言語独自の概念であり、HTTPの仕様で定義されている物ではないため
- 回避策:HTTPヘッダーに埋め込んで渡す
段階的にcontext.Contextをアプリケーションへマイグレーションしていくには?
- 結論:
context.TODO()
を使う - 理由:
context.TODO()
は非nilの空のcontextであり、処理にほとんど影響を与えない。- よって将来的にcontextを導入したいが、まだ呼び出し元や先が対応していないケースでは、一旦この
context.TODO()
を実装しておく事で、段階的にマイグレーションできる - 最終的には何等かの有効なcontextに置換する
- よって将来的にcontextを導入したいが、まだ呼び出し元や先が対応していないケースでは、一旦この

CHAPTER03 database/sqlパッケージ
sql.Open
を呼ぶのは一回だけ
- リクエストの度にOpen→Closeするとパフォーマンスに悪影響
- database/sqlパッケージは内部で自動的にコネクションをプールしてくれる
- よってアプリの起動時に一回だけ
sql.Open
を呼び、終了時に一回だけsql.Close
を実行すればOK
XxxメソッドとXxxContextが存在する場合は、Contextの方を使用する
- 前者はまだcontextパッケージが追加されていない時代に実装され、今も後方互換性を保つために残されている
- 基本的に現代のGoでのWeb開発ではcontextパッケージを利用して、コネクションなどの管理をgoroutine間に伝搬させるのが主流の様なので、Contextの方を使う
sql.ErrNoRowsが発生するのは*sql.Rowが返ってくるメソッドだけ
-
sql.ErrNoRows
はクエリで検索した結果に該当するレコードが0件の場合に発生する - しかし、database/sqlパッケージのメソッド中でもこのエラーが返ってくる物とそうでない物があるので、「クエリで検索し、sql.ErrNoRowsが発生した場合はエラーハンドリングする」コードを実装を行う場合は使用するメソッドがこのエラーを発生させるのかを確認の上実装する
トランザクション処理を行う際は、deferでRollbackメソッドを実行する
- トランザクション中にエラーなどが発生し、Rollbackしないまま処理が終わってしまう事を避けるため、必ずdeferでRollbackメソッドを呼び出す様にし、Rollback忘れをなくす
- なお、RollBackメソッドは正常にCommitされた場合やcontext経由でキャンセル済みのトランザクションに対してはRDBMS上でRollbackされないので、正常ケースで実行されてもOK

CHAPTER04 可視性とGo
public/privateとexported/unexportedの違い
- Goにおいて先頭が小文字の構造体名やフィールド、パッケージ変数はパッケージ内部からのみ参照できる
- このルールは標準パッケージを使用する際にも適用される
- よって、JSONやDBのレコードを格納するための構造体はexportedで定義する必要がある
- そうしないと標準パッケージから構造体にアクセスできないため

CHAPTER06 Goとオブジェクト指向プログラミング
Goはオブジェクト指向のプログラミング言語なのか?
- 結論: YesでもありNoでもある
- 理由: 本書では以下の3大要素を満たす言語をオブジェクト指向言語と定義しているが、Goはその一部しかサポートしていないため
- カプセル化
- 多態性
- 継承
継承について
- Goは継承をサポートしていない
- 構造体を入れ子にする事で擬似的に置換の様な機能を実現する事は可能(この手法を埋め込みと呼ぶ)
- もっと細かく言うと、インターフェース継承はできるが、実装継承はできない
- 継承よりもコンポジション

CHAPTER08 エラーハンドリングについて
前提
- Goには所謂try-catch-finallyの様な例外機構は存在しない
- したがって、エラーは他のintなどと同じく、ただの値として取り扱う
- スタックトレースもない
errorについて
-
fmt.Errorf
を用いる事で新たなエラーを生成する事ができる - ただし、そのままだと元のエラーの情報が失われてしまうため、元の情報を残したい場合は
%w
でラップする -
error.Is
を使うと、ラップされているエラーチェーンの中に特定のエラーが含まれているかを判定できる -
error.As
を使うと、エラーを指定した型へのキャストを試みる-
error
インターフェースのまま処理すると、Error
メソッドしか利用できず自作した独自のエラー情報が使用できないため
-
panicについて
- 想定外の異常状態の場合は、errorの代わりにpanicを用いる事が一般的
- panicがハンドリングされない場合、プログラムは異常終了する
- ライブラリとしてパッケージを実装する場合、パッケージ内部で発生したpanicはパッケージ外部には伝搬させない様にする