😺

キーワードより規約が偉い?規約が支配する5つのルール

に公開

はじめに

普段、Goを書いてると、誰もが一度は戸惑う点があるかと思います。

「あれ、publicprivateはどこに書くの?」

Goにはクラスやアクセス修飾子といったキーワードがありません。
しかし、もちろん外部からアクセスできる/できない、といった可視性の制御は存在します。Goはそれを、キーワードではなく 命名規則 で実現しているのです。

これはC#を使っている自分からすると、かなり驚いたと同時にGoのシンプルな設計思想の奥深さも感じました。

この記事では、Goがいかに「命名規則」を重視し、それによってシンプルさと一貫性を実現しているかを探ります。単なるコーディング規約ではなく、名前そのものがプログラムの振る舞いを決定する Goの面白い世界を一緒に見ていきましょう。

1. すべての始まり:大文字と小文字が PublicPrivate を決める

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 mainfunc 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