Go 言語に興味があるという人のためにシンプルさをアピールする

この記事は 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) GetIDName() string {
    return fmt.Sprintf("%d: %s", u.ID, u.Name)
}

func main() {
    user := User{ID: 1, Name: "Test User"} // User を初期化
    fmt.Println(user.GetIDName()) // "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()
}

開発時の関連ツール

パッケージ管理やフォーマッタ、テストツールが標準装備されていることも個人的に嬉しいポイントです(パッケージ管理については以前書いた記事がありますので興味のある方はぜひご覧ください)。
私は普段 Visual Studio Code を使っていますが、この拡張機能も使うことで快適にコードが書けています。

まとめ

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