Go 言語でエラーを返却し、受け取った側で適切に処理する

はじめに

エラーハンドリングは、コードの安定性・堅牢性には必要不可欠です。

今回は、 前回作成した greetings モジュール に少しだけコードを追加しながら、以下の内容について学んで行きます。

  • モジュールからエラーを返却する方法
  • モジュールから返却されたエラーを呼び出し元でハンドリングする方法

当エントリは以下のエントリの続きとなっています。事前に一読しておくことをおすすめします。

コーディング開始

早速コーディングを始めていきます。

1. greetings/greetings.go を開き、コードを追記

「挨拶をする相手の名前がわからない場合には、挨拶をせずにエラーとする」、 そんな仕様を追加してみます。

name パラメータが空だった場合には、呼び出し元にエラーを返却します。 以下のコードを greetings.go に記述します。

package greetings

import (
    "errors"
    "fmt"
)

func Hello(name string) (string, error) {
    if name == "" {
        return "", errors.New("empty name")
    }

    message := fmt.Sprintf("Hi, %v. Welcome!", name)

    return message, nil
}

もともとのコード から変更した箇所は以下のようになります。( git diff 実行結果を添付 )

@@ -1,9 +1,16 @@
 package greetings

-import "fmt"
+import (
+       "errors"
+       "fmt"
+)
+
+func Hello(name string) (string, error) {
+       if name == "" {
+               return "", errors.New("empty name")
+       }

-func Hello(name string) string {
        message := fmt.Sprintf("Hi, %v. Welcome!", name)

-       return message
+       return message, nil
 }

コード解説

かんたんに解説します。

  • 関数は 2 つの値を返却するように変更されています。
    • 1 つは string 型の値、 1 つは error 型の値。
    • 呼び出し元では 2 つ目に返却された値をチェックすることで、エラーが発生したかがわかります ( Go の関数は複数の値を返却することができるんですね ) 。
  • Go の errors という標準ライブラリをインポートし、 errors.New 関数 ( 後述 ) を利用できるようにします。
  • if 構文を使って、name パラメータが妥当か(空ではないか?)をチェックしています。
    • 空の場合
    • エラーを返却します。
    • エラーを返却する場合は、 error 型の値を生成するために errors.New 関数を利用します。任意のメッセージを格納することができます。
    • 空でない場合
    • エラーを返却しません。
    • エラーを返却しない場合は、 2 つ目の戻り値に nil を設定します。

Hello 関数の呼び出し元は 2 つ目の戻り値を確認し、 nil であれば正常終了したと判断できます。

2. hello/hello.go を開き、 Hello 関数からの戻り値をハンドリングするコードを追加

以下のコードを貼り付けます。

package main

import (
    "fmt"
    "log"

    "genzouw/greetings"
)

func main() {
    log.SetPrefix("greetings: ")
    log.SetFlags(0)

    message, err := greetings.Hello("")

    if err != nil {
        log.Fatal(err)
    }

    fmt.Println(message)
}

もともとのコード から変更した箇所は以下のようになります。( git diff 実行結果を添付 )

$ git diff
diff --git hello.go hello.go
index 747ec74..22adb60 100644
--- hello.go
+++ hello.go
@@ -2,11 +2,20 @@ package main

 import (
        "fmt"
+       "log"
+
        "genzouw/greetings"
 )

 func main() {
-       message := greetings.Hello("genzouw")
+       log.SetPrefix("greetings: ")
+       log.SetFlags(0)
+
+       message, err := greetings.Hello("")
+
+       if err != nil {
+               log.Fatal(err)
+       }

        fmt.Println(message)
 }

コード解説

かんたんに解説します。

  • log パッケージを import しています。
    • すべてのログのプレフィックスとして greetings: という文字列を出力する。
    • すべてのログのプレフィックスとして タイムスタンプソースコード名 を出力しない。 ( log.SetFlags(0) )
  • Hello 関数の戻り値 2 つを受け取ります。
  • Hello 関数の引数を変更し、 genzouw という名前 から 空文字 をセットするようにしています。
  • err 変数が nil ではない場合、処理を中断します。
  • 標準ライブラリ logFatal 関数を呼び出すことで、エラーメッセージを出力しています。
    • log.Fatal 実行後はプログラムがエラーステータスで終了します。

アプリケーション実行

hello モジュールの配置されているディレクトリで、コードを実行してみます。

$ go run .
greetings: empty name
exit status 1

想定通り、異常終了してくれました。

greetings.Hello("") の部分を greetings.Hello("genzouw") のように空文字列ではないものに変更すれば、正常に動作します。

$ go run .
Hi, genzouw. Welcome!

まとめ

Go 言語では、関数の呼び出し元にエラーを通知する方法として、C++/Java/C#といった他の言語のように 「例外を投げる方法」 ではなく、 「戻り値として返却する方法」 を取ります。

ひとこと

次回は 「挨拶」プログラムの実行結果を実行のたびにランダムに変更 してみます。

Posted by genzouw