📑

【Go】(f *File) Close()のdefer処理をエラーハンドリングする

2023/01/30に公開

今回は、defer f.Close()を例にして、defer処理内でのエラーハンドリングに関して書きます。

ファイルのClose処理

deferを使いファイルをクローズします。

f, _ := os.Create("test.json")
defer f.Close()

※記述の便宜上、エラーハンドリングを省略しています。

まずは、Close()の実装を見てみましょう。
関数を確認するとerrorを返却していることがわかります。

func (f *File) Close() error {
	if f == nil {
		return ErrInvalid
	}
	return f.file.close()
}

現状、 defer f.Close()だけだと、Close()が返却するerrorは握り潰されています。

無名関数とnamed return valueを使いハンドリングする

無名関数とnamed return valueを使うことによって以下のように書けます。

f, _ := os.Create("test.json")
defer func() {
	if closeErr := f.Close(); closeErr != nil {
		err = fmt.Errorf("defer close error: %v", closeErr)
	}
}()

しかしこの書き方の場合、他の箇所で発生したエラーが上書きされてしまいます。

以下のコードを例にして解説をします。
まずは、上書きされない場合です。

func main() {
	fmt.Println(createFile())
}

func createFile() (err error) {
	f, _ := os.Create("test.json")
	defer func() {
		if closeErr := f.Close(); closeErr != nil {
			err = fmt.Errorf("defer close error: %v", closeErr)
		}
	}()
	return fmt.Errorf("errorです")
}

実行結果

% go run main.go
errorです

createFile()は、処理Xが失敗したと仮定して、fmt.Errorf("errorです")を使いエラー詳細を返却しています。
ここで、f = nilとし、意図的にdefer内部でエラーを発生させます。

defer func() {
	f = nil
	if closeErr := f.Close(); closeErr != nil {
		err = fmt.Errorf("defer close error: %v", closeErr)
	}
}()

実行結果

% go run main.go
defer close error: invalid argument

fmt.Errorf("errorです")という情報が上書きされて、fmt.Errorf("defer close error: %v", closeErr)のみが出力されました。

デバックを行うときに重要な情報であるfmt.Errorf("errorです")が上書きされてしまいました。
そのため次のようにコードを修正します。

無名関数内でエラー情報をラップする

大元のエラー情報とf.Close()のエラー情報を合わせて返してあげるように変更します。

defer func() {
	if closeErr := f.Close(); closeErr != nil {
		err = fmt.Errorf("original error: %v, defer close error: %v", err, closeErr)
	}
}()

実行結果

% go run main.go
original error: errorです, defer close error: invalid argument

無事に両方のエラー情報を返却することができました。

最終サンプルコード

package main

import (
	"fmt"
	"os"
)

func main() {
	fmt.Println(createFile())
}

func createFile() (err error) {
	f, _ := os.Create("test.json")
	defer func() {
		if closeErr := f.Close(); closeErr != nil {
			err = fmt.Errorf("original error: %v, defer close error: %v", err, closeErr)
		}
	}()
	return fmt.Errorf("errorです")
}

より良い書き方があればご意見お待ちしております。

Discussion