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

[インデックス 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関数の削除と、それに伴う環境変数取得ロジックの変更です。

  1. os.Getenverrorの削除: src/pkg/os/env.goからGetenverror関数とその関連するエラー変数ENOENVが完全に削除されました。これにより、osパッケージの外部からこの関数を呼び出すことはできなくなります。

  2. 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
    }
    
  3. 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.Getenvos.Environを組み合わせて同様のロジックを再実装したものです。

  4. テストコードの更新: test/env.go内のテストコードも、os.Getenverrorの削除に合わせて更新されました。以前はos.Getenverrorの戻り値とos.ENOENVエラーをチェックしていましたが、変更後はos.Getenvの戻り値(空文字列かどうか)のみをチェックするように簡略化されています。これは、os.Getenvが環境変数の非存在をエラーとして扱わなくなったことを反映しています。

この変更により、Goの標準ライブラリは、環境変数の存在チェックに関してより一貫性のあるイディオム(syscall.Getenvの真偽値戻り値やos.Environの利用)を推奨する形に進化しました。

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

このコミットにおける主要なコード変更は以下のファイルに集中しています。

  1. src/pkg/os/env.go:

    • Getenverror関数とその関連するENOENVエラー変数が削除されました。
    • Getenv関数が、内部でGetenverrorを呼び出す代わりに、直接syscall.Getenvを呼び出すように変更されました。
  2. misc/dashboard/builder/main.go:

    • os.Getenverrorの呼び出しが、新しく定義されたヘルパー関数getenvOkの呼び出しに置き換えられました。
    • getenvOk関数が追加されました。この関数は、os.Getenvos.Environを組み合わせて、環境変数が存在するかどうかを判断します。
  3. test/env.go:

    • os.Getenverrorを使用していたテストロジックが、os.Getenvの戻り値(空文字列かどうか)を直接チェックするように変更されました。
  4. doc/go1.html および doc/go1.tmpl:

    • Go 1のドキュメントに、Getenverrorが削除されたことと、環境変数の非存在と空文字列を区別するための新しい推奨方法(os.Environまたはsyscall.Getenv)が追記されました。

コアとなるコードの解説

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.Getenverroros.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

参考にした情報源リンク