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

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

このコミットは、Go言語の標準ライブラリであるflagパッケージ内のsrc/lib/flag.goファイルに対する変更です。flagパッケージは、コマンドライン引数を解析するための機能を提供します。このファイルは、フラグの定義、値の型、およびフラグの解析ロジックを実装しています。

コミット

このコミットは、flagパッケージ内のValueインターフェースの名前を_Valueに変更し、それに伴う関連コードの修正を行っています。これにより、Valueインターフェースがパッケージ外部からアクセスできない(unexported)ようになりました。また、allFlags構造体の初期化方法も変更されています。

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

https://github.com/golang/go/commit/55ebef615b48480f6b1d898ff811b145aa629c89

元コミット内容

casify flag.

R=rsc
DELTA=16  (0 added, 9 deleted, 7 changed)
OCL=22959
CL=22961
---
 src/lib/flag.go | 19 +++++--------------
 1 file changed, 5 insertions(+), 14 deletions(-)

diff --git a/src/lib/flag.go b/src/lib/flag.go
index 9bed00db4c..3274a854e5 100644
--- a/src/lib/flag.go
+++ b/src/lib/flag.go
@@ -213,7 +213,7 @@ func (s *stringValue) str() string {
 }
 
 // -- Value interface
-type Value interface {
+type _Value interface {
 	str() string;
 }
 
@@ -221,25 +221,16 @@ export type Flag struct {
 	name	string;
 	usage	string;
-\tvalue\tValue;
+\tvalue\t_Value;
 }
 
 type allFlags struct {
 	actual map[string] *Flag;
 	formal map[string] *Flag;
-\tfirst_arg\tint;\
+\tfirst_arg\tint;\t// 0 is the program name, 1 is first arg
 }
 
-\
-func New() *allFlags {
-\tf := new(allFlags);\
-\tf.first_arg = 1;\t// 0 is the program name, 1 is first arg
-\tf.actual = make(map[string] *Flag);\
-\tf.formal = make(map[string] *Flag);\
-\treturn f;\
-}\
-\
-var flags *allFlags = New();
+var flags *allFlags = &allFlags{make(map[string] *Flag), make(map[string] *Flag), 1}
 
 export func PrintDefaults() {
 	for k, f := range flags.formal {
@@ -273,7 +264,7 @@ export func NArg() int {
 	return sys.argc() - flags.first_arg
 }
 
-func add(name string, value Value, usage string) {
+func add(name string, value _Value, usage string) {
 	f := new(Flag);\
 	f.name = name;\
 	f.usage = usage;\

変更の背景

この変更の背景には、Go言語の設計思想と命名規則が深く関わっています。Go言語では、識別子(変数名、関数名、型名など)の最初の文字が大文字であるか小文字であるかによって、その識別子がパッケージ外部に公開される(exported)か、パッケージ内部のみで利用可能(unexported)かが決まります。

元のコードではValueというインターフェースが定義されており、これは大文字で始まるためexportedでした。しかし、flagパッケージの内部実装において、このインターフェースが外部に直接公開される必要がない、あるいは公開されるべきではないと判断された可能性があります。インターフェースがexportedであると、そのインターフェースを実装する型も外部から参照可能になり、APIの複雑性を増したり、将来的な変更が難しくなったりする可能性があります。

「casify flag.」というコミットメッセージは、この命名規則(大文字・小文字の区別)に基づいて識別子の可視性を変更したことを明確に示しています。これにより、Valueインターフェースはパッケージ内部の詳細となり、flagパッケージの外部APIがよりシンプルで安定したものになります。

また、allFlags構造体の初期化方法の変更は、New()関数を介した初期化から、構造体リテラルを用いた直接的な初期化へと簡略化されています。これは、初期化ロジックが単純であり、専用のファクトリ関数を必要としないと判断されたためと考えられます。

前提知識の解説

Go言語のパッケージと可視性

Go言語では、コードは「パッケージ」という単位で整理されます。パッケージは、関連する機能や型をまとめるための基本的な単位です。Goの識別子には、以下の可視性ルールがあります。

  • Exported (公開): 識別子の最初の文字が大文字の場合、その識別子はパッケージ外部からアクセス可能です。これは、他のパッケージから利用できるAPIの一部となります。
  • Unexported (非公開): 識別子の最初の文字が小文字(またはアンダースコア_)の場合、その識別子は定義されたパッケージ内でのみアクセス可能です。これは、パッケージの内部実装の詳細であり、外部からは見えません。

このルールは、APIの設計において非常に重要です。外部に公開すべきでない内部実装の詳細はunexportedにすることで、パッケージの利用者は必要な機能のみに集中でき、内部実装の変更が外部に影響を与えるリスクを減らすことができます。

Go言語のインターフェース

Go言語のインターフェースは、メソッドのシグネチャの集合を定義する型です。特定のインターフェースを実装する型は、そのインターフェースが定義するすべてのメソッドを持っている必要があります。インターフェースは、ポリモーフィズムを実現し、柔軟な設計を可能にします。

このコミットでは、Valueというインターフェースが_Valueに変更されました。これは、flagパッケージがコマンドライン引数の値を扱うために内部的に使用するインターフェースであり、外部に公開する必要がないと判断されたためです。

flagパッケージ

Go言語の標準ライブラリであるflagパッケージは、コマンドライン引数を解析するための機能を提供します。プログラムが起動される際に渡される-name=value形式の引数を、Goプログラム内で簡単に処理できるようにします。例えば、flag.String(), flag.Int(), flag.Bool()などの関数を使って、特定の型のフラグを定義し、flag.Parse()を呼び出すことで引数を解析します。

技術的詳細

このコミットの技術的な変更点は主に以下の2つです。

  1. Valueインターフェースから_Valueインターフェースへの名称変更と可視性の変更:

    • 元のコード: type Value interface { str() string; }
    • 変更後のコード: type _Value interface { str() string; }
    • この変更により、ValueインターフェースはGoの命名規則に従い、パッケージ外部からアクセスできないunexportedなインターフェース_Valueとなりました。これに伴い、Flag構造体のvalueフィールドの型、およびadd関数の引数の型もValueから_Valueに変更されています。
    • この変更は、flagパッケージのAPI設計におけるカプセル化を強化するものです。_Valueインターフェースはflagパッケージの内部実装の詳細となり、外部のコードがこのインターフェースに直接依存することを防ぎます。これにより、将来的にflagパッケージの内部実装が変更されても、外部のコードに影響を与える可能性が低くなります。
  2. allFlags構造体の初期化方法の変更:

    • 元のコードでは、New()というファクトリ関数を使ってallFlags構造体を初期化していました。
      func New() *allFlags {
          f := new(allFlags);
          f.first_arg = 1;    // 0 is the program name, 1 is first arg
          f.actual = make(map[string] *Flag);
          f.formal = make(map[string] *Flag);
          return f;
      }
      var flags *allFlags = New();
      
    • 変更後のコードでは、構造体リテラルを使ってallFlags構造体を直接初期化しています。
      var flags *allFlags = &allFlags{make(map[string] *Flag), make(map[string] *Flag), 1}
      
    • この変更は、初期化ロジックが非常に単純であるため、専用のファクトリ関数を削除し、より簡潔な記述に置き換えたものです。make(map[string] *Flag)でマップを初期化し、first_arg1を設定するという処理は、構造体リテラルで直接表現できます。これにより、コードの行数が減り、初期化の意図がより明確になります。

これらの変更は、Go言語の初期段階におけるAPI設計の洗練と、コードの簡潔性・保守性の向上を目指したものです。

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

--- a/src/lib/flag.go
+++ b/src/lib/flag.go
@@ -213,7 +213,7 @@ func (s *stringValue) str() string {
 }
 
 // -- Value interface
-type Value interface {
+type _Value interface {
 	str() string;
 }
 
@@ -221,25 +221,16 @@ export type Flag struct {
 	name	string;
 	usage	string;
-\tvalue\tValue;
+\tvalue\t_Value;
 }
 
 type allFlags struct {
 	actual map[string] *Flag;
 	formal map[string] *Flag;
-\tfirst_arg\tint;\
+\tfirst_arg\tint;\t// 0 is the program name, 1 is first arg
 }
 
-\
-func New() *allFlags {
-\tf := new(allFlags);\
-\tf.first_arg = 1;\t// 0 is the program name, 1 is first arg
-\tf.actual = make(map[string] *Flag);\
-\tf.formal = make(map[string] *Flag);\
-\treturn f;\
-}\
-\
-var flags *allFlags = New();
+var flags *allFlags = &allFlags{make(map[string] *Flag), make(map[string] *Flag), 1}
 
 export func PrintDefaults() {
 	for k, f := range flags.formal {
@@ -273,7 +264,7 @@ export func NArg() int {
 	return sys.argc() - flags.first_arg
 }
 
-func add(name string, value Value, usage string) {
+func add(name string, value _Value, usage string) {
 	f := new(Flag);\
 	f.name = name;\
 	f.usage = usage;\

コアとなるコードの解説

Valueインターフェースの名称変更

  • -type Value interface { から +type _Value interface {
    • これは、flagパッケージ内で使用されるValueインターフェースの名前を_Valueに変更したものです。Go言語の命名規則により、アンダースコアで始まる識別子(または小文字で始まる識別子)はunexported(非公開)となります。これにより、このインターフェースはflagパッケージの内部でのみ使用され、外部のパッケージからは直接参照できなくなります。これは、APIの安定性とカプセル化を向上させるための重要な変更です。

Flag構造体のvalueフィールドの型変更

  • -\tvalue\tValue; から +\tvalue\t_Value;
    • Flag構造体は、個々のコマンドラインフラグの情報を保持します。そのvalueフィールドは、フラグの値を表すインターフェース型です。Valueインターフェースが_Valueに変更されたため、このフィールドの型もそれに合わせて_Valueに変更されています。

allFlags構造体の初期化方法の変更

  • -func New() *allFlags { ... } および -var flags *allFlags = New();
    • 元のコードでは、allFlags構造体のインスタンスを生成し、初期化するためのNew()ファクトリ関数が定義されていました。そして、グローバル変数flagsがこのNew()関数を呼び出して初期化されていました。
  • +var flags *allFlags = &allFlags{make(map[string] *Flag), make(map[string] *Flag), 1}
    • 変更後のコードでは、New()関数が削除され、flags変数が構造体リテラルを使用して直接初期化されています。&allFlags{...}allFlags構造体の新しいインスタンスを生成し、そのポインタを返します。make(map[string] *Flag)actualformalマップを初期化し、1first_argフィールドに設定されます。この変更は、初期化ロジックが単純であるため、専用の関数を介さずに直接初期化することでコードを簡潔にする意図があります。

add関数の引数の型変更

  • -func add(name string, value Value, usage string) { から +func add(name string, value _Value, usage string) {
    • add関数は、新しいフラグをflagパッケージに登録するために使用される内部関数です。この関数のvalue引数も、Valueインターフェースが_Valueに変更されたことに伴い、型が_Valueに更新されています。

これらの変更は、Go言語の初期開発段階において、APIの設計原則(特にカプセル化と可視性)をより厳密に適用し、コードの簡潔性を追求した結果と言えます。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント (上記「関連リンク」に記載)
  • Gitのdiff形式に関する一般的な知識
  • Go言語の初期のコミット履歴と設計思想に関する一般的な理解