Goのコードを整理する
Organizing Go code by Andrew Gerrand
はじめに
Goのコードは他の言語のものとは異なる形で整理されます。本稿では、プログラムの利用者に 最良の形で応えるために、Goプログラムの要素をどのように命名し、パッケージ化するかについて述べます。
良い名前を選ぶ
選んだ名前はコードに対する考え方に影響を与えます。ですから、パッケージやそこからエクスポートする 識別子の命名には注意を払いましょう。
パッケージ名は、その中身に文脈を与えます。例えば、標準ライブラリのbytesパッケージは、
Buffer 型をエクスポートしています。 Buffer という名前だけを見ても、あまり説明的ではありませんが、
パッケージ名と組み合わせると bytes.Buffer となり、その意味は明確になります。もしパッケージの名前が
util のようにあまり説明的でないものだったら、このバッファは util.BytesBuffer という、より長く
ぎこちない名前になっていたことでしょう。
作業をする中で、物おじせずに名前を変えましょう。プログラムと向き合う時間が長くなるほど、
各部分がどのように組み合わさっているかを理解できるようになり、それに伴って適切な名前も
わかってきます。早い段階での決定に縛られる必要はありません。( gofmtコマンド
には -r フラグがあり、構文を意識した検索・置換ができるため、大規模なリファクタリングも簡単になります。)
良い名前はソフトウェアインターフェースの中でもっとも重要な部分です。名前は、そのコードを利用する すべてのクライアントが最初に目にするものだからです。それゆえ、うまく選ばれた名前は、良いドキュメントの 出発点になります。これから紹介する習慣の多くは、良い命名から自然と導き出されるものです。
「go get」できる良いインポートパスを選ぶ
インポートパスとは、利用者がパッケージをインポートする際に使う文字列です。これは、パッケージの
ソースコードが存在するディレクトリを( $GOROOT/src/pkg または $GOPATH/src からの相対パスとして)
指定するものです。
インポートパスはグローバルに一意であるべきです。そのため、ソースリポジトリのパスをベースとして
使いましょう。例えば、 go.net サブリポジトリの websocket パッケージは、
"golang.org/x/net/websocket" というインポートパスを持っています。Goプロジェクトは
"github.com/golang" というパスを所有しているため、そのパスを別の作者が別のパッケージのために
使うことはできません。リポジトリのURLとインポートパスが同一であるため、 go get コマンドは
自動的にパッケージを取得してインストールできます。
ホスティングされたソースリポジトリを使わない場合は、ドメイン名や会社名、プロジェクト名など、
何か一意なプレフィックスを選びましょう。一例を挙げると、Google社内のGoコードのインポートパスは
すべて "google" という文字列から始まります。
インポートパスの最後の要素は、通常はパッケージ名と同じです。例えば、インポートパス
"net/http" は http パッケージを含んでいます。これは必須ではなく、望むなら異なる名前にも
できますが、予測可能性のためにこの慣習に従うべきです。 "foo/bar" をインポートすると
quux という識別子がパッケージの名前空間に導入される、というのでは利用者が驚いてしまうでしょう。
GOPATH をソースリポジトリのルートに設定し、 "src/my/package" のようにリポジトリのルートからの
相対ディレクトリにパッケージを置く人もいます。これによりインポートパスは短くなります
( "github.com/me/project/my/package" ではなく "my/package" になります)が、その一方で
go get が壊れてしまい、利用者はそのパッケージを使うために GOPATH を設定し直さなければ
ならなくなります。このようなことはしないようにしましょう。
エクスポートするインターフェースを最小限にする
コードはおそらく、有用な小さなコードの断片が数多く組み合わさってできているでしょう。 そのため、その機能の多くをパッケージのエクスポートするインターフェースとして公開したくなるものです。 しかし、その誘惑には抵抗してください!
提供するインターフェースが大きくなるほど、サポートしなければならないものも増えます。利用者は エクスポートされたあらゆる型、関数、変数、定数にすぐさま依存するようになり、それは暗黙の契約となって、 その後も永続的に守り続けなければならなくなります。そうしなければ、利用者のプログラムを 壊してしまう危険を冒すことになります。Go 1を準備する際、私たちは標準ライブラリのエクスポートされた インターフェースを注意深く見直し、コミットする準備ができていない部分を取り除きました。自分の ライブラリを配布する際にも、同様の注意を払うべきです。
迷ったら、含めないこと!
パッケージに何を入れるか
すべてを「なんでも入れ」のパッケージに放り込んでしまうのは簡単ですが、これはパッケージ名の意味を 薄めてしまいますし(多くの機能を包含しなければならなくなるため)、パッケージのごく一部だけを 使いたい利用者にも、多くの無関係なコードをコンパイルしリンクすることを強いてしまいます。
一方で、コードを小さなパッケージに分割しすぎるのもまた簡単なことです。その場合、単に仕事を 片付けるというよりも、インターフェース設計に頭を悩ませることになりがちです。
指針としてGoの標準ライブラリを参考にしてください。大きなパッケージもあれば、小さなパッケージも あります。例えば、 httpパッケージは(テストを除いて)17個のGoソースファイルからなり、 109個の識別子をエクスポートしていますが、 hashパッケージはたった1つのファイルで、 わずか3つの宣言しかエクスポートしていません。厳密な規則はなく、どちらのアプローチも、その文脈次第で 適切なものになります。
とはいえ、 package main は他のパッケージよりも大きくなることがよくあります。複雑なコマンドには、
その実行可能ファイルという文脈の外ではほとんど役に立たないコードが多く含まれており、それらをすべて
1か所にまとめておくほうが単純である場合が多いのです。例えば、 go ツールは
34個のファイルにまたがる12000行以上のコードでできています。
コードにドキュメントを書く
良いドキュメントは、使いやすく保守しやすいコードに欠かせない資質です。良いドキュメントコメントの 書き方を学ぶには、 Godocを使ってGoのコードをドキュメント化する という記事を読んでください。
By Andrew Gerrand