Go言語における命名規則
コードを書く上でプログラマーは様々な箇所で名前を付けます。
例えばGo言語で命名する対象を挙げると以下のものがあります。
- 変数名
- 関数名
- 構造体名
- パッケージ名
- インターフェース名
この記事ではこれらの名前付けの際のポイントとして、Go言語ではどういったことを考慮して命名するべきか、推奨されている規則をご紹介していきたいと思います。
その前になぜ推奨されている命名規則に合わせてGoらしい命名を行うべきかについて、メリットを少し考えてみましょう。
私が思う命名規則のメリットは次の通りです。
命名規則に従うことで、Go言語を知っているエンジニア間で意図で汲み取りやすくなる
基本的にプログラミング言語ごとに命名規則があり、その規則に合わせることでその言語に慣れている開発者間で意図が伝えやすくなるメリットがあります。
また、Goの場合はオフィシャルでもどのような名前を付けるべきか推奨しているもの(Effective Goなど)があるため、それらを意識して名前を付けることで統一感のある命名にすることができます。
逆にこういった規則を無視して開発者ごとに好きな命名を付けてしまうと、やたら長い名前を付ける人もいれば、短い名前を付ける人もいるなど、コード全体としてのバランスも悪く、読んでいて「統一感がなく理解しづらい」と感じることでしょう。
様々なGoのプロジェクトに入っても基本的な命名規則が揃っていれば読みやすく理解も早くなりますのでぜひGoを始めた方は最初に命名規則を意識しながら名前を付けるようにしてみましょう。
チームメンバー間で都度命名規則について議論する必要がなくなり、時間を節約し、意見の衝突を避けることができる
命名規則が定まっていない状態で複数人で開発を行なってしまうと、お互いに都度命名規則について議論し、どういう名前にするのがいいのかルールを決めることから始めることになるでしょう。
実際これはあまり楽しいものではなく、お互いに意見が食い違い議論が長引いたり、好みの違いで意見がぶつかりメンバー間の関係性が悪化なんてことも考えられます。
Goの基本的な命名規則を理解していれば、細かな部分でルール決めから始める必要はなくなるので、上記のような議論を避けることができ、その分時間も節約することができます。
冗長な命名を避けることができる
命名規則を間違えるとメソッドの呼び出し時など複数回同じキーワードが発生して冗長になってしまうケースがあります。
例として以下のパッケージと関数名を見てみましょう。
package http
type Server sturct { ... }
func NewHTTPServer() *Server { ... }
このNewHTTPServerを呼び出す場合、以下のように記述する必要があります。
server := http.NewHTTPServer()
このようにパッケージ名であるhttpに加えて関数名にもHTTPというキーワードが含まれていて冗長なネーミングになっています。
Goの命名規則に従うことで、うっかり冗長な命名をしてしまうことも避けることができます。
推奨される命名規則
それでは命名する対象ごとに推奨される命名規則を見ていきましょう。
変数名
パッケージ外への公開、非公開で先頭の大文字/小文字を分ける
このルールは単純な命名規則というよりは変数の公開範囲に影響するものです。
Go言語では変数名の先頭が大文字の場合、外部のパッケージから参照可能となり、先頭が小文字の場合同じパッケージ内からしか参照できなくなります。
コード例を記載すると以下の通りです。
package pkg1
var Value1 int
var value2 int
func PrintValue() {
println(Value1)
// 同一パッケージのため参照可能
println(value2)
}
package pkg2
import "sample/pkg1"
func PrintValue() {
// pkg1で公開されている変数のため参照可能
println(pkg1.Value1)
// Error: pkg1でパッケージ外に非公開にしている変数のため参照不可
println(pkg1.value2)
}
このように変数の先頭が大文字であるか、小文字であるかによって他のパッケージから利用可能かどうかが変わります。
パッケージ内でのみ使用する変数には先頭を小文字にした名前を付け、パッケージ外で使用が想定されているものについては先頭を大文字にします。
複数の単語を含める場合キャメルケースにする
Go言語では複数の単語を含める名前を変数名に付ける場合キャメルケースを使用します。
func main() {
// Bad
// Goでは変数名をスネークケースにしない
var max_length
// Good
// キャメルケースを使用する
var MaxLength
var maxLength
}
URLやHTTPなどは大文字/小文字を混合しない
URLやHTTPのような複数の単語の頭文字を組み合わせた語(頭字語)については、大文字/小文字を混合せずに、大文字か小文字に統一する。
func main() {
// Bad
// 大文字小文字を混ぜるのはNG
var Url
var Http
// Good
// パッケージ外に公開する場合
var URL
// パッケージ外に非公開にする場合
var http
var userID
}
errorインターフェースを満たす型名には接尾辞にErrorを付ける
errorインターフェースを満たす型には接尾辞にErrorを付けます。
errorインターフェースとはgoに標準ライブラリのerrros
パッケージに定義されているインターフェースで以下のように定義されています。
type error interface {
Error() string
}
Errorというメソッド名でstringを返すものを定義するだけのシンプルなインターフェースになっていますね。
このinterfaceを満たすことでerror型として振る舞う型を作ることができます。
例えば、Goにbuiltinで定義されているMarshalerErrorは次のようになっています。
// A MarshalerError represents an error from calling a MarshalJSON or MarshalText method.
type MarshalerError struct {
Type reflect.Type
Err error
sourceFunc string
}
func (e *MarshalerError) Error() string {
srcFunc := e.sourceFunc
if srcFunc == "" {
srcFunc = "MarshalJSON"
}
return "json: error calling " + srcFunc +
" for type " + e.Type.String() +
": " + e.Err.Error()
}
Errorメソッドを定義することで、MarshalerError構造体が持つプロパティを使用してエラーメッセージを生成しています。
このようにerrorインターフェースを実装した型の名前にはMarshalerErrorのように末尾にErrorを付けるのが慣習となっています。
エラーの変数名にはErrを接頭辞にする
errorインターフェースを満たす型のインスタンスを変数に入れる場合、Errという接頭辞を付けた変数に代入するのが慣習となっています。
例えば次のような形です。
func main() {
ErrSample := errors.New("sample error")
}
errors.Newで新しいerrorのインスタンスを作ることができます。
これの代入先はErrSampleというようにErrを接頭辞にします。
もしパッケージ内でしか使用しない場合は、errSample
になります。
型にメソッドを実装する場合、自身のインスタンスを表す変数は型名の頭文字を使用する
以下のように型にメソッドを定義する場合は、自身の変数名に型名の頭文字を使用した命名にするのが慣習になっています。
type MarshalerError struct {
Type reflect.Type
Err error
sourceFunc string
}
func (e *MarshalerError) Error() string {
...
}
この例では、*MarshalerErrorのインスタンスを指す変数名をErrorの頭文字であるe
にしています。
複数語からなる型の場合は、各頭文字を合わせたものでも問題ありません。
例えば上記の場合、me
という変数名でも問題ありません。
短いスコープの変数には短い名前を付ける
for文で使われるindex番号を格納する変数名など、明らかにスコープが短く使われる範囲が限定されているものに関しては、短い名前を付ます。
例えば、
- for文のindexに関しては、
i
という変数名にする。 - http requestを扱う変数の場合は、
req
という変数名にする。
などです。
スコープが短い変数の場合は、変数名が短縮形であっても簡単にそれが何かを推測しやすいため、不必要に説明的にはせずに短い名前を付けるのが好ましいとされています。
またrequestのように少し長めに単語は、reqのように省略系の表記もよく使われることを覚えておきましょう。
構造体名
基本的には変数名の命名規則と同じルールで命名すれば大丈夫です。
- パッケージ内で使い場合は先頭小文字、パッケージ外で使う時は先頭大文字。
- 複数単語から成る名前の場合キャメルケースにする。
関数名
パッケージ名と重複するキーワードを入れない
「冗長な命名を避けることができる」の部分でも触れましたましたが、メソッドなどを利用する場合に重複するキーワードが現れないようにパッケージ名を設計するのが望ましいです。
例えばhttpパッケージにHTTP Serverに関する関数を記述する場合、以下のようにHTTPServerという関数は作らず、単にServerとすることで、パッケージと繋げることでhttp.Serverと意味が伝わるようにします。
package http
// Bad
// パッケージ外部から呼び出す際にhttp.HTTPServerとなるので、httpが重複しており冗長
func HTTPServer { ... }
// Good
// http.Serverのようにパッケージと繋げることで用途が伝わる
func Server { ... }
その他にも、bufio.BufReader
ではなくbufio.Reader
のようにします。
ただし、time.Time
のようにパッケージ名そのものを定義するのは自明かつ冗長でないのでOKとされています。
パッケージ名
シンプルに1単語のパッケージ名にする
基本は1単語のパッケージ名にします。
どうしても複数単語でパッケージ名を表したい場合はディレクトリで階層を分けます。
例えばencoding jsonという複数単語からなるパッケージの場合以下のようにする(ちなみにこのパッケージはGoの標準ライブラリに含まれるものです)。
package json
...
Goらしい命名ではパッケージ名にスネークケース(複数単語を_で連結した命名)を使わないので注意してください。
用途をイメージしにくい汎用的な命名は避ける
utilといった汎用的なパッケージ名は避けて、具体的にどういった用途で使われるパッケージなのかをイメージできるような名前を付けます。
例えば文字列操作のパッケージならばstrings、暗号化関連のパッケージならばcryptといった感じです。
utilといった名前を付けると何でもそのパッケージ下に放り込まれてしまう可能性があり、util自体が何に使用されるパッケージなのかがイメージしずらいといったデメリットが生まれます。
インターフェース名
よく使われる命名規則として、erを付けたのがあります。
例えば
- Stringer
- Reader
- Writer
などです。
もしこのパターンで名前を付けられそうな場合は積極的にer
を付けた命名をするとインターフェースということがわかりやすくなります。
その他は構造体と同様以下のような命名にすることが一般的です。
- パッケージ内で使い場合は先頭小文字、パッケージ外で使う時は先頭大文字。
- 複数単語から成る名前の場合キャメルケースにする。
終わりに
どうでしたでしょうか。
プログラミングにおいては頻繁にさまざまな命名を検討する必要があります。
その際にどのような命名をすればGoらしい命名であるかを意識して名前を検討することをお勧めします。
いくつもの推奨されるルールがありますので、最初は推奨される命名を意識しながら考えてみてください。
慣れると自然とGoらしいコードが書けるようになってくると思います。