Close 関数のエラーを無視しない(『効率的なGo』読書会より)
『効率的なGo』の読書会は無事に最終回を迎えた。 今回は,読書会最終回の範囲(11章 最適化パターン)から個人的に気になった部分を取り上げる。
Close 関数のエラーを無視しない
以下のサンプルコードを起点にする。
package main
import (
"fmt"
"os"
)
func doWithFile(filename string) error {
f, err := os.Open(filename)
if err != nil {
return err
}
defer f.Close()
// Perform file operations here...
return nil
}
func main() {
err := doWithFile("example.txt")
if err != nil {
fmt.Fprintln(os.Stderr, err)
panic(err)
}
}
注目するのは defer f.Close() の部分。
f.Close() メソッドはエラーを返す可能性があるが,このコードではエラーを捨てている。
この手のサンプルコードはよく見るし私も過去記事で書いたような気がするが,エラーハンドリングの観点からは悪いコードということになる。
ファイルアクセスではクローズ処理の際にバッファに残ってるデータをフラッシュする場合がある。 特に zip ファイルの書き込み処理とかでは終端レコードをクローズ時に書き込んだりするので,絶対にエラーを無視してはいけない。
一般的にクロージング・メソッドを含むパッケージや構造体ではクローズ処理時のエラーを適切に処理する必要がある。
だからといって defer を使わないのはもったいない。
今回の場合は以下のように書き換える。
func doWithFile(filename string) (err error) {
f, err := os.Open(filename)
if err != nil {
return err
}
defer func() {
if cerr := f.Close(); cerr != nil && cerr != os.ErrClosed {
err = errors.Join(err, cerr)
}
}()
// Perform file operations here...
return err
}
ポイントは2つ。
- 返り値を
err errorと名前付きにすること defer処理を無名関数func() { ... }()で囲み,内部で適切にエラー処理を行うこと
最近の Go は(errors.Join() など)複数エラーを記述できるようになったのでハンドリングが簡潔になった1。
よしよし。
最後まで読み切ってからクローズする
次のサンプルコードはこれ。 HTTP レスポンスデータを処理する関数である。
func doWithResp(resp *http.Response) (err error) {
defer func() {
if cerr := resp.Body.Close(); cerr != nil && cerr != os.ErrClosed {
err = errors.Join(err, cerr)
}
}()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("bad status: %s", resp.Status)
}
// Perform operations with the response
return err
}
net/http パッケージのドキュメントには冒頭に
The caller must close the response body when finished with it:
resp, err := http.Get("http://example.com/") if err != nil { // handle error } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) // ...
と書かれていて,必ず http.Response.Body.Close() メソッドを呼び出すよう指示されている。
クローズ処理を行わないと内部でゴルーチン・リークが発生するらしい。
ただ http.Response.Body に関しては,ただクローズするだけでは駄目で,クローズする前にデータを最後まで読み切ることが推奨されている。
If the Body is not both read to EOF and closed, theClient’s underlyingRoundTripper(typicallyTransport) may not be able to re-use a persistent TCP connection to the server for a subsequent “keep-alive” request.
これについては『Go言語 100Tips』にも解説がある。
もう 1 つ覚えておくべき重要なことは、ボディをクローズするときの動作は、そのボディから読み込んだかどうかによって異なることです。
- 読み込まずにボディをクローズすると、デフォルトの HTTP トランスポートがコネクションをクローズするかもしれません。
- 読み込んだ後にボディをクローズしても、デフォルトの HTTP トランスポートはコネクションをクローズしないので、再利用できる可能性があります。
したがって、
getStatusCodeが繰り返し呼ばれ、キープアライブ(keep-alive)のコネクションを使いたい場合、必要がないと思ってもボディを読み込むべきです。
これを踏まえて,最初のサンプルコードを書き換えると以下のようになる。
func doWithResp(resp *http.Response) (err error) {
defer func() {
_, rerr := io.Copy(io.Discard, resp.Body)
if cerr := resp.Body.Close(); cerr != nil && cerr != os.ErrClosed {
err = errors.Join(err, rerr, cerr)
}
err = errors.Join(err, rerr)
}()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("bad status: %s", resp.Status)
}
// Perform operations with the response
return err
}
余談
『効率的なGo』は,ちぃっと難しかった。 何が難しいって書籍内参照が前の章や跡の章に飛びまくって「何だっけこれ?」ってなることが多くて。 まるで longjump だらけのスパゲッティコードを読んでる気分だった。 これを曲がりなりにも理解するにはあと2,3回は周回する必要があるかな。
『効率的なGo』では標準パッケージの中身にも踏み込んでカスタマイズしたパッケージ置き換えるといったことも行っている。 今回挙げた例についても,エラーハンドリングに関しては独自のパッケージが用意されている。 参考になるだろう。
今回の読書会の最後の方で話題になったのだが,2025年12月に『実用 Go言語 第2版』が出るそうで。 買わなきゃ。 先日買った『初めてのGo言語 第2版』もまだ読み終わってないんだけど。
参考図書
- 効率的なGo ―データ指向によるGoアプリケーションの性能最適化
- Bartłomiej Płotka (著), 山口 能迪 (翻訳)
- オライリー・ジャパン 2024-02-24
- 単行本(ソフトカバー)
- 4814400535 (ASIN), 9784814400539 (EAN), 4814400535 (ISBN)
- 評価
版元で Ebook を買える。Go言語のリファレンス本ではない。フトウェア工学,プログラミング(の考え方)を学ぶ教科書的な位置づけかなぁ。
- Go言語 100Tips ありがちなミスを把握し、実装を最適化する impress top gearシリーズ
- Teiva Harsanyi (著), 柴田 芳樹 (著)
- インプレス 2023-08-18 (Release 2023-08-18)
- Kindle版
- B0CFL1DK8Q (ASIN)
- 評価
版元で PDF 版を購入可能。事実上の Effective Go とも言える充実の内容。オリジナルは敢えてタイトルに “tips” という単語を入れるのを避けたのに邦題が「100 Tips」とかなっていて,原作者がお怒りとの噂(あくまで噂)
- 初めてのGo言語 第2版 ―他言語プログラマーのためのイディオマティックGo実践ガイド
- Jon Bodner (著), 武舎 広幸 (翻訳)
- オライリー・ジャパン 2025-08-05 (Release 2025-08-05)
- 単行本(ソフトカバー)
- 4814401191 (ASIN), 9784814401192 (EAN), 4814401191 (ISBN)
- 評価
第2版。 Generics の説明が大幅に改訂されたらしい。練習問題も追加されている。
- プログラミング言語Go (ADDISON-WESLEY PROFESSIONAL COMPUTING SERIES)
- Alan A.A. Donovan (著), Brian W. Kernighan (著), 柴田 芳樹 (翻訳)
- 丸善出版 2016-06-20
- 単行本(ソフトカバー)
- 4621300253 (ASIN), 9784621300251 (EAN), 4621300253 (ISBN)
- 評価
著者のひとりは(あの「バイブル」とも呼ばれる)通称 “K&R” の K のほうである。この本は Go 言語の教科書と言ってもいいだろう。と思ったら絶版状態らしい(2025-01 現在)。復刊を望む!
-
拙作の
goark/errsパッケージもよろしく(宣伝)。こちらも複数エラーに対応している他,エラーの出力を JSON 形式に出来る。 ↩︎



