この記事は Go Advent Calendar 2021 20日目の記事です。

Go というプログラミング言語をご存知でしょうか。
分類としてはコンパイル系言語で、とにかくシンプルにコードを書くことができます。
「名前は聞いたことがあるけど…」「どんな言語か気になっていた」「とにかく検索がしづらい」といった方へ向けて、今回は Go 言語の良さをいくつかアピールしたいと思います。

  • エラーハンドリング
  • ループ処理
  • 構造体
  • スコープ
  • 開発時の関連ツール

他にも良いところはたくさんあるのですが、なるべくよく使いそうなものをあげています。

まずは基本から

Hello World を出力します。

// hello_world.go
package main

import "fmt"

func main() {
    fmt.Println("Hello World")
}
$ go run hello_world.go
Hello World

それでは前述した良い点について、1つずつ見ていきたいと思います。

エラーハンドリング

try~catch なんてものは存在しません。
ではどうやってハンドリングするかというと、ずばり「戻り値」です。
以下は入力された値が奇数か偶数かを判定するコードですが、0を渡した場合はエラーとしています。

// handle_error.go
package main

import (
    "errors"
    "fmt"
    "os"
    "strconv"
)

// 関数は関数名(引数 型) (戻り値の型) のように書く
func isOdd(num int) (bool, error) {
    if num == 0 {
        return false, errors.New("Can't specify zero")
    }
    return num%2 == 1, nil
}

func main() {
    fmt.Println("Please enter the number")

    var value string
    fmt.Scan(&value) // 定義した変数のポインタを渡すことで、後続処理では変数に値が設定された状態になる

    // 入力された文字列を数値に変換する
    num, err := strconv.Atoi(value)
    if err != nil {
        // エラーを出力してここで終了
        fmt.Println(err.Error())
        os.Exit(1)
    }

    result, err := isOdd(num)
    // 呼び出し側は err が nil かどうかを判定する必要がある
    if err != nil {
        fmt.Println(err.Error())
        return
    }
    fmt.Println(fmt.Sprintf("IsOdd? %t", result))
}

呼び出し元で都度 err != nil というコードを書く必要があるので、try~catch がある言語に慣れている方はイマイチに感じるかもしれません。
ただ、起こりうるエラーを意識しながらコードを書くことができるという点や、関数が担う役割を明確にできていると考えれば、合理的なのではと思っています。
panic() を利用して強制終了させるという方法もあるにはありますが、これはアプリケーションの動作が継続不可能な場合以外はまず使いません。

ループ処理

Go のループ処理には原則 for を使います。
while や each なんてものは存在しません。
以下は FizzBuzz を出力するコードです。

// fizzbuzz.go
package main

import (
    "fmt"
    "strings"
)

func main() {
    for i := 1; i <= 30; i++ {
        m := []string{}
        if i%3 == 0 {
            m = append(m, "Fizz")
        }
        if i%5 == 0 {
            m = append(m, "Buzz")
        }

        if len(m) > 0 {
            fmt.Println(strings.Join(m, ""))
        } else {
            fmt.Println(i)
        }
    }
}

while や each と同じことがやりたい場合も for で書くことになります。

// loop.go
package main

import "fmt"

func while() {
    count := 0
    for count < 10 {
        fmt.Println(count)
        count++
    }
}

func foreach() {
    arr := []int{1, 2, 3}
    for _, v := range arr {
        fmt.Println(v)
    }
}

func main() {
    while()
    foreach()
}

コードレビューなどで「ここは○○を使ったほうが…」みたいな個人の意見に左右されづらいという点から、これも大きなメリットだと考えます。

構造体

構造体定義自体はそこまでシンプルでも無いんですが、オブジェクトを生成する際は結構シンプルに書けます。
構造体に関数を追加することもできます。

// user.go
package main

import "fmt"

type User struct {
    ID   int
    Name string
}

// User 構造体が持つ関数
func (u *User) Label() string {
    return fmt.Sprintf("%d: %s", u.ID, u.Name)
}

func main() {
    user := User{ID: 1, Name: "Test User"} // User を初期化
    fmt.Println(user.Label()) // "1: Test User"
}

スコープ

関数の先頭が大文字なら public(他パッケージから直接閲覧および操作が可能)、小文字なら private(他パッケージから直接閲覧および操作が不可能)です。
めちゃめちゃわかりやすいですよね。

// func.go
package main

import "fmt"

// 別 package からも参照できる
func Public() {
    fmt.Println("Public")
}

// 別 package からは参照できない
func private() {
    fmt.Println("private")
}

// main (go のプログラム実行時のエントリポイント) のような例外も存在する
func main() {
    Public()
    private()
}

開発時の関連ツール

パッケージ管理 (go mod) やフォーマッタ (go fmt)、静的解析 (go vet) やテストツール (go test) など、開発で必要な機能は一通り標準装備されており、これは個人的にかなり嬉しいポイントです。
ツールをどれにするか、どのルールを採用するかなどは好みが出るところですが、Go を使った場合有無を言わせず同じルールでコードを書くことになるので、チーム開発におけるコミュニケーションコスト削減につながると考えます。
私は普段 Visual Studio Code を使ってコードを書いていますが、この拡張機能も使うことで快適にコードが書けています。
パッケージ管理については以前書いた記事がありますので、興味のある方はぜひご覧ください。

まとめ

いかがでしたでしょうか。
Go は良くも悪くもシンプルな言語、ということがわかっていただけたのではと思います。
コードや関数の選択肢が少ないからこそ、誰が書いても同じようなコードになります。
これは様々なスキルのメンバーでプロダクト開発をする際には大きなメリットになります。
それでは、皆さんも素敵な Go ライフを!