— 2 min read
本記事は 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
から来ていることを思い出すとヒントになるかもしれません。
base
, util
, common
, lib
, misc
といった "utility" パッケージは様々な機能がそのパッケージから提供されることによって発生します。
パッケージ名は前述の通り、一言で何をするかを表したいですが、util
では何をするのかが自明ではありません。
"utility" パッケージは「ゴミ箱」と言っても良いかもしれません。
一度 "utility" パッケージが作られると、その後そのプロジェクトではどのパッケージに配置するか決まりにくい機能はすべてそのパッケージに放り投げられてしまうでしょう。
"utility" パッケージを避ける方法は、そのパッケージが何をしているかを分析することです。
たとえば、文字列操作の関数と暗号化の関数がそのパッケージにあることが分かれば、strings
パッケージや crypto
パッケージに分割することができ、util.TrimSpace
や util.Encrypt
ではなく、concise, evocative な strings.TrimSpace
や crypto.Encrypt
で参照することができ、パッケージクライアントとしてもとても使いやすくなります。
もちろん、この例の strings
や crypto
は標準パッケージと衝突するので、適宜ユースケースにしたがって命名しましょう (標準パッケージと命名が衝突することは決して悪いことではないです)。
パッケージは繰り返さないようにします。
たとえば、bufio
パッケージでは BufReader
ではなく bufio.Reader
と定義されています。
パッケージに対するクライアントからすると、呼び出し時に io.Reader
や bufio.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
ではなく c
、sliceIndex
という変数であれば s
ではなく i
です。
その変数が何を表しているのか・担っているのか、という本質的な意味を表現する変数名にしましょう。
逆にグローバル変数などは短い変数名だと不明瞭であるため出来る限り説明的にしましょう。
error
を変数として宣言する場合、Err
prefix を付けます。
prefix 無しでも Error
prefix でもありません。
1var ErrNotFound = errors.New("not found")
map
の存在チェックをする際、bool 値の変数名は原則 ok
とします。
exists
などは使いません。
1user, ok := users[userID]
interface の命名は io パッケージ がいかにも Go らしく参考になると思います。
原則として、メソッド名 + er
(あるいは or
など適したそれに類するもの) にします。
たとえば、Write
メソッドを持つ interface であれば Writer
にします。
目的語がある場合は、目的語 + 動詞 + er
にすることも一考でしょう。
たとえば、PrintObject
メソッドを持つ interface であれば ObjectPrinter
です。
この場合、原則として慣習がある場合それに従い、慣習が無い場合いい感じの名前を考えます。
慣習というのはたとえば、
io パッケージの ReadCloser
のように、メソッドを連ねて前述の er
を付けるパターンや、
1type ReadCloser interface {2 Read(p []byte) (n int, err error)3 Close() error4}
以下の Codec
のように、特別なメソッドの組み合わせのときに使われるパターンがあります。
もちろん、以下の Codec
は EncodeDecoder
だったり他の命名にすることもあります。
1type Codec interface {2 Encode() error3 Decode() error4}
出来れば 1 文字で最小にします。
たとえば、型名が Client
なら c
または cl
などにします。
レシーバ名は必ず一貫させます。
すべてのレシーバ名は統一します。
たとえば、一方で c
とすると、他方で cl
としません。
修飾語を使わないというのはレシーバ名でも同様で、たとえば、ObjectPrinter
なら、o
ではなく p
を使うようにしましょう。
このようなケースで op
と 2 文字や 3 文字程度で略称を使っているケースを見ますが、個人的は基本的に 1 文字でも自明なので 1 文字で良いと思います。
変数名や引数名に型の名前を使わないようにしましょう。
たとえば、以下のようなケースはよく見ます。
1var usersMap map[string]*User
型 map
の変数に対して Map
suffix を付けていますね。
静的型付き言語の Go において変数の型は自明であるため、変数名に型の名前を入れるのは冗長です。
このケースにおいて、users
という名前で表現力が足りないのであれば userMap
も同様でしょう (型の情報を足しただけで、前述の通り無意味な情報であるため)。
以下のような引数名においても同様です。
1func WriteConfig(w io.Writer, config *Config) {2 ...3}
簡潔にするために c
, conf
など適切かつ短い名前を考えましょう。
Go ではいわゆる Getter には Get
prefix を付けません。
たとえば User
を返したいなら、メソッド名は GetUser
ではなく User
とします。
Setter の場合は SetUser
のように Set
prefix を付けましょう。
(Go で Setter を作るケースはあまり見ませんが。)
現在よく利用されている有名 OSS でも命名規則に反しているコードが散見されることがあるので、必ずしも命名規則に則る必要は無いとは思いますが、一貫性があって合理的な命名規則は、バグを減らしアジリティに寄与することは間違いないでしょう。