[インデックス 13427] ファイルの概要
このコミットは、Go言語のコマンドラインツール cmd/go
におけるHTTPクライアントの利用方法に関する変更です。具体的には、httpGet
関数がグローバル変数 httpClient
を使用するように修正されています。コミットメッセージには「No change, just for consistency.」とあり、機能的な変更ではなく、コードの一貫性を保つためのリファクタリングであることが示されています。
コミット
commit 35030a996672b3678397f69c1168c4c125393ab2
Author: Amir Mohammad Saied <amir@gluegadget.com>
Date: Sat Jun 30 12:27:57 2012 -0700
cmd/go: httpGet function does not use global variable httpClient
No change, just for consistency.
R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/6346048
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/35030a996672b3678397f69c1168c4c125393ab2
元コミット内容
cmd/go: httpGet function does not use global variable httpClient
No change, just for consistency.
R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/6346048
変更の背景
この変更の背景には、Go言語の標準ライブラリである net/http
パッケージの http.DefaultClient
の利用と、テスト容易性の向上が関係しています。
元のコードでは、httpGET
関数内で直接 http.Get(url)
を呼び出していました。しかし、同じファイル内には httpClient
というグローバル変数が http.DefaultClient
で初期化されており、コメントには「httpClient is the default HTTP client, but a variable so it can be changed by tests, without modifying http.DefaultClient.」と明記されていました。
このコメントが示すように、httpClient
変数はテスト時にHTTPクライアントの挙動をモックしたり、差し替えたりするために用意されたものです。http.DefaultClient
を直接変更すると、アプリケーション全体に影響を与え、並行テストなどにおいて予期せぬ副作用を引き起こす可能性があります。そのため、テストの際に安全にHTTPクライアントを差し替えられるように、httpClient
という変数が導入されていました。
しかし、httpGET
関数がこの httpClient
変数を使用していなかったため、その意図が十分に活かされていませんでした。今回のコミットは、機能的な変更を加えることなく、この httpClient
変数の存在意義と、テスト容易性向上のための設計意図に沿って、httpGET
関数が httpClient
を利用するように修正することで、コードベース全体の一貫性を高めることを目的としています。
前提知識の解説
Go言語の net/http
パッケージ
Go言語の net/http
パッケージは、HTTPクライアントとサーバーを実装するための機能を提供します。
http.Get(url string) (*Response, error)
: 指定されたURLに対してHTTP GETリクエストを送信し、そのレスポンスを返します。内部的にはhttp.DefaultClient
を使用します。http.DefaultClient
:net/http
パッケージが提供するデフォルトのHTTPクライアントです。通常、特別な設定が不要な場合に手軽にHTTPリクエストを行うために使用されます。しかし、このグローバル変数を直接変更することは、アプリケーション全体に影響を与えるため、テスト時などには注意が必要です。http.Client
構造体: HTTPリクエストを行うためのクライアントを表す構造体です。Timeout
やTransport
などのフィールドを通じて、リクエストの挙動を細かく制御できます。Client.Get(url string) (*Response, error)
:http.Client
のメソッドで、指定されたURLに対してHTTP GETリクエストを送信します。
グローバル変数とテスト容易性
プログラミングにおいて、グローバル変数はプログラムのどこからでもアクセスできる変数です。手軽に利用できる反面、以下のような問題を引き起こす可能性があります。
- 状態の管理の複雑化: どこで変数が変更されたか追跡しにくくなり、デバッグが困難になります。
- テストの困難さ: テストごとにグローバル変数の状態をリセットする必要があり、テストの独立性を保つのが難しくなります。特に、並行して実行されるテストでは、グローバル変数の共有が競合状態を引き起こす可能性があります。
このコミットで登場する httpClient
のように、グローバル変数としてHTTPクライアントを定義し、それをテスト時に差し替え可能にするパターンは、依存性注入(Dependency Injection)の一種と見なすことができます。これにより、http.DefaultClient
のような変更が難しいグローバルな依存関係を、テスト可能な形で抽象化しています。
技術的詳細
このコミットは、src/cmd/go/http.go
ファイル内の httpGET
関数と httpClient
変数の定義位置に関する変更です。
変更前は、httpClient
変数の宣言が httpGET
関数の後にありました。
// httpGET returns the data from an HTTP GET request for the given URL.
func httpGET(url string) ([]byte, error) {
resp, err := http.Get(url) // ここで直接 http.Get を呼び出している
if err != nil {
return nil, err
}
defer resp.Body.Close()
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
return b, nil
}
// httpClient is the default HTTP client, but a variable so it can be
// changed by tests, without modifying http.DefaultClient.
var httpClient = http.DefaultClient // 変数の宣言が関数の後
変更後、httpClient
変数の宣言が httpGET
関数の前に移動し、かつ httpGET
関数内で http.Get(url)
の代わりに httpClient.Get(url)
を使用するように修正されました。
// httpClient is the default HTTP client, but a variable so it can be
// changed by tests, without modifying http.DefaultClient.
var httpClient = http.DefaultClient // 変数の宣言が関数の前
// httpGET returns the data from an HTTP GET request for the given URL.
func httpGET(url string) ([]byte, error) {
resp, err := httpClient.Get(url) // httpClient を使用するように変更
if err != nil {
return nil, err
}
defer resp.Body.Close()
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
return b, nil
}
この変更は、以下の2つの側面で「一貫性」を高めています。
- 宣言順序の一貫性:
httpClient
変数がhttpGET
関数内で使用されるため、その宣言を関数の前に移動させることで、コードの読みやすさと論理的な順序を改善しています。Go言語の慣習として、使用される変数は使用される前に宣言されるのが一般的です。 - 設計意図との一貫性:
httpClient
変数がテスト容易性のために導入されたものであるというコメントの意図に沿って、実際にhttpGET
関数がその変数を使用するように修正されました。これにより、将来的にこのコードをテストする際に、httpClient
を差し替えることでhttpGET
の挙動を制御できるようになります。
機能的には何も変更されていませんが、コードの保守性、可読性、そしてテスト容易性が向上しています。
コアとなるコードの変更箇所
--- a/src/cmd/go/http.go
+++ b/src/cmd/go/http.go
@@ -20,9 +20,13 @@ import (
"net/url"
)
+// httpClient is the default HTTP client, but a variable so it can be
+// changed by tests, without modifying http.DefaultClient.
+var httpClient = http.DefaultClient
+
// httpGET returns the data from an HTTP GET request for the given URL.
func httpGET(url string) ([]byte, error) {
- resp, err := http.Get(url)
+ resp, err := httpClient.Get(url)
if err != nil {
return nil, err
}
@@ -37,10 +41,6 @@ func httpGET(url string) ([]byte, error) {
return b, nil
}
-// httpClient is the default HTTP client, but a variable so it can be
-// changed by tests, without modifying http.DefaultClient.
-var httpClient = http.DefaultClient
-
// httpsOrHTTP returns the body of either the importPath's
// https resource or, if unavailable, the http resource.
func httpsOrHTTP(importPath string) (urlStr string, body io.ReadCloser, err error) {
コアとなるコードの解説
上記のdiffは、src/cmd/go/http.go
ファイルに対する変更を示しています。
-
行 20-23 (
+
で始まる行):+// httpClient is the default HTTP client, but a variable so it can be +// changed by tests, without modifying http.DefaultClient. +var httpClient = http.DefaultClient
この部分は、
httpClient
変数の宣言とそのコメントを、httpGET
関数の定義の直前に移動したことを示しています。これにより、httpGET
関数がhttpClient
を使用する前に、その変数が宣言されていることが明確になります。コメントは、この変数がテストのためにhttp.DefaultClient
を変更せずに差し替え可能であることを説明しています。 -
行 26 (
-
で始まる行):- resp, err := http.Get(url)
この行は削除されたことを示しています。元のコードでは、
httpGET
関数内でnet/http
パッケージのグローバル関数http.Get
を直接呼び出していました。 -
行 27 (
+
で始まる行):+ resp, err := httpClient.Get(url)
この行は追加されたことを示しています。削除された行の代わりに、
httpClient
グローバル変数のGet
メソッドを呼び出すように変更されています。これにより、httpGET
関数は、テスト時に差し替え可能なhttpClient
を介してHTTPリクエストを行うようになります。 -
行 37-40 (
-
で始まる行):-// httpClient is the default HTTP client, but a variable so it can be -// changed by tests, without modifying http.DefaultClient. -var httpClient = http.DefaultClient
この部分は、
httpClient
変数の元の宣言位置が削除されたことを示しています。これは、変数の宣言がファイルのより適切な位置(httpGET
関数の前)に移動したためです。
全体として、この変更は機能的な動作を変えることなく、コードの構造と意図をより明確にし、テスト容易性を向上させるためのリファクタリングです。
関連リンク
- Go CL 6346048 - このコミットに対応するGoのコードレビューシステム(Gerrit)のチェンジリスト。
参考にした情報源リンク
- GoDoc: net/http package
- Go言語におけるテストの書き方と設計 (一般的なGoのテストと依存性注入の概念を理解するために参照)
- Go言語のテストとモック (Goにおけるテストとモックの概念を理解するために参照)