[インデックス 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
が作成され、外側の x
は if
ブロック内からは見えなくなります。これは特に 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」という言葉は、直訳すると「いぼ」ですが、プログラミングの文脈では「些細だが不快な欠陥」「コードの品質を損なうが、必ずしもエラーではない問題」といった意味合いで使われます。このコミットでは、シャドーイングがまさにそのような「いぼ」であると認識され、修正の対象となりました。
技術的詳細
このコミットで行われた技術的な変更は、主に以下のパターンに集約されます。
-
:=
から=
への変更:- 最も一般的な修正パターンは、変数の再代入時に
:=
(短い変数宣言) を使用している箇所を=
(代入) に変更することです。 :=
は変数の宣言と初期化を同時に行いますが、その変数が既に現在のスコープで宣言されている場合、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
変数に代入されるようになります。 - 最も一般的な修正パターンは、変数の再代入時に
-
変数のリネーム:
- シャドーイングを避けるために、内側のスコープで宣言される変数の名前を、外側のスコープの変数と異なる名前に変更するパターンです。
- これは、
:=
を=
に変更することが適切でない場合(例えば、異なる意味を持つ変数を意図的に宣言している場合)や、シャドーイングによって混乱が生じる可能性が高い場合に適用されます。
例:
- 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
に変更されています。 -
不要な変数宣言の削除:
- 一部のケースでは、変数が宣言されているものの、その後のコードで実際に使用されていない、またはシャドーイングの原因となっているため、宣言自体が不要と判断され削除されています。
例:
- 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 fieldParameters
をvar fp fieldParameters
に変更し、その後の参照もfp
に修正。
src/pkg/encoding/gob/encode.go
:encOpFor
関数内で、elemOp, indir := ...
のindir
をelemIndir
にリネームし、シャドーイングを回避。配列とスライスの両方のケースで同様の修正。
src/pkg/fmt/print.go
:printArg
およびprintValue
関数内で、wasString
変数のシャドーイングを避けるため、wasString
をisString
にリネーム。
src/pkg/fmt/scan.go
:convertFloat
関数内で、n, err := strconv.Atoi(...)
のn
をm
にリネームし、シャドーイングを回避。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 err
をreturn
に変更し、名前付き戻り値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)
printArg
と printValue
関数において、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(...)
の n
が m
にリネームされています。これは、関数の引数にも 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 err
が return
に変更されています。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.go
や src/pkg/os/path_test.go
と同様に、_, std, _ := nextStdChunk(layout)
の std := ...
を std = ...
に変更し、既存の std
変数への再代入に修正しています。これにより、std
変数のシャドーイングが解消されます。
これらの変更は、Go言語のコードベース全体で一貫したスタイルと品質を維持するための継続的な取り組みの一環であり、go vet -shadow
のような静的解析ツールの重要性を示しています。
関連リンク
- Go vet documentation:
go vet
ツールの公式ドキュメント。 - Go blog post about
go vet
:go vet
の機能や使い方について解説したGo公式ブログの記事。 - Go Code Review Comments - Variable Shadowing: Goのコードレビューガイドラインにおける変数シャドーイングに関する項目。
参考にした情報源リンク
- Go言語の公式ドキュメントおよびブログ
- Go言語のソースコード(コミット差分)
go vet
の機能に関する一般的な知識