...
Run Format

Goコードの書き方

イントロダクション

このドキュメントでは簡単なGoパッケージの開発の説明、goツール、Goパッケージとコマンドの標準的なフェッチ・ビルド・インストールの方法を紹介しています。

goツールの利用に際しては、コードを特定の方法で構成する必要があります。このドキュメントを注意深く読みましょう。最も簡単にGoをインストールし、使い始める方法を説明しています。

同様の説明をスクリーンキャストで見ることができます。

コードの構成

ワークスペース

goツールは、公開レポジトリでメンテナンスされているオープンソースコードを扱うことを想定して設計されています。実際にコードを公開する必要はありませんが、公開する場合もしない場合も同じ方法で環境の設定を行うことができます。

Goコードはワークスペース内に置いておく必要があります。ワークスペースはルートに以下の3つのディレクトリを持つディレクトリ階層です。

goツールはソースパッケージをビルドし、その結果生成されるバイナリをpkgおよびbinにインストールします。

srcサブディレクトリは一般的に1つ以上のソースパッケージの開発を追跡するバージョンコントロールレポジトリ(Git、Mercurialなど)を複数内包します。

実際にワークスペースがどのようになっているか把握していただくために、次の例をご覧ください。

bin/
    streak                         # コマンド実行形式ファイル
    todo                           # コマンド実行形式ファイル
pkg/
    linux_amd64/
        code.google.com/p/goauth2/
            oauth.a                # パッケージオブジェクト
        github.com/nf/todo/
            task.a                 # パッケージオブジェクト
src/
    code.google.com/p/goauth2/
        .hg/                       # mercurialレポジトリメタデータ
        oauth/
            oauth.go               # パッケージソース
            oauth_test.go          # テストソース
    github.com/nf/
        streak/
            .git/                  # gitレポジトリメタデータ
            oauth.go               # コマンドソース
            streak.go              # コマンドソース
        todo/
            .git/                  # gitレポジトリメタデータ
            task/
                task.go            # パッケージソース
            todo.go                # コマンドソース

このワークスペースには2つのコマンド(streak and todo)と2つのライブラリ(oauth and task)から成る3つのレポジトリ(goauth2streaktodo)が入っています。

コマンド、ライブラリは異なる種類のソースパッケージからビルドされます。この区別は以降で議論します。

GOPATH環境変数

GOPATH環境変数はワークスペースの場所を示してします。これがGoコードを開発する際に設定が必要な唯一の環境変数のはずです。

始めるにあたっては、ワークスペースディレクトリを作成し、適宜GOPATHをセットしてください。ワークスペースはどこでも好きな場所に置いてよいですが、このドキュメントでは$HOME/goとして説明しています。Goをインストールしたパスを指定してはいけないことを注意してください。

$ mkdir $HOME/go
$ export GOPATH=$HOME/go

利便性向上のため、以下のようにワークスペースのbinサブディレクトリをPATHに追加してください。

$ export PATH=$PATH:$GOPATH/bin

パッケージパス

標準ライブラリのパッケージは、"fmt""net/http"のように短いパスを与えられています。自分で作ったパッケージについては、将来追加されるライブラリやその他の外部ライブラリと衝突しないであろうベースパスを選ぶ必要があります。

コードをどこかのソースレポジトリに置く場合は、ベースパスとしてソースレポジトリのルートを利用する必要があります。例えば、github.com/userにあるGitHubアカウントを利用している場合には、これがベースパスとなります。

ビルドができる状態になる前にコードをリモートレポジトリに公開する必要はないことに留意してください。いつの日にか公開することを見越してコードを構成することはよい行いです。実質的には、標準ライブラリや大Goエコシステムに対してユニークである限り、いずれの任意のパス名も選ぶことができます。

github.com/userをベースパスに利用します。ソースコードを置くワークスペースの中に以下のようにディレクトリを作成してください。

$ mkdir -p $GOPATH/src/github.com/user

初めてのプログラム

簡単なプログラムをコンパイルし実行するために、まずパッケージパスを選び(ここではgithub.com/user/helloを利用します)、以下のようにワークスペースに対応するパッケージディレクトリを作成します。

$ mkdir $GOPATH/src/github.com/user/hello

次に、作成したディレクトリにhello.goという名前のファイルを作成し、以下のGoコードを記述します。

package main

import "fmt"

func main() {
	fmt.Printf("Hello, world.\n")
}

これで以下のようにgoツールでプログラムをビルドし、インストールできるようになります。

$ go install github.com/user/hello

システムのどこからでもこのコマンドを実行することができます。goツールはGOPATHで指定されたワークスペース内のgithub.com/user/helloパッケージを検索することでソースコードを見つけ出します。

以下のように、対象のパッケージディレクトリからgo installを実行する場合にはパッケージパスを省略することもできます。

$ cd $GOPATH/src/github.com/user/hello
$ go install

このコマンドはhelloコマンドをビルドし、実行可能バイナリを生成します。さらにそのバイナリをhelloという名前(Windowsではhello.exe)でワークスペースのbinディレクトリにインストールします。上記の例では、$GOPATH/bin/hello(つまり、$HOME/go/bin/hello)となります。

goツールはエラーが発生した際にのみ出力をプリントします。つまり、これらのコマンドで何も出力されなかった場合には正しく実行されたことになります。

これで以下のようにコマンドラインにフルパスを入力することでプログラムを実行できるようになります。

$ $GOPATH/bin/hello
Hello, world.

もしくは、$GOPATH/binPATHに追加されていれば、以下のようにバイナリ名を入力するだけです。

$ hello
Hello, world.

ソース管理システムを利用している場合は、このときがレポジトリを初期化し、ファイルを追加し、最初の変更をコミットするのに適したタイミングです。もう一度確認しておきますが、このステップは任意です。Goコードを書くのにソース管理が必要だということではありません。

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

リモートレポジトリへのコードのプッシュの方法は、読者の皆さんへの演習課題として残しておきます。

初めてのライブラリ

ライブラリを書いて、それをhelloプログラムで利用しましょう。

ここでも、最初のステップはパッケージパスを選び(ここではgithub.com/user/newmathとします)、以下のようにパッケージディレクトリを作成することです。

$ mkdir $GOPATH/src/github.com/user/newmath

次に、このディレクトリにsqrt.goという名前のファイルを作成し、以下の内容を記述します。

// newmathパッケージはちょっとしたサンプルパッケージです。
package newmath

// Sqrtはxの平方根の近似値を返します。
func Sqrt(x float64) float64 {
	z := 1.0
	for i := 0; i < 1000; i++ {
		z -= (z*z - x) / (2 * z)
	}
	return z
}

そして、go buildでパッケージがコンパイルできることをテストします。

$ go build github.com/user/newmath

もしくは、パッケージのソースディレクトリで作業している場合には、単に以下のようにします。

$ go build

このコマンドはファイルの出力を行いません。ファイルを出力するには、go installを用いる必要があります。これにより、ワークスペースのpkgディレクトリにパッケージオブジェクトが生成されます。

newmathパッケージがビルドされたことを確認してから、元のhello.go$GOPATH/src/github.com/user/helloにある)を以下のように修正してこのパッケージを利用するようにします。

package main

import (
	"fmt"

	"github.com/user/newmath"
)

func main() {
	fmt.Printf("Hello, world.  Sqrt(2) = %v\n", newmath.Sqrt(2))
}

goツールがパッケージやバイナリをインストールする際には必ず、それらが持つすべての依存関係がインストールされます。ですので以下のようにhelloプログラムをインストールする場合には、

$ go install github.com/user/hello

newmathが同時に自動でインストールされます。

この新しいバージョンのプログラムを実行すると、以下のように何らかの数値が出力されることを確認できるはずです。

$ hello
Hello, world.  Sqrt(2) = 1.414213562373095

上記のステップを行うと、ワークスペースは以下のような状態になっているはずです。

bin/
    hello              # コマンド実行形式ファイル
pkg/
    linux_amd64/       # ここはOSとアーキテクチャ名が反映されます
        github.com/user/
            newmath.a  # パッケージオブジェクト
src/
    github.com/user/
        hello/
            hello.go   # コマンドソース
        newmath/
            sqrt.go    # パッケージソース

go installを実行すると、newmath.aオブジェクトがpkg/linux_amd64の中にソースディレクトリと同じ階層で置かれます。これは、今後のgoツールの呼び出しの際、既存のパッケージオブジェクトを検索することで不必要なパッケージの再コンパイルを避けるようにするためです。linux_amd64の部分はクロスコンパイルを可能にするためにあり、利用しているオペレーティングシステムとアーキテクチャの名前が反映されます。

Goのコマンド実行形式ファイルは静的リンクされており、Goプログラムの実行にパッケージオブジェクトは必要ありません。

パッケージ名

Goソースファイルの最初のステートメントは以下のようでなければなりません。

package name

nameの部分にはインポートするパッケージのデフォルト名が入ります。(パッケージ内のすべてのファイルで同じnameを利用します。)

Goの規則ではパッケージ名はインポートパスの最後の要素となります。例えば、"crypto/rot13"というパッケージをインポートする場合は、rot13とします。

実行形式ファイルのコマンドは常にpackage mainとする必要があります。

パッケージ名は、ある1つのバイナリにリンクされたすべてのパッケージを通じて一意である必要はありません。インポートパス(完全ファイル名)のみ一意である必要があります。

Goの命名規則についてさらに詳しく学びたい場合は、Effective Goをご覧ください。

テスティング

Goはgo testコマンドとtestingパッケージで構成される軽量なテストフレームワークを提供します。

テストを書くには、_test.goで終わる名前のファイルを作成し、func (t *testing.T)というシグネチャのTestXXXという名前の関数を作成します。テストフレームワークはこれらの関数を一つ一つ実行し、t.Errort.Failといったフェイル関数が呼ばれると、テストが失敗したとみなします。

newmathパッケージのテストを追加しましょう。$GOPATH/src/github.com/user/newmath/sqrt_test.goというファイルを作成し、以下のGoコードを記述してください。

package newmath

import "testing"

func TestSqrt(t *testing.T) {
	const in, out = 4, 2
	if x := Sqrt(in); x != out {
		t.Errorf("Sqrt(%v) = %v, want %v", in, x, out)
	}
}

そして、go testでテストを実行しましょう。

$ go test github.com/user/newmath
ok  	github.com/user/newmath 0.165s

いつもどおり、goツールをパッケージのディレクトリで実行する場合は、パッケージのパスを省略できます。

$ go test
ok  	github.com/user/newmath 0.165s

さらに詳しい情報については、go help testを実行し、testingパッケージドキュメントをご覧ください。

リモートパッケージ

GitやMercurialといったリビジョン管理システムを利用したパッケージのソースコードを利用する場合、インポートパスにはどのリビジョン管理システムから取得するかを記述するようにします。goはこのプロパティを利用してリモートレポジトリからパッケージを自動的にフェッチします。例えば、このドキュメントに記述されている例はGoogle Codeにホストされているcode.google.com/p/go.example Mercurialレポジトリにも収録されています。パッケージインポートパスにレポジトリのURLが含まれている場合は、以下のようにgo getで自動的にフェッチ、ビルド、インストールができます。

$ go get code.google.com/p/go.example/hello
$ $GOPATH/bin/hello
Hello, world.  Sqrt(2) = 1.414213562373095

指定されたパッケージがワークスペースに存在しない場合、go getGOPATHで最初に指定されているワークスペース内に配置します。(パッケージがすでに存在する場合、go getはリモートからのフェッチをスキップし、go installと同じように振る舞います。)

上記のgo getコマンドを発行すると、ワークスペースのディレクトリツリーは以下のようになります。

bin/
    hello                 # コマンド実行形式ファイル
pkg/
    linux_amd64/
        code.google.com/p/go.example/
            newmath.a     # パッケージオブジェクト
        github.com/user/
            newmath.a     # パッケージオブジェクト
src/
    code.google.com/p/go.example/
        hello/
            hello.go      # コマンドソース
        newmath/
            sqrt.go       # パッケージソース
            sqrt_test.go  # テストソース
    github.com/user/
        hello/
            hello.go      # コマンドソース
        newmath/
            sqrt.go       # パッケージソース
            sqrt_test.go  # テストソース

Google Codeにホストされているhelloは同じレポジトリ内のnewmathパッケージに依存しています。hello.goファイル内のインポートには同じインポートパスの規則を用います。そのため、go getコマンドは依存するパッケージも同様に場所の特定・インストールをすることができます。

import "code.google.com/p/go.example/newmath"

この規則により、自分で作ったGoパッケージを他の人が非常に容易に使えるようになります。Go Wikigodoc.orgに外部のGoプロジェクトの一覧があります。

goツールでリモートレポジトリを利用する際のさらに詳しい情報は、go help importpathをご覧ください。

次のステップ

Goの新しい安定バージョンのリリース通知を受け取るにはgolang-announceメーリングリストを購読しましょう。

きれいで慣用的なGoコードを書くためのティップスはEffective Goをご覧ください。

きちんと言語を学ぶにはA Tour of Goを利用しましょう。

Go言語とライブラリ、ツールに関するさらに詳細な記事についてはdocumentation pageをご覧ください。

ヘルプを得るには

リアルタイムヘルプについては、Freenode IRCサーバの#go-nutsで協力的なGopher達に質問してください。

Go言語のディスカッションのためのオフィシャルメーリングリストはGo Nutsです。

バグのレポートにはGo issue trackerを利用してください。