[インデックス 10746] ファイルの概要
このコミットは、Go言語の標準ライブラリであるstrconv
パッケージにおけるエラーメッセージの改善を目的としています。具体的には、数値変換エラー(NumError
)が発生した際に、エラー文字列にどのstrconv
パッケージの関数(例: ParseInt
, ParseUint
, ParseFloat
)でエラーが発生したかを含めるように変更されています。これにより、エラーの発生源をより明確にし、デバッグを容易にすることが意図されています。
コミット
commit 02f6719d44a0a70f0fddfa3228ca9225f06d766c
Author: Rob Pike <r@golang.org>
Date: Tue Dec 13 10:42:05 2011 -0800
strconv: include package and function name in error strings
Fixes #2548.
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/5484062
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/02f6719d44a0a70f0fddfa3228ca9225f06d766c
元コミット内容
strconv: include package and function name in error strings
Fixes #2548.
このコミットは、strconv
パッケージのエラー文字列にパッケージ名と関数名を含めるように変更します。これはIssue #2548を修正するものです。
変更の背景
Go言語のstrconv
パッケージは、文字列と数値の相互変換を行うための機能を提供します。例えば、ParseInt
は文字列を整数に、ParseFloat
は文字列を浮動小数点数に変換します。これらの変換処理中に、入力文字列が不正な形式であったり、変換結果が指定された型や範囲に収まらない場合、エラーが発生します。
このコミット以前は、strconv
パッケージから返されるエラーメッセージは、エラーの原因(例: invalid syntax
、value out of range
)と不正な入力文字列は示していましたが、どのstrconv
関数がエラーを発生させたのかが明確ではありませんでした。
例えば、strconv.ParseInt("foo", 10, 64)
とstrconv.ParseUint("bar", 10, 64)
の両方がparsing "..." invalid syntax
のようなエラーを返す場合、エラーメッセージだけではどちらの関数が問題を引き起こしたのかを特定するのが困難でした。特に、複雑なアプリケーションでは、複数の場所でstrconv
関数が使用されるため、エラーのデバッグや原因特定に時間がかかる可能性がありました。
この問題を解決するため、エラーメッセージにパッケージ名(strconv
)とエラーを発生させた関数名(例: ParseInt
、ParseUint
)を含めることで、エラーの発生源をより具体的にし、開発者が問題を迅速に特定・解決できるようにすることが、この変更の背景にあります。これは、Go言語のエラーハンドリングの原則である「エラーは詳細であるべき」という考え方にも合致しています。
前提知識の解説
このコミットを理解するためには、以下のGo言語の基本的な概念とstrconv
パッケージの知識が必要です。
Go言語のエラーハンドリング
Go言語では、エラーは通常、関数の最後の戻り値としてerror
インターフェース型で返されます。error
インターフェースは、Error() string
という単一のメソッドを持つシンプルなインターフェースです。このメソッドは、エラーに関する説明的な文字列を返します。
type error interface {
Error() string
}
開発者は、カスタムエラー型を定義することで、より詳細なエラー情報を提供できます。カスタムエラー型は、error
インターフェースを実装する任意の構造体です。
strconv
パッケージ
strconv
パッケージは、文字列と基本的なデータ型(ブール値、整数、浮動小数点数)の間で変換を行うための関数を提供します。主な関数には以下のようなものがあります。
ParseBool(s string) (bool, error)
: 文字列をブール値に変換します。ParseInt(s string, base int, bitSize int) (int64, error)
: 文字列を符号付き整数に変換します。base
は基数(2から36)、bitSize
は結果のビットサイズ(0, 8, 16, 32, 64)を指定します。ParseUint(s string, base int, bitSize int) (uint64, error)
: 文字列を符号なし整数に変換します。ParseFloat(s string, bitSize int) (float64, error)
: 文字列を浮動小数点数に変換します。bitSize
は結果のビットサイズ(32, 64)を指定します。
strconv.NumError
型
strconv
パッケージの変換関数が失敗した場合、通常は*strconv.NumError
型のエラーを返します。このNumError
構造体は、変換が失敗した原因に関する情報を含んでいます。
このコミット以前のNumError
の定義は以下のようでした。
type NumError struct {
Num string // the input string that caused the error
Err error // the reason the conversion failed (ErrRange, ErrSyntax)
}
そして、そのError()
メソッドは、parsing "入力文字列": エラー原因
のような形式の文字列を返していました。
ErrSyntax
: 入力文字列が正しい構文ではない場合に発生します(例: "abc"を整数に変換しようとした場合)。ErrRange
: 変換結果が指定された型や範囲に収まらない場合に発生します(例: "256"をuint8
に変換しようとした場合)。
このコミットは、このNumError
構造体とError()
メソッドの挙動を変更し、エラーメッセージにコンテキストを追加します。
技術的詳細
このコミットの技術的な核心は、strconv.NumError
構造体の変更と、エラー生成ヘルパー関数の導入、そして既存のstrconv
関数からのエラー生成箇所の更新です。
-
NumError
構造体の変更:NumError
構造体にFunc string
フィールドが追加されました。このフィールドは、エラーを発生させたstrconv
パッケージ内の関数名(例: "ParseInt", "ParseUint", "ParseFloat", "ParseBool")を格納するために使用されます。変更前:
type NumError struct { Num string // the input Err error // the reason the conversion failed (ErrRange, ErrSyntax) }
変更後:
type NumError struct { Func string // the failing function (ParseBool, ParseInt, ParseUint, ParseFloat) Num string // the input Err error // the reason the conversion failed (ErrRange, ErrSyntax) }
-
NumError.Error()
メソッドの変更:NumError
のError()
メソッドが変更され、Func
フィールドの値がエラー文字列の先頭に追加されるようになりました。これにより、エラーメッセージはstrconv.関数名: parsing "入力文字列": エラー原因
という形式になります。変更前:
func (e *NumError) Error() string { return `parsing "` + e.Num + `": ` + e.Err.Error() }
変更後:
func (e *NumError) Error() string { return "strconv." + e.Func + ": " + `parsing "` + e.Num + `": ` + e.Err.Error() }
この変更により、例えば
strconv.ParseUint
が範囲外エラーを返した場合、エラーメッセージはstrconv.ParseUint: parsing "256": value out of range
のようになります。 -
エラー生成ヘルパー関数の導入: エラー生成を簡素化し、一貫性を保つために、以下の2つのヘルパー関数が
strconv/atoi.go
に導入されました。syntaxError(fn, str string) *NumError
: 構文エラー(ErrSyntax
)を表すNumError
を生成します。rangeError(fn, str string) *NumError
: 範囲エラー(ErrRange
)を表すNumError
を生成します。
func syntaxError(fn, str string) *NumError { return &NumError{fn, str, ErrSyntax} } func rangeError(fn, str string) *NumError { return &NumError{fn, str, ErrRange} }
これらのヘルパー関数は、
NumError
のFunc
フィールドに適切な関数名を自動的に設定します。 -
既存の
strconv
関数の更新:strconv
パッケージ内のParseBool
,ParseInt
,ParseUint
,ParseFloat
などの関数で、エラーを生成する箇所が、新しいNumError
構造体とヘルパー関数を使用するように更新されました。これにより、これらの関数が返すエラーには、常に適切な関数名が含まれるようになります。例えば、
ParseUint
では&NumError{s0, err}
が&NumError{"ParseUint", s0, err}
に、ParseInt
では&NumError{s, ErrSyntax}
がsyntaxError(fnParseInt, s)
に置き換えられています。また、ParseInt
のように内部でParseUint
を呼び出す関数では、ParseUint
から返されたNumError
のFunc
フィールドをParseInt
に上書きする処理も追加されています。 -
テストコードの更新: エラーメッセージの形式が変更されたため、
strconv
パッケージのテストコード(特にatof_test.go
,atoi_test.go
,exp/sql/convert_test.go
)も、新しいエラー文字列の形式に合うように更新されました。これにより、テストが引き続き正しく機能し、新しいエラー形式が期待通りであることを保証します。
これらの変更により、strconv
パッケージから返されるエラーは、より情報豊富でデバッグしやすいものになりました。
コアとなるコードの変更箇所
このコミットで変更された主要なファイルとコードスニペットは以下の通りです。
src/pkg/strconv/atoi.go
NumError
構造体の定義が変更され、Func
フィールドが追加されました。
Error()
メソッドが変更され、Func
フィールドの値がエラー文字列に含まれるようになりました。
syntaxError
とrangeError
ヘルパー関数が追加されました。
ParseUint
およびParseInt
関数内でNumError
を生成する箇所が更新されました。
--- a/src/pkg/strconv/atoi.go
+++ b/src/pkg/strconv/atoi.go
@@ -14,11 +14,22 @@ var ErrSyntax = errors.New("invalid syntax")
// A NumError records a failed conversion.
type NumError struct {
- Num string // the input
- Err error // the reason the conversion failed (ErrRange, ErrSyntax)
+ Func string // the failing function (ParseBool, ParseInt, ParseUint, ParseFloat)
+ Num string // the input
+ Err error // the reason the conversion failed (ErrRange, ErrSyntax)
}
-func (e *NumError) Error() string { return `parsing "` + e.Num + `": ` + e.Err.Error() }
+func (e *NumError) Error() string {
+ return "strconv." + e.Func + ": " + `parsing "` + e.Num + `": ` + e.Err.Error()
+}
+
+func syntaxError(fn, str string) *NumError {
+ return &NumError{fn, str, ErrSyntax}
+}
+
+func rangeError(fn, str string) *NumError {
+ return &NumError{fn, str, ErrRange}
+}
const intSize = 32 << uint(^uint(0)>>63)
@@ -116,7 +127,7 @@ func ParseUint(s string, b int, bitSize int) (n uint64, err error) {
return n, nil
Error:
- return n, &NumError{s0, err}
+ return n, &NumError{"ParseUint", s0, err}
}
// ParseInt interprets a string s in the given base (2 to 36) and
@@ -134,13 +145,15 @@ Error:
// to s cannot be represented by a signed integer of the
// given size, err.Error = ErrRange.
func ParseInt(s string, base int, bitSize int) (i int64, err error) {
+ const fnParseInt = "ParseInt"
+
if bitSize == 0 {
bitSize = int(IntSize)
}
// Empty string bad.
if len(s) == 0 {
- return 0, &NumError{s, ErrSyntax}
+ return 0, syntaxError(fnParseInt, s)
}
// Pick off leading sign.
@@ -157,15 +170,16 @@ func ParseInt(s string, base int, bitSize int) (i int64, err error) {
var un uint64
un, err = ParseUint(s, base, bitSize)
if err != nil && err.(*NumError).Err != ErrRange {
+ err.(*NumError).Func = fnParseInt
err.(*NumError).Num = s0
return 0, err
}
cutoff := uint64(1 << uint(bitSize-1))
if !neg && un >= cutoff {
- return int64(cutoff - 1), &NumError{s0, ErrRange}
+ return int64(cutoff - 1), rangeError(fnParseInt, s0)
}
if neg && un > cutoff {
- return -int64(cutoff), &NumError{s0, ErrRange}
+ return -int64(cutoff), rangeError(fnParseInt, s0)
}
n := int64(un)
if neg {
src/pkg/strconv/atob.go
ParseBool
関数内でNumError
を生成する箇所が更新されました。
--- a/src/pkg/strconv/atob.go
+++ b/src/pkg/strconv/atob.go
@@ -14,7 +14,7 @@ func ParseBool(str string) (value bool, err error) {
case "0", "f", "F", "false", "FALSE", "False":
return false, nil
}\n- return false, &NumError{str, ErrSyntax}\n+ return false, syntaxError("ParseBool", str)\n }\n \n // FormatBool returns "true" or "false" according to the value of b
src/pkg/strconv/atof.go
atof32
およびatof64
関数内でNumError
を生成する箇所が更新されました。
--- a/src/pkg/strconv/atof.go
+++ b/src/pkg/strconv/atof.go
@@ -338,6 +338,8 @@ func (d *decimal) atof32() (f float32, ok bool) {
return
}
+const fnParseFloat = "ParseFloat"
+
func atof32(s string) (f float32, err error) {
if val, ok := special(s); ok {
return float32(val), nil
@@ -345,7 +347,7 @@ func atof32(s string) (f float32, err error) {
var d decimal
if !d.set(s) {
- return 0, &NumError{s, ErrSyntax}
+ return 0, syntaxError(fnParseFloat, s)
}
if optimize {
if f, ok := d.atof32(); ok {
@@ -355,7 +357,7 @@ func atof32(s string) (f float32, err error) {
b, ovf := d.floatBits(&float32info)
f = math.Float32frombits(uint32(b))
if ovf {
- err = &NumError{s, ErrRange}
+ err = rangeError(fnParseFloat, s)
}
return f, err
}
@@ -367,7 +369,7 @@ func atof64(s string) (f float64, err error) {
var d decimal
if !d.set(s) {
- return 0, &NumError{s, ErrSyntax}
+ return 0, syntaxError(fnParseFloat, s)
}
if optimize {
if f, ok := d.atof64(); ok {
@@ -377,7 +379,7 @@ func atof64(s string) (f float64, err error) {
b, ovf := d.floatBits(&float64info)
f = math.Float64frombits(b)
if ovf {
- err = &NumError{s, ErrRange}
+ err = rangeError(fnParseFloat, s)
}
return f, err
}
テストファイルの変更
src/pkg/exp/sql/convert_test.go
, src/pkg/strconv/atof_test.go
, src/pkg/strconv/atoi_test.go
では、期待されるエラー文字列が新しい形式に合わせて更新されています。
例: src/pkg/exp/sql/convert_test.go
--- a/src/pkg/exp/sql/convert_test.go
+++ b/src/pkg/exp/sql/convert_test.go
@@ -55,10 +55,10 @@ var conversionTests = []conversionTest{
// Strings to integers
{s: "255", d: &scanuint8, wantuint: 255},
- {s: "256", d: &scanuint8, wanterr: `converting string "256" to a uint8: parsing "256": value out of range`},
+ {s: "256", d: &scanuint8, wanterr: `converting string "256" to a uint8: strconv.ParseUint: parsing "256": value out of range`},
{s: "256", d: &scanuint16, wantuint: 256},
{s: "-1", d: &scanint, wantint: -1},
- {s: "foo", d: &scanint, wanterr: `converting string "foo" to a int: parsing "foo": invalid syntax`},
+ {s: "foo", d: &scanint, wanterr: `converting string "foo" to a int: strconv.ParseInt: parsing "foo": invalid syntax`},
// True bools
{s: true, d: &scanbool, wantbool: true},
コアとなるコードの解説
NumError
構造体の変更とError()
メソッド
NumError
構造体にFunc string
フィールドが追加されたことで、エラーが発生したstrconv
関数名を格納できるようになりました。これは、エラーメッセージにコンテキストを追加するための最も重要な変更点です。
Error()
メソッドの変更は、このFunc
フィールドを活用して、エラーメッセージのフォーマットをstrconv.関数名: parsing "入力文字列": エラー原因
という形式に統一します。これにより、エラーログやデバッグ時に、どのstrconv
関数が問題を引き起こしたのかが一目でわかるようになります。例えば、以前は単にparsing "256": value out of range
と表示されていたエラーが、strconv.ParseUint: parsing "256": value out of range
と表示されるようになります。
syntaxError
とrangeError
ヘルパー関数
これらのヘルパー関数は、NumError
インスタンスを生成する際のボイラープレートコードを削減し、エラー生成の一貫性を保証します。strconv
パッケージ内の複数の場所でエラーが生成されるため、これらのヘルパー関数を使用することで、コードの重複を避け、将来的なエラーメッセージのフォーマット変更にも柔軟に対応できるようになります。
例えば、以前はreturn 0, &NumError{s, ErrSyntax}
のように直接NumError
を生成していましたが、新しいコードではreturn 0, syntaxError(fnParseFloat, s)
のようにヘルパー関数を呼び出すことで、より簡潔かつ意図が明確なコードになります。また、Func
フィールドに適切な関数名が自動的に設定されるため、開発者が手動で設定する手間が省け、ミスも減ります。
ParseInt
におけるFunc
フィールドの上書き
ParseInt
関数は内部でParseUint
を呼び出すことがあります。ParseUint
がエラーを返した場合、そのエラーは*NumError
型であり、そのFunc
フィールドは"ParseUint"
に設定されています。しかし、ユーザーが直接呼び出したのはParseInt
であるため、エラーメッセージにはParseInt
が原因であることが示されるべきです。
このため、ParseInt
内でParseUint
から返されたエラーがNumError
型であり、かつErrRange
以外のエラーである場合に、err.(*NumError).Func = fnParseInt
という行でFunc
フィールドを"ParseInt"
に上書きしています。これにより、エラーメッセージがユーザーの期待に沿ったものになります。
この変更は、Go言語のエラーハンドリングにおける「エラーはコンテキストを持つべきである」という原則を具体的に示しています。エラーメッセージに詳細なコンテキストを含めることで、開発者は問題をより迅速に診断し、解決することができます。
関連リンク
- Go言語の
strconv
パッケージのドキュメント: https://pkg.go.dev/strconv - Go言語のエラーハンドリングに関する公式ブログ記事(古いものですが、概念は共通): https://go.dev/blog/error-handling-and-go
- このコミットが修正したIssue: https://github.com/golang/go/issues/2548
参考にした情報源リンク
- Go言語の公式ドキュメント
- GitHubのGoリポジトリのコミット履歴とIssueトラッカー
- Go言語のエラーハンドリングに関する一般的な知識
strconv
パッケージのソースコード分析