BT

最新技術を追い求めるデベロッパのための情報コミュニティ

寄稿

Topics

地域を選ぶ

InfoQ ホームページ ニュース Goのプログラミングパターン

Goのプログラミングパターン

原文(投稿日:2016/03/13)へのリンク

QCon London 2016において、Peter Bourgon氏は「Successful Go Program Design, 6 Years On」というプレゼンを行い、Goでプログラミングするときに使うべきパターンと避けるべきパターンについて説明した。

GOPATH: 環境変数PATHにGOPATH/binを加え、関係バイナリを簡単にアクセスできるようにする。Bourgon氏は一つのグローバルなGOPATHを使うことを推奨する。たいていの場合、これでうまくいく。自分のコードと外部依存のコードを明確に分離したい人は、2つのGOPATHを作るのが好みだろう。gbを使って、環境変数をセットせずにプロジェクトごとに構築するという選択肢もある。

リポジトリ構成: リポジトリの構成はプロジェクトに依存する。プライベートなプロジェクトで決して公開しないなら、好きな構成で構わない。オープンソースの場合には、go getで簡単に取得できるよう、リモートパッケージのガイダンスに従うべきだ。以下に示すように、Bourgon氏はベースディレクトリにプログラムの主たる成果物を置き、サブディレクトリにヘルパーパッケージを置くことを推奨する。

go-patterns-repo

コードの整形: 使ったコードを読みやすくするため、Bourgon氏はGoの標準スタイルを尊重するのが重要だと言う。彼の意見では、きちんと整形されていないコードは初心者が書いたものだと、コミュニティでは扱われるそうだ。保存するたびにgofmtを実行してコードを整形してもよい。コードの整形について、彼はGo Code Reviewガイドラインを参照し、レビュアーとオーナーに共通言語とプラクティスを与えるよう述べた。また「もしあなたがやれば全員が感謝するだろう」と、Goにおける変数、関数、エクスポートなどの命名にはAndrew Gerrand氏のおすすめを支持している。

コンフィグレーション: Bourgon氏はコンフィグレーション設定について「明示的にきちんと文書化する」ようアドバイスした。彼は標準ライブラリであるflagパッケージを使っているが、「もっとわかりやすければよかったのに」と思っている。彼はコンフィグレーションドメインを明示的にすることが重要だと言う。コンフィグレーションを環境変数で渡すと、システムのパラメータを理解するのに十分な情報をユーザーに与えることができない。コンフィグレーション情報はヘルプに入れるべきだと考えている。

パッケージ名: パッケージが含んでいるものではなく、パッケージが提供するものに基づいて命名すべきだ。HelloWorldメッセージを含んでいるパッケージは、commonconstsではなく、greetingsと呼ぶべきだ。パッケージに含まれているものではなく、パッケージがやることがわかるように命名しなくてはならない。

ドットインポート: パッケージ名の前にドットを付けることで、パッケージ名を書かずに各パッケージに定義された変数にアクセスできるが、Bourgon氏は「ドットインポート」を使わないよう勧めている。これは不慣れな人にとって読みにくく、変数がどのパッケージに属しているか理解しなくてはならないためだ。Goは「常に暗黙よりも明示を好む」。

フラグ: フラグの初期化をinit()関数でやるのは望ましくなく、main()関数でやるべきだとBourgon氏は考える。これはグローバルスコープで呼び出して、テストで役に立つ依存するものに触るのを防ぐためだ。

コンストラクタ: 無効あるいは不完全な状態のオブジェクトを渡すのを避けるため、コンストラクタの呼び出しでは、初期化したstructをパラメータとすることを勧める。以下に例を示す。

foo := newFoo(*fooKey, fooConfig{
    Bar:    bar,
    Baz:    baz,
    Period: 100 * time.Millisecond,
})

使えないデフォルト: フィールドの変数をnilで初期化し、使うたびにnilテストするのではなく、変数を何もしない値で初期化する方が良い。例えば、output変数にはioutil.Discardを使う。

相互参照するコンポーネント: 2つのコンポーネントが相互参照を含む場合がある。例えば、次の2つのstructのように、一方をコンストラクトするには他方をコンストラクトする必要があるが、後者をコンストラクトするときには前者が必要になる。

type bar struct {
    baz *baz
}
type baz struct {
    bar *bar
}

Bourgon氏はこの問題を扱うための3つの方法を紹介した。

1. 一体化。相互に関連する2つのオブジェクトを1つにまとめられる場合がある。この場合、barbaz structになる。

2. 分離。2つのコンポーネントを分離しておきたいなら、次のような戦略をとることができる。

type bar struct {
    a *atom
    monad
}

type baz struct {
    atom
    m *monad
}

a := &atom{...}
m := newMonad(...)

bar := newBar(a, m, ...)
baz := newBaz(a, m, ...)

3. コミュニケーション。前の2つの方法が適切ない場合、互いにメッセージを渡す方法がある。

type bar struct {
    toBaz chan<- event
}

type baz struct {
    fromBar <-chan event
}

c := make(chan event)
bar := newBar(c, ...)
baz := newBaz(c, ...)

依存関係: Bourgon氏が紹介したすぐれたコツの1つは「依存関係を明示的にする」ことだ。例えば、

func (f *foo) process() {
    log.Printf("bar: %v", result) // ...
}

は次のように置き換えるべきだ。

func (f *foo) process() {
    f.Logger.Printf("bar: %v", result) // ...
}

log.Printfは実際にはLoggerを呼び出すが、依存関係が隠されている。これを明示するには、構築フェーズでLoggerを作成し、その値がnilならioutil.Discardに初期化する必要がある。

チャネル: Bourgon氏はgoroutineとgoroutineを統合するchannelとの間でメモリを共有する際には、mutexを使うことを推奨する。

ロギング: ロギングはコストがかかり、アプリケーションのボトルネックになる可能性がある。したがって、彼のアドバイスは、人間が読む、あるいは機械が利用する、絶対に必要な情報だけをログ記録することだ。infoとdebug情報だけをログ記録しよう。

計測: Bourgon氏は計測は安価だと考え、Prometheusを使って全リソースをモニターすることを推奨する。

グローバル状態: 暗黙のグローバルな依存関係とグローバル状態を取り除こう。

テスト: パッケージテストを実行しよう。Design for testing by writing using a functional style which implies making 依存関係をパラメータとして明示するような機能スタイルを使って書くことで、テストを設計しよう。グローバル状態への依存を避けよう、インターフェイスを使おう。

依存物の管理: プロジェクトのリポジトリにある依存物をコピーして、バイナリの構築時に利用しよう。Bourgon氏はニーズにあわせて、gvtvendetta, glidegbなどを使うことを提案する。

ビルド: go buildの代わりにgo installを使おう。go installは依存するものをキャッシュして、呼び出しやすいようGOPATH/binに置いてくれるためだ。

これらの推奨は、マイクロサービス構築のための分散プログラミングツールキットGo kitを作るときに彼が適用したものだ。

Bourgon氏は2009年以降、SoundCloudとWeaveworksでGoを使っており、時系列イベントデータベースRoshiとGo kitを開発している。

QCon London 2016のセッション「Successful Go Program Design, 6 Years On」は今年の終わりに公開される予定だ。

 
 

Rate this Article

Relevance
Style
 
 

この記事に星をつける

おすすめ度
スタイル

BT