今回は、エラーハンドリングについて学びましょう。
<目次>
学習記事まとめ
エラーハンドリング
Go言語に限らず、全てのプログラミング言語にとってエラーハンドリングは重要です。
例えば、Go言語では関数の戻り値にError typeを定義し、関数呼び出し後の条件判定に使用します。
簡単なサンプルを載せておきます。
1 2 |
touch main.go touch data.txt |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
// main.go package main import ( "bufio" "fmt" "log" "os" "strconv" ) func OpenFile(fileName string) (*os.File, error) { fmt.Println("Opening", fileName) return os.Open(fileName) } func CloseFile(file *os.File) { fmt.Println("Close file") file.Close() } func GetFloats(fileName string) ([]float64, error) { var numbers []float64 file, err := OpenFile(fileName) if err != nil { return nil, err } scanner := bufio.NewScanner(file) for scanner.Scan() { number, err := strconv.ParseFloat(scanner.Text(), 64) if err != nil { return nil, err } numbers = append(numbers, number) } CloseFile(file) if scanner.Err() != nil { return nil, scanner.Err() } return numbers, nil } func main() { numbers, err := GetFloats(os.Args[1]) if err != nil { log.Fatal(err) } var sum float64 = 0 for _, number := range numbers { sum += number } fmt.Printf("Sum: %0.2f\n", sum) } |
1 2 3 4 5 |
// data.txt 10.5 30.3 20.1 33.3 |
上記のコードは、data.txtから数値を読み取って合計値を求めるサンプルコードです。実行してみましょう。
1 2 3 4 |
$ go run main.go data.txt Opening data.txt Close file Sum: 94.20 |
問題なく実行できましたね。しかし、このプログラムはstringの数値をfloatに型変換するものなので、文字列など数値に変換できない値が入った場合はエラーが発生します。
1 2 |
# e-data.txt hello |
1 2 3 4 |
$ go run main.go e-data.txt Opening e-data.txt 2019/10/31 21:45:53 strconv.ParseFloat: parsing "hello": invalid syntax exit status 1 |
このような場合、「Close file」の呼び出しが無いことがわかります。つまり、一度開いたファイルが閉じられていない状態でプログラムが終了してしまっています。
遅延関数の呼び出し
関数やメソッドの呼び出しの先頭に「defer」キーワードをつけて実装するとそれらを”遅延状態“にすることができます。
サンプルコードを書きます。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
package main import "fmt" func hello() { defer fmt.Println("Mr.Yamada") fmt.Println("Hello") } func main() { defer fmt.Println("Goodbye") hello() } |
このプログラムを実行すると結果はどうなると思いますか?少し考えてください。

答えはこれです。
1 2 3 4 5 |
$ go run main.go Hello Mr.Yamada Goodbye |
通常ならGoodbye->Mr.Yamada->Helloの順に呼び出されるはずですが、Hello->Mr.Yamada->Goodbyeとなっています。
実は、deferキーワードと共に関数やメソッドを宣言すると「処理の終了時」に呼び出されるようになります。
もう一つ例をあげてみましょう。
1 2 3 4 5 6 7 8 9 |
package main import "fmt" func main() { defer fmt.Println(1) defer fmt.Println(2) defer fmt.Println(3) } |
この結果はどうなると思いますか?
プログラムを実行して確認してみましょう。
1 2 3 4 |
$ go run main.go 3 2 1 |
「3->2->1」と出力されました。deferキーワード同士では一番最後に定義されたものが一番最初に呼び出されるようです。
deferキーワードの使い所
deferキーワードは、結構便利です。return文の実行後に処理を実行できます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
package main import ( "fmt" "log" ) func hello(feel string) error { defer fmt.Println("Sorry.") if feel == "NG" { return fmt.Errorf("I didn't like you...") } fmt.Println("You want to talk with me?") return nil } func main() { err := hello("NG") if err != nil { log.Fatal(err) } } |
1 2 3 4 |
$ go run main.go Sorry. 2019/11/03 22:14:31 I didn't like you... exit status 1 |
注目して欲しいのは、普通の関数(“You want to talk with me?”)の方は処理が出力されていないところです。
やってみよう
deferキーワードを設定すると「どんな場合」でも実行したい処理を呼び出すことができそうです。
deferキーワードを使って、冒頭に実装したサンプルコードを書き換えてみましょう。このサンプルコードで問題になったのは、「ファイルが開いたまま処理が終了すること」でしたね。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
// main.go package main import ( "bufio" "fmt" "log" "os" "strconv" ) func OpenFile(fileName string) (*os.File, error) { fmt.Println("Opening", fileName) return os.Open(fileName) } func CloseFile(file *os.File) { fmt.Println("Close file") file.Close() } func GetFloats(fileName string) ([]float64, error) { var numbers []float64 file, err := OpenFile(fileName) if err != nil { return nil, err } defer CloseFile(file) // Declear defer keyword scanner := bufio.NewScanner(file) for scanner.Scan() { number, err := strconv.ParseFloat(scanner.Text(), 64) if err != nil { return nil, err } numbers = append(numbers, number) } CloseFile(file) if scanner.Err() != nil { return nil, scanner.Err() } return numbers, nil } func main() { numbers, err := GetFloats(os.Args[1]) if err != nil { log.Fatal(err) } var sum float64 = 0 for _, number := range numbers { sum += number } fmt.Printf("Sum: %0.2f\n", sum) } |
data.txtの中身を以下のようにして実行します。
1 2 3 4 5 |
10.5 30.3 sum 20.1 33.3 |
1 2 3 4 5 6 |
go run main.go data.txt Opening data.txt Close file 2019/11/03 22:28:44 strconv.ParseFloat: parsing "sum": invalid syntax exit status 1 |
今度は、Close fileが出力されました。
このように必ず処理したい関数やメソッドの前にdeferを定義しておくと便利そうです。
panicを起こせ!
Go言語では、不適切な処理に直面すると”panic“が発生します。
例えば、次のような処理の場合です。
1 2 3 4 5 6 7 8 9 10 |
package main import "fmt" func main() { myArray := [7]string{"one", "two", "three", "four", "five", "six", "seven"} for i := 0; i <= len(myArray); i++ { fmt.Println(i, myArray[i]) } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
$ go run main.go 0 one 1 two 2 three 3 four 4 five 5 six 6 seven panic: runtime error: index out of range goroutine 1 [running]: main.main() /Users/hoge/go/src/github.com/hoge/main.go:8 +0x12f exit status 2 |
Arrayのindexを超えた要素にアクセスした際に、panicが発生しました。
panicは、意図的に発生させることも可能です。
1 2 3 4 5 |
package main func main() { panic("P A N I C ! ! !") } |
1 2 3 4 5 6 7 |
$ go run main.go panic: P A N I C ! ! ! goroutine 1 [running]: main.main() /Users/hoge/go/src/github.com/hoge/main.go:4 +0x39 exit status 2 |
Stack Trace
panicが発生すると一緒にStack Traceが吐き出されます。これは、エラー原因を探るのに適しています。
Stack Traceは、関数の呼び出し先から順に出力されます。
例えば、次のような感じです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
package main func main() { one() } func one() { two() } func two() { three() } func three() { panic("Check Stack Trace!") } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
$ go run main.go panic: Check Stack Trace! goroutine 1 [running]: main.three(...) /Users/hoge/go/src/github.com/hoge/main.go:16 main.two(...) /Users/hoge/go/src/github.com/hoge/main.go:12 main.one(...) /Users/hoge/go/src/github.com/hoge/main.go:8 main.main() /Users/hoge/go/src/github.com/hoge/main.go:4 +0x3c exit status 2 |
panicとdeferキーワードの併用
deferキーワードをpanicと共に使用すると、プログラムがクラッシュする前にdeferをつけた関数が実行されます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
package main import "fmt" func main() { one() } func one() { defer fmt.Println("Do one function before program crash!") two() } func two() { defer fmt.Println("Do two function before program crash!") panic("P A N I C ! ! !") } |
one関数とtwo関数にそれぞれdeferをつけた関数を設定しました。この状態でプログラムを実行します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
$ go run main.go panic: Check Stack Trace! $ go run main.go Do two function before program crash! Do one function before program crash! panic: P A N I C ! ! ! goroutine 1 [running]: main.two() /Users/hoge/go/src/github.com/hoge/main.go:16 +0x95 main.one() /Users/hoge/go/src/github.com/hoge/main.go:11 +0x7e main.main() /Users/hoge/go/src/github.com/hoge/main.go:6 +0x20 exit status 2 |
プログラムがクラッシュする前に、deferキーワードで定義された関数が実行されたことがわかりますね。
recover関数
時折、Stack Traceの出力結果は開発者にとって見にくくなる場合があるため、表示したくない場合があるかもしれません。
そんな時は、recover関数が便利です。
recover関数は、Go言語の組み込み関数の一つで、プログラムがパニックになっているときに呼び出すと、パニックが停止します。
例えば、次のように使用します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
package main import "fmt" func stopPanic() { recover() } func occurPanic() { defer stopPanic() panic("P A N I C ! ! !") } func main() { recover() fmt.Println("DONE MAIN FUNCTION") } |
プログラムを実行してみましょう。
1 2 3 |
$ go run main.go DONE MAIN FUNCTION |
PanicおよびStack Traceが発生していないことがわかります。
ちなみに、panicは発生しなかった場合、recover関数自体はnilを返します。
1 2 3 4 5 6 7 |
package main import "fmt" func main() { fmt.Println(recover()) } |
1 2 |
$ go run main.go <nil> |
注意点としては、panic関数の後にrecover関数を実行してもrecover関数が実行されないことです。
1 2 3 4 5 6 7 8 9 10 11 |
package main func occurPanic() { panic("P A N I C ! ! !") recover() // 実行されない } func main() { occurPanic() } |
1 2 3 4 5 6 7 8 9 |
$ go run main.go panic: P A N I C ! ! ! goroutine 1 [running]: main.occurPanic() /Users/hoge/go/src/github.com/hoge/main.go:4 +0x39 main.main() /Users/hoge/go/src/github.com/hoge/main.go:9 +0x20 exit status 2 |
recover関数の戻り値
前述の通り、panicが発生していない場合、recover関数はnilを返します。
しかし、panicが発生した場合は、panicへ渡された値を返却します。
1 2 3 4 5 6 7 8 9 10 11 12 |
package main import "fmt" func stopPanic() { fmt.Println(recover()) } func main() { defer stopPanic() panic("P A N I C ! ! !") } |
1 2 |
$ go run main.go P A N I C ! ! ! |
まとめ

最後にこの章で学んだことをまとめます。
・ deferキーワードを用いるとエラーが発生しても実行したい処理を実行できる
・ panic関数で意図的にエラーを発生させることができる
・ recover関数を用いるとプログラムのクラッシュを停止できる
・ panic時に発生するStack Traceはデバッグ時に役立つ
・ panicが発生していない時にrecover関数を呼び出すとnilを返す
・ panicへ渡したメッセージをrecover関数から取得できる
次回
次回は、GoroutineとChannelについて学びましょう。
コメントを残す
コメントを投稿するにはログインしてください。