Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 1496] ファイルの概要

このコミットは、Go言語の標準ライブラリであるtimeパッケージ内の識別子(関数、変数など)の可視性を調整するものです。具体的には、パッケージ内部でのみ使用されるべき識別子を、外部からアクセスできないように「非公開化」(unexport)しています。これは、Go言語の命名規則と可視性ルールに従い、APIの明確性と内部実装の隠蔽を強化するための変更です。

コミット

commit e83c85accb1c02409390b2a11fda95c131a692d9
Author: Russ Cox <rsc@golang.org>
Date:   Fri Jan 16 10:14:12 2009 -0800

    casify time
    
    R=r
    DELTA=103  (1 added, 0 deleted, 102 changed)
    OCL=22914
    CL=22937

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/e83c85accb1c02409390b2a11fda95c131a692d9

元コミット内容

コミットメッセージは「casify time」と非常に簡潔です。これは、Go言語における「case」(大文字・小文字)のルールに従って、timeパッケージ内の識別子の可視性を調整したことを意味します。具体的には、外部に公開する必要のない関数や変数の名前を、大文字始まりから小文字始まりに変更することで、パッケージ内部でのみアクセス可能にしています。

変更の背景

Go言語では、識別子(変数、関数、型など)の最初の文字が大文字であるか小文字であるかによって、その可視性(エクスポートされるか否か)が決定されます。

  • 大文字始まり: パッケージ外からアクセス可能(エクスポートされる)
  • 小文字始まり: パッケージ内からのみアクセス可能(エクスポートされない、非公開)

このコミットの背景には、Go言語の設計思想における「APIの明確化」と「内部実装の隠蔽」があります。パッケージの利用者が直接触れるべきでない内部的なヘルパー関数や定数を非公開にすることで、パッケージのAPIサーフェスを小さく保ち、利用者が混乱することなく、意図された方法でパッケージを使用できるようにします。また、内部実装の変更が外部に影響を与えないようにすることで、将来的なリファクタリングや最適化を容易にします。

この変更は、Go言語がまだ初期段階にあった2009年に行われたものであり、言語の設計原則が固まっていく過程で、よりGoらしい(idiomatic Go)コードベースにするための調整の一環と考えられます。

前提知識の解説

Go言語における識別子の可視性(Exported vs. Unexported)

Go言語の最も特徴的な設計の一つが、識別子の可視性を名前の先頭文字の大文字・小文字で制御する仕組みです。

  • エクスポートされた識別子 (Exported Identifiers):

    • 名前が大文字で始まる識別子(例: MyFunction, MyVariable, MyType)。
    • これらは、その識別子が定義されているパッケージをインポートする他のパッケージからアクセス可能です。
    • パッケージの公開APIの一部となります。
    • 外部から利用されることを意図しているため、明確なドキュメントが求められます。
  • 非公開の識別子 (Unexported Identifiers):

    • 名前が小文字で始まる識別子(例: myFunction, myVariable, myType)。
    • これらは、その識別子が定義されているパッケージ内からのみアクセス可能です。
    • パッケージの内部実装の詳細であり、外部からは直接アクセスできません。
    • これにより、パッケージの内部構造が外部に漏れるのを防ぎ、パッケージの利用者は公開されたAPIのみに集中できます。

Go言語の命名規則

Go言語では、可視性ルールに加えて、以下のような一般的な命名規則が推奨されています。

  • パッケージ名: 小文字で単一の単語(例: time, fmt, net/http)。
  • 関数・変数名:
    • エクスポートされるもの: CamelCase(例: ParseTime, MaxConnections)。
    • 非公開のもの: camelCase(例: parseHelper, tempBuffer)。
  • 定数: ALL_CAPITAL_LETTERSはGoでは一般的ではなく、通常は変数と同様にCamelCaseまたはcamelCaseが使われます。ただし、このコミットでは一部の定数にアンダースコアプレフィックスが追加されています。
  • 型(構造体、インターフェース): 大文字始まりのCamelCase(例: User, Reader)。
  • ファイル名: 小文字で、単語間はアンダースコアで区切る(例: my_file.go, time_test.go)。

このコミットは、特に「関数・変数名」と「定数」の命名規則に焦点を当て、非公開化を進めています。

技術的詳細

このコミットでは、src/lib/timeパッケージ内の複数のファイルにおいて、識別子の名前が変更されています。主な変更は以下の通りです。

  1. 関数名の変更:

    • Ticker -> ticker (src/lib/time/tick.go)
    • Months -> months (src/lib/time/time.go)
    • Copy -> _Copy (src/lib/time/time.go)
    • Decimal -> _Decimal (src/lib/time/time.go)
    • AddString -> _AddString (src/lib/time/time.go)
    • Format -> _Format (src/lib/time/time.go)
    • ParseZoneinfo -> parseinfo (src/lib/time/zoneinfo.go)
    • ReadFile -> readfile (src/lib/time/zoneinfo.go)
    • ReadZoneinfoFile -> readinfofile (src/lib/time/zoneinfo.go)
    • SetupZone -> _SetupZone (src/lib/time/zoneinfo.go)

    これらの関数は、パッケージ内部でのみ使用されるヘルパー関数であり、外部に公開する必要がないため、小文字始まりの名前に変更されています。一部の関数には、さらにアンダースコアプレフィックス(_)が付与されています。これは、Goの慣習としては一般的ではありませんが、当時のGoの初期段階における命名の試行錯誤、あるいは特定の内部的な役割を示すためのものかもしれません。

  2. 変数名の変更:

    • RegularMonths -> nonleapyear (src/lib/time/time.go)
    • LeapMonths -> leapyear (src/lib/time/time.go)
    • LongDayNames -> _LongDayNames (src/lib/time/time.go)
    • ShortDayNames -> _ShortDayNames (src/lib/time/time.go)
    • ShortMonthNames -> _ShortMonthNames (src/lib/time/time.go)
    • utctests -> utctests (型定義はTimeTest -> _TimeTest) (src/lib/time/time_test.go)
    • localtests -> localtests (型定義はTimeTest -> _TimeTest) (src/lib/time/time_test.go)

    これらの変数も、パッケージ内部でのみ使用される定数やテストデータであるため、非公開化されています。

  3. 定数名の変更:

    • SecondsPerDay -> _SecondsPerDay (src/lib/time/time.go)
    • DaysPer400Years -> _DaysPer400Years (src/lib/time/time.go)
    • DaysPer100Years -> _DaysPer100Years (src/lib/time/time.go)
    • DaysPer4Years -> _DaysPer4Years (src/lib/time/time.go)
    • Days1970To2001 -> _Days1970To2001 (src/lib/time/time.go)
    • MaxFileSize -> _MaxFileSize (src/lib/time/zoneinfo.go)
    • HeaderSize -> _HeaderSize (src/lib/time/zoneinfo.go)

    これらの定数も、日付計算やファイル処理の内部的な詳細であり、外部に公開する必要がないため、アンダースコアプレフィックス付きの小文字始まりの名前に変更されています。

  4. 型名の変更:

    • TimeTest -> _TimeTest (src/lib/time/time_test.go)
    • Data -> _Data (src/lib/time/zoneinfo.go)
    • Zone -> _Zone (src/lib/time/zoneinfo.go)
    • Zonetime -> _Zonetime (src/lib/time/zoneinfo.go)

    テスト用の構造体や、ゾーン情報解析の内部的なデータ構造も、外部に公開する必要がないため、非公開化されています。

これらの変更により、timeパッケージの公開APIはより洗練され、内部実装の複雑さが外部から隠蔽されることになります。

コアとなるコードの変更箇所

src/lib/time/tick.go

--- a/src/lib/time/tick.go
+++ b/src/lib/time/tick.go
@@ -25,7 +25,7 @@ import (
 //		tc <- nsec;
 //	}
 
-func Ticker(ns int64, c chan int64) {
+func ticker(ns int64, c chan int64) {
 	var tv syscall.Timeval;
 	now := time.Nanoseconds();
 	when := now;
@@ -54,7 +54,7 @@ export func Tick(ns int64) chan int64 {
 	return nil
 	}
 	c := make(chan int64);
-	go Ticker(ns, c);
+	go ticker(ns, c);
 	return c;
 }

Ticker関数がtickerにリネームされ、それに伴いTick関数からの呼び出しも変更されています。これにより、tickertimeパッケージ内部でのみ利用可能な関数となります。

src/lib/time/time.go

--- a/src/lib/time/time.go
+++ b/src/lib/time/time.go
@@ -46,39 +46,39 @@ export type Time struct {
 	zone string;
 }
 
-var RegularMonths = []int{
+var nonleapyear = []int{
 	31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
 }
-var LeapMonths = []int{
+var leapyear = []int{
 	31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
 }
 
-func Months(year int64) []int {
+func months(year int64) []int {
 	if year%4 == 0 && (year%100 != 0 || year%400 == 0) {
-		return LeapMonths
+		return leapyear
 	}
-	return RegularMonths
+	return nonleapyear
 }
 
 const (
-	SecondsPerDay = 24*60*60;
+	_SecondsPerDay = 24*60*60;
 
-	DaysPer400Years = 365*400+97;
-	DaysPer100Years = 365*100+24;
-	DaysPer4Years = 365*4+1;
+	_DaysPer400Years = 365*400+97;
+	_DaysPer100Years = 365*100+24;
+	_DaysPer4Years = 365*4+1;
 
-	Days1970To2001 = 31*365+8;
+	_Days1970To2001 = 31*365+8;
 )

RegularMonthsLeapMonthsがそれぞれnonleapyearleapyearに、Months関数がmonthsにリネームされています。また、日付計算に使用される定数も、すべてアンダースコアプレフィックス付きの小文字始まりの名前に変更されています。

src/lib/time/time_test.go

--- a/src/lib/time/time_test.go
+++ b/src/lib/time/time_test.go
@@ -9,27 +9,27 @@ import (
 	"time";
 )
 
-type TimeTest struct {
+type _TimeTest struct {
 	seconds int64;
 	golden Time;
 }
 
-var utctests = []TimeTest {
-	TimeTest{0, Time{1970, 1, 1, 0, 0, 0, Thursday, 0, "GMT"}},
-	TimeTest{1221681866, Time{2008, 9, 17, 20, 4, 26, Wednesday, 0, "GMT"}},
-	TimeTest{-1221681866, Time{1931, 4, 16, 3, 55, 34, Thursday, 0, "GMT"}},
-	TimeTest{1e18, Time{31688740476, 10, 23, 1, 46, 40, Friday, 0, "GMT"}},
-	TimeTest{-1e18, Time{-31688736537, 3, 10, 22, 13, 20, Tuesday, 0, "GMT"}},
-	TimeTest{0x7fffffffffffffff, Time{292277026596, 12, 4, 15, 30, 7, Sunday, 0, "GMT"}},
-	TimeTest{-0x8000000000000000, Time{-292277022657, 1, 27, 8, 29, 52, Sunday, 0, "GMT"}}\n+var utctests = []_TimeTest {
+	_TimeTest{0, Time{1970, 1, 1, 0, 0, 0, Thursday, 0, "GMT"}},
+	_TimeTest{1221681866, Time{2008, 9, 17, 20, 4, 26, Wednesday, 0, "GMT"}},
+	_TimeTest{-1221681866, Time{1931, 4, 16, 3, 55, 34, Thursday, 0, "GMT"}},
+	_TimeTest{1e18, Time{31688740476, 10, 23, 1, 46, 40, Friday, 0, "GMT"}},
+	_TimeTest{-1e18, Time{-31688736537, 3, 10, 22, 13, 20, Tuesday, 0, "GMT"}},
+	_TimeTest{0x7fffffffffffffff, Time{292277026596, 12, 4, 15, 30, 7, Sunday, 0, "GMT"}},
+	_TimeTest{-0x8000000000000000, Time{-292277022657, 1, 27, 8, 29, 52, Sunday, 0, "GMT"}}\n }\n 
-var localtests = []TimeTest {
-	TimeTest{0, Time{1969, 12, 31, 16, 0, 0, Wednesday, -8*60*60, "PST"}},
-	TimeTest{1221681866, Time{2008, 9, 17, 13, 4, 26, Wednesday, -7*60*60, "PDT"}}\n+var localtests = []_TimeTest {
+	_TimeTest{0, Time{1969, 12, 31, 16, 0, 0, Wednesday, -8*60*60, "PST"}},
+	_TimeTest{1221681866, Time{2008, 9, 17, 13, 4, 26, Wednesday, -7*60*60, "PDT"}}\n }\n 
-func Same(t, u *Time) bool {
+func _Same(t, u *Time) bool {
 	return t.year == u.year
 		&& t.month == u.month
 		&& t.day == u.day
@@ -50,7 +50,7 @@ export func TestSecondsToUTC(t *testing.T) {
 	if newsec != sec {
 		t.Errorf("SecondsToUTC(%d).Seconds() = %d", sec, newsec);
 	}
-	if !Same(tm, golden) {
+	if !_Same(tm, golden) {
 		t.Errorf("SecondsToUTC(%d):", sec);
 		t.Errorf("  want=%v", *golden);
 		t.Errorf("  have=%v", *tm);
@@ -67,7 +67,7 @@ export func TestSecondsToLocalTime(t *testing.T) {
 	if newsec != sec {
 		t.Errorf("SecondsToLocalTime(%d).Seconds() = %d", sec, newsec);
 	}
-	if !Same(tm, golden) {
+	if !_Same(tm, golden) {
 		t.Errorf("SecondsToLocalTime(%d):", sec);
 		t.Errorf("  want=%v", *golden);
 		t.Errorf("  have=%v", *tm);

テスト用の構造体TimeTest_TimeTestに、ヘルパー関数Same_Sameにリネームされています。これにより、テストコードの内部的な詳細が明確になります。

コアとなるコードの解説

このコミットの主要な目的は、Go言語の可視性ルールを厳密に適用し、timeパッケージの内部実装と公開APIを明確に分離することです。

  • Tickerからtickerへの変更: Ticker関数は、Tick関数によってゴルーチンとして起動される内部的な処理を担当していました。外部から直接呼び出されることを意図していないため、小文字始まりのtickerにリネームすることで、パッケージ内部でのみ利用可能な非公開関数となりました。これにより、timeパッケージの利用者は、より高レベルなTick関数を通じてのみタイマー機能を利用するよう促されます。

  • RegularMonths, LeapMonths, Monthsの非公開化: これらの変数と関数は、日付計算の内部ロジックで使用されるものであり、パッケージの外部に公開する必要はありません。非公開化することで、日付計算の内部実装が変更されても、外部のコードに影響を与えるリスクが低減されます。

  • 定数へのアンダースコアプレフィックス追加: SecondsPerDayなどの定数は、日付や時刻の計算における内部的な単位やオフセットを表します。これらを_SecondsPerDayのように変更することで、これらがパッケージの内部的な詳細であり、外部のコードが直接参照すべきではないことを示唆しています。Goの慣習では、アンダースコアプレフィックスはあまり一般的ではありませんが、このコミットの時点では、内部的な定数を明確に区別するための手段として採用された可能性があります。

  • Format, Copy, Decimal, AddStringなどのヘルパー関数の非公開化: これらの関数は、時刻のフォーマットや文字列操作といった、より大きな機能を実現するための小さな部品です。これらを非公開にすることで、timeパッケージの公開APIは、Asctime(), RFC850(), RFC1123(), String()といった、より高レベルで意味のあるメソッドに限定されます。これにより、APIの利用者は、内部的な実装の詳細に煩わされることなく、必要な機能に集中できます。

  • zoneinfo.goにおける解析関連の関数・型の非公開化: zoneinfo.goはタイムゾーン情報の解析を担当するファイルです。ParseZoneinfo, ReadFile, ReadZoneinfoFileといった関数や、Data, Zone, Zonetimeといった型は、タイムゾーンデータの内部的な処理に使われるものです。これらを非公開にすることで、タイムゾーン情報の読み込みや解析の複雑なロジックがパッケージ内部にカプセル化され、外部からはLookupTimezoneのような公開された関数を通じてのみタイムゾーン情報にアクセスできるようになります。

全体として、このコミットはGo言語の「最小限の公開API」という原則を徹底し、パッケージの内部構造を隠蔽することで、コードの保守性、再利用性、そして利用者の使いやすさを向上させています。

関連リンク

  • Effective Go - Names: Go言語における命名規則と可視性に関する公式ドキュメント。

参考にした情報源リンク