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

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

このコミットは、Go言語の標準ライブラリであるnetパッケージ内のunicast_test.goファイルに対する変更です。具体的には、テストコードにおける不要な型アサーションの削除と、テスト中のリソースリークの修正が行われています。

コミット

commit 7905faaee2ee5ebd628856b05f22b9e1264b7b92
Author: Mikio Hara <mikioh.mikioh@gmail.com>
Date:   Tue Mar 20 10:57:54 2012 +0900

    net: drop unnecessary type assertions and fix leak in test
    
    R=golang-dev, r
    CC=golang-dev
    https://golang.org/cl/5847064

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

https://github.com/golang/go/commit/7905faaee2ee5ebd628856b05f22b9e1264b7b92

元コミット内容

net: drop unnecessary type assertions and fix leak in test

R=golang-dev, r
CC=golang-dev
https://golang.org/cl/5847064

変更の背景

このコミットの背景には、Go言語のインターフェースの設計思想と、テストコードにおけるリソース管理のベストプラクティスがあります。

  1. 不要な型アサーションの削除: Go言語では、特定のインターフェースを満たす型であれば、そのインターフェース型の変数に直接代入できます。また、インターフェースが持つメソッドを呼び出す際に、その具体的な型を知る必要はありません。io.CloserインターフェースはClose()メソッドを定義しており、netパッケージのリスナー(net.Listenernet.PacketConn)は内部的にこのインターフェースを満たしています。したがって、l1.(io.Closer).Close()のように明示的にio.Closerへの型アサーションを行うことは冗長であり、不要です。この変更は、よりGoらしい(Idiomatic Go)コードスタイルへの改善を目的としています。

  2. テストにおけるリソースリークの修正: テストコードでは、テスト対象の機能が正しく動作するかを確認するだけでなく、テストが終了した後に使用したリソース(ファイルディスクリプタ、ネットワーク接続など)を適切に解放することが重要です。リソースが解放されないままテストが終了すると、リソースリークが発生し、テストの実行環境に悪影響を与えたり、後続のテストに影響を与えたりする可能性があります。特に、ネットワーク接続を確立するテストでは、接続が成功した場合でも失敗した場合でも、必ず接続を閉じる必要があります。このコミットでは、Dialが成功した場合に確立された接続が閉じられていなかったというリソースリークが修正されています。

前提知識の解説

このコミットを理解するためには、以下のGo言語の概念とネットワークプログラミングの基礎知識が必要です。

  • Go言語のインターフェース: Goのインターフェースは、メソッドのシグネチャの集合を定義します。型がインターフェースのすべてのメソッドを実装していれば、その型はそのインターフェースを満たします。Goでは、型が暗黙的にインターフェースを満たすため、JavaやC#のようにimplementsキーワードを明示的に記述する必要がありません。

    • io.Closerインターフェース: Close() errorという単一のメソッドを持つインターフェースです。ファイルやネットワーク接続など、使用後に閉じる必要があるリソースを表すためによく使われます。
    • net.Listenerインターフェース: ネットワーク接続をリッスンするためのインターフェースで、Accept()Addr()Close()などのメソッドを持ちます。Close()メソッドを持つため、io.Closerインターフェースも満たします。
    • net.PacketConnインターフェース: パケット指向のネットワーク接続(UDPなど)のためのインターフェースで、ReadFrom()WriteTo()LocalAddr()Close()などのメソッドを持ちます。これもClose()メソッドを持つため、io.Closerインターフェースを満たします。
  • 型アサーション: Goでは、インターフェース型の変数が保持している具体的な型を、value.(Type)という構文を使って確認したり、別のインターフェース型に変換したりすることができます。しかし、インターフェースが持つメソッドを呼び出すだけであれば、型アサーションは不要です。

  • ネットワークプログラミングの基礎:

    • ソケット: ネットワーク通信のエンドポイントです。プログラムはソケットを通じてデータを送受信します。
    • リスナー: サーバー側で特定のポートで接続要求を待ち受けるエンティティです。接続が確立されると、新しいソケットが作成されます。
    • ダイアル: クライアント側でサーバーに接続を開始する操作です。
    • リソース管理: ネットワーク接続やファイルディスクリプタなどのシステムリソースは有限であるため、使用後は必ず解放する必要があります。Goでは、deferステートメントを使って関数の終了時にリソースを解放する処理を記述するのが一般的です。

技術的詳細

このコミットは、Go言語のnetパッケージのテストコードunicast_test.goに焦点を当てています。

  1. ioパッケージのインポート削除: 変更前: import ("io" ...) 変更後: import (... (ioが削除) これは、l1.(io.Closer).Close()という型アサーションが不要になったため、ioパッケージ自体も不要になったことによるものです。コードの依存関係を減らし、クリーンさを保つための変更です。

  2. 不要な型アサーションの削除: 変更前: l1.(io.Closer).Close() 変更後: l1.Close() net.Listenernet.PacketConnは、その定義上Close()メソッドを持っているため、自動的にio.Closerインターフェースを満たします。Goのインターフェースの特性により、l1net.Listenerまたはnet.PacketConn型であれば、直接l1.Close()と呼び出すことができます。これにより、コードがより簡潔になり、Goのインターフェースの柔軟性を最大限に活用しています。

  3. Dial成功時のリソースリーク修正: 変更前:

    _, err := Dial(tt.net, tt.addr+":"+port)
    if err == nil {
        t.Fatalf("Dial(%q, %q) should fail", tt.net, tt.addr)
    }
    

    変更後:

    c, err := Dial(tt.net, tt.addr+":"+port)
    if err == nil {
        c.Close() // 接続が成功した場合に閉じる
        t.Fatalf("Dial(%q, %q) should fail", tt.net, tt.addr)
    }
    

    TestProhibitionaryDialArgsテストは、特定の不正な引数でDialを呼び出した場合にエラーが発生することを確認するためのものです。しかし、もし何らかの理由でDialが成功してしまった場合(つまりerr == nilの場合)、確立された接続cが閉じられずにテストが終了していました。これはリソースリークにつながります。修正後は、Dialが成功してしまった場合でも、c.Close()を呼び出して接続を明示的に閉じることで、リソースリークを防いでいます。これにより、テストの堅牢性が向上し、テスト実行環境への影響を最小限に抑えることができます。

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

src/pkg/net/unicast_test.goファイルにおいて、以下の変更が行われています。

--- a/src/pkg/net/unicast_test.go
+++ b/src/pkg/net/unicast_test.go
@@ -5,7 +5,6 @@
  package net
  
  import (
-	"io"
  	"runtime"
  	"syscall"
  	"testing"
@@ -67,7 +66,7 @@ func TestTCPListener(t *testing.T) {
  		case syscall.AF_INET6:
  		\ttestIPv6UnicastSocketOptions(t, fd)
  		}
-\t\tl1.(io.Closer).Close()\
+\t\tl1.Close()\
  	}\
  }\
  
@@ -112,7 +111,7 @@ func TestUDPListener(t *testing.T) {
  		case syscall.AF_INET6:
  		\ttestIPv6UnicastSocketOptions(t, fd)
  		}
-\t\tl1.(io.Closer).Close()\
+\t\tl1.Close()\
  	}\
  }\
  
@@ -134,7 +133,7 @@ func TestSimpleTCPListener(t *testing.T) {
  		checkFirstListener(t, tt.net, tt.laddr+":"+port, l1)
  		l2, err := Listen(tt.net, tt.laddr+":"+port)
  		checkSecondListener(t, tt.net, tt.laddr+":"+port, err, l2)
-\t\tl1.(io.Closer).Close()\
+\t\tl1.Close()\
  	}\
  }\
  
@@ -169,7 +168,7 @@ func TestSimpleUDPListener(t *testing.T) {
  		checkFirstListener(t, tt.net, tt.laddr+":"+port, l1)
  		l2, err := ListenPacket(tt.net, tt.laddr+":"+port)
  		checkSecondListener(t, tt.net, tt.laddr+":"+port, err, l2)
-\t\tl1.(io.Closer).Close()\
+\t\tl1.Close()\
  	}\
  }\
  
@@ -530,8 +529,9 @@ func TestProhibitionaryDialArgs(t *testing.T) {
  	defer l.Close()\
  
  	for _, tt := range prohibitionaryDialArgTests {\
-\t\t_, err := Dial(tt.net, tt.addr+":"+port)\
+\t\tc, err := Dial(tt.net, tt.addr+":"+port)\
  	\tif err == nil {\
+\t\t\tc.Close()\
  	\t\tt.Fatalf("Dial(%q, %q) should fail", tt.net, tt.addr)\
  	\t}\
  	}\

コアとなるコードの解説

上記の差分から、以下の変更点とその意図が明確に読み取れます。

  1. import ("io") の削除: unicast_test.goの冒頭部分からioパッケージのインポートが削除されています。これは、後述するl1.(io.Closer).Close()という型アサーションが不要になったため、io.Closerインターフェースを明示的に参照する必要がなくなったためです。これにより、コードの依存関係が減り、よりクリーンな状態が保たれます。

  2. l1.(io.Closer).Close() から l1.Close() への変更: TestTCPListener, TestUDPListener, TestSimpleTCPListener, TestSimpleUDPListenerの各テスト関数内で、リスナーオブジェクトl1を閉じる際に、l1.(io.Closer).Close()という記述がl1.Close()に変更されています。 Go言語では、型が特定のインターフェースのメソッドセットを実装していれば、そのインターフェースを満たします。net.Listenernet.PacketConnといったリスナー型は、Close() errorメソッドを実装しているため、自動的にio.Closerインターフェースを満たします。したがって、l1がこれらのリスナー型である場合、明示的な型アサーションなしに直接Close()メソッドを呼び出すことができます。この変更は、Goのインターフェースのイディオムに沿ったものであり、コードの冗長性を排除し、可読性を向上させます。

  3. TestProhibitionaryDialArgs におけるリソースリーク修正: TestProhibitionaryDialArgsテスト関数内で、Dial関数の呼び出し部分が変更されています。 変更前は、Dialの戻り値である接続オブジェクトを_で破棄していました。これは、テストの意図が「Dialがエラーを返すこと」であり、接続オブジェクト自体には関心がなかったためです。しかし、もしDialが予期せず成功してしまった場合(err == nil)、確立された接続は閉じられることなく残ってしまい、リソースリークを引き起こす可能性がありました。 変更後は、c, err := Dial(...)として接続オブジェクトをc変数に受け取るようにし、if err == nilのブロック内でc.Close()を呼び出すように修正されています。これにより、Dialが成功してしまったという異常なケースでも、確立された接続が確実に閉じられるようになり、テストのリソース管理が改善され、リークが防止されます。これは、テストの堅牢性を高める上で非常に重要な修正です。

これらの変更は、Go言語の設計原則である「シンプルさ」と「効率性」を反映しており、コードの品質とテストの信頼性を向上させています。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Go言語のソースコード(特にnetパッケージ)
  • Go言語のテストに関する一般的なプラクティス
  • Go言語のインターフェースに関する解説記事
  • Go言語におけるリソース管理(deferなど)に関する解説記事