[インデックス 12048] ファイルの概要
このコミットは、Go言語の標準ライブラリos
パッケージからGetenverror
関数を削除するものです。Getenverror
は環境変数の値を取得し、存在しない場合にはエラーを返す関数でしたが、このコミットにより、環境変数が存在しない場合と空文字列である場合を区別するための推奨される方法が変更されました。具体的には、os.Environ
またはsyscall.Getenv
を使用することが推奨されるようになりました。
コミット
commit efacb2a1b48df1a389289c045754ddb30f1a4038
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date: Sat Feb 18 21:18:13 2012 -0800
os: remove Getenverror
Fixes #3065
R=golang-dev, dsymonds, rsc
CC=golang-dev
https://golang.org/cl/5675094
---
doc/go1.html | 7 +++++++
doc/go1.tmpl | 7 +++++++
misc/dashboard/builder/main.go | 20 ++++++++++++++++----\
src/pkg/os/env.go | 23 ++---------------------\
test/env.go | 12 ++++--------
5 files changed, 36 insertions(+), 33 deletions(-)
diff --git a/doc/go1.html b/doc/go1.html
index 59d8e25246..9e98a9782f 100644
--- a/doc/go1.html
+++ b/doc/go1.html
@@ -1451,7 +1451,14 @@ with more Go-like names, such as
<a href="/pkg/os/#ErrPermission"><code>ErrPermission</code></a>
and
<a href="/pkg/os/#ErrNoEnv"><code>ErrNoEnv</code></a>.
+</p>
\n+<p>
+The <code>Getenverror</code> function has been removed. To distinguish
+between a non-existent environment variable and an empty string,
+use <a href="/pkg/os/#Environ"><code>os.Environ</code></a> or
+<a href="/pkg/syscall/#Getenv"><code>syscall.Getenv</code></a>.
+</p>
\n <p>
<em>Updating</em>:
diff --git a/doc/go1.tmpl b/doc/go1.tmpl
index 58eb1073bd..6155fb41cf 100644
--- a/doc/go1.tmpl
+++ b/doc/go1.tmpl
@@ -1354,7 +1354,14 @@ with more Go-like names, such as
<a href="/pkg/os/#ErrPermission"><code>ErrPermission</code></a>
and
<a href="/pkg/os/#ErrNoEnv"><code>ErrNoEnv</code></a>.
+</p>
\n+<p>
+The <code>Getenverror</code> function has been removed. To distinguish
+between a non-existent environment variable and an empty string,
+use <a href="/pkg/os/#Environ"><code>os.Environ</code></a> or
+<a href="/pkg/syscall/#Getenv"><code>syscall.Getenv</code></a>.
+</p>
\n <p>
<em>Updating</em>:
diff --git a/misc/dashboard/builder/main.go b/misc/dashboard/builder/main.go
index 7ca627670b..5d0d6b2960 100644
--- a/misc/dashboard/builder/main.go
+++ b/misc/dashboard/builder/main.go
@@ -480,8 +480,7 @@ func (b *Builder) envv() []string {\n \t\t\"GOROOT_FINAL=/usr/local/go\",\n \t}\n \tfor _, k := range extraEnv {\n-\t\ts, err := os.Getenverror(k)\n-\t\tif err == nil {\n+\t\tif s, ok := getenvOk(k); ok {\n \t\t\te = append(e, k+\"=\"+s)\n \t\t}\n \t}\n@@ -497,8 +496,7 @@ func (b *Builder) envvWindows() []string {\n \t\t\"GOBUILDEXIT\": \"1\", // exit all.bat with completion status.\n \t}\n \tfor _, name := range extraEnv {\n-\t\ts, err := os.Getenverror(name)\n-\t\tif err == nil {\n+\t\tif s, ok := getenvOk(name); ok {\n \t\t\tstart[name] = s\n \t\t}\n \t}\n@@ -782,3 +780,17 @@ func defaultSuffix() string {\n \t}\n \treturn \".bash\"\n }\n+\n+func getenvOk(k string) (v string, ok bool) {\n+\tv = os.Getenv(k)\n+\tif v != \"\" {\n+\t\treturn v, true\n+\t}\n+\tkeq := k + \"=\"\n+\tfor _, kv := range os.Environ() {\n+\t\tif kv == keq {\n+\t\t\treturn \"\", true\n+\t\t}\n+\t}\n+\treturn \"\", false\n+}\ndiff --git a/src/pkg/os/env.go b/src/pkg/os/env.go
index 207e0a0ec7..eb265f2413 100644
--- a/src/pkg/os/env.go
+++ b/src/pkg/os/env.go
@@ -6,10 +6,7 @@
package os
-import (
-\t\"errors\"\n-\t\"syscall\"\n-)
+import \"syscall\"\n \n // Expand replaces ${var} or $var in the string based on the mapping function.\n // Invocations of undefined variables are replaced with the empty string.\n@@ -77,26 +74,10 @@ func getShellName(s string) (string, int) {\n \treturn s[:i], i\n }\n \n-// ENOENV is the error indicating that an environment variable does not exist.\n-var ENOENV = errors.New(\"no such environment variable\")\n-\n-// Getenverror retrieves the value of the environment variable named by the key.\n-// It returns the value and an error, if any.\n-func Getenverror(key string) (value string, err error) {\n-\tif len(key) == 0 {\n-\t\treturn \"\", ErrInvalid\n-\t}\n-\tval, found := syscall.Getenv(key)\n-\tif !found {\n-\t\treturn \"\", ENOENV\n-\t}\n-\treturn val, nil\n-}\n-\n // Getenv retrieves the value of the environment variable named by the key.\n // It returns the value, which will be empty if the variable is not present.\n func Getenv(key string) string {\n-\tv, _ := Getenverror(key)\n+\tv, _ := syscall.Getenv(key)\n \treturn v\n }\n \ndiff --git a/test/env.go b/test/env.go
index 4dcf4443a7..972374679a 100644
--- a/test/env.go
+++ b/test/env.go
@@ -15,18 +15,14 @@ import (\n )\n \n func main() {\n-\tga, e0 := os.Getenverror(\"GOARCH\")\n-\tif e0 != nil {\n-\t\tprint(\"$GOARCH: \", e0.Error(), \"\\n\")\n-\t\tos.Exit(1)\n-\t}\n+\tga := os.Getenv(\"GOARCH\")\n \tif ga != runtime.GOARCH {\n \t\tprint(\"$GOARCH=\", ga, \"!= runtime.GOARCH=\", runtime.GOARCH, \"\\n\")\n \t\tos.Exit(1)\n \t}\n-\txxx, e1 := os.Getenverror(\"DOES_NOT_EXIST\")\n-\tif e1 != os.ENOENV {\n-\t\tprint(\"$DOES_NOT_EXIST=\", xxx, \"; err = \", e1.Error(), \"\\n\")\n+\txxx := os.Getenv(\"DOES_NOT_EXIST\")\n+\tif xxx != \"\" {\n+\t\tprint(\"$DOES_NOT_EXIST=\", xxx, \"\\n\")\n \t\tos.Exit(1)\n \t}\n }\
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/efacb2a1b48df1a389289c045754ddb30f1a4038
元コミット内容
このコミットは、Go言語のos
パッケージからGetenverror
関数を削除することを目的としています。この変更は、Issue 3065を修正するものです。Getenverror
は環境変数の値を取得し、その環境変数が存在しない場合にはエラーを返していました。しかし、環境変数が存在しない場合と、環境変数が存在するがその値が空文字列である場合とを区別するために、os.Environ
またはsyscall.Getenv
を使用することが推奨されるようになりました。
変更の背景
Go言語の初期のバージョンでは、os.Getenv
関数は環境変数が存在しない場合と空文字列である場合を区別できませんでした。どちらの場合も空文字列を返していました。この挙動は、環境変数の存在自体が意味を持つシナリオ(例えば、特定の機能の有効/無効を環境変数で制御する場合など)において問題となることがありました。
この問題を解決するために、os.Getenverror
関数が導入されました。この関数は、環境変数が存在しない場合にos.ENOENV
という特定のエラーを返すことで、os.Getenv
では区別できなかった「存在しない」状態を明確に示せるようにしました。
しかし、Go言語の設計思想として、エラーは本当に例外的な状況を示すべきであり、一般的な制御フローの一部として使用すべきではないという考え方があります。環境変数の存在チェックは、多くの場合は例外的な状況ではなく、通常のプログラムロジックの一部と見なされます。
また、syscall.Getenv
は、環境変数の値と、その環境変数が存在したかどうかを示す真偽値の2つの値を返します。これは、エラーを返さずに環境変数の存在をチェックする、よりGoらしいイディオムです。os.Environ
もまた、すべての環境変数をKEY=VALUE
形式の文字列スライスとして返すため、環境変数の存在を直接確認できます。
このような背景から、Getenverror
はGoのエラーハンドリングのイディオムにそぐわないと判断され、より適切な代替手段(syscall.Getenv
の真偽値戻り値やos.Environ
)が存在するため、削除されることになりました。これにより、GoのAPIはより一貫性のあるものとなり、開発者は環境変数の存在チェックによりGoらしい方法で対処できるようになります。
前提知識の解説
環境変数 (Environment Variables)
環境変数とは、オペレーティングシステムが提供する動的な名前付きの値の集合です。これらは、実行中のプロセスに設定情報や構成データを提供するために使用されます。例えば、プログラムの実行パス、データベース接続文字列、デバッグフラグなどが環境変数として設定されることがあります。
Go言語における環境変数の操作
Go言語の標準ライブラリos
パッケージは、環境変数を操作するための関数を提供しています。
-
os.Getenv(key string) string
: 指定されたkey
に対応する環境変数の値を取得します。環境変数が存在しない場合や、値が空文字列の場合、この関数は空文字列""
を返します。このため、環境変数が「存在しない」のか「空文字列である」のかを区別できません。 -
os.Environ() []string
: 現在のプロセスの環境変数をすべて取得し、"KEY=VALUE"
形式の文字列スライスとして返します。このスライスをイテレートすることで、特定の環境変数が存在するかどうか、またその値が何かを正確に判断できます。 -
syscall.Getenv(key string) (value string, found bool)
:syscall
パッケージは、低レベルのシステムコールへのインターフェースを提供します。syscall.Getenv
は、指定されたkey
に対応する環境変数の値と、その環境変数が存在したかどうかを示す真偽値(found
)を返します。このfound
戻り値があるため、os.Getenv
では区別できなかった「存在しない」状態を明確に区別できます。
Go言語のエラーハンドリングのイディオム
Go言語では、エラーハンドリングは通常、関数の最後の戻り値としてerror
型を返すことで行われます。慣例として、エラーが発生しなかった場合はnil
を返します。
value, err := someFunction()
if err != nil {
// エラー処理
}
// value を使用
しかし、Goの設計思想では、エラーは「予期せぬ問題」や「例外的な状況」を示すべきであり、通常の制御フローの一部として使用すべきではないとされています。例えば、マップからキーを検索する際にキーが存在しない場合、Goではエラーを返すのではなく、value, ok := myMap[key]
のように真偽値ok
を返すイディオムが一般的です。これは、キーの存在チェックが通常の操作の一部であり、エラーと見なすべきではないという考えに基づいています。
os.Getenverror
は、環境変数の「非存在」をエラーとして扱っていたため、このGoのイディオムにそぐわないと判断されました。
技術的詳細
このコミットの技術的な核心は、os.Getenverror
関数の削除と、それに伴う環境変数取得ロジックの変更です。
-
os.Getenverror
の削除:src/pkg/os/env.go
からGetenverror
関数とその関連するエラー変数ENOENV
が完全に削除されました。これにより、os
パッケージの外部からこの関数を呼び出すことはできなくなります。 -
os.Getenv
の実装変更: 以前のos.Getenv
は内部的にos.Getenverror
を呼び出し、そのエラーを無視していました。// 変更前 func Getenv(key string) string { v, _ := Getenverror(key) return v }
このコミットにより、
os.Getenv
は直接syscall.Getenv
を呼び出すように変更されました。syscall.Getenv
は値と真偽値(存在するかどうか)を返しますが、os.Getenv
は真偽値を無視して値のみを返します。この挙動は、環境変数が存在しない場合と空文字列の場合に空文字列を返すというos.Getenv
の元々のセマンティクスを維持します。// 変更後 func Getenv(key string) string { v, _ := syscall.Getenv(key) // syscall.Getenvは (string, bool) を返す return v }
-
misc/dashboard/builder/main.go
の適応:misc/dashboard/builder/main.go
内のコードは、以前os.Getenverror
を使用して環境変数を取得していました。このコミットでは、os.Getenverror
の削除に伴い、getenvOk
という新しいヘルパー関数が導入されました。getenvOk
関数は、os.Getenv
を使用して環境変数の値を取得し、その値が空文字列でない場合はtrue
を返します。しかし、値が空文字列の場合でも、os.Environ
を走査してKEY=
という形式の環境変数が存在するかどうかを確認することで、「空文字列だが存在する」ケースと「存在しない」ケースを区別します。 このgetenvOk
関数は、os.Getenverror
が提供していた「存在しない場合にエラーを返す」という機能の代替として、os.Getenv
とos.Environ
を組み合わせて同様のロジックを再実装したものです。 -
テストコードの更新:
test/env.go
内のテストコードも、os.Getenverror
の削除に合わせて更新されました。以前はos.Getenverror
の戻り値とos.ENOENV
エラーをチェックしていましたが、変更後はos.Getenv
の戻り値(空文字列かどうか)のみをチェックするように簡略化されています。これは、os.Getenv
が環境変数の非存在をエラーとして扱わなくなったことを反映しています。
この変更により、Goの標準ライブラリは、環境変数の存在チェックに関してより一貫性のあるイディオム(syscall.Getenv
の真偽値戻り値やos.Environ
の利用)を推奨する形に進化しました。
コアとなるコードの変更箇所
このコミットにおける主要なコード変更は以下のファイルに集中しています。
-
src/pkg/os/env.go
:Getenverror
関数とその関連するENOENV
エラー変数が削除されました。Getenv
関数が、内部でGetenverror
を呼び出す代わりに、直接syscall.Getenv
を呼び出すように変更されました。
-
misc/dashboard/builder/main.go
:os.Getenverror
の呼び出しが、新しく定義されたヘルパー関数getenvOk
の呼び出しに置き換えられました。getenvOk
関数が追加されました。この関数は、os.Getenv
とos.Environ
を組み合わせて、環境変数が存在するかどうかを判断します。
-
test/env.go
:os.Getenverror
を使用していたテストロジックが、os.Getenv
の戻り値(空文字列かどうか)を直接チェックするように変更されました。
-
doc/go1.html
およびdoc/go1.tmpl
:- Go 1のドキュメントに、
Getenverror
が削除されたことと、環境変数の非存在と空文字列を区別するための新しい推奨方法(os.Environ
またはsyscall.Getenv
)が追記されました。
- Go 1のドキュメントに、
コアとなるコードの解説
src/pkg/os/env.go
の変更
--- a/src/pkg/os/env.go
+++ b/src/pkg/os/env.go
@@ -6,10 +6,7 @@
package os
-import (
-\t"errors"\n-\t"syscall"\n-)
+import "syscall"\n
// Expand replaces ${var} or $var in the string based on the mapping function.
// Invocations of undefined variables are replaced with the empty string.\n //
@@ -77,26 +74,10 @@ func getShellName(s string) (string, int) {\n return s[:i], i\n }\n \n-// ENOENV is the error indicating that an environment variable does not exist.\n-var ENOENV = errors.New("no such environment variable")\n-\n-// Getenverror retrieves the value of the environment variable named by the key.\n-// It returns the value and an error, if any.\n-func Getenverror(key string) (value string, err error) {\n-\tif len(key) == 0 {\n-\t\treturn "", ErrInvalid\n-\t}\n-\tval, found := syscall.Getenv(key)\n-\tif !found {\n-\t\treturn "", ENOENV
-\t}\n-\treturn val, nil
-}\n-\n // Getenv retrieves the value of the environment variable named by the key.\n // It returns the value, which will be empty if the variable is not present.\n func Getenv(key string) string {\n-\tv, _ := Getenverror(key)\n+\tv, _ := syscall.Getenv(key)\n \treturn v
}\n```
この変更は、`os`パッケージの環境変数操作の根幹に関わります。
* `errors`パッケージのインポートと`ENOENV`変数が削除されました。これは、環境変数の非存在をエラーとして扱わないという新しい方針を反映しています。
* `Getenverror`関数が完全に削除されました。
* `Getenv`関数は、以前`Getenverror`を呼び出していた部分を`syscall.Getenv`の呼び出しに置き換えました。`syscall.Getenv`は`value, found`の2つの戻り値を持ちますが、`Getenv`は`found`(環境変数の存在を示す真偽値)を`_`で破棄し、`value`のみを返します。これにより、`os.Getenv`の既存のセマンティクス(存在しない場合や空文字列の場合に空文字列を返す)が維持されます。
### `misc/dashboard/builder/main.go` の変更
```diff
--- a/misc/dashboard/builder/main.go
+++ b/misc/dashboard/builder/main.go
@@ -480,8 +480,7 @@ func (b *Builder) envv() []string {\n "GOROOT_FINAL=/usr/local/go",\n }\n for _, k := range extraEnv {\n-\t\ts, err := os.Getenverror(k)\n-\t\tif err == nil {\n+\t\tif s, ok := getenvOk(k); ok {\n \t\t\te = append(e, k+"="+s)\n \t\t}\n \t}\
@@ -497,8 +496,7 @@ func (b *Builder) envvWindows() []string {\n "GOBUILDEXIT": "1", // exit all.bat with completion status.\n }\n for _, name := range extraEnv {\n-\t\ts, err := os.Getenverror(name)\n-\t\tif err == nil {\n+\t\tif s, ok := getenvOk(name); ok {\n \t\t\tstart[name] = s\n \t\t}\n \t}\
@@ -782,3 +780,17 @@ func defaultSuffix() string {\n }\n return ".bash"\n }\n+\n+func getenvOk(k string) (v string, ok bool) {\n+\tv = os.Getenv(k)\n+\tif v != "" {\n+\t\treturn v, true\n+\t}\n+\tkeq := k + "="\n+\tfor _, kv := range os.Environ() {\n+\t\tif kv == keq {\n+\t\t\treturn "", true\n+\t\t}\n+\t}\n+\treturn "", false\n+}\
このファイルでは、os.Getenverror
の削除に対応するため、getenvOk
という新しいヘルパー関数が導入されました。
getenvOk(k string) (v string, ok bool)
: この関数は、os.Getenv(k)
を呼び出して環境変数の値v
を取得します。- もし
v
が空文字列でなければ、その値とtrue
を返します(環境変数が存在し、非空の値を持つ)。 - もし
v
が空文字列の場合、os.Environ()
を走査します。os.Environ()
は"KEY=VALUE"
形式の文字列スライスを返します。このスライスの中にk + "="
という文字列(例:PATH=
)が存在するかどうかをチェックします。- もし存在すれば、それは環境変数
k
が空文字列として設定されていることを意味するため、空文字列とtrue
を返します(環境変数が存在し、空の値を持つ)。 - もし存在しなければ、環境変数
k
は存在しないと判断し、空文字列とfalse
を返します(環境変数が存在しない)。 このgetenvOk
関数は、os.Getenverror
が提供していた「環境変数の存在有無」を、Goのイディオムに沿ったvalue, ok
の形式で再実装したものです。
- もし存在すれば、それは環境変数
- もし
test/env.go
の変更
--- a/test/env.go
+++ b/test/env.go
@@ -15,18 +15,14 @@ import (\n )\n \n func main() {\n-\tga, e0 := os.Getenverror("GOARCH")\n-\tif e0 != nil {\n-\t\tprint("$GOARCH: ", e0.Error(), "\n")\n-\t\tos.Exit(1)\n-\t}\n+\tga := os.Getenv("GOARCH")\n \tif ga != runtime.GOARCH {\n \t\tprint("$GOARCH=", ga, "!= runtime.GOARCH=", runtime.GOARCH, "\n")\n \t\tos.Exit(1)\n \t}\n-\txxx, e1 := os.Getenverror("DOES_NOT_EXIST")\n-\tif e1 != os.ENOENV {\n-\t\tprint("$DOES_NOT_EXIST=", xxx, "; err = ", e1.Error(), "\n")\n+\txxx := os.Getenv("DOES_NOT_EXIST")\n+\tif xxx != "" {\n+\t\tprint("$DOES_NOT_EXIST=", xxx, "\n")\n \t\tos.Exit(1)\n \t}\n }\
テストコードも、Getenverror
の削除に合わせて簡略化されました。
GOARCH
環境変数のテストでは、os.Getenverror
の代わりにos.Getenv
を直接使用し、エラーチェックは不要になりました。- 存在しない環境変数
DOES_NOT_EXIST
のテストでは、os.Getenverror
とos.ENOENV
のチェックが削除され、os.Getenv
が空文字列を返すことを期待するシンプルなチェックに変わりました。これは、os.Getenv
が非存在の場合に空文字列を返すという仕様に合致しています。
これらの変更は、Go言語のAPI設計における一貫性とイディオムへの適合性を高めるための重要なステップを示しています。
関連リンク
-
Go Issue 3065: os: Getenverror is not idiomatic: このコミットが修正したGitHub Issueです。
Getenverror
がGoのエラーハンドリングのイディオムに合致しないという議論がなされています。 https://github.com/golang/go/issues/3065 -
Gerrit Change-Id 5675094: このコミットに対応するGoのコードレビューシステムGerritのチェンジリストです。 https://golang.org/cl/5675094
参考にした情報源リンク
- Go言語公式ドキュメント:
os
パッケージ https://pkg.go.dev/os - Go言語公式ドキュメント:
syscall
パッケージ https://pkg.go.dev/syscall - Go言語におけるエラーハンドリングの慣習に関する議論 (一般的なGoのエラーハンドリングのベストプラクティスに関する情報源) https://go.dev/blog/error-handling-and-go https://go.dev/blog/errors-are-values
- Go 1 Release Notes (該当する変更が記載されている可能性のあるバージョン)
https://go.dev/doc/go1
(このコミットはGo 1のリリース前に行われた変更であり、
doc/go1.html
とdoc/go1.tmpl
の変更がその証拠です。)