Go言語でエラー出力(Print Error)の使い方を解説
この記事では、Go言語でエラーを出力する基本的な方法をシンプルにご紹介します。
標準ライブラリのfmtやlogを利用したエラーメッセージの表示方法を取り上げ、コードが読みやすくなるポイントについて解説します。
初心者の方にも分かりやすい内容となっています。
エラー出力の基本
errorインターフェースの概要
Go言語では、エラーは組み込みのerrorインターフェースで定義されています。
errorインターフェースは以下のように定義されており、エラーを表現するために単一のメソッドError()を要求します。
type error interface {
    Error() string
}この構造により、任意の型がError()メソッドを実装すれば、その型をエラーとして扱うことができます。
例えば、独自のエラー情報を保持するために構造体を定義する場合も、Error()メソッドを実装するだけでerrorインターフェースの要件を満たすようになります。
以下のサンプルコードは、独自エラー型MyErrorを定義し、Error()メソッドによってエラーメッセージを返す例です。
package main
import (
	"fmt"
)
// MyErrorは独自のエラー型です。
type MyError struct {
	Message string
}
// ErrorはMyErrorのエラーメッセージを返します。
func (e *MyError) Error() string {
	return fmt.Sprintf("Error: %s", e.Message)
}
func main() {
	// エラー変数にMyErrorのインスタンスを代入
	var err error = &MyError{Message: "サンプルエラーです"}
	// エラーメッセージを出力します
	fmt.Println(err.Error())
}Error: サンプルエラーですエラー出力の流れと役割
エラー出力は、関数やメソッドが何らかの問題に遭遇した際に、エラー情報を返却するために行われます。
基本的な流れは以下のようになります。
- 関数やメソッドが処理中にエラー発生を検知する。
 - 発生したエラーを表す
error型の値を返却する。 - 呼び出し元で返却されたエラー値が
nilかどうかを判定し、nilでなければエラー処理を行う。 
この仕組みにより、関数呼び出し後に返されたエラー値に応じて、適切なエラー出力や処理分岐が可能となります。
エラー情報はデバッグやログ記録、ユーザへの通知に役立ちます。
以下のサンプルコードは、関数内でエラーを検知し、エラー出力を行う基本的な例です。
package main
import (
	"errors"
	"fmt"
)
// doSomethingはエラーが発生する可能性のある処理です。
func doSomething(flag bool) error {
	if flag {
		// エラー生成:flagがtrueの場合にエラーを返します。
		return errors.New("処理中にエラーが発生しました")
	}
	// エラーがなければnilを返します。
	return nil
}
func main() {
	// エラーが発生するケースを例示します。
	err := doSomething(true)
	if err != nil {
		// エラー発生時にエラーメッセージを出力します。
		fmt.Println("エラー:", err)
	} else {
		fmt.Println("正常に処理が完了しました")
	}
}エラー: 処理中にエラーが発生しましたfmtパッケージによるエラー表示
fmt.Printlnによるシンプルな出力
fmt.Printlnはエラーメッセージなどの文字列を整形せずにそのまま出力する際に使います。
エラー変数を直接渡すことで、内部で定義されたError()メソッドの返却値が表示されます。
以下のサンプルコードは、fmt.Printlnを使ってエラーを出力するシンプルな例です。
package main
import (
	"errors"
	"fmt"
)
func main() {
	// errors.Newでエラーを生成
	err := errors.New("シンプルなエラーです")
	// fmt.Printlnでエラーメッセージをそのまま出力します
	fmt.Println(err)
}シンプルなエラーですfmt.Errorfで整形エラーメッセージを作成
fmt.Errorfは、フォーマット指定子を使って整形されたエラーメッセージを作成する際に利用します。
エラーメッセージに変数や情報を組み込みたい場合に便利です。
以下のサンプルコードは、fmt.Errorfを使ってエラーメッセージを整形する例です。
package main
import (
	"fmt"
)
func main() {
	// 変数情報を組み込んだエラーメッセージを生成します。
	userName := "Alice"
	err := fmt.Errorf("ユーザ %s の認証に失敗しました", userName)
	// エラーメッセージを出力します
	fmt.Println(err)
}ユーザ Alice の認証に失敗しましたlogパッケージを利用したエラー表示
log.Printとlog.Fatalの使い分け
logパッケージは、エラーや情報のログ出力に便利です。
log.Printはエラーメッセージを出力した後、プログラムの実行を続行します。log.Fatalはエラーメッセージを出力後、プログラムを終了します。
以下のサンプルコードは、log.Printとlog.Fatalの違いを示しています。
package main
import (
	"errors"
	"log"
)
func main() {
	// サンプルエラーを生成
	err := errors.New("ログ出力のサンプルエラーです")
	// エラーメッセージをログに記録し、処理を続行する例
	log.Print("エラー発生:", err)
	// 以下の行は、log.Fatalが呼ばれると実行されません。
	log.Fatal("致命的なエラー:", err)
	// この行は実行されません
	// fmt.Println("プログラム終了")
}2023/10/05 12:00:00 エラー発生: ログ出力のサンプルエラーです
2023/10/05 12:00:00 致命的なエラー: ログ出力のサンプルエラーですログ出力時の注意点
ログを使用する際は、以下の点に注意してください。
- ログ出力にはタイムスタンプやログレベルなどの情報が自動的に付加されます。状況に応じてログフォーマットを変更することも可能です。
 - 重大なエラーの場合は
log.Fatalを利用して、早期にプログラムを終了する処理を行います。 - 例外的なエラーではなく情報レベルの出力は
log.Printやlog.Printlnで十分な場合が多いです。 
カスタムエラーの生成と出力
構造体を用いたカスタムエラーの定義
Errorメソッドの実装例
カスタムエラーでは、構造体に任意のフィールドを追加することでエラーの詳細情報を保持できます。
また、Error()メソッドを実装することで、エラーとして使用できるようになります。
以下のサンプルコードは、CustomErrorというカスタムエラー構造体を定義し、エラーメッセージにコード情報を含める例です。
package main
import (
	"fmt"
)
// CustomErrorは独自のエラー情報を保持する構造体です。
type CustomError struct {
	Message string  // エラーメッセージ
	Code    int     // エラーコード
}
// ErrorはCustomErrorのエラーメッセージを返します。
func (e *CustomError) Error() string {
	return fmt.Sprintf("エラー: %s [コード: %d]", e.Message, e.Code)
}
func main() {
	// CustomErrorのインスタンスを作成しエラーとして返します。
	err := &CustomError{Message: "予期せぬ動作", Code: 404}
	fmt.Println(err.Error())
}エラー: 予期せぬ動作 [コード: 404]型アサーションによるエラー判別と分岐処理
カスタムエラーを利用すると、エラーの型で分岐処理を行うことができます。
型アサーションや型スイッチを使用することで、エラーごとに異なる処理を実装できます。
以下のサンプルコードは、エラーの型を判別し、適切な処理を分岐して行う例です。
package main
import (
	"fmt"
)
// NetworkErrorは通信関連のエラーです。
type NetworkError struct {
	Msg string
}
// ErrorはNetworkErrorのエラーメッセージを返します。
func (e *NetworkError) Error() string {
	return fmt.Sprintf("ネットワークエラー: %s", e.Msg)
}
// IOErrorは入出力関連のエラーです。
type IOError struct {
	Msg string
}
// ErrorはIOErrorのエラーメッセージを返します。
func (e *IOError) Error() string {
	return fmt.Sprintf("IOエラー: %s", e.Msg)
}
func main() {
	// サンプルとしてNetworkErrorを発生させます。
	err := generateError(true)
	// 型スイッチを利用してエラーの種類を判定します。
	switch e := err.(type) {
	case *NetworkError:
		fmt.Println("ネットワークエラーの処理:", e.Msg)
	case *IOError:
		fmt.Println("IOエラーの処理:", e.Msg)
	default:
		fmt.Println("その他のエラー:", err)
	}
}
// generateErrorは条件に応じて異なるエラーを返します。
func generateError(flag bool) error {
	if flag {
		return &NetworkError{Msg: "接続タイムアウト"}
	}
	return &IOError{Msg: "ファイルが見つかりません"}
}ネットワークエラーの処理: 接続タイムアウト複数エラーの管理方法
複数エラー出力時のチェック手法
複数のエラーが存在する場合、それぞれのエラーに対して適切に処理を分岐する必要があります。
その手法として、エラーの型または生成済みのエラーインスタンスを比較する方法があります。
エラーの一覧を管理することで、各エラーに対し適切な対処を行えます。
以下のサンプルコードは、エラーの種類ごとに異なる処理を行い、適切なエラーメッセージを出力する例です。
package main
import (
	"errors"
	"fmt"
)
// 事前にエラーインスタンスを定義
var (
	ErrNetwork = errors.New("ネットワークエラー")
	ErrIO      = errors.New("IOエラー")
)
// processErrorはエラー種類に応じた処理を行います。
func processError(err error) {
	if err != nil {
		// エラーインスタンスの比較による判定
		if err == ErrNetwork {
			fmt.Println("処理:ネットワークエラー発生")
		} else if err == ErrIO {
			fmt.Println("処理:IOエラー発生")
		} else {
			fmt.Println("処理:その他のエラー", err)
		}
	} else {
		fmt.Println("エラーは発生していません")
	}
}
func main() {
	// 複数のエラーケースをテスト
	processError(ErrNetwork)
	processError(nil)
	processError(ErrIO)
}処理:ネットワークエラー発生
エラーは発生していません
処理:IOエラー発生エラーごとの適切な処理例
エラーの種類ごとに、ユーザー通知や再試行、プログラムの終了など適切な処理を行うことが大切です。
例えば、ネットワークエラーの場合は再試行の可能性を検討し、ファイルIOエラーの場合はエラー内容に応じたフォールバック処理を実装することが望まれます。
以下のサンプルコードは、エラーの型を判別し、各ケースに合わせた処理を実施する例です。
package main
import (
	"fmt"
)
// DownloadErrorはダウンロード時のエラーを表します。
type DownloadError struct {
	StatusCode int
}
// ErrorはDownloadErrorのエラーメッセージを返します。
func (e *DownloadError) Error() string {
	return fmt.Sprintf("ダウンロードエラー [ステータスコード: %d]", e.StatusCode)
}
// SaveErrorはファイル保存時のエラーを表します。
type SaveError struct {
	Filename string
	Reason   string
}
// ErrorはSaveErrorのエラーメッセージを返します。
func (e *SaveError) Error() string {
	return fmt.Sprintf("保存エラー: %s (ファイル: %s)", e.Reason, e.Filename)
}
func main() {
	// サンプルエラーを生成
	var err error = simulateError("download")
	handleError(err)
	err = simulateError("save")
	handleError(err)
}
// simulateErrorは指定された種類のエラーを返します。
func simulateError(errorType string) error {
	if errorType == "download" {
		return &DownloadError{StatusCode: 500}
	} else if errorType == "save" {
		return &SaveError{Filename: "output.txt", Reason: "ディスクスペース不足"}
	}
	return nil
}
// handleErrorはエラーの種類に応じた処理を行います。
func handleError(err error) {
	if err != nil {
		// 型スイッチでエラーを判別
		switch e := err.(type) {
		case *DownloadError:
			fmt.Println("ダウンロードエラー処理:", e.Error())
		case *SaveError:
			fmt.Println("保存エラー処理:", e.Error())
		default:
			fmt.Println("その他のエラー処理:", err.Error())
		}
	} else {
		fmt.Println("エラーはありません")
	}
}ダウンロードエラー処理: ダウンロードエラー [ステータスコード: 500]
保存エラー処理: 保存エラー: ディスクスペース不足 (ファイル: output.txt)まとめ
今回は、Go言語のエラー出力の基本から、fmtやlogパッケージを利用した表示、カスタムエラーの生成と分岐処理、複数エラー管理の手法について実例を交えて解説しました。
総括すると、各種エラーの出力方法や判別、適切な処理分岐を実践的に理解できる内容でした。
ぜひ、実践の中でエラー処理の改善に活用してみてください。