[インデックス 1509] ファイルの概要
このコミットは、Go言語の標準ライブラリfmt
パッケージにおける内部関数の可視性(エクスポート状態)を変更するリファクタリングです。具体的には、fmt/format.go
内のPrec
関数とfmt/print.go
内のPrinter
関数が、それぞれdoPrec
とnewPrinter
に名称変更され、これにより外部から直接アクセスできない(アンエクスポートされた)内部関数となりました。
コミット
commit db1656f3e654d45e611fc4782d9aed9be8e11c5e
Author: Rob Pike <r@golang.org>
Date: Fri Jan 16 13:29:43 2009 -0800
two more casifications in fmt
R=rsc
DELTA=14 (0 added, 0 deleted, 14 changed)
OCL=22960
CL=22962
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/db1656f3e654d45e611fc4782d9aed9be8e11c5e
元コミット内容
fmt
パッケージ内のPrec
関数とPrinter
関数が、それぞれdoPrec
とnewPrinter
にリネームされました。これにより、これらの関数はパッケージ外部からアクセスできなくなりました。
変更の背景
Go言語では、識別子(関数名、変数名、型名など)の最初の文字が大文字であるか小文字であるかによって、その可視性(エクスポートされるか否か)が決定されます。大文字で始まる識別子はパッケージ外部にエクスポートされ、小文字で始まる識別子はパッケージ内部でのみ使用可能な非エクスポート(アンエクスポート)識別子となります。
このコミットメッセージにある「casifications」とは、このGo言語の命名規則に基づき、識別子の「ケース(大文字・小文字)」を変更することで、その可視性を調整する行為を指します。
この変更の背景には、Go言語の設計思想である「APIのシンプルさと安定性」があります。パッケージの内部実装の詳細を外部に公開しないことで、以下のようなメリットが生まれます。
- APIの安定性: 内部関数が外部に公開されていないため、将来的に内部実装を変更しても、その変更が外部のコードに影響を与えるリスクが低減されます。これにより、パッケージのAPIがより安定し、後方互換性を維持しやすくなります。
- カプセル化と情報隠蔽: パッケージの利用者は、内部の詳細を知る必要がなく、公開されたAPIのみに集中できます。これにより、コードの理解が容易になり、誤用を防ぐことができます。
- 保守性の向上: 内部実装の変更がパッケージ内部に限定されるため、開発者はより自由にリファクタリングや最適化を行うことができます。
このコミットは、fmt
パッケージの初期開発段階において、これらの関数が本来はパッケージ内部でのみ使用されるべきであると判断され、APIの整理とカプセル化を強化するために行われたリファクタリングの一環と考えられます。
前提知識の解説
Go言語の可視性(エクスポートルール)
Go言語における識別子の可視性ルールは非常にシンプルかつ強力です。
- エクスポートされた識別子(Exported Identifiers): 識別子の最初の文字が大文字の場合、その識別子はパッケージ外部からアクセス可能です。これは、他のパッケージからその関数、変数、型、メソッドなどを参照できることを意味します。例えば、
fmt.Println
のPrintln
はエクスポートされた関数です。 - 非エクスポートされた識別子(Unexported Identifiers): 識別子の最初の文字が小文字の場合、その識別子は宣言されたパッケージ内でのみアクセス可能です。これは、パッケージの内部実装の詳細を隠蔽し、外部からの直接的な操作を防ぐために使用されます。
このルールは、Go言語のモジュール性、カプセル化、そしてAPI設計の基盤となっています。
fmt
パッケージ
fmt
パッケージは、Go言語の標準ライブラリの一部であり、フォーマットされたI/O(入出力)を実装するための機能を提供します。C言語のprintf
やscanf
に似た機能を提供し、文字列、数値、構造体などの様々なデータ型を整形して出力したり、入力から解析したりするために使用されます。
fmt.Printf
: フォーマット文字列と引数に基づいて標準出力に整形されたテキストを出力します。fmt.Sprintf
: フォーマット文字列と引数に基づいて整形されたテキストを文字列として返します。fmt.Fprint
: 指定されたio.Writer
にテキストを出力します。
このコミットで変更されたPrec
(doPrec
)関数は、浮動小数点数の精度を決定する内部ロジックに関連し、Printer
(newPrinter
)関数は、fmt
パッケージの出力処理を行う内部構造体pp
(printer)のインスタンスを生成する役割を担っていました。
技術的詳細
このコミットは、Go言語の可視性ルールを適用し、内部関数をアンエクスポートすることで、fmt
パッケージの内部構造をより明確にし、外部からの不必要な依存を防ぐことを目的としています。
src/lib/fmt/format.go
の変更
format.go
ファイルは、fmt
パッケージにおける様々なデータ型のフォーマット処理、特に数値や文字列の整形ロジックを扱っています。
変更前:
func Prec(f *Fmt, def int) int {
// ...
}
変更後:
func doPrec(f *Fmt, def int) int {
// ...
}
Prec
関数は、浮動小数点数の出力精度を決定するための内部ヘルパー関数でした。この関数は、Fmt
構造体(フォーマットの状態を保持する)とデフォルトの精度を受け取り、最終的な精度を返します。この関数がパッケージ外部から直接呼び出されることは想定されておらず、fmt
パッケージ内部のFmt_e64
, Fmt_f64
, Fmt_g64
などの浮動小数点数フォーマット関数からのみ使用されていました。そのため、doPrec
とリネームすることで、その内部的な性質を明確にし、外部からのアクセスを制限しました。
src/lib/fmt/print.go
の変更
print.go
ファイルは、fmt
パッケージの主要な出力関数(Printf
, Sprintf
など)の実装を含んでいます。
変更前:
func Printer() *pp {
// ...
}
変更後:
func newPrinter() *pp {
// ...
}
Printer
関数は、pp
(printer)構造体の新しいインスタンスを生成し、初期化して返す役割を担っていました。pp
構造体は、実際のフォーマット処理とバッファリングを行うための内部的な状態を保持します。この関数もまた、fmt
パッケージの公開API(Fprintf
, Printf
, Sprintf
など)の内部でpp
インスタンスを取得するためにのみ使用されており、外部に公開する必要はありませんでした。newPrinter
とリネームすることで、Go言語におけるコンストラクタ関数の慣習(NewType
のような命名)にも沿いつつ、内部関数であることを明示しています。
これらの変更は、Go言語の初期段階におけるコードベースの成熟と、API設計原則の適用を示す良い例です。
コアとなるコードの変更箇所
src/lib/fmt/format.go
--- a/src/lib/fmt/format.go
+++ b/src/lib/fmt/format.go
@@ -413,7 +413,7 @@ func (f *Fmt) Fmt_q(s string) *Fmt {
// floating-point
-func Prec(f *Fmt, def int) int {
+func doPrec(f *Fmt, def int) int {
if f.prec_present {
return f.prec;
}
@@ -428,15 +428,15 @@ func fmtString(f *Fmt, s string) *Fmt {
// float64
func (f *Fmt) Fmt_e64(a float64) *Fmt {
- return fmtString(f, strconv.Ftoa64(a, 'e', Prec(f, 6)));
+ return fmtString(f, strconv.Ftoa64(a, 'e', doPrec(f, 6)));
}
func (f *Fmt) Fmt_f64(a float64) *Fmt {
- return fmtString(f, strconv.Ftoa64(a, 'f', Prec(f, 6)));
+ return fmtString(f, strconv.Ftoa64(a, 'f', doPrec(f, 6)));
}
func (f *Fmt) Fmt_g64(a float64) *Fmt {
- return fmtString(f, strconv.Ftoa64(a, 'g', Prec(f, -1)));
+ return fmtString(f, strconv.Ftoa64(a, 'g', doPrec(f, -1)));
}
func (f *Fmt) Fmt_fb64(a float64) *Fmt {
@@ -447,15 +447,15 @@ func (f *Fmt) Fmt_fb64(a float64) *Fmt {
// cannot defer to float64 versions
// because it will get rounding wrong in corner cases.
func (f *Fmt) Fmt_e32(a float32) *Fmt {
- return fmtString(f, strconv.Ftoa32(a, 'e', Prec(f, 6)));
+ return fmtString(f, strconv.Ftoa32(a, 'e', doPrec(f, 6)));
}
func (f *Fmt) Fmt_f32(a float32) *Fmt {
- return fmtString(f, strconv.Ftoa32(a, 'f', Prec(f, 6)));
+ return fmtString(f, strconv.Ftoa32(a, 'f', doPrec(f, 6)));
}
func (f *Fmt) Fmt_g32(a float32) *Fmt {
- return fmtString(f, strconv.Ftoa32(a, 'g', Prec(f, -1)));
+ return fmtString(f, strconv.Ftoa32(a, 'g', doPrec(f, -1)));
}
func (f *Fmt) Fmt_fb32(a float32) *Fmt {
src/lib/fmt/print.go
--- a/src/lib/fmt/print.go
+++ b/src/lib/fmt/print.go
@@ -45,7 +45,7 @@ type pp struct {
fmt *Fmt;
}
-func Printer() *pp {
+func newPrinter() *pp {
p := new(pp);
p.fmt = fmt.New();
return p;
@@ -130,7 +130,7 @@ func (p *pp) doprint(v reflect.StructValue, addspace, addnewline bool);
export func Fprintf(w io.Write, format string, a ...) (n int, error *os.Error) {
v := reflect.NewValue(a).(reflect.PtrValue).Sub().(reflect.StructValue);
- p := Printer();
+ p := newPrinter();
p.doprintf(format, v);
n, error = w.Write(p.buf[0:p.n]);
return n, error;
@@ -143,7 +143,7 @@ export func Printf(format string, v ...) (n int, errno *os.Error) {
export func Sprintf(format string, a ...) string {
v := reflect.NewValue(a).(reflect.PtrValue).Sub().(reflect.StructValue);
- p := Printer();
+ p := newPrinter();
p.doprintf(format, v);
s := string(p.buf)[0 : p.n];
return s;
@@ -154,7 +154,7 @@ export func Sprintf(format string, a ...) string {
export func Fprint(w io.Write, a ...) (n int, error *os.Error) {
v := reflect.NewValue(a).(reflect.PtrValue).Sub().(reflect.StructValue);
- p := Printer();
+ p := newPrinter();
p.doprint(v, false, false);
n, error = w.Write(p.buf[0:p.n]);
return n, error;
@@ -167,7 +167,7 @@ export func Print(v ...) (n int, errno *os.Error) {
export func Sprint(a ...) string {
v := reflect.NewValue(a).(reflect.PtrValue).Sub().(reflect.StructValue);
- p := Printer();
+ p := newPrinter();
p.doprint(v, false, false);
s := string(p.buf)[0 : p.n];
return s;
@@ -179,7 +179,7 @@ export func Sprint(a ...) string {
export func Fprintln(w io.Write, a ...) (n int, error *os.Error) {
v := reflect.NewValue(a).(reflect.PtrValue).Sub().(reflect.StructValue);
- p := Printer();
+ p := newPrinter();
p.doprint(v, true, true);
n, error = w.Write(p.buf[0:p.n]);
return n, error;
@@ -192,7 +192,7 @@ export func Println(v ...) (n int, errno *os.Error) {
export func Sprintln(a ...) string {
v := reflect.NewValue(a).(reflect.PtrValue).Sub().(reflect.StructValue);
- p := Printer();
+ p := newPrinter();
p.doprint(v, true, true);
s := string(p.buf)[0 : p.n];
return s;
コアとなるコードの解説
このコミットのコアとなる変更は、Go言語の命名規則を利用した関数の可視性(エクスポート状態)の変更です。
-
Prec
からdoPrec
への変更 (src/lib/fmt/format.go
):- 元の関数名
Prec
は、Go言語のルールではエクスポートされた関数を意味します。しかし、この関数はfmt
パッケージ内部の浮動小数点数フォーマット処理でのみ使用されており、外部に公開する必要がありませんでした。 doPrec
という名前に変更することで、最初の文字が小文字になり、この関数はfmt
パッケージ内部でのみアクセス可能な非エクスポート関数となりました。これにより、パッケージの内部実装が外部から隠蔽され、APIのクリーンさが保たれます。- この変更に伴い、
Fmt_e64
,Fmt_f64
,Fmt_g64
,Fmt_e32
,Fmt_f32
,Fmt_g32
といった関連するフォーマット関数内のPrec
への呼び出しもすべてdoPrec
に更新されています。
- 元の関数名
-
Printer
からnewPrinter
への変更 (src/lib/fmt/print.go
):- 同様に、元の関数名
Printer
はエクスポートされた関数を意味していました。この関数は、fmt
パッケージの出力処理を行う内部構造体pp
のインスタンスを生成する役割を担っていました。 newPrinter
という名前に変更することで、最初の文字が小文字になり、この関数もfmt
パッケージ内部でのみアクセス可能な非エクスポート関数となりました。- Go言語では、型
T
の新しいインスタンスを返す関数にはNewT
のような命名が慣習的に用いられます。newPrinter
という名前は、この慣習にも沿っており、関数がpp
構造体の新しいインスタンスを生成する役割を持つことをより明確に示しています。 - この変更に伴い、
Fprintf
,Printf
,Sprintf
,Fprint
,Print
,Sprint
,Fprintln
,Println
,Sprintln
といったfmt
パッケージの主要な出力関数内のPrinter()
への呼び出しもすべてnewPrinter()
に更新されています。
- 同様に、元の関数名
これらの変更は、機能的な振る舞いを一切変えることなく、コードの構造とAPI設計を改善するための純粋なリファクタリングです。Go言語の設計原則である「シンプルさ」と「カプセル化」を体現するコミットと言えます。
関連リンク
- Go言語の公式ドキュメント: https://go.dev/doc/
- Go言語のパッケージとモジュールに関するドキュメント: https://go.dev/doc/modules/
- Go言語のEffective Go - Naming: https://go.dev/doc/effective_go#names
参考にした情報源リンク
- Go言語の公式ドキュメント
- Go言語のソースコード(
fmt
パッケージ) - Go言語の命名規則に関する一般的な情報源(Effective Goなど)
- GitHubのコミット履歴