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

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

このコミットは、Go言語の標準ライブラリおよびコマンドラインツールにおいて、go vet -shadow ツールによって検出された「シャドーイング(shadowing)」の問題を修正するものです。シャドーイングとは、内側のスコープで宣言された変数が、外側のスコープの同名の変数を「隠してしまう」現象を指します。これはエラーではないものの、コードの可読性を低下させたり、意図しないバグを引き起こす可能性があるため、Goコミュニティでは避けるべき「いぼ(warts)」と見なされています。このコミットは、そのような潜在的な混乱や誤解を招くコードパターンを排除することを目的としています。

コミット

commit 0bc7e79afda9d76fb470eac695445e7332cf7ecd
Author: Rob Pike <r@golang.org>
Date:   Thu Jun 20 16:14:40 2013 -0700

    all: excise some warts found by vet -shadow
    These are not erroneous, just poor or confusing.
    
    R=golang-dev, bradfitz
    CC=golang-dev
    https://golang.org/cl/10448043

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

https://github.com/golang/go/commit/0bc7e79afda9d76fb470eac695445e7332cf7ecd

元コミット内容

commit 0bc7e79afda9d76fb470eac695445e7332cf7ecd
Author: Rob Pike <r@golang.org>
Date:   Thu Jun 20 16:14:40 2013 -0700

    all: excise some warts found by vet -shadow
    These are not erroneous, just poor or confusing.
    
    R=golang-dev, bradfitz
    CC=golang-dev
    https://golang.org/cl/10448043
---\n src/cmd/go/get.go                | 2 +-\n src/pkg/encoding/asn1/marshal.go | 4 ++--\n src/pkg/encoding/gob/encode.go   | 8 ++++----\n src/pkg/fmt/print.go             | 8 ++++----\n src/pkg/fmt/scan.go              | 7 +++----\n src/pkg/net/dial_test.go         | 1 -\n src/pkg/net/unix_test.go         | 1 -\n src/pkg/os/path_test.go          | 6 +++---\n src/pkg/syscall/syscall_bsd.go   | 3 +--\n src/pkg/time/format.go           | 2 +-\n 10 files changed, 19 insertions(+), 23 deletions(-)\n\ndiff --git a/src/cmd/go/get.go b/src/cmd/go/get.go
index 8c08ab2616..f9a1cfce46 100644
--- a/src/cmd/go/get.go
+++ b/src/cmd/go/get.go
@@ -286,7 +286,7 @@ func downloadPackage(p *Package) error {
 \t\t}\n \t\t// Some version control tools require the parent of the target to exist.\n \t\tparent, _ := filepath.Split(root)\n-\t\tif err := os.MkdirAll(parent, 0777); err != nil {\n+\t\tif err = os.MkdirAll(parent, 0777); err != nil {\n \t\t\treturn err\n \t\t}\n \t\tif err = vcs.create(root, repo); err != nil {\ndiff --git a/src/pkg/encoding/asn1/marshal.go b/src/pkg/encoding/asn1/marshal.go
index d38694d666..ed17e41a55 100644
--- a/src/pkg/encoding/asn1/marshal.go
+++ b/src/pkg/encoding/asn1/marshal.go
@@ -441,11 +441,11 @@ func marshalBody(out *forkableWriter, value reflect.Value, params fieldParameter
 \t\t\treturn\n \t\t}\n \n-\t\tvar params fieldParameters\n+\t\tvar fp fieldParameters\n \t\tfor i := 0; i < v.Len(); i++ {\n \t\t\tvar pre *forkableWriter\n \t\t\tpre, out = out.fork()\n-\t\t\terr = marshalField(pre, v.Index(i), params)\n+\t\t\terr = marshalField(pre, v.Index(i), fp)\n \t\t\tif err != nil {\n \t\t\t\treturn\n \t\t\t}\ndiff --git a/src/pkg/encoding/gob/encode.go b/src/pkg/encoding/gob/encode.go
index 6fcf8f9a5d..2726bcd7e7 100644
--- a/src/pkg/encoding/gob/encode.go
+++ b/src/pkg/encoding/gob/encode.go
@@ -575,21 +575,21 @@ func (enc *Encoder) encOpFor(rt reflect.Type, inProgress map[reflect.Type]*encOp
 \t\t\t\tbreak\n \t\t\t}\n \t\t\t// Slices have a header; we decode it to find the underlying array.\n-\t\t\telemOp, indir := enc.encOpFor(t.Elem(), inProgress)\n+\t\t\telemOp, elemIndir := enc.encOpFor(t.Elem(), inProgress)\n \t\t\top = func(i *encInstr, state *encoderState, p unsafe.Pointer) {\n \t\t\t\tslice := (*reflect.SliceHeader)(p)\n \t\t\t\tif !state.sendZero && slice.Len == 0 {\n \t\t\t\t\treturn\n \t\t\t\t}\n \t\t\t\tstate.update(i)\n-\t\t\t\tstate.enc.encodeArray(state.b, unsafe.Pointer(slice.Data), *elemOp, t.Elem().Size(), indir, int(slice.Len))\n+\t\t\t\tstate.enc.encodeArray(state.b, unsafe.Pointer(slice.Data), *elemOp, t.Elem().Size(), elemIndir, int(slice.Len))\n \t\t\t}\n \t\tcase reflect.Array:\n \t\t\t// True arrays have size in the type.\n-\t\t\telemOp, indir := enc.encOpFor(t.Elem(), inProgress)\n+\t\t\telemOp, elemIndir := enc.encOpFor(t.Elem(), inProgress)\n \t\t\top = func(i *encInstr, state *encoderState, p unsafe.Pointer) {\n \t\t\t\tstate.update(i)\n-\t\t\t\tstate.enc.encodeArray(state.b, p, *elemOp, t.Elem().Size(), indir, t.Len())\n+\t\t\t\tstate.enc.encodeArray(state.b, p, *elemOp, t.Elem().Size(), elemIndir, t.Len())\n \t\t\t}\n \t\tcase reflect.Map:\n \t\t\tkeyOp, keyIndir := enc.encOpFor(t.Key(), inProgress)\ndiff --git a/src/pkg/fmt/print.go b/src/pkg/fmt/print.go
index fa9eb52c6a..2da95b58af 100644
--- a/src/pkg/fmt/print.go
+++ b/src/pkg/fmt/print.go
@@ -811,8 +811,8 @@ func (p *pp) printArg(arg interface{}, verb rune, plus, goSyntax bool, depth int
 \t\tp.fmt.plus = oldPlus\n \t\tp.fmt.sharp = oldSharp\n \t\t// If the type is not simple, it might have methods.\n-\t\tif wasString, handled := p.handleMethods(verb, plus, goSyntax, depth); handled {\n-\t\t\treturn wasString\n+\t\tif isString, handled := p.handleMethods(verb, plus, goSyntax, depth); handled {\n+\t\t\treturn isString\n \t\t}\n \t\t// Need to use reflection\n \t\treturn p.printReflectValue(reflect.ValueOf(arg), verb, plus, goSyntax, depth)\n@@ -849,8 +849,8 @@ func (p *pp) printValue(value reflect.Value, verb rune, plus, goSyntax bool, dep
 \tif value.CanInterface() {\n \t\tp.arg = value.Interface()\n \t}\n-\tif wasString, handled := p.handleMethods(verb, plus, goSyntax, depth); handled {\n-\t\treturn wasString\n+\t\tif isString, handled := p.handleMethods(verb, plus, goSyntax, depth); handled {\n+\t\t\treturn isString\n \t}\n \n \treturn p.printReflectValue(value, verb, plus, goSyntax, depth)\ndiff --git a/src/pkg/fmt/scan.go b/src/pkg/fmt/scan.go
index d2d7867da7..c7e648579a 100644
--- a/src/pkg/fmt/scan.go
+++ b/src/pkg/fmt/scan.go
@@ -781,7 +781,7 @@ func (s *ss) convertFloat(str string, n int) float64 {\n \t\t\t}\n \t\t\ts.error(err)\n \t\t}\n-\t\tn, err := strconv.Atoi(str[p+1:])\n+\t\tm, err := strconv.Atoi(str[p+1:])\n \t\tif err != nil {\n \t\t\t// Put full string into error.\n \t\t\t// if e, ok := err.(*strconv.NumError); ok {\n@@ -789,7 +789,7 @@ func (s *ss) convertFloat(str string, n int) float64 {\n \t\t\ts.error(err)\n \t\t}\n-\t\treturn math.Ldexp(f, n)\n+\t\treturn math.Ldexp(f, m)\n \t}\n \tf, err := strconv.ParseFloat(str, n)\n \tif err != nil {\n@@ -858,8 +858,7 @@ func (s *ss) quotedString() string {\n \t\t\t\t// In a legal backslash escape, no matter how long, only the character\n \t\t\t\t// immediately after the escape can itself be a backslash or quote.\n \t\t\t\t// Thus we only need to protect the first character after the backslash.\n-\t\t\t\tr := s.mustReadRune()\n-\t\t\t\ts.buf.WriteRune(r)\n+\t\t\t\ts.buf.WriteRune(s.mustReadRune())\n \t\t\t} else if r == \'\"\' {\n \t\t\t\tbreak\n \t\t\t}\ndiff --git a/src/pkg/net/dial_test.go b/src/pkg/net/dial_test.go
index e24fecc8d4..c7bd4d7bfa 100644
--- a/src/pkg/net/dial_test.go
+++ b/src/pkg/net/dial_test.go
@@ -381,7 +381,6 @@ func TestDialer(t *testing.T) {\n \tdefer ln.Close()\n \tch := make(chan error, 1)\n \tgo func() {\n-\t\tvar err error\n \t\tc, err := ln.Accept()\n \t\tif err != nil {\n \t\t\tch <- fmt.Errorf(\"Accept failed: %v\", err)\ndiff --git a/src/pkg/net/unix_test.go b/src/pkg/net/unix_test.go
index 5e63e9d9de..e1c3ff2215 100644
--- a/src/pkg/net/unix_test.go
+++ b/src/pkg/net/unix_test.go
@@ -212,7 +212,6 @@ func TestUnixgramConnLocalAndRemoteNames(t *testing.T) {\n \n \t\tvar la *UnixAddr\n \t\tif laddr != \"\" {\n-\t\t\tvar err error\n \t\t\tif la, err = ResolveUnixAddr(\"unixgram\", laddr); err != nil {\n \t\t\t\tt.Fatalf(\"ResolveUnixAddr failed: %v\", err)\n \t\t\t}\ndiff --git a/src/pkg/os/path_test.go b/src/pkg/os/path_test.go
index 16c4120dc6..27abf59826 100644
--- a/src/pkg/os/path_test.go
+++ b/src/pkg/os/path_test.go
@@ -91,7 +91,7 @@ func TestRemoveAll(t *testing.T) {\n \tif err = RemoveAll(path); err != nil {\n \t\tt.Fatalf(\"RemoveAll %q (first): %s\", path, err)\n \t}\n-\tif _, err := Lstat(path); err == nil {\n+\tif _, err = Lstat(path); err == nil {\n \t\tt.Fatalf(\"Lstat %q succeeded after RemoveAll (first)\", path)\n \t}\n \n@@ -153,7 +153,7 @@ func TestRemoveAll(t *testing.T) {\n \t\tChmod(dpath, 0777)\n \n \t\tfor _, s := range []string{fpath, path + \"/zzz\"} {\n-\t\t\tif _, err := Lstat(s); err == nil {\n+\t\t\tif _, err = Lstat(s); err == nil {\n \t\t\t\tt.Fatalf(\"Lstat %q succeeded after partial RemoveAll\", s)\n \t\t\t}\n \t\t}\n@@ -161,7 +161,7 @@ func TestRemoveAll(t *testing.T) {\n \tif err = RemoveAll(path); err != nil {\n \t\tt.Fatalf(\"RemoveAll %q after partial RemoveAll: %s\", path, err)\n \t}\n-\tif _, err := Lstat(path); err == nil {\n+\t\tif _, err = Lstat(path); err == nil {\n \t\t\tt.Fatalf(\"Lstat %q succeeded after RemoveAll (final)\", path)\n \t}\n }\ndiff --git a/src/pkg/syscall/syscall_bsd.go b/src/pkg/syscall/syscall_bsd.go
index 3e7870a0a5..7dc57acf9b 100644
--- a/src/pkg/syscall/syscall_bsd.go
+++ b/src/pkg/syscall/syscall_bsd.go
@@ -532,10 +532,9 @@ func Sendmsg(fd int, p, oob []byte, to Sockaddr, flags int) (err error) {\n \tvar ptr uintptr\n \tvar salen _Socklen\n \tif to != nil {\n-\t\tvar err error\n \t\tptr, salen, err = to.sockaddr()\n \t\tif err != nil {\n-\t\t\treturn err\n+\t\t\treturn\n \t\t}\n \t}\n \tvar msg Msghdr\ndiff --git a/src/pkg/time/format.go b/src/pkg/time/format.go
index 7fe0402312..7aa60e75dc 100644
--- a/src/pkg/time/format.go
+++ b/src/pkg/time/format.go
@@ -780,7 +780,7 @@ func parse(layout, value string, defaultLocation, local *Location) (Time, error)\n \t\t\t// Special case: do we have a fractional second but no\n \t\t\t// fractional second in the format?\n \t\t\tif len(value) >= 2 && value[0] == \'.\' && isDigit(value, 1) {\n-\t\t\t\t_, std, _ := nextStdChunk(layout)\n+\t\t\t\t_, std, _ = nextStdChunk(layout)\n \t\t\t\tstd &= stdMask\n \t\t\t\tif std == stdFracSecond0 || std == stdFracSecond9 {\n \t\t\t\t\t// Fractional second in the layout; proceed normally\n```

## 変更の背景

このコミットの背景には、Go言語のコード品質と可読性を向上させるという目的があります。特に、`go vet` ツールとその `shadow` オプションの導入が深く関わっています。

`go vet` は、Goプログラムの静的解析ツールであり、コンパイラが検出できないような疑わしいコード構造や潜在的なバグを特定するために使用されます。その中でも `-shadow` オプションは、変数のシャドーイング、つまり内側のスコープで宣言された変数が外側のスコープの同名の変数を隠してしまうパターンを検出します。

シャドーイング自体はGo言語の文法上は合法であり、コンパイルエラーにはなりません。しかし、これは以下のような問題を引き起こす可能性があります。

1.  **可読性の低下と混乱**: 開発者がどの変数を参照しているのかを誤解しやすくなります。特に、関数やブロックが長くなると、シャドーイングされた変数の存在に気づきにくくなります。
2.  **意図しないバグ**: 外側の変数を更新するつもりで、誤って内側のシャドーイングされた変数を操作してしまうなど、論理的な誤りを引き起こす可能性があります。これは特に、エラー変数 `err` のシャドーイングで頻繁に発生します。

このコミットは、`go vet -shadow` がGo標準ライブラリのコードベースをスキャンした結果、検出されたこれらの「いぼ(warts)」、つまりエラーではないが「貧弱または混乱を招く」コードパターンを体系的に修正するために行われました。これにより、Goの標準ライブラリ自体のコード品質と保守性が向上し、Go言語のベストプラクティスを示す模範となることを目指しています。

## 前提知識の解説

### Go言語におけるスコープと変数宣言

Go言語では、変数は宣言されたブロック(`{}`で囲まれた領域)内で有効です。この有効範囲を「スコープ」と呼びます。

*   **パッケージスコープ**: パッケージレベルで宣言された変数は、そのパッケージ内のどこからでもアクセス可能です。
*   **関数スコープ**: 関数内で宣言された変数は、その関数内でのみ有効です。
*   **ブロックスコープ**: `if`文、`for`ループ、`switch`文などのブロック内で宣言された変数は、そのブロック内でのみ有効です。

### 変数のシャドーイング (Variable Shadowing)

シャドーイングは、内側のスコープで宣言された変数が、外側のスコープで既に宣言されている同名の変数を「隠す」現象です。

```go
package main

import "fmt"

func main() {
	x := 10 // 外側のスコープの x

	if true {
		x := 20 // 内側のスコープの x。外側の x をシャドーイングする
		fmt.Println("Inner x:", x) // 出力: Inner x: 20
	}

	fmt.Println("Outer x:", x) // 出力: Outer x: 10 (外側の x は変更されていない)
}

上記の例では、ifブロック内で x := 20 と新しい変数を宣言しているため、外側の x とは別の x が作成され、外側の xifブロック内からは見えなくなります。これは特に err 変数で問題になりがちです。

package main

import (
	"fmt"
	"os"
)

func main() {
	f, err := os.Open("nonexistent.txt") // err はファイルオープンエラー
	if err != nil {
		fmt.Println("Error opening file:", err)
		return
	}
	defer f.Close()

	// ここで別の操作を行い、誤って err をシャドーイングしてしまう
	if _, err := fmt.Println("File opened successfully"); err != nil { // 新しい err を宣言
		fmt.Println("Error printing:", err) // この err は fmt.Println のエラー
	}

	// ここで f.Close() のエラーをチェックしたいが、
	// もし上の行で err をシャドーイングしていなければ、
	// ここで err を使ってファイルクローズのエラーを処理できたかもしれない。
	// しかし、シャドーイングされているため、上の err は別の err になってしまっている。
}

go vet ツール

go vet は、Go言語のソースコードを静的に解析し、潜在的なバグや疑わしい構造を報告するツールです。Go SDKに標準で含まれており、go vet ./... のように実行することでプロジェクト全体のコードをチェックできます。

go vet は様々なチェック(アナライザ)を持っており、その一つが -shadow オプションによって有効になるシャドーイング検出です。

「Warts」(いぼ)という表現

コミットメッセージにある「warts」という言葉は、直訳すると「いぼ」ですが、プログラミングの文脈では「些細だが不快な欠陥」「コードの品質を損なうが、必ずしもエラーではない問題」といった意味合いで使われます。このコミットでは、シャドーイングがまさにそのような「いぼ」であると認識され、修正の対象となりました。

技術的詳細

このコミットで行われた技術的な変更は、主に以下のパターンに集約されます。

  1. := から = への変更:

    • 最も一般的な修正パターンは、変数の再代入時に := (短い変数宣言) を使用している箇所を = (代入) に変更することです。
    • := は変数の宣言と初期化を同時に行いますが、その変数が既に現在のスコープで宣言されている場合、Goは新しい変数を宣言するのではなく、既存の変数に値を再代入します。しかし、内側のスコープで同名の変数が存在しない場合、:= は新しい変数を宣言し、外側の変数をシャドーイングしてしまいます。
    • このコミットでは、特に if err := ...; err != nil のようなパターンで、err が既に外側のスコープで宣言されているにもかかわらず、内側の if ブロックで再度 err := ... と宣言されている箇所が修正されています。これを err = ... に変更することで、外側の err 変数に結果が代入され、シャドーイングが解消されます。

    例:

    - if err := os.MkdirAll(parent, 0777); err != nil {
    + if err = os.MkdirAll(parent, 0777); err != nil {
    

    この変更により、os.MkdirAll が返すエラーは、if文の外側で既に宣言されている err 変数に代入されるようになります。

  2. 変数のリネーム:

    • シャドーイングを避けるために、内側のスコープで宣言される変数の名前を、外側のスコープの変数と異なる名前に変更するパターンです。
    • これは、:== に変更することが適切でない場合(例えば、異なる意味を持つ変数を意図的に宣言している場合)や、シャドーイングによって混乱が生じる可能性が高い場合に適用されます。

    例:

    - var params fieldParameters
    - for i := 0; i < v.Len(); i++ {
    - 	var pre *forkableWriter
    - 	pre, out = out.fork()
    - 	err = marshalField(pre, v.Index(i), params)
    + var fp fieldParameters // params を fp にリネーム
    + for i := 0; i < v.Len(); i++ {
    + 	var pre *forkableWriter
    + 	pre, out = out.fork()
    + 	err = marshalField(pre, v.Index(i), fp) // リネームされた変数を使用
    

    この例では、params という変数がループ内でシャドーイングされる可能性があったため、fp にリネームされています。

    - elemOp, indir := enc.encOpFor(t.Elem(), inProgress)
    + elemOp, elemIndir := enc.encOpFor(t.Elem(), inProgress) // indir を elemIndir にリネーム
    

    この変更では、indir という変数がシャドーイングされる可能性があったため、より具体的な名前である elemIndir に変更されています。

  3. 不要な変数宣言の削除:

    • 一部のケースでは、変数が宣言されているものの、その後のコードで実際に使用されていない、またはシャドーイングの原因となっているため、宣言自体が不要と判断され削除されています。

    例:

    - var err error // この err は次の行でシャドーイングされるため不要
    	c, err := ln.Accept()
    

    この例では、var err error が宣言されていますが、直後の c, err := ln.Accept() で新しい err が宣言され、前の err をシャドーイングしていました。そのため、最初の var err error は不要と判断され削除されました。

これらの変更は、Go言語のコードスタイルガイドラインと go vet の推奨事項に沿ったものであり、コードの意図をより明確にし、潜在的なバグのリスクを低減することを目的としています。

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

このコミットは、Go標準ライブラリの複数のパッケージにわたる広範な変更を含んでいます。以下に主要な変更箇所をファイルごとに示します。

  • src/cmd/go/get.go:
    • downloadPackage 関数内で、os.MkdirAll のエラーハンドリングにおいて、err := ...err = ... に変更し、既存の err 変数への再代入に修正。
  • src/pkg/encoding/asn1/marshal.go:
    • marshalBody 関数内で、params 変数のシャドーイングを避けるため、var params fieldParametersvar fp fieldParameters に変更し、その後の参照も fp に修正。
  • src/pkg/encoding/gob/encode.go:
    • encOpFor 関数内で、elemOp, indir := ...indirelemIndir にリネームし、シャドーイングを回避。配列とスライスの両方のケースで同様の修正。
  • src/pkg/fmt/print.go:
    • printArg および printValue 関数内で、wasString 変数のシャドーイングを避けるため、wasStringisString にリネーム。
  • src/pkg/fmt/scan.go:
    • convertFloat 関数内で、n, err := strconv.Atoi(...)nm にリネームし、シャドーイングを回避。
    • quotedString 関数内で、r := s.mustReadRune()s.buf.WriteRune(r)s.buf.WriteRune(s.mustReadRune()) に統合し、一時変数 r の宣言を削除。
  • src/pkg/net/dial_test.go:
    • TestDialer 関数内で、go func() { ... } 内の var err error の宣言を削除。これは直後の c, err := ln.Accept()err がシャドーイングされるため不要。
  • src/pkg/net/unix_test.go:
    • TestUnixgramConnLocalAndRemoteNames 関数内で、if laddr != "" { ... } 内の var err error の宣言を削除。これも直後の if la, err = ResolveUnixAddr(...)err がシャドーイングされるため不要。
  • src/pkg/os/path_test.go:
    • TestRemoveAll 関数内で、if _, err := Lstat(path); err == nil { ... }err := ...err = ... に変更し、既存の err 変数への再代入に修正。同様の修正が複数箇所に適用。
  • src/pkg/syscall/syscall_bsd.go:
    • Sendmsg 関数内で、if to != nil { ... } 内の var err error の宣言を削除。これは直後の ptr, salen, err = to.sockaddr()err がシャドーイングされるため不要。また、return errreturn に変更し、名前付き戻り値 err を利用するように修正。
  • src/pkg/time/format.go:
    • parse 関数内で、_, std, _ := nextStdChunk(layout)std := ...std = ... に変更し、既存の std 変数への再代入に修正。

コアとなるコードの解説

src/cmd/go/get.go の変更

--- a/src/cmd/go/get.go
+++ b/src/cmd/go/get.go
@@ -286,7 +286,7 @@ func downloadPackage(p *Package) error {
 		}
 		// Some version control tools require the parent of the target to exist.
 		parent, _ := filepath.Split(root)
-		if err := os.MkdirAll(parent, 0777); err != nil {
+		if err = os.MkdirAll(parent, 0777); err != nil {
 			return err
 		}
 		if err = vcs.create(root, repo); err != nil {

この変更は、downloadPackage 関数内で os.MkdirAll の呼び出し結果を処理する部分です。元のコードでは if err := os.MkdirAll(...) となっており、これは if ブロック内で新しい err 変数を宣言し、外側のスコープの err をシャドーイングしていました。修正後は if err = os.MkdirAll(...) となり、外側のスコープで既に宣言されている err 変数に os.MkdirAll の結果が代入されるようになります。これにより、err 変数のシャドーイングが解消され、コードの意図が明確になります。

src/pkg/encoding/asn1/marshal.go の変更

--- a/src/pkg/encoding/asn1/marshal.go
+++ b/src/pkg/encoding/asn1/marshal.go
@@ -441,11 +441,11 @@ func marshalBody(out *forkableWriter, value reflect.Value, params fieldParameter
 			return
 		}
 
-		var params fieldParameters
+		var fp fieldParameters
 		for i := 0; i < v.Len(); i++ {
 			var pre *forkableWriter
 			pre, out = out.fork()
-			err = marshalField(pre, v.Index(i), params)
+			err = marshalField(pre, v.Index(i), fp)
 			if err != nil {
 				return
 			}

ここでは、marshalBody 関数内のループ処理において、params という変数がシャドーイングされる可能性がありました。元のコードでは var params fieldParameters と宣言されていますが、この params が外側の関数の引数 params と同名であるため、内側の params が外側の params をシャドーイングしていました。これを var fp fieldParameters とリネームすることで、名前の衝突を避け、コードの可読性を向上させています。

src/pkg/encoding/gob/encode.go の変更

--- a/src/pkg/encoding/gob/encode.go
+++ b/src/pkg/encoding/gob/encode.go
@@ -575,21 +575,21 @@ func (enc *Encoder) encOpFor(rt reflect.Type, inProgress map[reflect.Type]*encOp
 				break
 			}
 			// Slices have a header; we decode it to find the underlying array.
-			elemOp, indir := enc.encOpFor(t.Elem(), inProgress)
+			elemOp, elemIndir := enc.encOpFor(t.Elem(), inProgress)
 			op = func(i *encInstr, state *encoderState, p unsafe.Pointer) {
 				slice := (*reflect.SliceHeader)(p)
 				if !state.sendZero && slice.Len == 0 {
 					return
 				}
 				state.update(i)
-				state.enc.encodeArray(state.b, unsafe.Pointer(slice.Data), *elemOp, t.Elem().Size(), indir, int(slice.Len))
+				state.enc.encodeArray(state.b, unsafe.Pointer(slice.Data), *elemOp, t.Elem().Size(), elemIndir, int(slice.Len))
 			}
 		case reflect.Array:
 			// True arrays have size in the type.
-			elemOp, indir := enc.encOpFor(t.Elem(), inProgress)
+			elemOp, elemIndir := enc.encOpFor(t.Elem(), inProgress)
 			op = func(i *encInstr, state *encoderState, p unsafe.Pointer) {
 				state.update(i)
-				state.enc.encodeArray(state.b, p, *elemOp, t.Elem().Size(), indir, t.Len())
+				state.enc.encodeArray(state.b, p, *elemOp, t.Elem().Size(), elemIndir, t.Len())
 			}
 		case reflect.Map:
 			keyOp, keyIndir := enc.encOpFor(t.Key(), inProgress)

この変更は、encOpFor 関数内でスライスと配列のエンコード処理を行う部分です。元のコードでは elemOp, indir := enc.encOpFor(...) となっており、indir という変数が宣言されています。この indir が、おそらく他の場所で使われている indir と混同される可能性があったため、より具体的な意味を持つ elemIndir にリネームされています。これにより、変数の役割が明確になり、シャドーイングによる混乱が避けられます。

src/pkg/fmt/print.go の変更

--- a/src/pkg/fmt/print.go
+++ b/src/pkg/fmt/print.go
@@ -811,8 +811,8 @@ func (p *pp) printArg(arg interface{}, verb rune, plus, goSyntax bool, depth int
 		p.fmt.plus = oldPlus
 		p.fmt.sharp = oldSharp
 		// If the type is not simple, it might have methods.
-		if wasString, handled := p.handleMethods(verb, plus, goSyntax, depth); handled {
-			return wasString
+		if isString, handled := p.handleMethods(verb, plus, goSyntax, depth); handled {
+			return isString
 		}
 		// Need to use reflection
 		return p.printReflectValue(reflect.ValueOf(arg), verb, plus, goSyntax, depth)
@@ -849,8 +849,8 @@ func (p *pp) printValue(value reflect.Value, verb rune, plus, goSyntax bool, dep
 	if value.CanInterface() {
 		p.arg = value.Interface()
 	}
-	if wasString, handled := p.handleMethods(verb, plus, goSyntax, depth); handled {
-		return wasString
+	if isString, handled := p.handleMethods(verb, plus, goSyntax, depth); handled {
+		return isString
 	}
 
 	return p.printReflectValue(value, verb, plus, goSyntax, depth)

printArgprintValue 関数において、wasString という変数が isString にリネームされています。これは、変数の意味をより明確にし、シャドーイングの可能性を減らすための変更です。wasString は過去形であり、現在の状態を示す isString の方が適切であるという判断も含まれている可能性があります。

src/pkg/fmt/scan.go の変更

--- a/src/pkg/fmt/scan.go
+++ b/src/pkg/fmt/scan.go
@@ -781,7 +781,7 @@ func (s *ss) convertFloat(str string, n int) float64 {
 			}
 			s.error(err)
 		}
-		n, err := strconv.Atoi(str[p+1:])
+		m, err := strconv.Atoi(str[p+1:])
 		if err != nil {
 			// Put full string into error.
 			// if e, ok := err.(*strconv.NumError); ok {
@@ -789,7 +789,7 @@ func (s *ss) convertFloat(str string, n int) float64 {
 			s.error(err)
 		}
-		return math.Ldexp(f, n)
+		return math.Ldexp(f, m)
 	}
 	f, err := strconv.ParseFloat(str, n)
 	if err != nil {
@@ -858,8 +858,7 @@ func (s *ss) quotedString() string {
 				// In a legal backslash escape, no matter how long, only the character
 				// immediately after the escape can itself be a backslash or quote.
 				// Thus we only need to protect the first character after the backslash.
-				r := s.mustReadRune()
-				s.buf.WriteRune(r)
+				s.buf.WriteRune(s.mustReadRune())
 			} else if r == '"' {
 				break
 			}

convertFloat 関数では、n, err := strconv.Atoi(...)nm にリネームされています。これは、関数の引数にも n が存在するため、シャドーイングを避けるための変更です。

quotedString 関数では、r := s.mustReadRune()s.buf.WriteRune(r) の2行が s.buf.WriteRune(s.mustReadRune()) の1行にまとめられています。これにより、一時変数 r の宣言が不要になり、シャドーイングの可能性が排除されるとともに、コードがより簡潔になっています。

src/pkg/net/dial_test.go および src/pkg/net/unix_test.go の変更

--- a/src/pkg/net/dial_test.go
+++ b/src/pkg/net/dial_test.go
@@ -381,7 +381,6 @@ func TestDialer(t *testing.T) {
 	defer ln.Close()
 	ch := make(chan error, 1)
 	go func() {
-\t\tvar err error
 		c, err := ln.Accept()
 		if err != nil {
 			ch <- fmt.Errorf("Accept failed: %v", err)
--- a/src/pkg/net/unix_test.go
+++ b/src/pkg/net/unix_test.go
@@ -212,7 +212,6 @@ func TestUnixgramConnLocalAndRemoteNames(t *testing.T) {
 
 		var la *UnixAddr
 		if laddr != "" {
-\t\t\tvar err error
 			if la, err = ResolveUnixAddr("unixgram", laddr); err != nil {
 				t.Fatalf("ResolveUnixAddr failed: %v", err)
 			}

これらのテストファイルでは、var err error のような不要な変数宣言が削除されています。これらの err 変数は、直後の c, err := ...la, err = ... のような短い変数宣言によってシャドーイングされるため、冗長であり、go vet -shadow によって検出されていました。宣言を削除することで、コードがよりクリーンになります。

src/pkg/os/path_test.go の変更

--- a/src/pkg/os/path_test.go
+++ b/src/pkg/os/path_test.go
@@ -91,7 +91,7 @@ func TestRemoveAll(t *testing.T) {
 	if err = RemoveAll(path); err != nil {
 		t.Fatalf("RemoveAll %q (first): %s", path, err)
 	}
-	if _, err := Lstat(path); err == nil {
+	if _, err = Lstat(path); err == nil {
 		t.Fatalf("Lstat %q succeeded after RemoveAll (first)", path)
 	}

この変更は src/cmd/go/get.go と同様に、if _, err := Lstat(...)err := ...err = ... に変更し、既存の err 変数への再代入に修正しています。これにより、Lstat が返すエラーが外側のスコープの err 変数に適切に代入され、シャドーイングが解消されます。

src/pkg/syscall/syscall_bsd.go の変更

--- a/src/pkg/syscall/syscall_bsd.go
+++ b/src/pkg/syscall/syscall_bsd.go
@@ -532,10 +532,9 @@ func Sendmsg(fd int, p, oob []byte, to Sockaddr, flags int) (err error) {
 	var ptr uintptr
 	var salen _Socklen
 	if to != nil {
-\t\tvar err error
 		ptr, salen, err = to.sockaddr()
 		if err != nil {
-\t\t\treturn err
+\t\t\treturn
 		}
 	}
 	var msg Msghdr

ここでも var err error の不要な宣言が削除されています。また、return errreturn に変更されています。Goでは、関数に名前付き戻り値(この場合は err error)がある場合、return だけを記述すると、その時点での名前付き戻り値の現在の値が返されます。この場合、to.sockaddr() がエラーを返した場合、そのエラーが名前付き戻り値 err に代入されているため、明示的に return err と書く必要はありません。これにより、コードがより簡潔になります。

src/pkg/time/format.go の変更

--- a/src/pkg/time/format.go
+++ b/src/pkg/time/format.go
@@ -780,7 +780,7 @@ func parse(layout, value string, defaultLocation, local *Location) (Time, error)\n 			// Special case: do we have a fractional second but no\n 			// fractional second in the format?\n 			if len(value) >= 2 && value[0] == '.' && isDigit(value, 1) {
-\t\t\t\t_, std, _ := nextStdChunk(layout)\n+\t\t\t\t_, std, _ = nextStdChunk(layout)\n 				std &= stdMask
 				if std == stdFracSecond0 || std == stdFracSecond9 {
 					// Fractional second in the layout; proceed normally

この変更も src/cmd/go/get.gosrc/pkg/os/path_test.go と同様に、_, std, _ := nextStdChunk(layout)std := ...std = ... に変更し、既存の std 変数への再代入に修正しています。これにより、std 変数のシャドーイングが解消されます。

これらの変更は、Go言語のコードベース全体で一貫したスタイルと品質を維持するための継続的な取り組みの一環であり、go vet -shadow のような静的解析ツールの重要性を示しています。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメントおよびブログ
  • Go言語のソースコード(コミット差分)
  • go vet の機能に関する一般的な知識