キーワードより規約が偉い?規約が支配する5つのルール
はじめに
普段、Goを書いてると、誰もが一度は戸惑う点があるかと思います。
「あれ、public
やprivate
はどこに書くの?」
Goにはクラスやアクセス修飾子といったキーワードがありません。
しかし、もちろん外部からアクセスできる/できない、といった可視性の制御は存在します。Goはそれを、キーワードではなく 命名規則 で実現しているのです。
これはC#を使っている自分からすると、かなり驚いたと同時にGoのシンプルな設計思想の奥深さも感じました。
この記事では、Goがいかに「命名規則」を重視し、それによってシンプルさと一貫性を実現しているかを探ります。単なるコーディング規約ではなく、名前そのものがプログラムの振る舞いを決定する Goの面白い世界を一緒に見ていきましょう。
Public
と Private
を決める
1. すべての始まり:大文字と小文字が Goにおける可視性のルールは、驚くほどシンプルです。
- 大文字で始まる識別子(変数、関数、構造体フィールドなど): Publicな存在。外部のパッケージからアクセスできます。(エクスポートされている と言います)
- 小文字で始まる識別子: Privateな存在。定義されたパッケージ内からしかアクセスできません。(エクスポートされていない と言います)
例えば、JSONデータを構造体にマッピングする json.Unmarshal
を使う場面で、このルールは直接的に影響します。
package main
import (
"encoding/json"
"fmt"
)
var jsonData = []byte(`{"name": "Yamada", "age": 30}`)
// ✅ Publicなフィールドは正しく値が入る
type UserPublic struct {
Name string
Age int
}
// ❌ Privateなフィールドはゼロ値のまま
type UserPrivate struct {
name string
age int
}
func main() {
var user1 UserPublic
json.Unmarshal(jsonData, &user1)
// -> Name: 'Yamada', Age: 30
fmt.Printf("Public -> Name: '%s', Age: %d\n", user1.Name, user1.Age)
var user2 UserPrivate
json.Unmarshal(jsonData, &user2)
// `encoding/json`パッケージがアクセスできず、値は初期値のまま
// -> name: '', age: 0
fmt.Printf("Private -> name: '%s', age: %d\n", user2.name, user2.age)
}
encoding/json
は外部パッケージなので、UserPrivate
の小文字で始まるフィールドにはアクセスできません。結果、フィールドは初期値であるゼロ値(string
なら""
、int
なら0
)のままになってしまいます。
Public/Privateのアクセスイメージ
この概念を図にしてみました。
この図によって、パッケージの境界を越えられるのは大文字のフィールドだけ、ということが視覚的に理解できると思います。
2. 🧪 go test がテストを楽にする
Goの標準テストツール go test
は、特定の命名規則に従う関数を自動的にテスト対象として認識します。
_test.go
という接尾辞を持つファイル内に、以下の名前の関数を書くと、それぞれが特別な役割を果たします。
-
func TestXxx(*testing.T)
:Test
で始まる関数は、ユニットテスト として実行されます。 -
func BenchmarkXxx(*testing.B)
:Benchmark
で始まる関数は、ベンチマークテスト としてコードのパフォーマンスを計測します。 -
func ExampleXxx()
:Example
で始まる関数は、使用例を示すテスト として実行され、コードのドキュメントにもなります。
// ユニットテスト
func TestCalculateTax(t *testing.T) { /* ... */ }
// ベンチマークテスト
func BenchmarkSortLargeSlice(b *testing.B) { /* ... */ }
ここでも、特別なキーワードやアノテーションではなく、関数の接頭辞 という命名規則がツールの振る舞いを決定しています。
3. 🏁 プログラムの入口と準備運動
プログラムの実行そのものも、命名規則によって定められています。
-
package main
とfunc main()
: Goのプログラムを実行可能ファイル にするには、パッケージ名をmain
にし、その中にfunc main()
を定義する必要があります。これがプログラムの開始地点(エントリーポイント)となります。 -
func init()
:init
という名前の関数を定義しておくと、main
関数が実行される前に自動的に実行されます。パッケージの初期化処理に利用される、これもまた特別な名前です。
4. ✍️ Goらしさが光る命名の「作法」
これまではコンパイラやツールが強制するルールでしたが、Goコミュニティには、強制ではないものの非常に強く推奨される「命名の慣習(Idiom)」があります。
-er
サフィックス
インターフェースの Goの標準ライブラリを見ると、多くのインターフェースが er
で終わっていることに気づくでしょう。
-
Read()
メソッドを持つインターフェース →Reader (io.Reader)
-
Write()
メソッドを持つインターフェース →Writer (io.Writer)
-
String()
メソッドを持つインターフェース →Stringer (fmt.Stringer)
これは、「そのインターフェースが何をするのか」 を端的に表現する命名規則です。Javaの IReadable や C#の IReadble のように I を付けたりせず、振る舞いそのものを名前にするこのスタイルは、Goの思想を象徴しています。
この図は、Read()
というメソッドさえ持っていれば、どんな型でも Reader
として扱えるGoの柔軟性を示しています。
_
ブランク修飾子 最後に、名前が持つ特殊な役割として _
(ブランク修飾子)を紹介します。これは、代入される値を意図的に破棄するための特別な名前です。
// 戻り値の value は使わないので _ で破棄
value, err := someFunction()
// ↓
_, err := someFunction() // これで「errだけに関心がある」と明示できる
// パッケージの副作用(init()の実行など)のためだけにインポート
import _ "[github.com/go-sql-driver/mysql](https://github.com/go-sql-driver/mysql)"
使わない変数があるとコンパイルエラーになるGoにおいて、_
は意図的に使わない ことをコンパイラに伝える重要な役割を果たします。
まとめ
Goの世界では、命名規則は単なるコーディングスタイルではありません。
- 可視性を決める (
Public
/Private
) - ツールの振る舞いを決める(
go test
) - プログラムのライフサイクルを決める(
main
/init
) - 設計の思想を表現する(
-er
サフィックス)
キーワードを極力減らし、シンプルで一貫性のある「規約」で全体を構成する。これが、Goのコードが高い可読性と生産性を持つ理由の一つと思います。
これからGoを書くときは、ぜひ「この名前にはどんな意味があるんだろう?」と考えてみると良いかもしれないです。キーワードを探すのではなく、名前の付け方に注目することで、Goのコードがもっと読みやすく、書きやすくなるはずです。
Discussion