[インデックス 10252] ファイルの概要
このコミットは、Go言語の標準ライブラリにおいて、エラーを返すメソッドの命名規則を統一することを目的としています。具体的には、レシーバに以前関連付けられたエラーを返すメソッドについて、その名前を Error
から Err
へと変更する新しい慣習を導入し、既存のコードベースに適用しています。これにより、エラー処理のAPIがより明確になり、Go言語のイディオムに沿ったコード記述が促進されます。
コミット
commit f2dc50b48d011d4d585d09d5e6bed350894add3d
Author: Gustavo Niemeyer <gustavo@niemeyer.net>
Date: Fri Nov 4 09:50:20 2011 -0400
html,bzip2,sql: rename Error methods that return error to Err
There are three classes of methods/functions called Error:
a) The Error method in the just introduced error interface
b) Error methods that create or report errors (http.Error, etc)
c) Error methods that return errors previously associated with
the receiver (Tokenizer.Error, rows.Error, etc).
This CL introduces the convention that methods in case (c)
should be named Err.
The reasoning for the change is:
- The change differentiates the two kinds of APIs based on
names rather than just on signature, unloading Error a bit
- Err is closer to the err variable name that is so commonly
used with the intent of verifying an error
- Err is shorter and thus more convenient to be used often
on error verifications, such as in iterators following the
convention of the sql package.
R=bradfitz, rsc
CC=golang-dev
https://golang.org/cl/5327064
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/f2dc50b48d011d4d585d09d5e6bed350894add3d
元コミット内容
html,bzip2,sql: rename Error methods that return error to Err
There are three classes of methods/functions called Error:
a) The Error method in the just introduced error interface
b) Error methods that create or report errors (http.Error, etc)
c) Error methods that return errors previously associated with
the receiver (Tokenizer.Error, rows.Error, etc).
This CL introduces the convention that methods in case (c)
should be named Err.
The reasoning for the change is:
- The change differentiates the two kinds of APIs based on
names rather than just on signature, unloading Error a bit
- Err is closer to the err variable name that is so commonly
used with the intent of verifying an error
- Err is shorter and thus more convenient to be used often
on error verifications, such as in iterators following the
convention of the sql package.
R=bradfitz, rsc
CC=golang-dev
https://golang.org/cl/5327064
変更の背景
Go言語では、エラーハンドリングは例外処理ではなく、戻り値としてエラーを明示的に返すというイディオムが採用されています。このコミットが行われた2011年当時、Go言語のエラー処理に関する慣習はまだ進化の途中にありました。
コミットメッセージによると、当時 Error
という名前を持つメソッドや関数には大きく分けて3つの種類が存在していました。
a) 導入されたばかりの error
インターフェースの Error
メソッド。これはエラーオブジェクト自身がエラーメッセージを文字列として返すための標準的なメソッドです。
b) http.Error
のように、新しいエラーを作成したり、エラーを報告したりするメソッド。
c) Tokenizer.Error
や rows.Error
のように、レシーバ(オブジェクト)に以前関連付けられたエラーを返すメソッド。
この状況では、Error
という名前が多義的であり、コードの可読性や意図の明確さに課題がありました。特に、(a)の error
インターフェースの Error
メソッドと、(c)のレシーバに紐づくエラーを返すメソッドが同じ Error
という名前を持つことで、混乱が生じる可能性がありました。
このコミットは、(c)のケース、つまり「レシーバに以前関連付けられたエラーを返すメソッド」について、その名前を Err
に変更するという新しい命名規則を導入することで、この多義性を解消し、APIの意図をより明確にすることを目的としています。
変更の主な理由は以下の通りです。
- APIの区別: シグネチャだけでなく、名前によって異なる種類のエラーAPIを区別できるようになり、
Error
という名前の負担を軽減します。 err
変数との親和性: Go言語ではエラーをチェックする際にif err != nil
のようにerr
という変数名が一般的に使われます。Err
という名前は、このerr
変数と視覚的にも概念的にも近く、エラー検証の意図をより明確にします。- 簡潔性と利便性:
Err
はError
よりも短く、sql
パッケージのイテレータのように頻繁にエラー検証が行われる場面で、より簡潔で便利な記述を可能にします。
これらの変更により、Go言語のエラーハンドリングの慣習がより洗練され、開発者がより一貫性のある、読みやすいコードを書けるようになることが期待されました。
前提知識の解説
Go言語のエラーハンドリングの基本
Go言語は、エラーハンドリングにおいて例外(exception)を使用せず、関数がエラーを返す場合は、戻り値として明示的に error
型の値を返すという設計思想を持っています。
-
エラーを戻り値として返す: Goの関数は、通常、結果とエラーの2つの値を返します。エラーがない場合は
nil
を返します。func doSomething() (resultType, error) { // ... 処理 ... if somethingWentWrong { return zeroValue, errors.New("something went wrong") } return actualResult, nil }
-
error
インターフェース: Goの組み込み型であるerror
はインターフェースであり、以下のように定義されています。type error interface { Error() string }
このインターフェースを実装する任意の型がエラーとして扱われます。
Error()
メソッドは、エラーの文字列表現を返します。 -
エラーのチェック: 関数呼び出し後には、
if err != nil
という形でエラーの有無をチェックするのがGoのイディオムです。result, err := doSomething() if err != nil { // エラー処理 log.Printf("Error: %v", err) return } // 正常処理 fmt.Println(result)
Error()
メソッドの多義性(変更前)
このコミットが行われる前は、Error()
という名前のメソッドが複数の異なる役割で使用されていました。
error
インターフェースのError()
: これはエラーオブジェクト自体がその内容を文字列として表現するためのメソッドです。例えば、errors.New("some error").Error()
は"some error"
という文字列を返します。- エラーを生成/報告する関数/メソッド:
http.Error(w, "Bad Request", http.StatusBadRequest)
のように、エラーを生成したり、HTTPレスポンスとしてエラーを報告したりする関数です。これらは通常、error
型の値を返しません。 - レシーバに紐づくエラーを返すメソッド:
Tokenizer.Error()
やrows.Error()
のように、特定のオブジェクト(レシーバ)の内部状態として保持されているエラーを返すメソッドです。これらのメソッドはerror
型の値を返します。
この3番目のケースが、error
インターフェースの Error()
メソッドと名前が衝突し、混乱を招く可能性がありました。例えば、someErrorVar.Error()
と someTokenizer.Error()
はどちらも Error()
という名前ですが、前者はエラーオブジェクト自身のメッセージを返し、後者はトークナイザの処理中に発生したエラーを返します。このコミットは、この曖昧さを解消するために、3番目のケースのメソッド名を Err()
に変更することを提案し、実装しました。
技術的詳細
このコミットの技術的な核心は、Go言語の標準ライブラリにおけるメソッド命名規則の変更と、それを既存のコードベースに適用するための gofix
ツールの活用にあります。
新しい命名規則 Err
の導入
コミットメッセージで述べられているように、Go言語のAPIにおいて Error
という名前が持つ多義性を解消するため、以下の新しい慣習が導入されました。
error
インターフェースのError()
メソッド: これは引き続きError()
という名前を使用します。これはエラーオブジェクト自身がその内容を文字列として表現するための標準的な方法です。- レシーバに以前関連付けられたエラーを返すメソッド: これらのメソッドは
Error()
ではなくErr()
という名前を使用するようになります。これにより、メソッドが「レシーバの内部状態として保持されているエラーを返す」という明確な意図が名前から読み取れるようになります。
この変更は、特にイテレータやストリーム処理など、繰り返し処理の中でエラー状態をチェックするパターンにおいて、コードの可読性と簡潔性を向上させます。例えば、sql
パッケージの Rows
オブジェクトで、イテレーション中に発生したエラーを取得する際に rows.Error()
ではなく rows.Err()
と記述することで、より自然なGoのイディオムに近づきます。err
という変数名がGoでエラーを扱う際の一般的な慣習であるため、Err()
はこの err
との関連性を視覚的にも強調します。
gofix
ツールの活用
Go言語には、古いAPIから新しいAPIへの移行を自動化するための gofix
というツールが存在します。このコミットでは、html
、bzip2
、sql
パッケージ内の該当する Error
メソッドを Err
にリネームするために、この gofix
ツールが利用されました。
gofix
は、Goのソースコードを解析し、定義された変換ルールに基づいてコードを自動的に書き換えることができます。このコミットでは、src/cmd/gofix/htmlerr.go
という新しいファイルが追加されており、これが html.Tokenizer.Error
メソッドを Err
にリネームするための gofix
ルールを定義しています。
htmlerr.go
の主要な部分は以下の通りです。
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"go/ast"
)
func init() {
register(htmlerrFix)
}
var htmlerrFix = fix{
"htmlerr",
"2011-11-04",
htmlerr,
`Rename html's Tokenizer.Error method to Err.
http://codereview.appspot.com/5327064/
`,
}
var htmlerrTypeConfig = &TypeConfig{
Func: map[string]string{
"html.NewTokenizer": "html.Tokenizer",
},
}
func htmlerr(f *ast.File) bool {
if !imports(f, "html") {
return false
}
typeof, _ := typecheck(htmlerrTypeConfig, f)
fixed := false
walk(f, func(n interface{}) {
s, ok := n.(*ast.SelectorExpr)
if ok && typeof[s.X] == "html.Tokenizer" && s.Sel.Name == "Error" {
s.Sel.Name = "Err"
fixed = true
}
})
return fixed
}
この htmlerr
関数は、Goの抽象構文木(AST)を走査し、html.Tokenizer
型のレシーバを持つ Error
メソッドの呼び出しを見つけると、そのメソッド名を Err
に変更します。gofix
を使用することで、手動での大規模なリネーム作業を避け、Goコミュニティ全体で新しい慣習への移行をスムーズに行うことが可能になります。
このアプローチは、Go言語がAPIの変更を行う際に、後方互換性を保ちつつ、既存のコードベースの更新を支援するための強力なメカニズムを提供していることを示しています。
コアとなるコードの変更箇所
このコミットでは、主に以下のファイルが変更されています。
src/cmd/gofix/Makefile
:gofix
ツールに新しい修正ルールhtmlerr.go
を追加するためのMakefileの変更。src/cmd/gofix/fix.go
:gofix
の内部コメントの修正。src/cmd/gofix/htmlerr.go
:html.Tokenizer.Error
をErr
にリネームするためのgofix
ルールを定義する新しいファイル。src/cmd/gofix/htmlerr_test.go
:htmlerr.go
で定義されたgofix
ルールのテストケース。src/pkg/compress/bzip2/bit_reader.go
:bitReader.Error()
メソッドをbitReader.Err()
にリネーム。src/pkg/compress/bzip2/bzip2.go
:bz2.br.Error()
の呼び出しをbz2.br.Err()
に変更。src/pkg/exp/sql/sql.go
:rows.Error()
メソッドをrows.Err()
にリネームし、関連するコメントを修正。src/pkg/html/parse.go
:p.tokenizer.Error()
の呼び出しをp.tokenizer.Err()
に変更。src/pkg/html/token.go
:Tokenizer.Error()
メソッドをTokenizer.Err()
にリネーム。src/pkg/html/token_test.go
:Tokenizer.Error()
の呼び出しをTokenizer.Err()
に変更。
これらの変更は、html
、bzip2
、sql
の各パッケージにおいて、「レシーバに以前関連付けられたエラーを返すメソッド」の命名を Error
から Err
へと統一するものです。特に gofix
関連のファイルは、この変更を自動化するためのツール側の対応を示しています。
コアとなるコードの解説
このコミットの核心的な変更は、html
、bzip2
、sql
パッケージにおけるエラーを返すメソッドの命名変更と、その自動化のための gofix
ツールの導入です。
gofix
ルール htmlerr.go
の解説
src/cmd/gofix/htmlerr.go
は、html.Tokenizer.Error
メソッドを Err
にリネームするための gofix
ルールを定義しています。
// src/cmd/gofix/htmlerr.go (抜粋)
func htmlerr(f *ast.File) bool {
// ファイルが "html" パッケージをインポートしているかチェック
if !imports(f, "html") {
return false // インポートしていなければ何もしない
}
// 型情報を取得 (html.NewTokenizer が html.Tokenizer 型を返すことを認識させる)
typeof, _ := typecheck(htmlerrTypeConfig, f)
fixed := false
// ASTを走査
walk(f, func(n interface{}) {
s, ok := n.(*ast.SelectorExpr) // セレクタ式 (例: obj.Method) かどうか
// セレクタ式であり、そのレシーバの型が "html.Tokenizer" で、メソッド名が "Error" の場合
if ok && typeof[s.X] == "html.Tokenizer" && s.Sel.Name == "Error" {
s.Sel.Name = "Err" // メソッド名を "Err" に変更
fixed = true // 変更があったことを記録
}
})
return fixed // 変更があったかどうかを返す
}
この関数は、Goのソースファイルを抽象構文木(AST)として解析し、html.Tokenizer
型のオブジェクトに対して Error()
メソッドが呼び出されている箇所を特定します。そして、そのメソッド名を Err()
に書き換えます。これにより、開発者が手動でこれらの変更を行う手間を省き、Go言語の新しい命名規則への移行を容易にします。
各パッケージでの具体的な変更例
src/pkg/html/token.go
と src/pkg/html/parse.go
html
パッケージでは、HTMLトークナイザの Tokenizer
型が持つエラー取得メソッドが変更されました。
変更前 (src/pkg/html/token.go
):
func (z *Tokenizer) Error() error { ... }
変更後 (src/pkg/html/token.go
):
func (z *Tokenizer) Err() error { ... }
これに伴い、src/pkg/html/parse.go
のような Tokenizer.Error()
を呼び出していた箇所も Tokenizer.Err()
に変更されています。
// src/pkg/html/parse.go (変更箇所抜粋)
// 変更前: return p.tokenizer.Error()
// 変更後: return p.tokenizer.Err()
src/pkg/compress/bzip2/bit_reader.go
と src/pkg/compress/bzip2/bzip2.go
bzip2
圧縮ライブラリでも同様の変更が行われました。
変更前 (src/pkg/compress/bzip2/bit_reader.go
):
func (br *bitReader) Error() error { ... }
変更後 (src/pkg/compress/bzip2/bit_reader.go
):
func (br *bitReader) Err() error { ... }
そして、src/pkg/compress/bzip2/bzip2.go
内の呼び出しも更新されています。
// src/pkg/compress/bzip2/bzip2.go (変更箇所抜粋)
// 変更前: brErr := bz2.br.Error()
// 変更後: brErr := bz2.br.Err()
src/pkg/exp/sql/sql.go
実験的な sql
パッケージでも、Rows
オブジェクトのエラー取得メソッドが変更されました。
変更前 (src/pkg/exp/sql/sql.go
):
func (rs *Rows) Error() error { ... }
変更後 (src/pkg/exp/sql/sql.go
):
func (rs *Rows) Err() error { ... }
これらの変更は、Go言語の標準ライブラリ全体で一貫したエラーハンドリングの命名規則を確立するための重要なステップでした。gofix
ツールの利用は、このような大規模なAPI変更を効率的かつ安全に行うためのGoの設計哲学を反映しています。
関連リンク
- Go Code Review: https://golang.org/cl/5327064
参考にした情報源リンク
- Go's error handling philosophy:
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFpNd_QxvRhoy56BdEpo2rkg_EGm8uCCQVUL6BgEUJlSngfDSGmmIYY0YN4NF1Jj3tX6Q-zl2QcFC3uIhYL5UDJ_eZzw_KITqWZ2KCfaJqMiWELbnGH-TL_BbqBPqe2Bf6TsGp7Kjdnubil1HaU1SX1Fg50Ic-ATr6GgBhAcnA1g0Cu
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFtlY0z7vYUFFgfOa2kYKsdzhcGTfM5JjKHvIxTmsKaChDEL9X2cmFEb5vZxxRbiVz85hdDpJuyM42_VJt8kiJwpU4QHW7doshYvTaJrwz8RXr4CPjJGw4DuNP505fFPKJ4FMdF78SMCI2KfkSJVZ7omXJ7Qv9oYzwJBKDdqVZBAO4rbkD9DM3j0a62e2g7qj7uQVaOX-05xnAb
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQH2efeypxu-4yo-LHzzki085ZUh7vpqXiaUYrVwdGZvsiFkKI_NnRYf5koOqVvtRQcLTitYvl_ypZuVdqL6Tkc97SeD45Rej6jyHDGpqPOhD9jKEA7GU6_B_Q==
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQGpSSuKgDebGTTYCMN6r1RWGXI-j-q3vEYtTs8gwQPUQzRC2byKJPwavClWPq83TOe35VfeoZ97Pxw5uxKSB2xXkyPRSc98QgUU8KbPaLS8E61cWG8JvAdsOO8MZ1TBwQbIMLMmUMKINlcKHZ8Or1AajaIS0MviNawJrx5C2XLHxM9nx7515pAJNsdj
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEluMN0ADS4PXoVZOx8tOncdrZWV6P9sRVqkr1VR7rt0G0DIifLVZCbeRiSGIjtcslQtbBXIV1uuSssw7f0kZSwgGr0qoCJADmW3jHVBrbwlaVtSDs1wd-xLaJnGqTKrLd05Q==
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEAB3QwMEe_EDOVlZJ070a3NlIwOov5Np2ys8-_ABJj_WuQd-X5UcDVGtzyYWHyrOVZboG1Z-8wZ6H0JGctzF-fU7vEPOHw_QB3QlxS6q045Kt5EbMFGzAbLxp0Wy_76pAvkvKt
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHuoRGMgVkKRUXSgH5Cr2u20QoI1FY0U3R38-eGZBlMFOe08PSFjyAIKyPCor41uNx1cgPuB3pIcZbax2HKkdm_EiKizXxZH1uJEEFKHrCQW4Byk5e-ybnpa4lLZ1RFb9_V9F3DvhBaGmceOEs1hi3H2wMXPhVT-whPVB2ObUlF5nXuAANf
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEUTq4XX6y1zM10FkKN0efhoV0ffD3FcCMX0dZDLt51_fuu8q7E7Cl0eCzzZADRaC7JudxQ8-lsHRp_0hHlqtW3ZCf6AN2hieLKOR-6AfeLoDqYW_d8qka93FBcHN8IInlDAOrjUtPSx8gLy3LaRKA9sL0OKbUPXKyk9klBk6MmNR_BrImPuNnwgjOcNTk=
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQESVDM5KqLxDPLcSV1AtKzpcDhAPfmHRJ864rV30K16gEZEH2AtL9Hx_WGTwTAUyi0ezkTA0xbe_ob1kvB-bd9fH1l2dwVCsiPT8nlJbseO_cmRNe2fiQh4FnSbIHasmh7eMKGaq8d3u_wHzQuuXCREVyJG5h75eoSAFWtcwPiDwi-PFDfDw0sfSfM