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つ。

  1. 返り値を err error と名前付きにすること
  2. 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, the Client’s underlying RoundTripper (typically Transport) 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版』もまだ読み終わってないんだけど。

参考図書

photo
効率的なGo ―データ指向によるGoアプリケーションの性能最適化
Bartłomiej Płotka (著), 山口 能迪 (翻訳)
オライリー・ジャパン 2024-02-24
単行本(ソフトカバー)
4814400535 (ASIN), 9784814400539 (EAN), 4814400535 (ISBN)
評価     

版元で Ebook を買える。Go言語のリファレンス本ではない。フトウェア工学,プログラミング(の考え方)を学ぶ教科書的な位置づけかなぁ。

reviewed by Spiegel on 2024-04-21 (powered by PA-APIv5)

photo
Go言語 100Tips ありがちなミスを把握し、実装を最適化する impress top gearシリーズ
Teiva Harsanyi (著), 柴田 芳樹 (著)
インプレス 2023-08-18 (Release 2023-08-18)
Kindle版
B0CFL1DK8Q (ASIN)
評価     

版元で PDF 版を購入可能。事実上の Effective Go とも言える充実の内容。オリジナルは敢えてタイトルに “tips” という単語を入れるのを避けたのに邦題が「100 Tips」とかなっていて,原作者がお怒りとの噂(あくまで噂)

reviewed by Spiegel on 2023-08-18 (powered by PA-APIv5)

photo
初めてのGo言語 第2版 ―他言語プログラマーのためのイディオマティックGo実践ガイド
Jon Bodner (著), 武舎 広幸 (翻訳)
オライリー・ジャパン 2025-08-05 (Release 2025-08-05)
単行本(ソフトカバー)
4814401191 (ASIN), 9784814401192 (EAN), 4814401191 (ISBN)
評価     

第2版。 Generics の説明が大幅に改訂されたらしい。練習問題も追加されている。

reviewed by Spiegel on 2025-11-02 (powered by PA-APIv5)

photo
プログラミング言語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 現在)。復刊を望む!

reviewed by Spiegel on 2016-07-13 (powered by PA-APIv5)


  1. 拙作の goark/errs パッケージもよろしく(宣伝)。こちらも複数エラーに対応している他,エラーの出力を JSON 形式に出来る。 ↩︎