December 11, 2019

Go の命名規則

go-naming-conventions

本記事は Go Advent Calendar 2019 11 日目の記事です。

Go はシンプルな言語機能・シンタックスが特徴であり、命名規則にもそのシンプルさが表れています。

本記事では、公式や著名な Go エンジニア、OSS などから見られる Go らしい命名規則を紹介します。

今更なテーマかもしれませんが、意外にも公私共々で命名規則が意識されていないコードを時折見かけるので、自戒も込めて記します。

誤った内容があれば Twitter でご指摘いただければと思います。

パッケージ名

簡潔にする

Effective Go では、short, concise, evocative なパッケージ名が望ましいとされます。 これはパッケージ名に限らずほとんどあらゆる命名において役立つ指針だと思います。 また、「パッケージ名は一言で何をするかを表すエレベーターピッチだ」という Dave Cheney 氏の言葉も指針となるでしょう。

また、すべて小文字かつ 1 単語で構成されるのが望ましく、snake case や mixed caps にはしません。
mixed caps は流石に見ることはありませんが、snake case は時々見かけるので、簡潔な命名を考案した方が良いかもしれません。
snake case にすると、そのパッケージを参照する場合、awesome_client のように参照するか、import 時に alias を付ける必要性が生まれます。
前者では Go らしくないコードになり、後者ではそのパッケージを import する際は常に alias を付けなければならないという良くない体験になります。

どうしても複数の単語を組み合わせる必要があるときは strconv パッケージが string conversion から来ていることを思い出すとヒントになるかもしれません。

“utility” パッケージにしない

base, util, common, lib , misc といった “utility” パッケージは様々な機能がそのパッケージから提供されることによって発生します。
パッケージ名は前述の通り、一言で何をするかを表したいですが、util では何をするのかが自明ではありません。
“utility” パッケージは「ゴミ箱」と言っても良いかもしれません。 一度 “utility” パッケージが作られると、その後そのプロジェクトではどのパッケージに配置するか決まりにくい機能はすべてそのパッケージに放り投げられてしまうでしょう。

“utility” パッケージを避ける方法は、そのパッケージが何をしているかを分析することです。
たとえば、文字列操作の関数と暗号化の関数がそのパッケージにあることが分かれば、strings パッケージや crypto パッケージに分割することができ、util.TrimSpaceutil.Encrypt ではなく、concise, evocativestrings.TrimSpacecrypto.Encrypt で参照することができ、パッケージクライアントとしてもとても使いやすくなります。
もちろん、この例の stringscrypto は標準パッケージと衝突するので、適宜ユースケースにしたがって命名しましょう (標準パッケージと命名が衝突することは決して悪いことではないです)。

パッケージ名を繰り返さない

パッケージは繰り返さないようにします。

たとえば、bufio パッケージでは BufReader ではなく bufio.Reader と定義されています。 パッケージに対するクライアントからすると、呼び出し時に io.Readerbufio.Reader という形で参照するので自明だからです。
もう一つ例を見ると、repository パッケージで UserRepository と定義すると、repository.UserRepository となり冗長ですね。

time.Time で見られるように、パッケージ名そのものは定義することは良いです。 何を指すのかが自明かつ冗長でないからです。

コンストラクタを作成する場合も繰り返さないようにします。
list パッケージにおける List を返すコンストラクタの名前は NewList ではなく New が適切です。
他方で、grpc パッケージにおける Server を返すコンストラクタ名前は grpc.New ではなく grpc.NewServer となります。

参考

変数・引数名

変数名は出来るだけ短くしましょう。 他言語では 1 文字変数は唾棄されることもありますが、Go では推奨されています。
たとえば、Config の変数なら、c, conf, cfg など、省略形を用い出来るだけ短い変数名を付けます。

Go に限らないことですが、短い変数名を使うときはスコープを意識する必要があります。
スコープが小さければ変数名も短く、大きければ変数名も長くする、というのが原則です。 短い変数名が有効になるケースは、スコープが小さい場合・自明な場合だからです。 ただそもそもスコープが大きい変数というのは避けた方が良くて、短い変数名は必然的にスコープを小さく保ってくれます。
制約でシンプルさを保つ Go らしいですね。

また、修飾語を変数名に使わないようにしましょう。 必ずしも頭文字を使えば良いというわけではありません。
たとえば、lineCount という変数であれば l ではなく csliceIndex という変数であれば s ではなく i です。
その変数が何を表しているのか・担っているのか、という本質的な意味を表現する変数名にしましょう。

逆にグローバル変数などは短い変数名だと不明瞭であるため出来る限り説明的にしましょう。

error 変数名

error を変数として宣言する場合、Err prefix を付けます。 prefix 無しでも Error prefix でもありません。

var ErrNotFound = errors.New("not found")

map の存在チェック

map の存在チェックをする際、bool 値の変数名は原則 ok とします。 exists などは使いません。

user, ok := users[userID]

参考

interface 名

interface の命名は io パッケージ がいかにも Go らしく参考になると思います。

メソッドが 1 つの場合

原則として、メソッド名 + er (あるいは or など適したそれに類するもの) にします。
たとえば、Write メソッドを持つ interface であれば Writer にします。

目的語がある場合は、目的語 + 動詞 + er にすることも一考でしょう。
たとえば、PrintObject メソッドを持つ interface であれば ObjectPrinter です。

メソッドが 2 つ以上の場合

この場合、原則として慣習がある場合それに従い、慣習が無い場合いい感じの名前を考えます。

慣習というのはたとえば、

io パッケージの ReadCloser のように、メソッドを連ねて前述の er を付けるパターンや、

type ReadCloser interface {
    Read(p []byte) (n int, err error)
    Close() error
}

以下の Codec のように、特別なメソッドの組み合わせのときに使われるパターンがあります。 もちろん、以下の CodecEncodeDecoder だったり他の命名にすることもあります。

type Codec interface {
    Encode() error
    Decode() error
}

参考

レシーバ名

出来れば 1 文字で最小にします。 たとえば、型名が Client なら c または cl などにします。

レシーバ名は必ず一貫させます。 すべてのレシーバ名は統一します。
たとえば、一方で c とすると、他方で cl としません。

修飾語を使わないというのはレシーバ名でも同様で、たとえば、ObjectPrinter なら、o ではなく p を使うようにしましょう。 このようなケースで op と 2 文字や 3 文字程度で略称を使っているケースを見ますが、個人的は基本的に 1 文字でも自明なので 1 文字で良いと思います。

参考

型の名前を使わない

変数名や引数名に型の名前を使わないようにしましょう。

たとえば、以下のようなケースはよく見ます。

var usersMap map[string]*User

map の変数に対して Map suffix を付けていますね。 静的型付き言語の Go において変数の型は自明であるため、変数名に型の名前を入れるのは冗長です。
このケースにおいて、users という名前で表現力が足りないのであれば userMap も同様でしょう (型の情報を足しただけで、前述の通り無意味な情報であるため)。

以下のような引数名においても同様です。

func WriteConfig(w io.Writer, config *Config) {
    ...
}

簡潔にするために c, conf など適切かつ短い名前を考えましょう。

参考

Getter 名

Go ではいわゆる Getter には Get prefix を付けません。 たとえば User を返したいなら、メソッド名は GetUser ではなく User とします。

Setter の場合は SetUser のように Set prefix を付けましょう。 (Go で Setter を作るケースはあまり見ませんが。)

参考

所感

現在よく利用されている有名 OSS でも命名規則に反しているコードが散見されることがあるので、必ずしも命名規則に則る必要は無いとは思いますが、一貫性があって合理的な命名規則は、バグを減らしアジリティに寄与することは間違いないでしょう。

参考

© micnncim 2019

Powered by Hugo & Kiss.