Goのコードの書き方

はじめに

このドキュメントでは、モジュール内のシンプルなGoパッケージの開発を実演し、Goモジュール、パッケージ、コマンドの取得、ビルド、インストールを行う標準的な方法であるgoツールを紹介します。

コードの構成

Goプログラムはパッケージに編成されます。パッケージとは、同じディレクトリにある、一緒にコンパイルされるソースファイルの集まりのことです。1つのソースファイルで定義された関数、型、変数、定数は、同じパッケージ内の他のすべてのソースファイルから参照できます。

リポジトリには、1つ以上のモジュールが含まれています。モジュールとは、一緒になってリリースされる関連するGoパッケージの集まりです。Goリポジトリには通常、リポジトリのルートにある1つのモジュールのみが含まれています。そこにあるgo.modという名前のファイルは、モジュールパスを宣言します。これは、モジュール内のすべてのパッケージのインポートパスのプレフィックスです。モジュールには、そのgo.modファイルを含むディレクトリ内のパッケージと、そのディレクトリのサブディレクトリ(次の別のgo.modファイルを含むサブディレクトリまで(存在する場合))が含まれます。

コードをビルドする前に、リモートリポジトリに公開する必要はないことに注意してください。モジュールは、リポジトリに属することなくローカルで定義できます。ただし、いつか公開するつもりでコードを整理するのは良い習慣です。

各モジュールのパスは、パッケージのインポートパスのプレフィックスとして機能するだけでなく、goコマンドがダウンロードする場所を示すものでもあります。たとえば、モジュールgolang.org/x/toolsをダウンロードするために、goコマンドはhttps://go.dokyumento.jp/x/toolsで示されるリポジトリを参照します(詳しくはこちらを参照してください)。

インポートパスとは、パッケージをインポートするために使用される文字列です。パッケージのインポートパスは、モジュールパスとそのモジュール内のサブディレクトリを結合したものです。たとえば、モジュールgithub.com/google/go-cmpには、cmp/ディレクトリにパッケージが含まれています。そのパッケージのインポートパスはgithub.com/google/go-cmp/cmpです。標準ライブラリのパッケージには、モジュールパスのプレフィックスはありません。

最初のプログラム

簡単なプログラムをコンパイルして実行するには、まずモジュールパス(example/user/helloを使用します)を選択し、それを宣言するgo.modファイルを作成します。

$ mkdir hello # Alternatively, clone it if it already exists in version control.
$ cd hello
$ go mod init example/user/hello
go: creating new go.mod: module example/user/hello
$ cat go.mod
module example/user/hello

go 1.16
$

Goソースファイルの最初のステートメントは、package nameである必要があります。実行可能コマンドは常にpackage mainを使用する必要があります。

次に、そのディレクトリ内に、次のGoコードを含むhello.goという名前のファイルを作成します。

package main

import "fmt"

func main() {
    fmt.Println("Hello, world.")
}

これで、goツールを使用してそのプログラムをビルドおよびインストールできます。

$ go install example/user/hello
$

このコマンドは、helloコマンドをビルドし、実行可能なバイナリを生成します。次に、そのバイナリを$HOME/go/bin/hello(または、Windowsの場合は%USERPROFILE%\go\bin\hello.exe)としてインストールします。

インストールディレクトリは、GOPATHGOBIN環境変数によって制御されます。GOBINが設定されている場合、バイナリはそのディレクトリにインストールされます。GOPATHが設定されている場合、バイナリはGOPATHリストの最初のディレクトリのbinサブディレクトリにインストールされます。それ以外の場合、バイナリはデフォルトのGOPATH$HOME/goまたは%USERPROFILE%\go)のbinサブディレクトリにインストールされます。

go envコマンドを使用して、今後のgoコマンドの環境変数のデフォルト値をポータブルに設定できます。

$ go env -w GOBIN=/somewhere/else/bin
$

go env -wで以前に設定された変数を設定解除するには、go env -uを使用します。

$ go env -u GOBIN
$

go installなどのコマンドは、現在の作業ディレクトリを含むモジュールのコンテキスト内で適用されます。作業ディレクトリがexample/user/helloモジュール内にない場合、go installは失敗する可能性があります。

便宜上、goコマンドは作業ディレクトリからの相対パスを受け入れ、他のパスが指定されていない場合は、現在の作業ディレクトリのパッケージをデフォルトにします。したがって、作業ディレクトリでは、次のコマンドはすべて同等です。

$ go install example/user/hello
$ go install .
$ go install

次に、プログラムが正常に動作することを確認するために、プログラムを実行しましょう。さらに便利にするために、バイナリの実行を簡単にするために、インストールディレクトリをPATHに追加します。

# Windows users should consult /wiki/SettingGOPATH
# for setting %PATH%.
$ export PATH=$PATH:$(dirname $(go list -f '{{.Target}}' .))
$ hello
Hello, world.
$

ソース管理システムを使用している場合は、リポジトリを初期化し、ファイルを追加して、最初の変更をコミットするのが良いでしょう。繰り返しますが、この手順はオプションです。Goコードを作成するためにソース管理を使用する必要はありません。

$ git init
Initialized empty Git repository in /home/user/hello/.git/
$ git add go.mod hello.go
$ git commit -m "initial commit"
[master (root-commit) 0b4507d] initial commit
 1 file changed, 7 insertion(+)
 create mode 100644 go.mod hello.go
$

goコマンドは、対応するHTTPS URLをリクエストし、HTMLレスポンスに埋め込まれたメタデータを読み取ることで、指定されたモジュールパスを含むリポジトリを見つけます(go help importpathを参照)。多くのホスティングサービスは、Goコードを含むリポジトリに対してすでにそのメタデータを提供しているため、モジュールを他の人が使用できるようにする最も簡単な方法は、通常、そのモジュールパスをリポジトリのURLと一致させることです。

モジュールからパッケージをインポートする

morestringsパッケージを作成し、helloプログラムから使用しましょう。まず、$HOME/hello/morestringsという名前のパッケージのディレクトリを作成し、次に、そのディレクトリに次の内容のreverse.goという名前のファイルを作成します。

// Package morestrings implements additional functions to manipulate UTF-8
// encoded strings, beyond what is provided in the standard "strings" package.
package morestrings

// ReverseRunes returns its argument string reversed rune-wise left to right.
func ReverseRunes(s string) string {
    r := []rune(s)
    for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 {
        r[i], r[j] = r[j], r[i]
    }
    return string(r)
}

ReverseRunes関数が大文字で始まるため、エクスポートされており、morestringsパッケージをインポートする他のパッケージで使用できます。

go buildでパッケージがコンパイルされることをテストしましょう。

$ cd $HOME/hello/morestrings
$ go build
$

これにより、出力ファイルは生成されません。代わりに、コンパイルされたパッケージがローカルビルドキャッシュに保存されます。

morestringsパッケージがビルドされることを確認したら、helloプログラムから使用しましょう。これを行うには、元の$HOME/hello/hello.goを修正してmorestringsパッケージを使用します。

package main

import (
    "fmt"

    "example/user/hello/morestrings"
)

func main() {
    fmt.Println(morestrings.ReverseRunes("!oG ,olleH"))
}

helloプログラムをインストールします。

$ go install example/user/hello

新しいバージョンのプログラムを実行すると、新しい反転されたメッセージが表示されます。

$ hello
Hello, Go!

リモートモジュールからパッケージをインポートする

インポートパスは、GitやMercurialなどのリビジョン管理システムを使用してパッケージのソースコードを取得する方法を記述できます。goツールはこのプロパティを使用して、リモートリポジトリからパッケージを自動的にフェッチします。たとえば、プログラムでgithub.com/google/go-cmp/cmpを使用するには

package main

import (
    "fmt"

    "example/user/hello/morestrings"
    "github.com/google/go-cmp/cmp"
)

func main() {
    fmt.Println(morestrings.ReverseRunes("!oG ,olleH"))
    fmt.Println(cmp.Diff("Hello World", "Hello Go"))
}

外部モジュールに依存するようになったので、そのモジュールをダウンロードし、そのバージョンをgo.modファイルに記録する必要があります。go mod tidyコマンドは、インポートされたパッケージに必要なモジュール要件を追加し、不要になったモジュールの要件を削除します。

$ go mod tidy
go: finding module for package github.com/google/go-cmp/cmp
go: found github.com/google/go-cmp/cmp in github.com/google/go-cmp v0.5.4
$ go install example/user/hello
$ hello
Hello, Go!
  string(
-     "Hello World",
+     "Hello Go",
  )
$ cat go.mod
module example/user/hello

go 1.16

require github.com/google/go-cmp v0.5.4
$

モジュールの依存関係は、GOPATH環境変数で示されるディレクトリのpkg/modサブディレクトリに自動的にダウンロードされます。モジュールの特定のバージョンのダウンロードされた内容は、そのバージョンをrequireする他のすべてのモジュールで共有されるため、goコマンドはそれらのファイルとディレクトリを読み取り専用としてマークします。ダウンロードされたすべてのモジュールを削除するには、go clean-modcacheフラグを渡します。

$ go clean -modcache
$

テスト

Goには、go testコマンドとtestingパッケージで構成される軽量のテストフレームワークがあります。

func (t *testing.T)シグネチャを持つTestXXXという名前の関数を含む、_test.goで終わる名前のファイルを作成することでテストを作成します。テストフレームワークは、そのような各関数を実行します。関数がt.Errort.Failなどの失敗関数を呼び出すと、テストは失敗したとみなされます。

次のGoコードを含むファイル$HOME/hello/morestrings/reverse_test.goを作成して、morestringsパッケージにテストを追加します。

package morestrings

import "testing"

func TestReverseRunes(t *testing.T) {
    cases := []struct {
        in, want string
    }{
        {"Hello, world", "dlrow ,olleH"},
        {"Hello, 世界", "界世 ,olleH"},
        {"", ""},
    }
    for _, c := range cases {
        got := ReverseRunes(c.in)
        if got != c.want {
            t.Errorf("ReverseRunes(%q) == %q, want %q", c.in, got, c.want)
        }
    }
}

次に、go testでテストを実行します。

$ cd $HOME/hello/morestrings
$ go test
PASS
ok  	example/user/hello/morestrings 0.165s
$

詳細については、go help testを実行し、testingパッケージのドキュメントを参照してください。

次は何をすべきか

Goの新しい安定バージョンがリリースされたときに通知を受け取るには、golang-announceメーリングリストを購読してください。

明確で慣用的なGoコードを作成するためのヒントについては、Effective Goを参照してください。

言語自体を学ぶには、Goツアーを受講してください。

Go言語とそのライブラリおよびツールに関する詳細な記事については、ドキュメントページにアクセスしてください。

ヘルプの入手

リアルタイムのヘルプが必要な場合は、コミュニティ運営のgophers Slackサーバーで親切なゴーファーに質問してください(招待状はこちらで入手してください)。

Go言語に関する議論のための公式メーリングリストは、Go Nutsです。

バグの報告は、Go issue trackerを使用してください。