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

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

このコミットは、Go言語の公式ツールであるcmd/vet(Go vet)のprint.goファイルに対する変更です。具体的には、go vetError()メソッドの呼び出しに対して誤って警告を発する問題を修正しています。

コミット

  • コミットハッシュ: e487ea84218d5095b6ff05ef1de6d44fc9b64183
  • 作者: Shenghou Ma minux.ma@gmail.com
  • コミット日時: 2013年1月18日 金曜日 02:24:12 +0800
  • 変更ファイル: src/cmd/vet/print.go (1ファイル)
  • 変更行数: 4行追加, 1行削除

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

https://github.com/golang/go/commit/e487ea84218d5095b6ff05ef1de6d44fc9b64183

元コミット内容

cmd/vet: don't complain about Error()
Fixes #4598.

R=golang-dev, dsymonds, r
CC=golang-dev
https://golang.org/cl/7102050

変更の背景

このコミットの背景には、Go言語の静的解析ツールであるgo vetが、errorインターフェースのError()メソッドの呼び出しに対して誤った警告を発するという問題がありました。

go vetは、Goプログラムの潜在的なバグや疑わしい構成を検出するためのツールです。特に、fmt.Printfのような可変引数関数(variadic function)の呼び出しにおいて、フォーマット文字列と引数の型や数が一致しない場合に警告を出す機能があります。これは、開発者がよく犯す間違いの一つであり、実行時エラーにつながる可能性があるため、go vetはこのような問題を事前に検出することで、コードの品質と堅牢性を向上させます。

しかし、errorインターフェースが持つError() stringメソッドは、引数を取らないにもかかわらず、go vetの内部ロジックがこれを可変引数関数の一種と誤認し、「引数がない」という理由で不必要な警告("no args in Error call")を出していました。これは、error型の変数が持つError()メソッドを呼び出すという、Go言語においてごく一般的な、かつ正しいコードパターンに対して誤検知が発生していることを意味します。

この誤検知は、開発者にとってノイズとなり、go vetの有用性を損なう可能性がありました。そのため、Issue #4598として報告され、このコミットによって修正されることになりました。

前提知識の解説

このコミットの理解には、以下のGo言語の概念とツールに関する知識が必要です。

  1. go vet: go vetは、Go言語のソースコードを静的に解析し、疑わしい構成や潜在的なバグを報告するツールです。例えば、printf系の関数におけるフォーマット文字列と引数の不一致、到達不能なコード、ロックの誤用などを検出します。開発ワークフローにおいて、コンパイル時には検出されないが実行時に問題を引き起こす可能性のあるエラーを早期に発見するために非常に有用です。

  2. errorインターフェース: Go言語において、エラーは組み込みのerrorインターフェースによって表現されます。このインターフェースは、単一のメソッドError() stringを定義しています。

    type error interface {
        Error() string
    }
    

    このメソッドは、エラーに関する文字列形式の記述を返します。Goの関数は、慣習として最後の戻り値としてerror型を返すことで、エラーの発生を呼び出し元に通知します。

  3. 可変引数関数(Variadic Functions)とprintf系関数: Go言語では、引数の数が可変である関数を定義できます。これは、引数リストの最後のパラメータの型に...を付けることで実現されます。例えば、fmt.Printfは可変引数関数であり、最初の引数にフォーマット文字列を取り、その後に任意の数の引数を取ります。

    func Printf(format string, a ...interface{}) (n int, err error)
    

    go vetは、このようなprintf系の関数が正しく使われているかをチェックする機能を持っています。具体的には、フォーマット文字列(例: %s, %d)と、それに続く引数の型や数が一致しているかを検証します。

  4. 抽象構文木(AST: Abstract Syntax Tree): コンパイラや静的解析ツールは、ソースコードを直接解析するのではなく、まずソースコードを抽象構文木(AST)というツリー構造に変換します。ASTは、プログラムの構造を抽象的に表現したもので、各ノードがコードの要素(関数呼び出し、変数宣言、式など)に対応します。go vetのようなツールは、このASTを走査して、特定のパターンや問題のある構造を検出します。このコミットで変更されているsrc/cmd/vet/print.goは、ASTを解析してprintf系の関数呼び出しをチェックする部分に関連しています。

技術的詳細

このコミットは、go vetツール内のprint.goファイルにあるcheckPrint関数に焦点を当てています。この関数は、fmt.Printfのような可変引数関数(print系の関数)の呼び出しを解析し、フォーマット文字列と引数の整合性をチェックする役割を担っています。

問題は、errorインターフェースのError()メソッドが引数を取らないにもかかわらず、go vetがこれをprint系の関数呼び出しとして誤って解釈し、「引数がない」という理由で警告を出していた点にありました。

変更前のコードでは、checkPrint関数内で引数の数がskip(フォーマット文字列などの必須引数の数)以下である場合に、verboseモードが有効でなく、かつisLnPrintlnのような関数かどうか)でない場合に警告を発していました。

// 変更前
if len(args) <= skip {
    if *verbose && !isLn { // ここが問題
        f.Badf(call.Pos(), "no args in %s call", name)
    }
    return
}

この条件*verbose && !isLnが、Error()メソッドの呼び出しに対して誤って真となり、警告が発せられていました。Error()メソッドは引数を取らないためlen(args) <= skipは常に真となります。

このコミットでは、この条件をより具体的に変更し、Error()メソッドの呼び出しを明示的に除外するようにしました。

// 変更後
if len(args) <= skip {
    // TODO: check that the receiver of Error() is of type error.
    if !isLn && name != "Error" { // ここが変更点
        f.Badf(call.Pos(), "no args in %s call", name)
    }
    return
}

変更後のコードでは、!isLnに加えてname != "Error"という条件が追加されています。これにより、呼び出されている関数の名前が"Error"である場合には、たとえ引数がなくても警告を発しないようになりました。

コメント// TODO: check that the receiver of Error() is of type error.は、将来的な改善点を示唆しています。現在の修正では、単に「Error」という名前の関数呼び出しであれば警告を出さないようにしていますが、より厳密には、そのError()メソッドがerrorインターフェースを実装した型(つまり、error型のレシーバを持つメソッド)である場合にのみ警告を抑制すべきである、という意図が込められています。これにより、例えば開発者が誤ってErrorという名前の引数なし関数を定義した場合に、その関数呼び出しがgo vetによってチェックされなくなるという潜在的な問題を防ぐことができます。しかし、このコミットの時点では、最も緊急性の高い誤検知の修正が優先されています。

この修正により、var e error; fmt.Println(e.Error())のような正当なコードがgo vetによって誤って警告されることがなくなり、開発体験が向上しました。

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

--- a/src/cmd/vet/print.go
+++ b/src/cmd/vet/print.go
@@ -253,7 +253,8 @@ func (f *File) checkPrint(call *ast.CallExpr, name string, skip int) {
 		}
 	}\n \tif len(args) <= skip {\n-\t\tif *verbose && !isLn {\n+\t\t// TODO: check that the receiver of Error() is of type error.\n+\t\tif !isLn && name != "Error" {\n \t\t\tf.Badf(call.Pos(), "no args in %s call", name)\n \t\t}\n \t\treturn\n@@ -299,6 +300,8 @@ func BadFunctionUsedInTests() {\n \tf.Warnf(0, "%s", "hello", 3) // ERROR "wrong number of args in Warnf call"\n \tf.Warnf(0, "%r", "hello")    // ERROR "unrecognized printf verb"\n \tf.Warnf(0, "%#s", "hello")   // ERROR "unrecognized printf flag"\n+\tvar e error\n+\tfmt.Println(e.Error()) // correct, used to trigger "no args in Error call"\n }\n \n // printf is used by the test.\n```

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

このコミットの主要な変更は、`src/cmd/vet/print.go`ファイルの`checkPrint`関数内にあります。

1.  **変更された条件式**:
    ```go
    -		if *verbose && !isLn {
    +		if !isLn && name != "Error" {
    ```
    この行が、`go vet`が「引数がない」という警告を出すかどうかの条件を制御しています。
    -   変更前: `*verbose`(詳細モードが有効か)と`!isLn`(`Println`系の関数ではないか)が真の場合に警告を出していました。
    -   変更後: `!isLn`(`Println`系の関数ではないか)と`name != "Error"`(呼び出されている関数の名前が"Error"ではないか)が真の場合に警告を出すように変更されました。
    この変更により、関数名が"Error"である場合は、引数がなくても警告が出力されなくなりました。これは、`error`インターフェースの`Error()`メソッドが引数を取らないことを考慮した修正です。

2.  **追加されたコメント**:
    ```go
    +		// TODO: check that the receiver of Error() is of type error.
    ```
    このコメントは、将来的な改善の方向性を示しています。現在の修正は、単に「Error」という名前の関数呼び出しを例外としていますが、より厳密には、その`Error()`メソッドが実際に`error`インターフェースを実装している型(つまり、`error`型のレシーバを持つメソッド)のものであることを確認すべきである、という意図が込められています。これにより、例えば開発者が誤って`Error`という名前の引数なし関数を定義した場合に、その関数呼び出しが`go vet`によってチェックされなくなるという潜在的な問題を防ぐことができます。

3.  **テストケースの追加**:
    ```go
    +	var e error
    +	fmt.Println(e.Error()) // correct, used to trigger "no args in Error call"
    ```
    `BadFunctionUsedInTests()`関数内に、`var e error; fmt.Println(e.Error())`という行が追加されました。これは、この修正が適用される前には「no args in Error call」という誤った警告をトリガーしていたコード例です。この行を追加することで、修正が正しく機能し、この正当なコードに対して警告が出ないことを確認するためのテストケースとして機能します。

これらの変更により、`go vet`は`error`インターフェースの`Error()`メソッド呼び出しを正しく認識し、不必要な警告を発しなくなりました。

## 関連リンク

-   **Gerrit Change-ID**: `https://golang.org/cl/7102050` (GoプロジェクトのコードレビューシステムであるGerritにおけるこの変更のID)
-   **Go Issue #4598**: `https://golang.org/issue/4598` (このコミットが修正したGitHub Issueへのリンク)

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

-   このコミットの元データ (`./commit_data/14903.txt`)
-   Go言語の公式ドキュメント(`go vet`、`error`インターフェース、可変引数関数に関する一般的な知識)
-   Go言語のソースコード(`src/cmd/vet/print.go`の変更前後の内容)
-   GitHubのコミットページ (`https://github.com/golang/go/commit/e487ea84218d5095b6ff05ef1de6d44fc9b64183`)
-   Go言語のIssueトラッカー (`https://golang.org/issue/4598`)