Golang Cafe #20 まとめ 続「Twelve Go Best Practices」を読む(要約編)

2014/03/09に開催された「Golang Cafe #20」についてのまとめです。
前回に引き続きTwelve Go Best Practicesを読んでいきました。残りは次回以降に続けて読みます。

重要なコードは最初に持ってくる

ソースコードにはまずライセンス情報、ビルドタグ、パッケージドキュメントを書きます。
import文では関連するグループ(標準バッケージやgo getしたパッケージ等)ごとに空行で区切って書きます。
ソースコードのはじめに最も重要な型を書き、最後にヘルパー関数や関連する型を書きます。

補足

ソースコードに対して以下の様にgofmtを適用すればある程度ソースコードを整形してくれます。

$ gofmt -w sample.go

ただしwindows環境では注意が必要です。改行コードをCRLFにしている場合、gofmtを使用すると改行コードがLFになってしまいます。

コードにドキュメントを書く

パッケージ名、関連するドキュメントを最初に書きます。
godocを実行した際にエクスポートされた(公開する)型が表示され、正しくドキュメントが記載されているはずです。

短いことは良いことだ

Javaでは比較的長い名前をつけますが、Goでは必ずしもそうではないようです。
自身を説明することができるもっとも短い名前を考えるべきだそうです。
例えば、MarshalWithIndentationではなくMarshalIndentのようにJavaではWithやByなどを多用しがちですが、Goでは省略するようです。
また型を使用する際には、パッケージ名の修飾があるので型の名前自体にパッケージを含めるべきではないそうです。
例えば、encoding/jsonパッケージではEncoderが定義してありますが、JSONEncoderとは定義されていません。
実際使用される場合には、json.Encoderの様に参照されます。

複数ファイルによるパッケージ

1つのパッケージを複数のファイルに分けるかどうか。
Javaでは1クラス1ファイルでクラス名とファイル名を一致させるというルールがありますが、Go言語ではC#のようにそこまで厳しいルールはないようです。
しかしこのおかげでどの型がどのファイルに書いてあるかを探すのに大変苦労します。そのうちに優秀なIDEが登場してくれることを祈ります。

あまりにも長いファイルは避けるべきです。(あまりにも長いというのが曖昧な表現ですが)

Goの標準ライブラリのnet/httpパッケージは47ファイルで15734行あるようです。

コードとテストは別のファイルに分ける

http.Cookie型はnet/http/cookie.goとnet/http/cookie_test.goのファイルに分かれています。
テストコードはテスト実行時にだけコンパイルされます。

パッケージドキュメントを別ファイルに分ける

1つのパッケージが複数のファイルで構成されている場合、パッケージのドキュメントのみを書いたdoc.goファイルを用意します。

go get で取得可能なパッケージにする

自分で書いたコードでも、その中の一部は後のプロジェクト等で再利用可能な(再利用したい)コードがあります。
そのようなコードはmainパッケージではなく、別のパッケージに配置しておきます。
そうすることで別のmainパッケージから参照して再利用することができます。
別のパッケージに配置したコードをgithub等で公開しておけば、go getで取得することもできます。

必要なものを求める

以下のようなメソッドを定義したとします。

func (g *Gopher) WriteToFile(f *os.File) (int64, error) {

このメソッドは名前から想定できる通り引数に指定されたファイルに出力するメソッドです。
このメソッドに対するテストを考えた場合、引数にファイル型のポインタを渡さなくてはなりません。今回はos.File型で比較的作成しやすい型なのでそれほど問題はありませんが、これが簡単には作成できないような型(様々なのもに依存してしまっている場合など)だったらどうでしょう。テストを書くことが非常に困難になってしまいます。
そこでこのメソッドの目的を再度考えてみます。ファイルに書き出すというのが目標です。ファイルに対してWriteだけできれば良いのです。
幸いなことにos.File型io.Writerを実装しています。
したがって以下のように書き換えても問題ありません。

func (g *Gopher) WriteToWriter(f io.Writer) (int64, error) {

このように必要最低限のことができるinterfaceで受け取るようにすべきです。こうすることで引数で渡されたものに対して誤った操作を行うことを防ぐ事もできます。

パッケージの独立を保つ

interfaceを使用してパッケージ間の依存関係を避けます。
例えばあるパッケージの中で以下のようなコードを書いたとします。

import (
    "image"

    "code.google.com/p/go.talks/2013/bestpractices/funcdraw/parser"
)

func DrawParsedFunc(f parser.ParsedFunc) image.Image {

これは自身のパッケージの中で別の独自パッケージを使用しています。この依存関係を削除します。

import "image"

type Function interface {
    Eval(float64) float64
}

func Draw(f Function) image.Image {

先ほどの「必要なものを求める」の例に考え方は近いですが、インポートしていた型の使用したいメソッドだけを定義したinterfaceを用意してこれを引数に取るように修正します。
Go言語ではダック・タイピングを採用しているのでinterfaceを後付することができます。(既存のパッケージ手を入れる必要がない)
依存関係を断ち切った上に誤った操作を行う危険性も削除することができました。

テスト

「必要なものを求める」と「パッケージの独立を保つ」の内容をテストコードを書く際に応用します。
具体的にはテストで実装するメソッドが引数を取る場合、具象型を使用するのではなくできるだけinterfaceを使用します。
具象型を使用した場合、テスト時にインスタンス化することが非常に困難な場合があったりします。
interfaceを使用しておけば、モックを作成する場合でも必要最低限のメソッドを実装するだけでよくなります。
このあたりの話は、最後に紹介している「レガシーコード改善ガイド」に詳しい話が書かれています。まだ読んだことがない方は一度読まれることをおすすめします。

読んでおいたほうがよい書籍

Twelve Go Best Practices」を読み進める上でGo言語の知識はもちろん必要ですが、以下の書籍にも目を通しておくほうが良いかもしれません。

増補改訂版Java言語で学ぶデザインパターン入門

増補改訂版Java言語で学ぶデザインパターン入門

レガシーコード改善ガイド (Object Oriented SELECTION)

レガシーコード改善ガイド (Object Oriented SELECTION)

いずれもJavaを対象に書かれていますが、考え方は適用できます。