Go：エラーの出力と処理の基本

# はじめに
こんにちは！株式会社HRBrainの25卒インターン生のizutakuです！
Goで個人開発を行っていた際、エラーハンドリングに一貫性が無いことに気づいたため、改めてエラーハンドリングの基礎を学びました。
この記事では主に以下のパッケージを使用したエラーハンドリングとカスタムエラーについて紹介しますので、ご興味があれば一読ください！

- errors
- fmt
- log
- os

 # errors 
シンプルなエラーメッセージを持つオブジェクトを生成する。
```golang:main.go
package main

import (
	"errors"
	"fmt"
)

func divide(a, b int) (int, error) {
	err := errors.New("cannot divide by zero") //エラーメッセージを持つオブジェクトを作成

	if b == 0 {
		return 0, err
	}

	result := a / b
	return result, nil
}

func main() {

	result, err := divide(100, 0)
	if err != nil {
		fmt.Println(err) //"cannot divide by zero"と出力される
	} else {
		fmt.Println(result)
	}
}

```
```go:出力
出力
Cannot divide by zero
```

# fmt
ここではfmt.Errorf()について解説する。
fmt.Sprintf()と同様に書式指定が可能となっている。
```golang:main.go
package main

import (
	"fmt"
)

func divide(a, b int) (int, error) {

	if b == 0 {
		err := fmt.Errorf("%vでは割れません。", b)
		return 0, err
	}

	result := a / b
	return result, nil
}

func main() {

	result, err := divide(100, 0)
	if err != nil {
		fmt.Println(err)
	} else {
		fmt.Println(result)
	}
}

```

```go:出力
0では割れません。
```


# log
logではエラーメッセージや情報をログとして出力する。（logの出力先指定方法などは追々書いていく。）
logによるエラーハンドリングでは、エラー発生時に以下の動作に分けられる。
- プログラムを継続させる
- プログラムを終了させる

## プログラムの継続
log.Println()、log.Printf()、log.Sprintf()などが該当する。
```go:main.go
package main

import (
	"fmt"
	"log"
	"os"
)

func main() {

	file, err := os.Open("test.txt")
	if err != nil {
		log.Println(err)
	}

	defer file.Close()

	fmt.Println("エラー出ても実行されるよ")
}

```
```go:出力
出力
2024/09/17 21:18:52 open test.txt: no such file or directory
エラー出ても実行されるよ
```




## プログラムの終了
log.Fatal()、log.Fatalf()、log.Fatalln()が該当する。
```go:main.go
package main

import (
	"fmt"
	"log"
	"os"
)

func main() {

	file, err := os.Open("test.txt")
	if err != nil {
		log.Fatalln(err)
	}

	defer file.Close()

	fmt.Println("エラーが出たら実行されないよ")
}

```
```go:出力
出力
2024/09/17 21:19:55 open test.txt: no such file or directory
exit status 1
```

# os
ここではos.Exit(code int)を紹介する。
```go:main.go
package main

import (
	"fmt"
	"os"
)

func main() {

	file, err := os.Open("test.txt")
	if err != nil {
		os.Exit(20)
	}

	defer file.Close()

	fmt.Println("エラー出たら実行されないよ")
}

```
```go:出力
出力
exit status 20

```
<details>
<summary>ステータスコードについて</summary>

- ステータスコードは0~125が推奨されている
- ステータスコードが0であることは、処理が正常に終了したことを示す
- ステータスコードが1~125ではエラーを示す
- ステータスコードの値は任意であり、エラーの種類(不正なフォーマット、認証失敗など)によりステタスコードを統一することでエラー改善をしやすくなる

参照元：https://pkg.go.dev/os#Exit

</details>

# カスタムエラー
Goではエラーインターフェース(error)を満たすカスタムエラーを定義することで、より詳細なエラー情報を得ることができる。

```go:main.go
package main

import (
	"fmt"
)

type Myerror struct {
	code int
	msg  string
}

func (e *Myerror) Error() string {
	return fmt.Sprintf("エラーコード:%d,エラーメッセージ:%s", e.code, e.msg)

}

func main() {

	err := &Myerror{code: 404, msg: "ページが見つかりません"}
	fmt.Println(err.Error())
}

```
```go:出力
出力
エラーコード:404,エラーメッセージ:ページが見つかりません
```
# まとめ
今回の解説で取り上げた内容をまとめると以下のようになります。

- fmt.Errorf()
fmt.Sprintf()のように書式指定が可能で、エラーメッセージを簡潔に作成できる。エラーを返す際に役立ちます。

- logパッケージ
エラーや情報をログとして出力する際に使用します。log.Println()やlog.Printf()などでプログラムを継続させつつエラーを記録でき、log.Fatal()を使うとエラーメッセージを表示しつつプログラムを強制終了させることが可能です。

- os.Exit()
指定したステータスコードを返し、プログラムを終了します。正常終了には0を、エラー終了には1~125を使用します。

- カスタムエラー
Goではエラーインターフェースを満たすカスタムエラーを定義することで、エラーコードやメッセージを含む独自のエラーを作成できます。これにより、より詳細なエラーメッセージを提供しやすくなります。

以上を踏まえ、Goでは状況に応じた適切なエラーハンドリング方法を選択することが重要です。ログの記録やエラー内容のカスタマイズを活用することで、問題発生時のトラブルシューティングがしやすくなり、プログラムの品質向上につながります。

# PR

株式会社HRBrainでは新しいメンバーを募集しています！

https://www.hrbrain.co.jp/recruit