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

[インデックス 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つの層に分かれています。

  1. 公開API層: ユーザーが直接利用する net.Dial, net.Listen などの関数群。これらの関数は、ユーザーからの入力を最初に受け取り、基本的な引数の検証(例: ネットワークタイプが正しいか、アドレスがnilでないかなど)を行います。
  2. ソケット/ファイルディスクリプタ管理層: 実際のソケット操作やファイルディスクリプタの管理を行う内部ヘルパー関数群(例: dialUDP, dialUnix)。この層は、API層からの呼び出しを前提としており、通常はAPI層で検証済みの引数を受け取ります。
  3. ネットワークポーリング層: 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.DialUDPnet.DialUnix のような公開API関数が、実際には内部のヘルパー関数 dialUDPdialUnix を呼び出す構造になっています。

変更前のコードでは、以下のようになっていました。

  1. net.DialUDP (公開API):

    • net.DialUDP は、引数 net, laddr, raddr を受け取ります。
    • この関数は、直接 dialUDP を呼び出します。
    • net.DialUDP 自体は、dialUDP に処理を委譲するだけで、引数チェックは行っていませんでした。
  2. dialUDP (内部ヘルパー関数):

    • dialUDP は、net の種類 ("udp", "udp4", "udp6") のチェックを行います。
    • raddr (リモートアドレス) が nil であるかどうかのチェックも行い、nil の場合は errMissingAddress を含む OpError を返していました。
    • その後、実際のソケット作成処理 (internetSocket) を呼び出します。

同様の構造が net.DialUnixdialUnix の間にも存在していました。

このコミットの変更点は、net.DialUDPnet.DialUnix という公開API関数が、内部のヘルパー関数 dialUDPdialUnix を呼び出す前に、自身で引数チェックを行うように修正した点です。

具体的には、DialUDPDialUnix の内部で、ネットワークタイプ ("udp", "unix" など) のチェックと、リモートアドレス (raddr) が nil でないことのチェックが追加されました。これにより、これらの公開API関数が、ユーザーからの入力を最初に検証する責任を負うことになります。

そして、内部のヘルパー関数 dialUDPdialUnix からは、これらの冗長な引数チェックが削除されました。なぜなら、これらのヘルパー関数が呼び出される時点では、既に公開API層で引数が検証済みであるため、再度チェックする必要がないからです。

この変更により、各層の責任が明確になります。

  • 公開API層: ユーザーからの入力を受け取り、基本的な引数検証を行う。不正な入力はここで早期に検出され、適切なエラーが返される。
  • 内部ヘルパー関数層: 公開API層によって検証済みの引数を受け取り、実際の低レベルなネットワーク操作(ソケットの作成、バインド、接続など)に集中する。

これにより、コードの重複が解消され、以下の利点が得られます。

  • コードの簡潔性: 同じチェックが複数箇所に書かれることがなくなり、コードがより簡潔になります。
  • 保守性の向上: 引数チェックのロジックを変更する必要がある場合、公開API層の一箇所を修正するだけで済みます。
  • パフォーマンスの微細な向上: 冗長なチェックがスキップされるため、ごくわずかですが実行パスが短縮されます。ただし、これは主要な目的ではありません。
  • 設計の明確化: 各関数の責任範囲が明確になり、コードの意図がより理解しやすくなります。

この変更は、Go言語の標準ライブラリが、堅牢性と効率性を追求しつつ、内部構造のクリーンアップと最適化を継続的に行っていることを示しています。

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

このコミットでは、src/pkg/net/udpsock_posix.gosrc/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 の変更点

  1. 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 がユーザーからの入力を最初に検証する責任を持つようになりました。
  2. dialUDP 関数の変更:

    • 変更前: dialUDP 関数内に、ネットワークタイプと raddrnil チェックがありました。
    • 変更後: 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 の変更点

DialUnixdialUnix の関係も DialUDP と同様です。

  1. DialUnix 関数の変更:

    • 変更前: DialUnix は直接 dialUnix を呼び出すだけでした。
    • 変更後: DialUnix 関数内に、ネットワークタイプ (net 引数) のチェックが追加されました。
      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)
      }
      
      Unixドメインソケットの場合、raddrnil であっても接続が可能なケースがあるため、raddr == nil のチェックはUDPの場合とは異なります。しかし、ネットワークタイプのチェックはここで行われるようになりました。
  2. 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言語の公式ドキュメント
  • Go言語の net パッケージのソースコード
  • Go言語のコミット履歴とコードレビューコメント (CL 13092043)
  • 一般的なネットワークプログラミングとOSのソケットAPIに関する知識
  • DRY (Don't Repeat Yourself) 原則に関するソフトウェア工学の概念