Go言語入門(3)「関数を完全理解!引数・戻り値・エラーハンドリング入門」

スポンサーリンク
Go

前回のブログ記事で、制御構文(if、for、switch)を学んだあなたに向けて、今回は関数についてご紹介しよう。

関数を理解することで、コードを整理し、再利用可能な部品として扱えるようになる。
これにより、保守性の高いプログラムを作成できる。

1. 関数の基本「コードを再利用する」

関数は、特定の処理をまとめて名前をつけたものである。一度定義すれば、何度でも呼び出すことができる。

基本的な関数の定義

package main

import "fmt"

// 引数なし、戻り値なし
func sayHello() {
    fmt.Println("こんにちは!")
}

func main() {
    sayHello() // 関数の呼び出し
}
// 出力: こんにちは!

関数定義の構造:

  • func: 関数を定義するキーワード
  • sayHello: 関数名
  • (): 引数のリスト(今回は空)
  • {}: 関数の本体

引数を受け取る関数

// 引数あり、戻り値なし
func greet(name string) {
    fmt.Println("こんにちは、", name, "さん!")
}

func main() {
    greet("太郎")    // 出力: こんにちは、 太郎 さん!
    greet("花子")    // 出力: こんにちは、 花子 さん!
}

戻り値を返す関数

// 引数あり、戻り値あり
func add(a int, b int) int {
    return a + b
}

func main() {
    result := add(10, 20)
    fmt.Println("10 + 20 =", result)
    // 出力: 10 + 20 = 30
}

戻り値の型:

  • 関数名と引数の後にintと書くことで、戻り値の型を指定する
  • return文で値を返す

同じ型の引数は省略できる

// a, b が両方 int なので省略可能
func multiply(a, b int) int {
    return a * b
}

func main() {
    product := multiply(5, 6)
    fmt.Println("5 × 6 =", product)
    // 出力: 5 × 6 = 30
}

2. 複数の戻り値(Goの特徴的な機能)

Goでは、関数から複数の値を返すことができる。これはエラーハンドリングで非常に重要な機能である。

2つの値を返す関数

// 割り算の商と余りを同時に返す
func divmod(a, b int) (int, int) {
    quotient := a / b     // 商
    remainder := a % b    // 余り
    return quotient, remainder
}

func main() {
    q, r := divmod(17, 5)
    fmt.Printf("17 ÷ 5 = %d 余り %d\n", q, r)
    // 出力: 17 ÷ 5 = 3 余り 2
}

戻り値の一部を無視する

戻り値の一部が不要な場合、_(アンダースコア)で無視できる。

func main() {
    _, remainder := divmod(20, 3)
    fmt.Println("余りのみ:", remainder)
    // 出力: 余りのみ: 2
}

3. エラーハンドリングの基本

Goでは、エラーを戻り値として返すのが一般的である。

error型を返す関数

import (
    "errors"
    "fmt"
)

func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, errors.New("0で割ることはできません")
    }
    return a / b, nil  // エラーがない場合はnilを返す
}

func main() {
    // 正常なケース
    result, err := divide(10, 2)
    if err != nil {
        fmt.Println("エラー:", err)
    } else {
        fmt.Println("結果:", result)
    }
    // 出力: 結果: 5
    
    // エラーケース
    result, err = divide(10, 0)
    if err != nil {
        fmt.Println("エラー:", err)
    } else {
        fmt.Println("結果:", result)
    }
    // 出力: エラー: 0で割ることはできません
}

Goのエラーハンドリングのパターン:

  1. 関数は(結果, error)の形で値を返す
  2. 呼び出し側でif err != nilでエラーをチェック
  3. エラーがなければnilを返す

これはGoの重要な慣習である。

4. 可変長引数「任意の数の引数を受け取る」

...を使うと、任意の数の引数を受け取ることができる。

func sum(numbers ...int) int {
    total := 0
    for _, num := range numbers {
        total += num
    }
    return total
}

func main() {
    fmt.Println("合計:", sum(1, 2, 3))           // 出力: 合計: 6
    fmt.Println("合計:", sum(1, 2, 3, 4, 5))     // 出力: 合計: 15
    fmt.Println("合計:", sum(10, 20))            // 出力: 合計: 30
}

可変長引数の特徴:

  • numbers ...intは、intのスライスとして扱われる
  • 引数の数は任意(0個でもOK)
  • rangeで各要素にアクセスできる

5. 名前付き戻り値「戻り値に名前をつける」

Goでは、戻り値に名前をつけることができる。

func rectangle(width, height int) (area int, perimeter int) {
    area = width * height
    perimeter = 2 * (width + height)
    return  // 名前付き戻り値は return だけでOK
}

func main() {
    area, perimeter := rectangle(5, 10)
    fmt.Printf("面積: %d, 周囲: %d\n", area, perimeter)
    // 出力: 面積: 50, 周囲: 30
}

名前付き戻り値の利点:

  • ドキュメントとして機能する
  • 裸のreturn(値を書かない)が使える
  • 関数の途中で値を設定できる

注意点:

  • 短い関数では逆に読みにくくなることもある
  • 使いすぎないように注意

6. 実践:すべてを組み合わせた例

学んだ内容を使って、簡単な計算機プログラムを作ってみよう。

package main

import (
    "errors"
    "fmt"
)

// 四則演算を行う関数(エラーハンドリング付き)
func calculate(a, b float64, operator string) (float64, error) {
    switch operator {
    case "+":
        return a + b, nil
    case "-":
        return a - b, nil
    case "*":
        return a * b, nil
    case "/":
        if b == 0 {
            return 0, errors.New("0で割ることはできません")
        }
        return a / b, nil
    default:
        return 0, errors.New("未対応の演算子です: " + operator)
    }
}

func main() {
    fmt.Println("=== 簡易計算機 ===")
    
    // テストケース
    testCases := []struct {
        a, b     float64
        operator string
    }{
        {10, 5, "+"},
        {10, 5, "-"},
        {10, 5, "*"},
        {10, 5, "/"},
        {10, 0, "/"},  // エラーケース
        {10, 5, "%"},  // エラーケース
    }
    
    for _, tc := range testCases {
        result, err := calculate(tc.a, tc.b, tc.operator)
        if err != nil {
            fmt.Printf("%.1f %s %.1f = エラー: %v\n", 
                tc.a, tc.operator, tc.b, err)
        } else {
            fmt.Printf("%.1f %s %.1f = %.1f\n", 
                tc.a, tc.operator, tc.b, result)
        }
    }
}

実行結果:

=== 簡易計算機 ===
10.0 + 5.0 = 15.0
10.0 - 5.0 = 5.0
10.0 * 5.0 = 50.0
10.0 / 5.0 = 2.0
10.0 / 0.0 = エラー: 0で割ることはできません
10.0 % 5.0 = エラー: 未対応の演算子です: %

プログラムのポイント

  1. 関数calculate:
    • 2つの数値と演算子を受け取る
    • (結果, error)の形で戻り値を返す
    • switch文で演算子ごとに処理を分岐
    • エラーケースを適切に処理
  2. main関数:
    • テストケースを用意して一括テスト
    • エラーハンドリングを実践
    • 結果を見やすく表示

関数を適切に使うことで、コードの再利用性と保守性が大幅に向上する。

次のステップとしては、スライスとマップについて学んでいくとよいだろう。

この記事のサンプルコードは、私のGitHubリポジトリで公開している: go-learning/basics/04-functions

GitHub Codespacesを使えば、ブラウザ上ですぐに実行できる!

コメント

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