[インデックス 17360] ファイルの概要
このコミットは、Go言語の標準ライブラリである net
パッケージにおける DialUDP
および DialUnix
関数から、冗長な引数チェックを削除するものです。これにより、コードの重複が解消され、パッケージの内部構造がより明確になります。
コミット
commit 3b961bf88b80e350e9d97aa8fba361a10c3f8a7f
Author: Mikio Hara <mikioh.mikioh@gmail.com>
Date: Thu Aug 22 10:33:06 2013 +0900
net: remove redundant argument check from Dial on udp, unix networks
The net package consists of thin three layers like the follwoing;
- Exposed API, that contains net.Dial, net.DialUDP, net.DialUnix
- Socket and network file descriptor, that contains net.netFD and
its methods, helper functions such as dialUDP, dialUnix
- Network pollster, that contains net.pollDesc and its methods
This CL removes redundant argument check which is already done by
API layer.
R=golang-dev, dave, bradfitz
CC=golang-dev
https://golang.org/cl/13092043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/3b961bf88b80e350e9d97aa8fba361a10c3f8a7f
元コミット内容
net: remove redundant argument check from Dial on udp, unix networks
このコミットは、net
パッケージ内の Dial
関数(特にUDPおよびUnixネットワーク関連)から、冗長な引数チェックを削除することを目的としています。コミットメッセージによると、net
パッケージは以下の3つの薄い層で構成されています。
- Exposed API:
net.Dial
,net.DialUDP
,net.DialUnix
など、外部に公開されるAPI。 - Socket and network file descriptor:
net.netFD
およびそのメソッド、dialUDP
,dialUnix
などのヘルパー関数を含む、ソケットとネットワークファイルディスクリプタを扱う層。 - Network pollster:
net.pollDesc
およびそのメソッドを含む、ネットワークポーリングを扱う層。
この変更は、API層で既に実行されている引数チェックが、下位層のヘルパー関数で重複して行われていたため、その重複を解消するものです。
変更の背景
Go言語の net
パッケージは、ネットワーク通信を抽象化し、TCP/UDP、Unixドメインソケットなどの様々なプロトコルを統一的なインターフェースで扱えるように設計されています。このパッケージは、パフォーマンスと堅牢性を両立させるために、複数の層に分割されたアーキテクチャを採用しています。
コミットメッセージにあるように、net
パッケージは大きく3つの層に分かれています。
- 公開API層: ユーザーが直接利用する
net.Dial
,net.Listen
などの関数群。これらの関数は、ユーザーからの入力を最初に受け取り、基本的な引数の検証(例: ネットワークタイプが正しいか、アドレスがnilでないかなど)を行います。 - ソケット/ファイルディスクリプタ管理層: 実際のソケット操作やファイルディスクリプタの管理を行う内部ヘルパー関数群(例:
dialUDP
,dialUnix
)。この層は、API層からの呼び出しを前提としており、通常はAPI層で検証済みの引数を受け取ります。 - ネットワークポーリング層: I/O多重化(epoll, kqueueなど)を利用して、ノンブロッキングI/Oを効率的に処理するための層。
このコミットが行われる前は、公開API層で引数チェックが行われた後、さらに下位のヘルパー関数(dialUDP
, dialUnix
)でも同じような引数チェックが重複して行われていました。これはコードの冗長性を生み、保守性を低下させる可能性がありました。
変更の背景には、Go言語の標準ライブラリ全体で進められていたコードのクリーンアップと最適化の取り組みがあります。冗長なコードを削除し、各層の責任を明確にすることで、コードベースの品質向上と将来的な拡張性の確保を目指していました。特に、エラーハンドリングや引数検証は、コードの信頼性を高める上で重要ですが、重複は避けるべきです。
前提知識の解説
このコミットを理解するためには、以下の概念についての知識が役立ちます。
Go言語の net
パッケージ
Go言語の net
パッケージは、ネットワークI/Oプリミティブを提供します。TCP/IP、UDP/IP、Unixドメインソケット、IPソケットなどのネットワークプロトコルをサポートし、ネットワーク接続の確立、データの送受信、リスニングなどの機能を提供します。
net.Dial(network, address string) (Conn, error)
: 指定されたネットワークアドレスへの接続を確立します。network
は "tcp", "udp", "unix" など、address
は "host:port" や "path" などです。net.DialUDP(network string, laddr, raddr *UDPAddr) (*UDPConn, error)
: UDPネットワークへの接続を確立します。laddr
はローカルアドレス、raddr
はリモートアドレスです。net.DialUnix(network string, laddr, raddr *UnixAddr) (*UnixConn, error)
: Unixドメインソケットへの接続を確立します。
ネットワークプログラミングの基本
- ソケット (Socket): ネットワーク通信のエンドポイントです。アプリケーションがネットワーク経由でデータを送受信するための抽象化されたインターフェースを提供します。
- ファイルディスクリプタ (File Descriptor): Unix系OSにおいて、ファイルやソケットなどのI/Oリソースを識別するための整数値です。
- UDP (User Datagram Protocol): コネクションレス型のプロトコルで、データの信頼性や順序性を保証しない代わりに、オーバーヘッドが少なく高速です。
- Unixドメインソケット (Unix Domain Socket): 同じホスト上のプロセス間通信(IPC)に使用されるソケットです。ネットワークスタックを介さないため、TCP/IPソケットよりも高速です。
Go言語のエラーハンドリング
Go言語では、エラーは戻り値として明示的に返されます。慣例として、関数の最後の戻り値は error
型であり、nil
でない場合はエラーが発生したことを示します。
net.OpError
:net
パッケージで発生する操作エラーを表す構造体です。どの操作 (Op
) で、どのネットワーク (Net
) で、どのリモートアドレス (Addr
) で、どのような根本的なエラー (Err
) が発生したかを示します。errMissingAddress
:net
パッケージ内部で定義されているエラーで、アドレスが指定されていない場合に返されます。
コードの冗長性 (Redundancy)
同じロジックやチェックがコードベースの複数の場所で繰り返される状態を指します。冗長なコードは、以下のような問題を引き起こします。
- 保守性の低下: 変更が必要な場合に、複数の場所を修正する必要があり、修正漏れのリスクが高まります。
- バグの温床: 修正漏れや、異なる場所で異なる実装がなされることで、バグが発生しやすくなります。
- 可読性の低下: 同じようなコードが散在することで、全体の構造が理解しにくくなります。
このコミットは、このような冗長性を排除し、コードのDRY (Don't Repeat Yourself) 原則に則った改善を目指しています。
技術的詳細
このコミットの核心は、Go言語の net
パッケージにおける内部関数の呼び出しフローと引数検証の責任分担の明確化にあります。
Goの net
パッケージでは、net.DialUDP
や net.DialUnix
のような公開API関数が、実際には内部のヘルパー関数 dialUDP
や dialUnix
を呼び出す構造になっています。
変更前のコードでは、以下のようになっていました。
-
net.DialUDP
(公開API):net.DialUDP
は、引数net
,laddr
,raddr
を受け取ります。- この関数は、直接
dialUDP
を呼び出します。 net.DialUDP
自体は、dialUDP
に処理を委譲するだけで、引数チェックは行っていませんでした。
-
dialUDP
(内部ヘルパー関数):dialUDP
は、net
の種類 ("udp"
,"udp4"
,"udp6"
) のチェックを行います。raddr
(リモートアドレス) がnil
であるかどうかのチェックも行い、nil
の場合はerrMissingAddress
を含むOpError
を返していました。- その後、実際のソケット作成処理 (
internetSocket
) を呼び出します。
同様の構造が net.DialUnix
と dialUnix
の間にも存在していました。
このコミットの変更点は、net.DialUDP
と net.DialUnix
という公開API関数が、内部のヘルパー関数 dialUDP
と dialUnix
を呼び出す前に、自身で引数チェックを行うように修正した点です。
具体的には、DialUDP
と DialUnix
の内部で、ネットワークタイプ ("udp"
, "unix"
など) のチェックと、リモートアドレス (raddr
) が nil
でないことのチェックが追加されました。これにより、これらの公開API関数が、ユーザーからの入力を最初に検証する責任を負うことになります。
そして、内部のヘルパー関数 dialUDP
と dialUnix
からは、これらの冗長な引数チェックが削除されました。なぜなら、これらのヘルパー関数が呼び出される時点では、既に公開API層で引数が検証済みであるため、再度チェックする必要がないからです。
この変更により、各層の責任が明確になります。
- 公開API層: ユーザーからの入力を受け取り、基本的な引数検証を行う。不正な入力はここで早期に検出され、適切なエラーが返される。
- 内部ヘルパー関数層: 公開API層によって検証済みの引数を受け取り、実際の低レベルなネットワーク操作(ソケットの作成、バインド、接続など)に集中する。
これにより、コードの重複が解消され、以下の利点が得られます。
- コードの簡潔性: 同じチェックが複数箇所に書かれることがなくなり、コードがより簡潔になります。
- 保守性の向上: 引数チェックのロジックを変更する必要がある場合、公開API層の一箇所を修正するだけで済みます。
- パフォーマンスの微細な向上: 冗長なチェックがスキップされるため、ごくわずかですが実行パスが短縮されます。ただし、これは主要な目的ではありません。
- 設計の明確化: 各関数の責任範囲が明確になり、コードの意図がより理解しやすくなります。
この変更は、Go言語の標準ライブラリが、堅牢性と効率性を追求しつつ、内部構造のクリーンアップと最適化を継続的に行っていることを示しています。
コアとなるコードの変更箇所
このコミットでは、src/pkg/net/udpsock_posix.go
と src/pkg/net/unixsock_posix.go
の2つのファイルが変更されています。
src/pkg/net/udpsock_posix.go
--- a/src/pkg/net/udpsock_posix.go
+++ b/src/pkg/net/udpsock_posix.go
@@ -170,10 +170,6 @@ func (c *UDPConn) WriteMsgUDP(b, oob []byte, addr *UDPAddr) (n, oobn int, err er
// which must be "udp", "udp4", or "udp6". If laddr is not nil, it is
// used as the local address for the connection.
func DialUDP(net string, laddr, raddr *UDPAddr) (*UDPConn, error) {
- return dialUDP(net, laddr, raddr, noDeadline)
-}
-
-func dialUDP(net string, laddr, raddr *UDPAddr, deadline time.Time) (*UDPConn, error) {
switch net {
case "udp", "udp4", "udp6":
default:
@@ -182,6 +178,10 @@ func dialUDP(net string, laddr, raddr *UDPAddr, deadline time.Time) (*UDPConn, e
if raddr == nil {
return nil, &OpError{"dial", net, nil, errMissingAddress}
}
+ return dialUDP(net, laddr, raddr, noDeadline)
+}
+
+func dialUDP(net string, laddr, raddr *UDPAddr, deadline time.Time) (*UDPConn, error) {
fd, err := internetSocket(net, laddr, raddr, deadline, syscall.SOCK_DGRAM, 0, "dial", sockaddrToUDP)
if err != nil {
return nil, err
src/pkg/net/unixsock_posix.go
--- a/src/pkg/net/unixsock_posix.go
+++ b/src/pkg/net/unixsock_posix.go
@@ -247,15 +247,15 @@ func (c *UnixConn) CloseWrite() error {
// which must be "unix", "unixgram" or "unixpacket". If laddr is not
// nil, it is used as the local address for the connection.
func DialUnix(net string, laddr, raddr *UnixAddr) (*UnixConn, error) {
-\treturn dialUnix(net, laddr, raddr, noDeadline)
-}
-\n-func dialUnix(net string, laddr, raddr *UnixAddr, deadline time.Time) (*UnixConn, error) {
switch net {
case "unix", "unixgram", "unixpacket":
default:
return nil, UnknownNetworkError(net)
}
+\treturn dialUnix(net, laddr, raddr, noDeadline)
+}\n+\n+func dialUnix(net string, laddr, raddr *UnixAddr, deadline time.Time) (*UnixConn, error) {
fd, err := unixSocket(net, laddr, raddr, "dial", deadline)
if err != nil {
return nil, err
コアとなるコードの解説
src/pkg/net/udpsock_posix.go
の変更点
-
DialUDP
関数の変更:- 変更前:
DialUDP
は直接dialUDP
を呼び出すだけでした。 - 変更後:
DialUDP
関数内に、ネットワークタイプ (net
引数) のチェックと、リモートアドレス (raddr
) がnil
でないことのチェックが追加されました。
これにより、func DialUDP(net string, laddr, raddr *UDPAddr) (*UDPConn, error) { switch net { case "udp", "udp4", "udp6": default: return nil, UnknownNetworkError(net) } if raddr == nil { return nil, &OpError{"dial", net, nil, errMissingAddress} } return dialUDP(net, laddr, raddr, noDeadline) }
DialUDP
がユーザーからの入力を最初に検証する責任を持つようになりました。
- 変更前:
-
dialUDP
関数の変更:- 変更前:
dialUDP
関数内に、ネットワークタイプとraddr
のnil
チェックがありました。 - 変更後:
dialUDP
関数から、これらのチェックが削除されました。func dialUDP(net string, laddr, raddr *UDPAddr, deadline time.Time) (*UDPConn, error) { // 以前ここにあったチェックはDialUDPに移動 fd, err := internetSocket(net, laddr, raddr, deadline, syscall.SOCK_DGRAM, 0, "dial", sockaddrToUDP) if err != nil { return nil, err } // ... }
dialUDP
は、DialUDP
によって既に検証された引数を受け取るため、これらのチェックは不要になりました。
- 変更前:
src/pkg/net/unixsock_posix.go
の変更点
DialUnix
と dialUnix
の関係も DialUDP
と同様です。
-
DialUnix
関数の変更:- 変更前:
DialUnix
は直接dialUnix
を呼び出すだけでした。 - 変更後:
DialUnix
関数内に、ネットワークタイプ (net
引数) のチェックが追加されました。
Unixドメインソケットの場合、func DialUnix(net string, laddr, raddr *UnixAddr) (*UnixConn, error) { switch net { case "unix", "unixgram", "unixpacket": default: return nil, UnknownNetworkError(net) } // raddr == nil のチェックはUnixドメインソケットでは必須ではないため、UDPとは異なる return dialUnix(net, laddr, raddr, noDeadline) }
raddr
がnil
であっても接続が可能なケースがあるため、raddr == nil
のチェックはUDPの場合とは異なります。しかし、ネットワークタイプのチェックはここで行われるようになりました。
- 変更前:
-
dialUnix
関数の変更:- 変更前:
dialUnix
関数内に、ネットワークタイプのチェックがありました。 - 変更後:
dialUnix
関数から、このチェックが削除されました。func dialUnix(net string, laddr, raddr *UnixAddr, deadline time.Time) (*UnixConn, error) { // 以前ここにあったチェックはDialUnixに移動 fd, err := unixSocket(net, laddr, raddr, "dial", deadline) if err != nil { return nil, err } // ... }
dialUnix
は、DialUnix
によって既に検証された引数を受け取るため、このチェックは不要になりました。
- 変更前:
これらの変更により、net
パッケージのAPI層が引数検証の主要な責任を負い、内部ヘルパー関数はより低レベルなソケット操作に特化するという、よりクリーンな設計が実現されました。
関連リンク
- Go言語
net
パッケージのドキュメント: https://pkg.go.dev/net - Go言語の
net
パッケージのソースコード (GitHub): https://github.com/golang/go/tree/master/src/net - Go言語の
syscall
パッケージのドキュメント: https://pkg.go.dev/syscall (ソケット操作の低レベルなシステムコールに関連)
参考にした情報源リンク
- Go言語の公式ドキュメント
- Go言語の
net
パッケージのソースコード - Go言語のコミット履歴とコードレビューコメント (CL 13092043)
- 一般的なネットワークプログラミングとOSのソケットAPIに関する知識
- DRY (Don't Repeat Yourself) 原則に関するソフトウェア工学の概念