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

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

このコミットは、Go言語の標準ライブラリfmtパッケージにおける内部関数の可視性(エクスポート状態)を変更するリファクタリングです。具体的には、fmt/format.go内のPrec関数とfmt/print.go内のPrinter関数が、それぞれdoPrecnewPrinterに名称変更され、これにより外部から直接アクセスできない(アンエクスポートされた)内部関数となりました。

コミット

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関数が、それぞれdoPrecnewPrinterにリネームされました。これにより、これらの関数はパッケージ外部からアクセスできなくなりました。

変更の背景

Go言語では、識別子(関数名、変数名、型名など)の最初の文字が大文字であるか小文字であるかによって、その可視性(エクスポートされるか否か)が決定されます。大文字で始まる識別子はパッケージ外部にエクスポートされ、小文字で始まる識別子はパッケージ内部でのみ使用可能な非エクスポート(アンエクスポート)識別子となります。

このコミットメッセージにある「casifications」とは、このGo言語の命名規則に基づき、識別子の「ケース(大文字・小文字)」を変更することで、その可視性を調整する行為を指します。

この変更の背景には、Go言語の設計思想である「APIのシンプルさと安定性」があります。パッケージの内部実装の詳細を外部に公開しないことで、以下のようなメリットが生まれます。

  1. APIの安定性: 内部関数が外部に公開されていないため、将来的に内部実装を変更しても、その変更が外部のコードに影響を与えるリスクが低減されます。これにより、パッケージのAPIがより安定し、後方互換性を維持しやすくなります。
  2. カプセル化と情報隠蔽: パッケージの利用者は、内部の詳細を知る必要がなく、公開されたAPIのみに集中できます。これにより、コードの理解が容易になり、誤用を防ぐことができます。
  3. 保守性の向上: 内部実装の変更がパッケージ内部に限定されるため、開発者はより自由にリファクタリングや最適化を行うことができます。

このコミットは、fmtパッケージの初期開発段階において、これらの関数が本来はパッケージ内部でのみ使用されるべきであると判断され、APIの整理とカプセル化を強化するために行われたリファクタリングの一環と考えられます。

前提知識の解説

Go言語の可視性(エクスポートルール)

Go言語における識別子の可視性ルールは非常にシンプルかつ強力です。

  • エクスポートされた識別子(Exported Identifiers): 識別子の最初の文字が大文字の場合、その識別子はパッケージ外部からアクセス可能です。これは、他のパッケージからその関数、変数、型、メソッドなどを参照できることを意味します。例えば、fmt.PrintlnPrintlnはエクスポートされた関数です。
  • 非エクスポートされた識別子(Unexported Identifiers): 識別子の最初の文字が小文字の場合、その識別子は宣言されたパッケージ内でのみアクセス可能です。これは、パッケージの内部実装の詳細を隠蔽し、外部からの直接的な操作を防ぐために使用されます。

このルールは、Go言語のモジュール性、カプセル化、そしてAPI設計の基盤となっています。

fmtパッケージ

fmtパッケージは、Go言語の標準ライブラリの一部であり、フォーマットされたI/O(入出力)を実装するための機能を提供します。C言語のprintfscanfに似た機能を提供し、文字列、数値、構造体などの様々なデータ型を整形して出力したり、入力から解析したりするために使用されます。

  • fmt.Printf: フォーマット文字列と引数に基づいて標準出力に整形されたテキストを出力します。
  • fmt.Sprintf: フォーマット文字列と引数に基づいて整形されたテキストを文字列として返します。
  • fmt.Fprint: 指定されたio.Writerにテキストを出力します。

このコミットで変更されたPrecdoPrec)関数は、浮動小数点数の精度を決定する内部ロジックに関連し、PrinternewPrinter)関数は、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言語の命名規則を利用した関数の可視性(エクスポート状態)の変更です。

  1. 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に更新されています。
  2. 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言語の公式ドキュメント
  • Go言語のソースコード(fmtパッケージ)
  • Go言語の命名規則に関する一般的な情報源(Effective Goなど)
  • GitHubのコミット履歴