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を使用する必要があります。

次に、そのディレクトリ内にhello.goというファイルを作成し、次の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)としてインストールします。

インストールディレクトリは、GOPATHおよびGOBIN環境変数によって制御されます。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パッケージで構成される軽量のテストフレームワークがあります。

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

$HOME/hello/morestrings/reverse_test.goというファイルを作成し、以下の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を参照してください。

言語自体を学ぶには、A Tour of Goを受講してください。

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

ヘルプの取得

リアルタイムのヘルプについては、コミュニティが運営するgophers Slackサーバーで親切なGopherに尋ねてください(招待状はこちらから入手できます)。

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

Go課題トラッカーを使用してバグを報告してください。