- 投稿日:2020-07-08T17:49:37+09:00
【Go】 deferに渡した関数内のエラーを呼び出し元に返す
はじめに
はじめまして。Go初学者です。
Gopher道場#8や自身の勉強にて、気づいた学びを共有しています。今回は、
defer
内でのエラーハンドリング方法と、名前付き戻り値の利用方針についてまとめました。結論から
deferに渡した関数のreturnは無視されるので、deferに渡した関数のエラーは
名前付き戻り値
に代入して呼び出し元に戻す。If the deferred function has any return values, they are discarded when the function completes. (See also the section on handling panics.)
deferred関数に戻り値がある場合は、関数が完了したときに破棄されます。
defer
is 何
いわゆる
Finally
句の様なもので、関数が終了する前に呼ばれます。
defer
キーワードの後に関数を渡すと、渡した順にスタックされていき、最後から順に実行されていきます。func main() { defer func() { fmt.Println("defer 1") }() defer func() { fmt.Println("defer 2") }() fmt.Println("hoge") }hoge defer 2 defer 1panicの場合でも、きちんと呼ばれてくれます。
func main() { defer func() { fmt.Println("defer 1") }() defer func() { fmt.Println("defer 2") }() panic("panic!") }defer 2 defer 1 panic: panic! goroutine 1 [running]: main.main() /tmp/sandbox481037905/prog.go:15 +0x68 Program exited: status 2.
以下の様に、ファイルのクローズ処理などによく用いられるそうです。
func main() { file, _ := os.Create("hoge.txt") defer file.Close() // 書き込み処理 }defer内での
return
は無視される下記の様な記述では、呼び出し元は
err
を受け取る事ができません。func main() { if err := hoge(); err != nil { fmt.Fprint(os.Stderr, err) os.Exit(1) } os.Exit(0) } func hoge() error { defer func() error { return fmt.Errorf("error occured!") }() return nil }では、先ほどの例でファイル操作をしている場合などに、エラー判定を呼び出し元に返すにはどのようにしたらよいのか?
この際に、名前付き戻り値を利用します。名前付き戻り値
is 何
Goでは戻り値に名称をつける事ができます。
また、付けられた名称は変数として関数内で利用する事が可能です。
名前付き戻り値を利用すると、return
のみでも名前を付けた変数が返却することができます。func square(i int) (squared int) { squared = i * i return }良い所
戻り値に名前をつける事が出来るので、関数の利用者は作成者が何を返したいのかより明確になります。
IDEなどでも以下の様に表示されます。ただ、そもそも関数名、引数名と引数の型、戻り値の型などを使って戻り値の意味が連想できないのはBadかなとは思いますが...
気になる所
私個人的には、
return
で省略できる点については、ソースの可読性を下げる恐れがあると感じています。
なので、実際はreturn squared
の様に、明示的に記載することにしています。deferに渡した関数内のエラーを呼び出し元に返す
本題です。
以下の様に、defer内で名前付き戻り値にエラーを設定することで、エラーを呼び出し元に返却する事が可能になります。func main() { if err := file("hoge"); err != nil { fmt.Fprint(os.Stderr, err) os.Exit(1) } os.Exit(0) } func file(fileName string) (err error) { // errorに名前をつける file, _ := os.Create(fileName) defer func() { err = file.Close() // errに処理結果を代入 }() // 書き込み処理 return nil }おわりに
基本的に名前付き戻り値は使わずに実装が可能ですが、上記のパターンの場合は使わずに返却することは難しいようでした。
名前付き戻り値の使い所はこういうパターンもあるんですね参考
- 投稿日:2020-07-08T16:33:28+09:00
【Go言語】 time.Time - t.Sub(u)はu→t の正確な経過時間を返さない(かもしれない)
問題
time.TimeのSubメソッドを利用すると、2つのtime.Time間のDurationを取得することが出来ます。
例えば↓なカンジで処理時間を計測したりします。
start := time.Now() (何か処理) elapsed := time.Now().Sub(start) fmt.Println(elapsed)このコード自体間違いではないですが、今回実行していて明らかに実際の経過時間を返していない事象に遭遇し、軽くハマりました。
9:02にスタートし9:10に終了しているのに、elapsedに3m
などと出力されたり。startとnowも出力↓
start:2020-07-08 09:02:00.243095 +0900 JST m=+0.003321332
now:2020-07-08 09:10:25.388989 +0900 JST m=+222.388991487
elapsed:3m42.385670155sstartとnowも確かに正しい日時が入っています。何が起きているのでしょうか・・・ ?
(鋭い人はこのログでピンと来てそうですね)タネあかし
ログを見ると
m=+222.388991487
みたいな出力が見られます。
これの単位を秒とみなすと、nowとstartの差分がちょうどelapsedと一致します。これは実はmonotonic clockと呼ばれるもので、time.Timeは時刻を表す情報(wall clock)とは別に時間計測に使う為の情報(monotonic clock)を持つことが出来ます。
time.Now()
で作成したtime.Timeは内部にmonotonic clockを持ちます。Subメソッドの様な時間経過を得る為の関数1はwall clockよりmonotonic clockを優先的に使用して計算を行います。それにより、例えば処理中にOS時刻を変更されたりしても、正しく経過時間を取得することが出来ます。
さてここまで見る限り処理時間の計測方法としては正しいと思われる(実際正しい)のですが、なぜズレが生じてしまったのでしょうか。
公式ドキュメントにそのものズバり書かれていました(-ω-;
https://golang.org/pkg/time/#hdr-Monotonic_ClocksOn some systems the monotonic clock will stop if the computer goes to sleep. On such a system, t.Sub(u) may not accurately reflect the actual time that passed between t and u.
「いくつかのシステムではコンピューターがスリープした場合monoritic clockが停止して、t.Sub(u)が正しい経過時間を返さないことがある」とのことでした。
そうです。今回私は実行中に何度かスリープ→回復していたのです。2
明示的にwall clockで計算したい場合は、t.Sub(u)のt、uどちらかのmonotonic clock情報を削除すればOKです。
monotonic clock情報はt.Round(0)
の様にすれば削除出来ます。最後に
monotonic clockについては、今まで「時間計測に使用される」、「t.Timeを==演算子で比較するのは危険」くらいの曖昧な知識しかありませんでした。
time.Timeのドキュメント見ると真っ先にmonotonic clockの説明が書かれているのに、全然読んでませんでした(猛省)。
公式ドキュメントはちゃんと読まなきゃですね。。参考にした記事
- 投稿日:2020-07-08T12:32:09+09:00
go getすると410 Goneのエラーが出る
背景
GitHub Enterpriseのリポジトリに作成したリポジトリをgo get仕様とすると、
410 Gone
のエラーが出た。$ go get git.enterprise.com/repo/package verifying git.enterprise.com/repo/package@v0.1.1/go.mod: git.enterprise.com/repo/package@v0.1.1/go.mod: reading https://sum.golang.org/lookup/git.enterprise.com/repo/package@v0.1.1: 410 Gonesum.golang.org を経由せずにダウンロードしたい。
intelliJのGoModuleの設定確認
Preference > 言語&フレームワーク > Go Moduleの設定を確認。
プロキシーに「direct」が入ってなかったので入れる。
GOPRIVATEの設定をして再度 go get
$ GOPRIVATE="git.dmm.com,direct" $ go get -u git.enterprise.com/repo/packageintelliJの再起動
わたしは何故かintelliJの再起動までし無いと、IDE上で反映がされませんでしたので再起動をしました。
参考