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

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

このコミットは、Go言語の標準ライブラリnetパッケージ内のdial関数のコードを簡素化することを目的としています。具体的には、dial関数内で各ネットワークタイプ(TCP, UDP, IP, Unix)に応じたdial関数を呼び出す際の、冗長なエラーチェックと一時変数への代入を排除し、直接returnするように変更しています。これにより、コードの可読性と簡潔性が向上しています。

コミット

commit 88411547d4d8fca47403ce6518e0c170d8cc8282
Author: Mikio Hara <mikioh.mikioh@gmail.com>
Date:   Thu Aug 15 05:53:53 2013 +0900

    net: simplify dial
    
    R=golang-dev, bradfitz
    CC=golang-dev
    https://golang.org/cl/12884044
---
 src/pkg/net/dial.go | 16 ++++++----------
 1 file changed, 6 insertions(+), 10 deletions(-)

diff --git a/src/pkg/net/dial.go b/src/pkg/net/dial.go
index b6ed830511..8df4f77849 100644
--- a/src/pkg/net/dial.go
+++ b/src/pkg/net/dial.go
@@ -146,30 +146,26 @@ func (d *Dialer) Dial(network, address string) (Conn, error) {
 	return resolveAndDial(network, address, d.LocalAddr, d.deadline())
 }
 
-func dial(net, addr string, la, ra Addr, deadline time.Time) (c Conn, err error) {
+func dial(net, addr string, la, ra Addr, deadline time.Time) (Conn, error) {
 	if la != nil && la.Network() != ra.Network() {
 		return nil, &OpError{Op: "dial", Net: net, Addr: ra, Err: errors.New("mismatched local address type " + la.Network())}
 	}\n 	switch ra := ra.(type) {\n 	case *TCPAddr:\n 		la, _ := la.(*TCPAddr)\n-\t\tc, err = dialTCP(net, la, ra, deadline)\n+\t\treturn dialTCP(net, la, ra, deadline)\n \tcase *UDPAddr:\n \t\tla, _ := la.(*UDPAddr)\n-\t\tc, err = dialUDP(net, la, ra, deadline)\n+\t\treturn dialUDP(net, la, ra, deadline)\n \tcase *IPAddr:\n \t\tla, _ := la.(*IPAddr)\n-\t\tc, err = dialIP(net, la, ra, deadline)\n+\t\treturn dialIP(net, la, ra, deadline)\n \tcase *UnixAddr:\n \t\tla, _ := la.(*UnixAddr)\n-\t\tc, err = dialUnix(net, la, ra, deadline)\n+\t\treturn dialUnix(net, la, ra, deadline)\n \tdefault:\n-\t\terr = &OpError{Op: "dial", Net: net, Addr: ra, Err: &AddrError{Err: "unexpected address type", Addr: addr}}\n+\t\treturn nil, &OpError{Op: "dial", Net: net, Addr: ra, Err: &AddrError{Err: "unexpected address type", Addr: addr}}\n \t}\n-\tif err != nil {\n-\t\treturn nil, err\n-\t}\n-\treturn\n }\n \n type stringAddr struct {\n```

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

[https://github.com/golang/go/commit/88411547d4d8fca47403ce6518e0c170d8cc8282](https://github.com/golang/go/commit/88411547d4d8fca47403ce6518e0c170d8cc8282)

## 元コミット内容

変更前の`dial`関数は、以下のような構造を持っていました。

```go
func dial(net, addr string, la, ra Addr, deadline time.Time) (c Conn, err error) {
	if la != nil && la.Network() != ra.Network() {
		return nil, &OpError{Op: "dial", Net: net, Addr: ra, Err: errors.New("mismatched local address type " + la.Network())}
	}
	switch ra := ra.(type) {
	case *TCPAddr:
		la, _ := la.(*TCPAddr)
		c, err = dialTCP(net, la, ra, deadline)
	case *UDPAddr:
		la, _ := la.(*UDPAddr)
		c, err = dialUDP(net, la, ra, deadline)
	case *IPAddr:
		la, _ := la.(*IPAddr)
		c, err = dialIP(net, la, ra, deadline)
	case *UnixAddr:
		la, _ := la.(*UnixAddr)
		c, err = dialUnix(net, la, ra, deadline)
	default:
		err = &OpError{Op: "dial", Net: net, Addr: ra, Err: &AddrError{Err: "unexpected address type", Addr: addr}}
	}
	if err != nil {
		return nil, err
	}
	return
}

このコードでは、switch文の各case内で、対応するdial関数(例: dialTCP, dialUDPなど)の戻り値をcerrという名前付き戻り値変数に一度代入していました。そして、switch文の後にif err != nilという共通のエラーチェックを行い、エラーがあればnil, errを返し、そうでなければreturn(裸のreturn)でcerrの現在の値を返していました。

変更の背景

この変更の背景には、Go言語におけるコードの簡潔性とイディオムへの準拠があります。

  1. 冗長なエラーチェックの排除: Goのエラーハンドリングのイディオムでは、関数がエラーを返す可能性がある場合、その呼び出し直後にエラーをチェックし、すぐに処理を終える「早期リターン(early return)」が推奨されます。元のコードでは、各dial関数の呼び出し後に一度変数に代入し、switch文の後にまとめてエラーチェックを行っていました。これは、各dial関数がエラーを返す可能性があるにもかかわらず、その場でエラーを処理せず、後続の共通エラーチェックに委ねる形になっており、冗長でした。

  2. 名前付き戻り値の適切な利用: Goの名前付き戻り値は、関数の戻り値に意味のある名前を付けることで可読性を高めることができます。また、引数なしのreturn(naked return)を使用すると、名前付き戻り値の現在の値が返されます。しかし、この機能は、特に長い関数や複雑なロジックを持つ関数では、どの値が返されるのかが分かりにくくなるという欠点もあります。このコミットでは、各case内で直接returnすることで、cerrという名前付き戻り値変数を介した間接的な値の受け渡しをなくし、より直接的で分かりやすいコードにしています。

  3. コードの簡素化と可読性の向上: 上記の冗長なエラーチェックと間接的な変数利用を排除することで、コード全体の行数が減り、ロジックがより直接的になります。これにより、dial関数が何を行っているのか、どのような結果を返すのかが、より一目で理解しやすくなります。

この変更は、Go言語の設計思想である「シンプルさ」と「明示性」を追求した結果と言えます。

前提知識の解説

Go言語のエラーハンドリング

Go言語では、エラーは通常、関数の最後の戻り値としてerrorインターフェース型で返されます。慣習として、操作が成功した場合はnilが返され、エラーが発生した場合は非nilerror値が返されます。呼び出し元は、if err != nilという形式でエラーの有無をチェックし、エラーがあれば適切な処理を行います。

result, err := someFunction()
if err != nil {
    // エラー処理
    return nil, err // あるいは別のエラーを返す
}
// 成功時の処理

このイディオムは、エラーパスを明示的にすることで、コードの堅牢性を高めます。

Go言語の名前付き戻り値 (Named Return Values)

Goの関数は、戻り値に名前を付けることができます。

func calculate(a, b int) (sum int, product int) {
    sum = a + b
    product = a * b
    return // naked return: sumとproductの現在の値を返す
}

名前付き戻り値は、関数内で通常の変数として扱われ、初期値はゼロ値(数値型なら0、文字列型なら""、インターフェース型ならnilなど)です。関数内でこれらの変数に値を代入し、引数なしのreturn文(「裸のreturn」または「naked return」と呼ばれる)を使用すると、その時点での名前付き戻り値の現在の値が返されます。

利点としては、戻り値の意味が明確になることや、短い関数でのコードの簡潔化が挙げられます。しかし、長い関数で多用すると、どの値が返されるのかが分かりにくくなるため、使用には注意が必要です。

Go言語のnetパッケージとDial関数

netパッケージは、Go言語でネットワークI/Oを扱うための基本的な機能を提供します。net.Dial関数は、指定されたネットワークアドレスへの接続を確立するために使用される主要な関数です。

net.Dial(network, address string) (Conn, error)

  • network: 使用するネットワークプロトコル(例: "tcp", "udp", "unix")。
  • address: 接続先のアドレス(例: "golang.org:80", "localhost:8080")。
  • Conn: 確立されたネットワーク接続を表すインターフェース。
  • error: 接続中にエラーが発生した場合に返される。

net.Dialの内部では、様々なネットワークタイプ(TCP, UDP, IP, Unixドメインソケットなど)に応じて、より具体的なdial関数(例: dialTCP, dialUDP)が呼び出されます。このコミットで変更されたdial関数は、まさにこの内部的なディスパッチロジックを担う部分でした。

技術的詳細

このコミットの技術的な核心は、Go言語の制御フローとエラーハンドリングのイディオムをより効果的に適用することにあります。

元のコードでは、switch文の各case内で、c, err = dialXXX(...)という形式で、dialTCPdialUDPなどの具体的なダイヤル関数の結果を、dial関数の名前付き戻り値であるcerrに代入していました。その後、switch文を抜けた後にif err != nil { return nil, err }という共通のエラーチェックを行い、最後にreturn(裸のreturn)で関数を終了していました。

このパターンは、以下のような問題を含んでいました。

  1. エラーの遅延処理: 各dialXXX関数がエラーを返す可能性があるにもかかわらず、そのエラーをすぐにチェックして処理せず、一度err変数に格納し、switch文の後にまとめて処理していました。これにより、エラーが発生した直後に処理を中断するというGoのイディオムから逸脱していました。
  2. 冗長なコード: 各casec, err = ...と代入し、その後に共通のエラーチェックとreturnを行うという流れは、各caseが独立して結果を返すことができるにもかかわらず、不必要な中間ステップを挟んでいました。

新しいコードでは、この問題を解決するために、各case内で具体的なdialXXX関数の呼び出し結果を**直接return**するように変更しました。

// 変更前
c, err = dialTCP(net, la, ra, deadline)
// ...
if err != nil {
    return nil, err
}
return

// 変更後
return dialTCP(net, la, ra, deadline)

この変更により、以下の利点が生まれます。

  • 即時エラーリターン: dialTCPなどがエラーを返した場合、その場でdial関数全体がエラーを返して終了します。これにより、エラーハンドリングがより直接的かつ効率的になります。
  • コードの簡潔化: c, err = ...という代入と、その後のif err != nilブロック、そして最後のreturn文が不要になります。これにより、コードの行数が減り、全体的にすっきりとした記述になります。
  • 可読性の向上: 各caseが独立した処理ブロックとなり、そのブロック内で完結して結果を返すため、コードのフローが追いやすくなります。

defaultケースについても同様に、エラーを生成して直接return nil, &OpError{...}するように変更されています。これにより、switch文の後に続くエラーチェックとreturnが完全に不要となり、関数全体がより簡潔な構造になりました。

この変更は、Go言語の「早期リターン」と「簡潔なエラーハンドリング」という設計原則を忠実に適用したものであり、コードの品質と保守性を向上させる典型的なリファクタリングと言えます。

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

--- a/src/pkg/net/dial.go
+++ b/src/pkg/net/dial.go
@@ -146,30 +146,26 @@ func (d *Dialer) Dial(network, address string) (Conn, error) {
 	return resolveAndDial(network, address, d.LocalAddr, d.deadline())
 }
 
-func dial(net, addr string, la, ra Addr, deadline time.Time) (c Conn, err error) {
+func dial(net, addr string, la, ra Addr, deadline time.Time) (Conn, error) {
 	if la != nil && la.Network() != ra.Network() {
 		return nil, &OpError{Op: "dial", Net: net, Addr: ra, Err: errors.New("mismatched local address type " + la.Network())}
 	}
 	switch ra := ra.(type) {\n 	case *TCPAddr:\n 		la, _ := la.(*TCPAddr)\n-\t\tc, err = dialTCP(net, la, ra, deadline)\n+\t\treturn dialTCP(net, la, ra, deadline)\n \tcase *UDPAddr:\n \t\tla, _ := la.(*UDPAddr)\n-\t\tc, err = dialUDP(net, la, ra, deadline)\n+\t\treturn dialUDP(net, la, ra, deadline)\n \tcase *IPAddr:\n \t\tla, _ := la.(*IPAddr)\n-\t\tc, err = dialIP(net, la, ra, deadline)\n+\t\treturn dialIP(net, la, ra, deadline)\n \tcase *UnixAddr:\n \t\tla, _ := la.(*UnixAddr)\n-\t\tc, err = dialUnix(net, la, ra, deadline)\n+\t\treturn dialUnix(net, la, ra, deadline)\n \tdefault:\n-\t\terr = &OpError{Op: "dial", Net: net, Addr: ra, Err: &AddrError{Err: "unexpected address type", Addr: addr}}\n+\t\treturn nil, &OpError{Op: "dial", Net: net, Addr: ra, Err: &AddrError{Err: "unexpected address type", Addr: addr}}\n \t}\n-\tif err != nil {\n-\t\treturn nil, err\n-\t}\n-\treturn\n }\n \n type stringAddr struct {\n```

## コアとなるコードの解説

このコミットの主要な変更点は、`dial`関数の内部ロジック、特に`switch`文の各`case`ブロックと、それに続くエラーチェックおよびリターン部分に集約されています。

1.  **関数シグネチャの変更**:
    *   変更前: `func dial(net, addr string, la, ra Addr, deadline time.Time) (c Conn, err error)`
    *   変更後: `func dial(net, addr string, la, ra Addr, deadline time.Time) (Conn, error)`
    *   名前付き戻り値`c Conn, err error`が、単に`Conn, error`に変更されています。これは、関数内で`c`と`err`という名前付き変数に値を代入し、最後に`return`(裸のreturn)で返すというパターンを廃止し、各`case`内で直接戻り値を返すようにしたため、名前を明示する必要がなくなったためです。

2.  **`switch`文内の各`case`の変更**:
    *   **変更前**: 各`case`では、対応する`dialXXX`関数(例: `dialTCP`, `dialUDP`など)を呼び出し、その結果を`dial`関数の名前付き戻り値である`c`と`err`に代入していました。
        ```go
        // 例: TCPAddrの場合
        c, err = dialTCP(net, la, ra, deadline)
        ```
    *   **変更後**: 各`case`では、`dialXXX`関数の呼び出し結果を**直接`return`**するように変更されました。
        ```go
        // 例: TCPAddrの場合
        return dialTCP(net, la, ra, deadline)
        ```
        これにより、`dialTCP`などが`Conn`と`error`を返すと、その値がそのまま`dial`関数の戻り値として使用され、関数がその場で終了します。エラーが発生した場合も、即座にエラーが呼び出し元に伝播されます。

3.  **`default`ケースの変更**:
    *   **変更前**: `default`ケースでは、`OpError`を生成し、それを`err`名前付き戻り値変数に代入していました。
        ```go
        err = &OpError{Op: "dial", Net: net, Addr: ra, Err: &AddrError{Err: "unexpected address type", Addr: addr}}
        ```
    *   **変更後**: `default`ケースでも、エラーを生成して**直接`return nil, &OpError{...}`**するように変更されました。
        ```go
        return nil, &OpError{Op: "dial", Net: net, Addr: ra, Err: &AddrError{Err: "unexpected address type", Addr: addr}}
        ```
        これにより、不明なアドレスタイプが渡された場合も、即座にエラーを返して関数が終了します。

4.  **冗長なエラーチェックと`return`の削除**:
    *   **変更前**: `switch`文の直後には、`if err != nil { return nil, err }`という共通のエラーチェックと、その後の`return`(裸のreturn)がありました。
    *   **変更後**: 各`case`が直接`return`するようになったため、この`if`ブロックと最後の`return`文は完全に不要となり、削除されました。

これらの変更により、`dial`関数はよりGoらしい、簡潔で直接的なエラーハンドリングと制御フローを持つようになりました。コードの行数が減り、各処理ブロックの意図がより明確になっています。

## 関連リンク

*   Go言語の`net`パッケージに関する公式ドキュメント: [https://pkg.go.dev/net](https://pkg.go.dev/net)
*   Go言語のエラーハンドリングに関する公式ブログ記事: [https://go.dev/blog/error-handling-and-go](https://go.dev/blog/error-handling-and-go)

## 参考にした情報源リンク

*   [https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQF04BUNvrjtg5peKPozmrP_gn63Gg7DB7ezY3gNeWZVGA2hE0y2rKD0IJ6KPba9UOjGPFsATyXL0tpedbmHO17n-Wre4SHIgG0xC_AORIuS4jU3crPUxdp6nUn6LMVxaJyz4DFYUQWqFOm2Fa2ltUhj27c5WrmcwVlY-LUnkto5Pj4=](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQF04BUNvrjtg5peKPozmrP_gn63Gg7DB7ezY3gNeWZVGA2hE0y2rKD0IJ6KPba9UOjGPFsATyXL0tpedbmHO17n-Wre4SHIgG0xC_AORIuS4jU3crPUxdp6nUn6LMVxaJyz4DFYUQWqFOm2Fa2ltUhj27c5WrmcwVlY-LUnkto5Pj4=)
*   [https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHmCTsXgl30P6-CNvkE6ZAsKQh47iVUuMiQ9325B7FO_HMdf0qi0ChusWLYZxNP-vmal9asKErKciWrCuwWDIJggvz9fBnUpIUxXIPR4t3rV68qeIlgAE2FpmUZPniIwPo10eNZcqvqk_-3WaE0Qmqtr_bZ8BKDuIxIoaaqTskU8mZq3n2P7PY=](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHmCTsXgl30P6-CNvkE6ZAsKQh47iVUuMiQ9325B7FO_HMdf0qi0ChusWLYZxNP-vmal9asKErKciWrCuwWDIJggvz9fBnUpIUxXIPR4t3rV68qeIlgAE2FpmUZPniIwPo10eNZcqvqk_-3WaE0Qmqtr_bZ8BKDuIxIoaaqTskU8mZq3n2P7PY=)
*   [https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQGZ1-E4CDPFJJczCl3K_k3k0jPcJgDtfbX6q9bfkp1wY2M4fK2FlyPb-MDX9DuWzKGJGRP_v67uR24gqIacdPh0f98F9WUHlmBscztNYmU9sbHF835esDsu](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQGZ1-E4CDPFJJczCl3K_k3k0jPcJgDtfbX6q9bfkp1wY2M4fK2FlyPb-MDX9DuWzKGJGRP_v67uR24gqIacdPh0f98F9WUHlmBscztNYmU9sbHF835esDsu)
*   [https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHPbn8qBwCsIvBu52D0fJ2skS-Sz-M11eoiP3KlpzK2mUPB1U6_qpYc-x1uGHpxrrbFIOAZkbC7cd54t4nZX3yCajkFWV3hjb-irgHIyHsUNGfi2yp5GEkpeIIYYFUmL0K-vuwYKURDhGTfppTpWyeeuP7SLnVKEl3PWhqPyKbBFPy9cfmKNQ==](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHPbn8qBwCsIvBu52D0fJ2skS-Sz-M11eoiP3KlpzK2mUPB1U6_qpYc-x1uGHpxrrbFIOAZkbC7cd54t4nZX3yCajkFWV3hjb-irgHIyHsUNGfi2yp5GEkpeIIYYFUmL0K-vuwYKURDhGTfppTpWyeeuP7SLnVKEl3PWhqP_yKbBFPy9cfmKNQ==)
*   [https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFWGCS_BeZlshfAJhwq0VPC7AB72D08VeG5Vnj-pWVBHjem5regPOs5ymDmsl_5E2TTD3nVjMKNt0iL-PwObDB7BBjZo8Mb5kM6Opg_5DG31A-aDM0X0Q2yKR1JSKANKS-n_tz5-MzmO1XfKf2z2aSWvMlNWMJP](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFWGCS_BeZlshfAJhwq0VPC7AB72D08VeG5Vnj-pWVBHjem5regPOs5ymDmsl_5E2TTD3nVjMKNt0iL-PwObDB7BBjZo8Mb5kM6Opg_5DG31A-aDM0X0Q2yKR1JSKANKS-n_tz5-MzmO1XfKf2z2aSWvMlNWMJP)
*   [https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQGPUd3xS7pVMrjrKyET4OpsNkOSu9WNJYWqM6bh1nl4oandCJs45GzB9EZWUxL0UK31dIESzVIWWMqwX5IMieBESa3RzBC1wBYxdet0aD2u-ZOJ_szXuiOQX-XfgS8lXd1Gidpr5OyAnMtJ9-5fubIhHROvoSrCzA6y9JWguIqsUToSsZjd66D9xK1wTOJpOvFQ6eV72s0XpmjN79zgracBbUm1JfQy0l3uPoKBBJigg==](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQGPUd3xS7pVMrjrKyET4OpsNkOSu9WNJYWqM6bh1nl4oandCJs45GzB9EZWUxL0UK31dIESzVIWWMqwX5IMieBESa3RzBC1wBYxdet0aD2u-ZOJ_szXuiQx-XfgS8lXd1Gidpr5OyAnMtJ9-5fubIhHROvoSrCzA6y9JWguIqsUToSsZjd66D9xK1wTOJpOvFQ6eV72s0XpmjN79zgracBbUm1JfQy0l3uPoKBBJigg==)
*   [https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQGjqHGw71sD45hmXzjXXZKOVMdPmpQHb2JQlCcMNYmaq8iFvmJxOdx3yao05Igz7qn3m7OOPMyi3LxWngAecbCnT6dZ7tvtn8M87_XVEH2E2AXTHA==](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQGjqHGw71sD45hmXzjXXZKOVMdPmpQHb2JQlCcMNYmaq8iFvmJxOdx3yao05Igz7qn3m7OOPMyi3LxWngAecbCnT6dZ7tvtn8M87_XVEH2E2AXTHA==)