この記事は 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 ライフを!