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

[インデックス 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.Errorrows.Error のように、レシーバ(オブジェクト)に以前関連付けられたエラーを返すメソッド。

この状況では、Error という名前が多義的であり、コードの可読性や意図の明確さに課題がありました。特に、(a)の error インターフェースの Error メソッドと、(c)のレシーバに紐づくエラーを返すメソッドが同じ Error という名前を持つことで、混乱が生じる可能性がありました。

このコミットは、(c)のケース、つまり「レシーバに以前関連付けられたエラーを返すメソッド」について、その名前を Err に変更するという新しい命名規則を導入することで、この多義性を解消し、APIの意図をより明確にすることを目的としています。

変更の主な理由は以下の通りです。

  • APIの区別: シグネチャだけでなく、名前によって異なる種類のエラーAPIを区別できるようになり、Error という名前の負担を軽減します。
  • err 変数との親和性: Go言語ではエラーをチェックする際に if err != nil のように err という変数名が一般的に使われます。Err という名前は、この err 変数と視覚的にも概念的にも近く、エラー検証の意図をより明確にします。
  • 簡潔性と利便性: ErrError よりも短く、sql パッケージのイテレータのように頻繁にエラー検証が行われる場面で、より簡潔で便利な記述を可能にします。

これらの変更により、Go言語のエラーハンドリングの慣習がより洗練され、開発者がより一貫性のある、読みやすいコードを書けるようになることが期待されました。

前提知識の解説

Go言語のエラーハンドリングの基本

Go言語は、エラーハンドリングにおいて例外(exception)を使用せず、関数がエラーを返す場合は、戻り値として明示的に error 型の値を返すという設計思想を持っています。

  1. エラーを戻り値として返す: Goの関数は、通常、結果とエラーの2つの値を返します。エラーがない場合は nil を返します。

    func doSomething() (resultType, error) {
        // ... 処理 ...
        if somethingWentWrong {
            return zeroValue, errors.New("something went wrong")
        }
        return actualResult, nil
    }
    
  2. error インターフェース: Goの組み込み型である error はインターフェースであり、以下のように定義されています。

    type error interface {
        Error() string
    }
    

    このインターフェースを実装する任意の型がエラーとして扱われます。Error() メソッドは、エラーの文字列表現を返します。

  3. エラーのチェック: 関数呼び出し後には、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 というツールが存在します。このコミットでは、htmlbzip2sql パッケージ内の該当する 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.ErrorErr にリネームするための 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() に変更。

これらの変更は、htmlbzip2sql の各パッケージにおいて、「レシーバに以前関連付けられたエラーを返すメソッド」の命名を Error から Err へと統一するものです。特に gofix 関連のファイルは、この変更を自動化するためのツール側の対応を示しています。

コアとなるコードの解説

このコミットの核心的な変更は、htmlbzip2sql パッケージにおけるエラーを返すメソッドの命名変更と、その自動化のための 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.gosrc/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.gosrc/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