[インデックス 11372] ファイルの概要
このコミットは、Go言語の標準ライブラリgo/scanner
パッケージにおいて、スキャンモードを表す型を汎用的なuint
から、より明示的なscanner.Mode
型へと変更するリファクタリングを行っています。これにより、コードの可読性、型安全性、および保守性が向上しています。go/parser
パッケージ内の関連する箇所も、この新しい型定義に合わせて更新されています。
コミット
- コミットハッシュ:
8b60613b921364a3674f3ef4d7a373e87a9fa280
- 作者: Robert Griesemer gri@golang.org
- コミット日時: 2012年1月24日 火曜日 16:49:03 -0800
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/8b60613b921364a3674f3ef4d7a373e87a9fa280
元コミット内容
go/scanner: Use explicit scanner.Mode type.
R=r, bradfitz
CC=golang-dev
https://golang.org/cl/5574059
変更の背景
この変更の主な背景は、Go言語のgo/scanner
パッケージにおけるスキャンモードの表現を、よりセマンティックで型安全なものにすることです。以前は、スキャンモードは単なるuint
(符号なし整数)として扱われていました。uint
は非常に汎用的な型であり、その値が何を表しているのか、どのようなフラグが設定されているのかがコードを読むだけでは直感的に理解しにくいという問題がありました。
特定の目的のためにカスタム型を定義することで、以下の利点が得られます。
- 可読性の向上:
scanner.Mode
という型名自体が、その変数がスキャンモードに関するものであることを明確に示します。 - 型安全性:
scanner.Mode
型として定義することで、誤って他のuint
型の値(例えば、ファイルサイズやカウントなど)をスキャンモードとして渡してしまうようなバグを防ぐことができます。コンパイラが型ミスマッチを検出し、開発段階でエラーを特定できるようになります。 - 自己文書化: コード自体がその意図をより明確に表現するようになります。
- 将来的な拡張性:
Mode
型にメソッドを追加したり、関連する定数をグループ化したりする際に、より構造化された方法で管理できるようになります。
このコミットは、Go言語の標準ライブラリが、より堅牢で保守性の高いコードベースを目指すという一般的な方針に沿ったものです。
前提知識の解説
このコミットを理解するためには、以下のGo言語の概念とパッケージに関する知識が必要です。
-
go/scanner
パッケージ:- Go言語のソースコードを字句解析(トークン化)するためのパッケージです。ソースコードの文字列を入力として受け取り、キーワード、識別子、演算子、リテラルなどの個々の「トークン」に分解します。
Scanner
構造体は、この字句解析の主要なコンポーネントであり、ソースコードの読み込み、現在の文字の追跡、次のトークンの生成などのロジックを含んでいます。Mode
は、スキャナーの動作を制御するためのフラグの集合です。例えば、コメントをトークンとして返すかどうかなどを指定できます。
-
go/parser
パッケージ:go/scanner
によって生成されたトークンストリームを受け取り、Go言語の構文木(AST: Abstract Syntax Tree)を構築するためのパッケージです。parser
構造体は、構文解析の主要なコンポーネントです。
-
token
パッケージ:- Go言語のソースコードを構成するトークン(識別子、キーワード、演算子など)とその位置情報(ファイル、行、列)を定義するパッケージです。
token.FileSet
は、複数のソースファイルにわたる位置情報を管理するための構造体です。
-
型定義 (
type
キーワード):- Go言語では、既存の型(プリミティブ型や構造体など)に基づいて新しい型を定義できます。
type NewType UnderlyingType
の形式で定義され、NewType
はUnderlyingType
と同じ基底型を持ちますが、コンパイラにとっては異なる型として扱われます。これにより、型安全性が向上します。
-
ビットフラグと
iota
:ScanComments
やdontInsertSemis
のような定数は、ビットフラグとして使用されます。これは、複数のブール値の状態を単一の整数値で表現する効率的な方法です。各フラグは、整数の特定のビットに対応します。iota
は、Go言語のconst
宣言内で使用される特別な識別子で、連続する整数値を自動的に生成します。1 << iota
の形式で使うと、1
、2
、4
、8
...といった2のべき乗の値を生成し、ビットフラグの定義に非常に便利です。
const ( FlagA = 1 << iota // 1 (0001) FlagB // 2 (0010) FlagC // 4 (0100) )
このコミットでは、
Mode = 1 << iota
とすることで、ScanComments
とdontInsertSemis
がMode
型を持つビットフラグとして定義されています。
技術的詳細
このコミットの技術的な核心は、Go言語の型システムを活用して、特定の目的を持つ整数値をより厳密に扱うようにした点にあります。
以前のコードでは、go/scanner
パッケージのScanner
構造体のmode
フィールドや、Init
関数のmode
引数、そしてgo/parser
パッケージのparser
構造体内のm
変数などが、すべて汎用的なuint
型として宣言されていました。
// 変更前 (src/pkg/go/scanner/scanner.go)
type Scanner struct {
// ...
mode uint // scanning mode
// ...
}
func (S *Scanner) Init(file *token.File, src []byte, err ErrorHandler, mode uint) {
// ...
}
// 変更前 (src/pkg/go/parser/parser.go)
func (p *parser) init(fset *token.FileSet, filename string, src []byte, mode Mode) {
// ...
var m uint // ここもuint
// ...
}
このコミットでは、src/pkg/go/scanner/scanner.go
に新しい型Mode
が導入されました。
// 変更後 (src/pkg/go/scanner/scanner.go)
type Mode uint
このMode
型はuint
を基底型としていますが、Goコンパイラにとってはuint
とは異なる型として扱われます。これにより、Scanner
のmode
フィールドやInit
関数の引数、そしてparser
内の関連変数をこの新しいMode
型に置き換えることで、コンパイラが型チェックをより厳密に行うようになります。
また、ScanComments
やdontInsertSemis
といったスキャンモードの定数も、この新しいMode
型を持つように変更されました。
// 変更後 (src/pkg/go/scanner/scanner.go)
const (
ScanComments Mode = 1 << iota // return comments as COMMENT tokens
dontInsertSemis // do not automatically insert semicolons - for testing only
)
これにより、これらの定数がscanner.Mode
型の値として扱われることが明確になり、uint
型の変数に誤って代入されることを防ぎます。
この変更は、機能的な振る舞いを変更するものではなく、コードの内部的な健全性と保守性を向上させるための純粋なリファクタリングです。Go言語の設計思想である「明示性」と「シンプルさ」に沿った改善と言えます。
コアとなるコードの変更箇所
このコミットで変更された主要なファイルとコードの変更箇所は以下の通りです。
src/pkg/go/parser/parser.go
--- a/src/pkg/go/parser/parser.go
+++ b/src/pkg/go/parser/parser.go
@@ -54,7 +54,7 @@ type parser struct {
func (p *parser) init(fset *token.FileSet, filename string, src []byte, mode Mode) {
p.file = fset.AddFile(filename, fset.Base(), len(src))
- var m uint
+ var m scanner.Mode
if mode&ParseComments != 0 {
m = scanner.ScanComments
}
var m uint
がvar m scanner.Mode
に変更されました。parser
のinit
関数内で使用されるローカル変数m
の型が、scanner
パッケージで定義された新しいMode
型に更新されています。
src/pkg/go/scanner/scanner.go
--- a/src/pkg/go/scanner/scanner.go
+++ b/src/pkg/go/scanner/scanner.go
@@ -40,7 +40,7 @@ type Scanner struct {
dir string // directory portion of file.Name()
src []byte // source
err ErrorHandler // error reporting; or nil
- mode uint // scanning mode
+ mode Mode // scanning mode
// scanning state
ch rune // current character
@@ -86,12 +86,14 @@ func (S *Scanner) next() {
}
}
-// The mode parameter to the Init function is a set of flags (or 0).
+// A mode value is set of flags (or 0).
// They control scanner behavior.
//
+type Mode uint
+
const (
-\tScanComments = 1 << iota // return comments as COMMENT tokens
-\tdontInsertSemis // do not automatically insert semicolons - for testing only
+\tScanComments Mode = 1 << iota // return comments as COMMENT tokens
+\tdontInsertSemis // do not automatically insert semicolons - for testing only
)
// Init prepares the scanner S to tokenize the text src by setting the
@@ -109,7 +111,7 @@ const (
// Note that Init may call err if there is an error in the first character
// of the file.
//
-func (S *Scanner) Init(file *token.File, src []byte, err ErrorHandler, mode uint) {
+func (S *Scanner) Init(file *token.File, src []byte, err ErrorHandler, mode Mode) {
// Explicitly initialize all fields since a scanner may be reused.
if file.Size() != len(src) {
panic("file size does not match src len")
Scanner
構造体のmode
フィールドの型がuint
からMode
に変更されました。type Mode uint
という新しい型定義が追加されました。ScanComments
とdontInsertSemis
の定数定義がMode = 1 << iota
の形式に変更され、明示的にMode
型を持つようになりました。Scanner.Init
関数のmode
引数の型がuint
からMode
に変更されました。
src/pkg/go/scanner/scanner_test.go
--- a/src/pkg/go/scanner/scanner_test.go
+++ b/src/pkg/go/scanner/scanner_test.go
@@ -281,7 +281,7 @@ func TestScan(t *testing.T) {
}
}
-func checkSemi(t *testing.T, line string, mode uint) {
+func checkSemi(t *testing.T, line string, mode Mode) {
var S Scanner
file := fset.AddFile("TestSemis", fset.Base(), len(line))
S.Init(file, []byte(line), nil, mode)
checkSemi
テスト関数のmode
引数の型がuint
からMode
に変更されました。これは、scanner.Init
関数のシグネチャ変更に合わせたものです。
コアとなるコードの解説
このコミットのコアとなる変更は、src/pkg/go/scanner/scanner.go
におけるtype Mode uint
の導入と、それに伴う関連箇所の型変更です。
-
type Mode uint
の導入:- これは、Go言語の型エイリアス(または基底型を持つ新しい型定義)の典型的な使用例です。
Mode
は内部的にはuint
と同じビット表現を持ちますが、コンパイラはMode
とuint
を異なる型として扱います。 - これにより、
Scanner
の動作モードを表す値が、単なる汎用的な整数ではなく、「スキャナーのモード」という特定の意味を持つことがコード上で明確になります。
- これは、Go言語の型エイリアス(または基底型を持つ新しい型定義)の典型的な使用例です。
-
Scanner
構造体フィールドの型変更:type Scanner struct { ... mode uint ... }
からtype Scanner struct { ... mode Mode ... }
への変更は、Scanner
インスタンスが保持するモード情報が、定義されたMode
型に厳密に従うことを保証します。これにより、Scanner
の内部状態の整合性が向上します。
-
定数定義の型付け:
const ( ScanComments = 1 << iota ... )
からconst ( ScanComments Mode = 1 << iota ... )
への変更は重要です。これにより、ScanComments
やdontInsertSemis
といった定数が、コンパイル時にMode
型として扱われるようになります。- 以前は、これらの定数は型なしの整数定数として扱われ、
uint
型の変数に代入される際に暗黙的に型付けされていました。明示的にMode
型を割り当てることで、これらの定数がscanner.Mode
の文脈でのみ使用されるべきであることが強調されます。
-
関数シグネチャの変更:
func (S *Scanner) Init(..., mode uint)
からfunc (S *Scanner) Init(..., mode Mode)
への変更は、Scanner
の初期化時に渡されるモード引数が、必ずMode
型であることを要求します。これにより、Init
関数の呼び出し元が、意図しない型の値を渡すことを防ぎ、APIの利用方法を明確にします。- 同様に、
go/parser
のinit
関数やscanner_test.go
のcheckSemi
関数におけるmode
引数の型もscanner.Mode
に変更されており、パッケージ間の整合性が保たれています。
これらの変更は、Go言語の型システムをより効果的に利用し、コードの意図を明確にし、潜在的なバグをコンパイル時に検出できるようにするためのベストプラクティスを反映しています。
関連リンク
- Go言語の
go/scanner
パッケージのドキュメント: https://pkg.go.dev/go/scanner - Go言語の
go/parser
パッケージのドキュメント: https://pkg.go.dev/go/parser - Go言語の
token
パッケージのドキュメント: https://pkg.go.dev/go/token - Go言語の
iota
に関する公式ドキュメント(Effective Goより): https://go.dev/doc/effective_go#iota - このコミットが参照しているGoの変更リスト (CL): https://go.dev/cl/5574059
参考にした情報源リンク
- 上記のGo言語公式ドキュメント
- Go言語における型定義と型エイリアスに関する一般的な情報源(Go言語の書籍やチュートリアルなど)
- ビットフラグと
iota
のGo言語での使用例に関する一般的な情報源