Go言語入門:効果的なGo -関数:複数の戻り値-

スポンサーリンク
Go言語入門:効果的なGo -関数:複数の戻り値- ノウハウ
Go言語入門:効果的なGo -関数:複数の戻り値-
この記事は約6分で読めます。
よっしー
よっしー

こんにちは。よっしーです(^^)

本日は、Go言語を効果的に使うためのガイドラインについて解説しています。

スポンサーリンク

背景

Go言語を学び始めて、より良いコードを書きたいと思い、Go言語の公式ドキュメント「Effective Go」を知りました。これは、いわば「Goらしいコードの書き方指南書」になります。単に動くコードではなく、効率的で保守性の高いコードを書くためのベストプラクティスが詰まっているので、これを読んだ時の内容を備忘として残しました。

関数における「複数の戻り値」

Goの珍しい特徴の一つは、関数やメソッドが複数の値を返すことができることです。この形式は、Cプログラムでのいくつかの不格好な慣用句を改善するために使用できます。例えば、EOFに対する-1のようなイン・バンド・エラー戻り値や、アドレスで渡された引数を変更することなどです。

関数における「複数の戻り値」

Cでは、書き込みエラーは負の数のカウントで知らされ、エラーコードは揮発性の場所に隠されます。Goでは、Writeはカウントエラーを返すことができます:「はい、いくつかのバイトは書き込まれましたが、デバイスがいっぱいになったため、すべては書き込まれませんでした。」。パッケージosのファイルのWriteメソッドのシグネチャは以下の通りです:

func (file *File) Write(b []byte) (n int, err error)

文書に記載されているように、これは書き込まれたバイト数と、n != len(b)の場合には非nilのerrorを返します。これは一般的なスタイルです。より多くの例については、エラーハンドリングのセクションを参照してください。

同様のアプローチにより、参照パラメータをシミュレートするために戻り値へのポインタを渡す必要がなくなります。以下は、バイトスライス内の位置から数値を取得し、その数値と次の位置を返すシンプルな関数です。

func nextInt(b []byte, i int) (int, int) {
    for ; i < len(b) && !isDigit(b[i]); i++ {
    }
    x := 0
    for ; i < len(b) && isDigit(b[i]); i++ {
        x = x*10 + int(b[i]) - '0'
    }
    return x, i
}

入力スライスb内の数値をスキャンするために、以下のように使用できます:

    for i := 0; i < len(b); {
        x, i = nextInt(b, i)
        fmt.Println(x)
    }

解説

1. 複数戻り値の意義

C言語との比較:

  • C言語: エラー情報を戻り値(-1など)やグローバル変数(errno)で表現
  • Go言語: 正常な結果とエラー情報を同時に返すことが可能

2. 具体的なメリット

エラーハンドリングの改善:

// C言語風(問題のあるパターン)
// int write(int fd, char* buf, int len);
// 戻り値: 成功時は書き込みバイト数、失敗時は-1
// エラー詳細はerrnoを確認する必要がある

// Go言語(改善されたパターン)
func (file *File) Write(b []byte) (n int, err error)
// 常に書き込みバイト数とエラー情報の両方を取得できる

3. コード例の詳細解説

nextInt関数の動作:

func nextInt(b []byte, i int) (int, int) {
    // 数字以外の文字をスキップ
    for ; i < len(b) && !isDigit(b[i]); i++ {
    }
    
    x := 0
    // 数字を読み取って数値に変換
    for ; i < len(b) && isDigit(b[i]); i++ {
        x = x*10 + int(b[i]) - '0'
    }
    
    return x, i  // 読み取った数値と次の位置を返す
}

使用例:

for i := 0; i < len(b); {
    x, i = nextInt(b, i)  // 複数戻り値を同時に受け取り
    fmt.Println(x)
}

4. 複数戻り値の実用パターン

1. 値とエラーの組み合わせ(最も一般的):

func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, errors.New("division by zero")
    }
    return a / b, nil
}

result, err := divide(10, 2)
if err != nil {
    log.Fatal(err)
}
fmt.Println(result) // 5

2. 値と存在確認:

func findUser(id int) (User, bool) {
    user, exists := userMap[id]
    return user, exists
}

user, found := findUser(123)
if !found {
    fmt.Println("ユーザーが見つかりません")
}

3. 複数の関連する値:

func getNameAndAge() (string, int) {
    return "太郎", 25
}

name, age := getNameAndAge()

5. 名前付き戻り値

func divide(a, b float64) (result float64, err error) {
    if b == 0 {
        err = errors.New("division by zero")
        return  // return result, err と同じ
    }
    result = a / b
    return  // return result, err と同じ
}

6. 複数戻り値の利点

  • 明確性: エラー状態と正常な結果を明確に区別
  • 効率性: ポインタ渡しが不要
  • 安全性: エラーの見落としを防ぎやすい
  • 慣用性: Go言語らしいコードスタイル

7. 注意点

  • すべての戻り値を受け取る必要はありません(_で無視可能)
  • エラーを無視するとコンパイル警告が出る場合があります
  • 戻り値が多すぎる場合は構造体を検討

複数戻り値は、Go言語の設計思想「シンプルで明確」を体現する重要な機能です。

おわりに 

本日は、Go言語を効果的に使うためのガイドラインについて解説しました。

よっしー
よっしー

何か質問や相談があれば、コメントをお願いします。また、エンジニア案件の相談にも随時対応していますので、お気軽にお問い合わせください。

それでは、また明日お会いしましょう(^^)

コメント

タイトルとURLをコピーしました