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

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

このコミットは、Go言語のドキュメンテーションツールであるgo/docパッケージから、os.NewErrorに関連する特定のアンチヒューリスティック(誤った推論を避けるための回避策)を削除するものです。これは、os.NewErrorが将来的に廃止されることを見越した変更であり、エラーハンドリングの進化に伴うものです。

コミット

commit 2b0c49f2e56df2b739981ae3e6069f74a776297c
Author: Russ Cox <rsc@golang.org>
Date:   Thu Oct 27 19:39:25 2011 -0700

    go/doc: remove os.NewError anti-heuristic
    
    It will be obsolete when error happens.
    
    Submitting this now will make the error transition earlier,
    at the cost of making a locally-built godoc viewing
    /pkg/syscall or /pkg/os have some functions appear
    under the Error type as constructors.
    
    R=golang-dev, adg
    CC=golang-dev
    https://golang.org/cl/5305067
---
 src/pkg/go/doc/doc.go | 15 ---------------
 1 file changed, 15 deletions(-)

diff --git a/src/pkg/go/doc/doc.go b/src/pkg/go/doc/doc.go
index c7fed97841..9174864339 100644
--- a/src/pkg/go/doc/doc.go
+++ b/src/pkg/go/doc/doc.go
@@ -175,8 +175,6 @@ func setFunc(table map[string]*ast.FuncDecl, f *ast.FuncDecl) {\n }\n \n func (doc *docReader) addFunc(fun *ast.FuncDecl) {\n-\tname := fun.Name.Name\n-\
 \t// determine if it should be associated with a type\n \tif fun.Recv != nil {\n \t\t// method\n@@ -205,19 +203,6 @@ func (doc *docReader) addFunc(fun *ast.FuncDecl) {\n \t\t\ttyp := doc.lookupTypeDoc(tname)\n \t\t\tif typ != nil {\n \t\t\t\t// named and exported result type\n-\n-\t\t\t\t// Work-around for failure of heuristic: In package os\n-\t\t\t\t// too many functions are considered factory functions\n-\t\t\t\t// for the Error type. Eliminate manually for now as\n-\t\t\t\t// this appears to be the only important case in the\n-\t\t\t\t// current library where the heuristic fails.\n-\t\t\t\tif doc.pkgName == \"os\" && tname == \"Error\" &&\n-\t\t\t\t\tname != \"NewError\" && name != \"NewSyscallError\" {\n-\t\t\t\t\t// not a factory function for os.Error\n-\t\t\t\t\tsetFunc(doc.funcs, fun) // treat as ordinary function\n-\t\t\t\t\treturn\n-\t\t\t\t}\n-\
 \t\t\t\tsetFunc(typ.factories, fun)\n \t\t\t\treturn\n \t\t\t}\n```

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

[https://github.com/golang/go/commit/2b0c49f2e56df2b739981ae3e6069f74a776297c](https://github.com/golang/go/commit/2b0c49f2e56df2b739981ae3e6069f74a776297c)

## 元コミット内容

`go/doc`パッケージから`os.NewError`に関連するアンチヒューリスティックを削除します。
`os.NewError`はエラーハンドリングの変更により廃止される予定です。
この変更を今適用することで、エラー移行を早めることができますが、一時的にローカルでビルドされたgodocが`/pkg/syscall`や`/pkg/os`を表示する際に、一部の関数が`Error`型のコンストラクタとして表示される可能性があります。

## 変更の背景

このコミットは、Go言語のエラーハンドリングメカニズムの進化と密接に関連しています。初期のGo言語では、`os.NewError`のような関数がエラーオブジェクトを作成するために使用されていました。しかし、Goのエラーハンドリングの哲学は、エラーを例外ではなく、明示的な値として扱うことに重点を置いています。

コミットメッセージにある「It will be obsolete when error happens.」という記述は、Go言語の将来的なエラーハンドリングの変更、特に`error`インターフェースの導入と、より柔軟なエラー作成・伝播メカニズムへの移行を示唆しています。`os.NewError`は、この新しいエラーハンドリングのパラダイムには適合しないため、廃止される運命にありました。

`go/doc`パッケージは、Goのソースコードからドキュメンテーションを生成するツールです。このツールは、関数が特定の型の「ファクトリ関数」(その型のインスタンスを生成する関数)であるかどうかをヒューリスティックに判断しようとします。しかし、`os`パッケージ内の`os.NewError`のような関数は、このヒューリスティックを誤動作させ、実際にはファクトリ関数ではないにもかかわらず、`Error`型のコンストラクタとして誤って認識される問題がありました。

このコミットは、`os.NewError`が廃止されることが決定したため、その誤認識を回避するための特別な「アンチヒューリスティック」コードが不要になったことを意味します。このコードを削除することで、`go/doc`のコードベースを簡素化し、将来のエラーハンドリングの変更への移行をスムーズにすることが目的です。一時的な副作用として、godocの表示に影響が出る可能性が言及されていますが、これは短期的なものであり、長期的な改善のためのトレードオフと見なされています。

## 前提知識の解説

### Go言語のエラーハンドリングの哲学

Go言語は、エラーを例外として扱うのではなく、通常の戻り値として扱うことを推奨しています。関数は、操作が成功した場合は結果と`nil`エラーを返し、失敗した場合は結果のゼロ値と非`nil`エラーを返します。これにより、開発者はエラーを明示的にチェックし、処理することが奨励されます。

```go
func doSomething() (resultType, error) {
    // ... 処理 ...
    if someCondition {
        return zeroValue, errors.New("something went wrong") // エラーを返す
    }
    return actualResult, nil // 成功
}

os.NewError (旧来のエラー作成方法)

Go言語の初期のバージョンでは、osパッケージにNewErrorという関数が存在し、これを使って新しいエラーを作成することができました。しかし、これはGoのエラーハンドリングの進化に伴い、より汎用的なerrors.Newfmt.Errorfに置き換えられ、廃止されました。

errors.New

errorsパッケージのNew関数は、静的な文字列メッセージを持つ新しいエラーを作成するための最も基本的な方法です。これは、特定の状況を示すための「センチネルエラー」(事前に定義されたエラー変数)を作成する際によく使用されます。

package main

import (
	"errors"
	"fmt"
)

var ErrNotFound = errors.New("item not found") // センチネルエラー

func findItem(id int) error {
	if id != 123 {
		return ErrNotFound
	}
	return nil
}

func main() {
	err := findItem(456)
	if err != nil {
		fmt.Println("Error:", err) // Output: Error: item not found
	}
}

fmt.Errorf

fmtパッケージのErrorf関数は、fmt.Printfと同様にフォーマット文字列と引数を使用して、動的なエラーメッセージを作成するために使用されます。これは、エラーにコンテキスト情報を含めたい場合に非常に便利です。

package main

import (
	"fmt"
)

func processFile(filename string) error {
	// ファイル処理中にエラーが発生したと仮定
	return fmt.Errorf("failed to process file: %s", filename)
}

func main() {
	err := processFile("config.txt")
	if err != nil {
		fmt.Println("Error:", err) // Output: Error: failed to process file: config.txt
	}
}

Go 1.13以降では、fmt.Errorf%w動詞を使ってエラーをラップする機能も追加され、エラーチェーンを作成できるようになりました。これにより、元のエラーを保持しつつ、より多くのコンテキストを追加することが可能になっています。

go/docパッケージ

go/docパッケージは、Goのソースコードを解析し、ドキュメンテーションを生成するためのライブラリです。godocコマンドはこのパッケージを利用して、Goの標準ライブラリやユーザーが作成したパッケージのドキュメントを生成・表示します。このパッケージは、関数、型、変数などの情報を抽出し、それらの関係性(例:ある型に関連するメソッドやファクトリ関数)を特定するヒューリスティックを使用します。

技術的詳細

go/docパッケージのdocReader構造体には、addFuncというメソッドがあります。このメソッドは、Goのソースコードから抽出された関数(*ast.FuncDecl)を受け取り、それがどの型に関連する関数であるか、あるいは通常の関数であるかを判断し、適切なドキュメンテーション構造に分類します。

特に、このメソッドは、関数が特定の型の「ファクトリ関数」であるかどうかを判断するロジックを含んでいます。ファクトリ関数とは、その型の新しいインスタンスを生成する役割を持つ関数のことです。go/docは、関数の戻り値の型が特定の型と一致する場合、その関数をその型のファクトリ関数であると推測するヒューリスティックを使用します。

しかし、osパッケージのos.NewErrorのような関数は、error型を返すため、このヒューリスティックによってerror型のファクトリ関数であると誤って認識される可能性がありました。実際には、os.NewErrorerrorインターフェースのインスタンスを生成するものの、特定のError構造体(もし存在すれば)のコンストラクタではありませんでした。

この誤認識は、godocが生成するドキュメントの表示に影響を与え、os.Error型の下にNewErrorがコンストラクタとして表示されてしまうという問題を引き起こしていました。これを回避するために、コミット前のコードには以下のような「アンチヒューリスティック」が組み込まれていました。

				// Work-around for failure of heuristic: In package os
				// too many functions are considered factory functions
				// for the Error type. Eliminate manually for now as
				// this appears to be the only important case in the
				// current library where the heuristic fails.
				if doc.pkgName == "os" && tname == "Error" &&
					name != "NewError" && name != "NewSyscallError" {
					// not a factory function for os.Error
					setFunc(doc.funcs, fun) // treat as ordinary function
					return
				}

このコードブロックは、以下の条件がすべて満たされた場合に適用される特殊なケースでした。

  1. 現在のパッケージがosパッケージである。
  2. 戻り値の型名がErrorである。
  3. 関数名がNewErrorでもNewSyscallErrorでもない。

これらの条件が満たされた場合、その関数はError型のファクトリ関数とは見なされず、通常の関数として扱われるようにしていました。これは、os.NewErroros.NewSyscallError以外の関数が誤ってError型のファクトリ関数として分類されるのを防ぐための、手動での例外処理でした。

このコミットでは、os.NewErrorが廃止されるため、このアンチヒューリスティックが不要になったと判断し、このコードブロック全体を削除しました。これにより、go/docのコードが簡素化され、将来のエラーハンドリングの変更に合わせたクリーンアップが行われました。

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

src/pkg/go/doc/doc.goファイルから、以下の15行が削除されました。

--- a/src/pkg/go/doc/doc.go
+++ b/src/pkg/go/doc/doc.go
@@ -175,8 +175,6 @@ func setFunc(table map[string]*ast.FuncDecl, f *ast.FuncDecl) {
 }
 
 func (doc *docReader) addFunc(fun *ast.FuncDecl) {
-\tname := fun.Name.Name
-\
 \t// determine if it should be associated with a type
 \tif fun.Recv != nil {
 \t\t// method
@@ -205,19 +203,6 @@ func (doc *docReader) addFunc(fun *ast.FuncDecl) {
 \t\t\ttyp := doc.lookupTypeDoc(tname)\n \t\t\tif typ != nil {\n \t\t\t\t// named and exported result type\n-\n-\t\t\t\t// Work-around for failure of heuristic: In package os\n-\t\t\t\t// too many functions are considered factory functions\n-\t\t\t\t// for the Error type. Eliminate manually for now as\n-\t\t\t\t// this appears to be the only important case in the\n-\t\t\t\t// current library where the heuristic fails.\n-\t\t\t\tif doc.pkgName == \"os\" && tname == \"Error\" &&\n-\t\t\t\t\tname != \"NewError\" && name != \"NewSyscallError\" {\n-\t\t\t\t\t// not a factory function for os.Error\n-\t\t\t\t\tsetFunc(doc.funcs, fun) // treat as ordinary function\n-\t\t\t\t\treturn\n-\t\t\t\t}\n-\
 \t\t\t\tsetFunc(typ.factories, fun)\n \t\t\t\treturn\n \t\t\t}\n```

具体的には、`addFunc`メソッド内の`name := fun.Name.Name`の行と、`typ != nil`の条件分岐内にある`os`パッケージの`Error`型に関する特別な処理ブロックが削除されています。

## コアとなるコードの解説

削除されたコードブロックは、`go/doc`がGoのソースコードを解析し、ドキュメンテーションを生成する際に、特定のヒューリスティックの誤動作を回避するためのものでした。

元のコードでは、`addFunc`関数内で、関数の戻り値の型が`Error`である場合に、その関数が`Error`型のファクトリ関数であると推測するロジックがありました。しかし、`os`パッケージには`NewError`や`NewSyscallError`といった、`error`インターフェースを返す関数が多数存在し、これらが`Error`型のファクトリ関数として誤って分類される問題がありました。

削除された`if`ブロックは、この誤分類を防ぐための「ワークアラウンド」でした。
-   `doc.pkgName == "os"`: 現在処理しているパッケージが`os`パッケージであるかを確認。
-   `tname == "Error"`: 関数の戻り値の型が`Error`であるかを確認。
-   `name != "NewError" && name != "NewSyscallError"`: 関数名が`NewError`でも`NewSyscallError`でもないことを確認。

これらの条件がすべて真の場合、つまり`os`パッケージ内で`Error`型を返す関数であり、かつ`NewError`や`NewSyscallError`ではない場合、その関数は`Error`型のファクトリ関数とは見なされず、通常の関数として`doc.funcs`に登録されるようにしていました。これにより、`godoc`が生成するドキュメントで、`os`パッケージ内の多くの関数が不適切に`Error`型のコンストラクタとして表示されるのを防いでいました。

`os.NewError`が廃止されることが決定したため、この特定のワークアラウンドはもはや必要ないと判断され、コードベースから削除されました。これは、Goのエラーハンドリングの設計が進化し、`os.NewError`のような特定の関数に依存しない、より汎用的なエラー作成メカニズムが導入された結果です。この変更により、`go/doc`のコードはよりシンプルになり、Go言語の進化に追従しています。

## 関連リンク

*   Go CL 5305067: [https://golang.org/cl/5305067](https://golang.org/cl/5305067)

## 参考にした情報源リンク

*   Go言語のエラーハンドリングの基本: [https://betterstack.com/community/guides/logging/go-error-handling/](https://betterstack.com/community/guides/logging/go-error-handling/)
*   Goのエラーハンドリングの進化: [https://interp.blog/posts/go-error-handling-evolution/](https://interp.blog/posts/go-error-handling-evolution/)
*   Go 1.13以降のエラーラッピング: [https://www.datadoghq.com/blog/go-error-handling/](https://www.datadoghq.com/blog/go-error-handling/)
*   Goのエラーハンドリングのベストプラクティス: [https://gabrieltanner.org/blog/go-error-handling](https://gabrieltanner.org/blog/go-error-handling)
*   Goのエラーハンドリングの哲学: [https://medium.com/@apoorv.garg/error-handling-in-go-a-comprehensive-guide-2023-b72121212121](https://medium.com/@apoorv.garg/error-handling-in-go-a-comprehensive-guide-2023-b72121212121)
*   Goのエラーハンドリングの概要: [https://medium.com/@apoorv.garg/error-handling-in-go-a-comprehensive-guide-2023-b72121212121](https://medium.com/@apoorv.garg/error-handling-in-go-a-comprehensive-guide-2023-b72121212121)