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

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

コミット

commit 5ce98da1a27f55a11107c861633b68760d9d03e6
Author: Dmitriy Vyukov <dvyukov@google.com>
Date:   Thu Jun 19 22:04:37 2014 -0700

    net: simplify code
    Single-case select with a non-nil channel is pointless.

    LGTM=mikioh.mikioh
    R=mikioh.mikioh
    CC=golang-codereviews
    https://golang.org/cl/103920044

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

https://github.com/golang/go/commit/5ce98da1a27f55a11107c861633b68760d9d03e6

元コミット内容

net: simplify code
Single-case select with a non-nil channel is pointless.

LGTM=mikioh.mikioh
R=mikioh.mikioh
CC=golang-codereviews
https://golang.org/cl/103920044

変更の背景

このコミットの主な目的は、Goのnetパッケージ内のコードを簡素化することです。コミットメッセージに「Single-case select with a non-nil channel is pointless.(nilでないチャネルに対する単一ケースのselectは無意味である)」と明記されている通り、既存のコードには冗長なselectステートメントが存在していました。

Goのselectステートメントは、複数のチャネル操作の中から準備ができたものを待機し、実行するために使用されます。しかし、selectステートメントが単一のcaseしか持たず、かつそのcaseがnilではない(つまり、常に操作可能である)チャネルからの読み取りまたは書き込みである場合、そのselectは通常のチャネル操作と機能的に全く同じになります。このようなselectは、コードの意図を不明瞭にし、不必要な複雑さを加えるだけで、何のメリットもありません。

この変更は、このような冗長な構造を特定し、より直接的なチャネル操作に置き換えることで、コードの可読性と保守性を向上させることを目的としています。パフォーマンスへの影響はほとんどありませんが、コードベース全体の品質と一貫性を高めるためのクリーンアップ作業の一環と言えます。

前提知識の解説

Goのselectステートメント

selectステートメントは、Goにおける並行処理の強力なプリミティブの一つです。複数のチャネル操作(送受信)を同時に待機し、そのうちのいずれかが準備できた時点でその操作を実行します。

基本的な構文は以下の通りです。

select {
case <-ch1:
    // ch1からの受信
case ch2 <- value:
    // ch2への送信
default:
    // どのチャネルも準備できていない場合に即座に実行される(オプション)
}

selectの動作にはいくつかの重要な特性があります。

  • ブロック: どのcaseも準備できていない場合、selectはブロックし、いずれかのcaseが準備できるまで待機します。
  • 非ブロック(default使用時): defaultケースが存在する場合、どのチャネル操作も即座に実行できない場合でもselectはブロックせず、defaultケースが実行されます。
  • ランダム選択: 複数のcaseが同時に準備できた場合、selectはそれらのうちの1つをランダムに選択して実行します。
  • nilチャネル: selectcaseでnilチャネルが使用された場合、そのcaseは決して準備ができたと見なされません。これは、特定のチャネル操作を一時的に無効にするために利用されることがあります。

Goのチャネル

チャネルは、Goのゴルーチン(軽量スレッド)間で値を安全に送受信するための通信メカニズムです。チャネルは型付けされており、特定の型の値のみを送受信できます。

  • 作成: make(chan Type) または make(chan Type, capacity)
  • 送信: ch <- value
  • 受信: value := <-ch または <-ch (値が不要な場合)

チャネルは、ゴルーチン間の同期にも広く使用されます。

単一ケースのselectの特性

今回のコミットの核心は、「nilでないチャネルに対する単一ケースのselectは無意味である」という点です。

selectステートメントが以下のような形式であるとします。

select {
case result := <-myChannel:
    // ...
}

ここでmyChannelがnilではない場合、このselectステートメントは常にmyChannelからの受信を試みます。これは、result := <-myChannelという直接的なチャネル受信操作と全く同じ動作をします。selectの持つ「複数の選択肢から一つを選ぶ」という機能が、単一のcaseしかない場合には発揮されず、単なる冗長なラッパーとなってしまうのです。

したがって、このようなコードは簡潔なresult := <-myChannelに置き換えることができ、コードの意図がより明確になります。

技術的詳細

このコミットは、src/pkg/net/dial.goファイル内のdialMulti関数におけるselectステートメントの変更に焦点を当てています。

dialMulti関数の役割

dialMulti関数は、Goのnetパッケージにおいて、複数のネットワークアドレスに対して並行してダイヤル(接続試行)を行うための内部関数です。これは、例えばDNSが複数のIPアドレスを返す場合や、IPv4とIPv6の両方で接続を試みる場合などに使用されます。

この関数は、複数の「レーサー」(racer)ゴルーチンを起動し、それぞれが独立して接続を試みます。最初に成功した接続、または最初のエラーを報告した接続の結果を待機し、適切な処理を行います。laneチャネルは、これらのレーサーゴルーチンからの結果(接続またはエラー)を受け取るために使用されます。

変更前のコードの課題

変更前のdialMulti関数内には、以下のようなループがありました。

for nracers > 0 {
    sig <- true // シグナルを送信
    select {
    case racer := <-lane: // laneチャネルからの受信を待機
        if racer.error == nil {
            return racer.Conn, nil // 成功した接続を返す
        }
        lastErr = racer.error
        nracers--
    }
}

ここで注目すべきは、selectステートメントがcase racer := <-lane:という単一のcaseしか持っていない点です。laneチャネルはdialMulti関数内で作成され、nilではないことが保証されています。

前述の「単一ケースのselectの特性」で説明した通り、nilではないチャネルに対する単一ケースのselectは、通常のチャネル受信操作と全く同じ動作をします。つまり、このselectブロックは、単にlaneチャネルからの値が利用可能になるまでブロックし、値を受信するという動作しか行いません。

このselectの使用は、コードの意図を不明瞭にし、不必要な構文上のオーバーヘッドを生み出していました。selectは通常、複数の選択肢がある場合にその真価を発揮しますが、この場合はその機能が全く活用されていませんでした。

変更による改善

このコミットでは、冗長なselectステートメントを削除し、直接的なチャネル受信操作に置き換えることで、コードを簡素化しました。

変更後のコードは以下のようになります。

for nracers > 0 {
    sig <- true // シグナルを送信
    racer := <-lane // laneチャネルからの受信
    if racer.error == nil {
        return racer.Conn, nil // 成功した接続を返す
    }
    lastErr = racer.error
    nracers--
}

この変更により、コードはより直接的で読みやすくなりました。laneチャネルからの受信が、selectという余分な構文なしに明示的に行われるため、開発者はこの行が単にチャネルからの値の到着を待っていることをすぐに理解できます。機能的な動作は変更されていませんが、コードの意図がより明確になり、保守性が向上しました。

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

--- a/src/pkg/net/dial.go
+++ b/src/pkg/net/dial.go
@@ -214,14 +214,12 @@ func dialMulti(net, addr string, la Addr, ras addrList, deadline time.Time) (Con
 		nracers := len(ras)
 		for nracers > 0 {
 			sig <- true
-			select {
-			case racer := <-lane:
-				if racer.error == nil {
-					return racer.Conn, nil
-				}
-				lastErr = racer.error
-				nracers--
-			}
+			racer := <-lane
+			if racer.error == nil {
+				return racer.Conn, nil
+			}
+			lastErr = racer.error
+			nracers--
 		}
 		return nil, lastErr
 	}

コアとなるコードの解説

変更はsrc/pkg/net/dial.goファイルのdialMulti関数内で行われています。

  • 変更前:

    			select {
    			case racer := <-lane:
    				if racer.error == nil {
    					return racer.Conn, nil
    				}
    				lastErr = racer.error
    				nracers--
    			}
    

    この部分では、selectステートメントが使用されており、laneチャネルからの受信(case racer := <-lane:)のみを待機しています。laneチャネルはnilではないため、このselectは常にlaneチャネルからの値が利用可能になるまでブロックし、その値を受信します。

  • 変更後:

    			racer := <-lane
    			if racer.error == nil {
    				return racer.Conn, nil
    			}
    			lastErr = racer.error
    			nracers--
    

    変更後では、冗長なselectステートメントが完全に削除され、racer := <-laneという直接的なチャネル受信操作に置き換えられています。これにより、コードはより簡潔になり、laneチャネルからの値の受信が直接的に表現されるようになりました。その後のエラーチェックとカウンタのデクリメントのロジックは変更されていません。

この変更は、機能的な動作を変えることなく、コードの明瞭さと簡潔さを向上させるためのリファクタリングです。

関連リンク

参考にした情報源リンク

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

コミット

commit 5ce98da1a27f55a11107c861633b68760d9d03e6
Author: Dmitriy Vyukov <dvyukov@google.com>
Date:   Thu Jun 19 22:04:37 2014 -0700

    net: simplify code
    Single-case select with a non-nil channel is pointless.

    LGTM=mikioh.mikioh
    R=mikioh.mikioh
    CC=golang-codereviews
    https://golang.org/cl/103920044

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

https://github.com/golang/go/commit/5ce98da1a27f55a11107c861633b68760d9d03e6

元コミット内容

net: simplify code
Single-case select with a non-nil channel is pointless.

LGTM=mikioh.mikioh
R=mikioh.mikioh
CC=golang-codereviews
https://golang.org/cl/103920044

変更の背景

このコミットの主な目的は、Goのnetパッケージ内のコードを簡素化することです。コミットメッセージに「Single-case select with a non-nil channel is pointless.(nilでないチャネルに対する単一ケースのselectは無意味である)」と明記されている通り、既存のコードには冗長なselectステートメントが存在していました。

Goのselectステートメントは、複数のチャネル操作の中から準備ができたものを待機し、実行するために使用されます。しかし、selectステートメントが単一のcaseしか持たず、かつそのcaseがnilではない(つまり、常に操作可能である)チャネルからの読み取りまたは書き込みである場合、そのselectは通常のチャネル操作と機能的に全く同じになります。このようなselectは、コードの意図を不明瞭にし、不必要な複雑さを加えるだけで、何のメリットもありません。

この変更は、このような冗長な構造を特定し、より直接的なチャネル操作に置き換えることで、コードの可読性と保守性を向上させることを目的としています。パフォーマンスへの影響はほとんどありませんが、コードベース全体の品質と一貫性を高めるためのクリーンアップ作業の一環と言えます。

前提知識の解説

Goのselectステートメント

selectステートメントは、Goにおける並行処理の強力なプリミティブの一つです。複数のチャネル操作(送受信)を同時に待機し、そのうちのいずれかが準備できた時点でその操作を実行します。

基本的な構文は以下の通りです。

select {
case <-ch1:
    // ch1からの受信
case ch2 <- value:
    // ch2への送信
default:
    // どのチャネルも準備できていない場合に即座に実行される(オプション)
}

selectの動作にはいくつかの重要な特性があります。

  • ブロック: どのcaseも準備できていない場合、selectはブロックし、いずれかのcaseが準備できるまで待機します。
  • 非ブロック(default使用時): defaultケースが存在する場合、どのチャネル操作も即座に実行できない場合でもselectはブロックせず、defaultケースが実行されます。
  • ランダム選択: 複数のcaseが同時に準備できた場合、selectはそれらのうちの1つをランダムに選択して実行します。
  • nilチャネル: selectcaseでnilチャネルが使用された場合、そのcaseは決して準備ができたと見なされません。これは、特定のチャネル操作を一時的に無効にするために利用されることがあります。

Goのチャネル

チャネルは、Goのゴルーチン(軽量スレッド)間で値を安全に送受信するための通信メカニズムです。チャネルは型付けされており、特定の型の値のみを送受信できます。

  • 作成: make(chan Type) または make(chan Type, capacity)
  • 送信: ch <- value
  • 受信: value := <-ch または <-ch (値が不要な場合)

チャネルは、ゴルーチン間の同期にも広く使用されます。

単一ケースのselectの特性

今回のコミットの核心は、「nilでないチャネルに対する単一ケースのselectは無意味である」という点です。

selectステートメントが以下のような形式であるとします。

select {
case result := <-myChannel:
    // ...
}

ここでmyChannelがnilではない場合、このselectステートメントは常にmyChannelからの受信を試みます。これは、result := <-myChannelという直接的なチャネル受信操作と全く同じ動作をします。selectの持つ「複数の選択肢から一つを選ぶ」という機能が、単一のcaseしかない場合には発揮されず、単なる冗長なラッパーとなってしまうのです。

したがって、このようなコードは簡潔なresult := <-myChannelに置き換えることができ、コードの意図がより明確になります。

技術的詳細

このコミットは、src/pkg/net/dial.goファイル内のdialMulti関数におけるselectステートメントの変更に焦点を当てています。

dialMulti関数の役割

dialMulti関数は、Goのnetパッケージにおいて、複数のネットワークアドレスに対して並行してダイヤル(接続試行)を行うための内部関数です。これは、例えばDNSが複数のIPアドレスを返す場合や、IPv4とIPv6の両方で接続を試みる場合などに使用されます。

この関数は、複数の「レーサー」(racer)ゴルーチンを起動し、それぞれが独立して接続を試みます。最初に成功した接続、または最初のエラーを報告した接続の結果を待機し、適切な処理を行います。laneチャネルは、これらのレーサーゴルーチンからの結果(接続またはエラー)を受け取るために使用されます。

変更前のコードの課題

変更前のdialMulti関数内には、以下のようなループがありました。

for nracers > 0 {
    sig <- true // シグナルを送信
    select {
    case racer := <-lane: // laneチャネルからの受信を待機
        if racer.error == nil {
            return racer.Conn, nil // 成功した接続を返す
        }
        lastErr = racer.error
        nracers--
    }
}

ここで注目すべきは、selectステートメントがcase racer := <-lane:という単一のcaseしか持っていない点です。laneチャネルはdialMulti関数内で作成され、nilではないことが保証されています。

前述の「単一ケースのselectの特性」で説明した通り、nilではないチャネルに対する単一ケースのselectは、通常のチャネル受信操作と全く同じ動作をします。つまり、このselectブロックは、単にlaneチャネルからの値が利用可能になるまでブロックし、値を受信するという動作しか行いません。

このselectの使用は、コードの意図を不明瞭にし、不必要な構文上のオーバーヘッドを生み出していました。selectは通常、複数の選択肢がある場合にその真価を発揮しますが、この場合はその機能が全く活用されていませんでした。

変更による改善

このコミットでは、冗長なselectステートメントを削除し、直接的なチャネル受信操作に置き換えることで、コードを簡素化しました。

変更後のコードは以下のようになります。

for nracers > 0 {
    sig <- true // シグナルを送信
    racer := <-lane // laneチャネルからの受信
    if racer.error == nil {
        return racer.Conn, nil // 成功した接続を返す
    }
    lastErr = racer.error
    nracers--
}

この変更により、コードはより直接的で読みやすくなりました。laneチャネルからの受信が、selectという余分な構文なしに明示的に行われるため、開発者はこの行が単にチャネルからの値の到着を待っていることをすぐに理解できます。機能的な動作は変更されていませんが、コードの意図がより明確になり、保守性が向上しました。

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

--- a/src/pkg/net/dial.go
+++ b/src/pkg/net/dial.go
@@ -214,14 +214,12 @@ func dialMulti(net, addr string, la Addr, ras addrList, deadline time.Time) (Con
 		nracers := len(ras)
 		for nracers > 0 {
 			sig <- true
-			select {
-			case racer := <-lane:
-				if racer.error == nil {
-					return racer.Conn, nil
-				}
-				lastErr = racer.error
-				nracers--
-			}
+			racer := <-lane
+			if racer.error == nil {
+				return racer.Conn, nil
+			}
+			lastErr = racer.error
+			nracers--
 		}
 		return nil, lastErr
 	}

コアとなるコードの解説

変更はsrc/pkg/net/dial.goファイルのdialMulti関数内で行われています。

  • 変更前:

    			select {
    			case racer := <-lane:
    				if racer.error == nil {
    					return racer.Conn, nil
    				}
    				lastErr = racer.error
    				nracers--
    			}
    

    この部分では、selectステートメントが使用されており、laneチャネルからの受信(case racer := <-lane:)のみを待機しています。laneチャネルはnilではないため、このselectは常にlaneチャネルからの値が利用可能になるまでブロックし、その値を受信します。

  • 変更後:

    			racer := <-lane
    			if racer.error == nil {
    				return racer.Conn, nil
    			}
    			lastErr = racer.error
    			nracers--
    

    変更後では、冗長なselectステートメントが完全に削除され、racer := <-laneという直接的なチャネル受信操作に置き換えられています。これにより、コードはより簡潔になり、laneチャネルからの値の受信が直接的に表現されるようになりました。その後のエラーチェックとカウンタのデクリメントのロジックは変更されていません。

この変更は、機能的な動作を変えることなく、コードの明瞭さと簡潔さを向上させるためのリファクタリングです。

関連リンク

参考にした情報源リンク