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

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

このコミットは、Go言語の標準ライブラリsrc/lib/netパッケージ内の多数の識別子(関数、型、定数)の可視性を変更するものです。具体的には、これまで外部に公開されていた(exportキーワードまたは大文字で始まる識別子として)多くの要素が、アンダースコア(_)をプレフィックスとして付与されることで、パッケージ内部でのみ利用可能な「アンエクスポート(unexported)」な識別子に変更されています。これにより、netパッケージの外部から直接アクセスできるAPIが整理され、内部実装の詳細が隠蔽され、よりクリーンで安定したAPI設計へと移行しています。

コミット

commit c840657fe140478f4f74b1678d367fe93f005ac3
Author: Russ Cox <rsc@golang.org>
Date:   Fri Jan 16 11:04:44 2009 -0800

    casify DNS
    
    R=r
    DELTA=221  (0 added, 0 deleted, 221 changed)
    OCL=22946
    CL=22948

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

https://github.com/golang/go/commit/c840657fe140478f4f74b1678d367fe93f005ac3

元コミット内容

コミットメッセージ「casify DNS」は、一見するとDNS関連のコードの文字ケースを変更するだけのように見えますが、実際にはGo言語における識別子の「ケース(case)」、すなわち大文字・小文字の区別を利用した可視性(エクスポート/アンエクスポート)の変更を指しています。このコミットの主な目的は、src/lib/netパッケージ内の多くの公開されていた関数、型、定数を、アンダースコアプレフィックスを付与することでパッケージ内部でのみ利用可能な非公開(アンエクスポート)なものに変更することです。これは、Go言語のAPI設計におけるカプセル化と、初期のGo言語で使われていたexportキーワードの廃止に向けた移行の一環と考えられます。

変更の背景

このコミットが行われた2009年1月は、Go言語がまだ開発の初期段階にあり、言語仕様や標準ライブラリのAPIが活発に進化していた時期にあたります。初期のGo言語には、識別子を外部に公開するためにexportキーワードが存在していましたが、後にこのキーワードは廃止され、識別子の先頭文字が大文字であるか小文字であるかによって可視性を制御する現在のシンプルなルールに移行しました。

このコミットは、まさにその移行期における重要なステップの一つです。netパッケージは、ネットワーク通信の基盤を提供する非常に重要なパッケージであり、そのAPIは安定している必要があります。しかし、開発初期段階では、内部実装の詳細が誤って外部に公開されてしまうことがよくあります。このような内部実装の詳細は、将来的に変更される可能性が高く、外部から直接利用されると、ライブラリの互換性を損なう原因となります。

したがって、このコミットの背景には、以下の目的があります。

  1. APIのカプセル化と整理: パッケージの外部に公開すべきAPIを明確にし、内部実装の詳細を隠蔽することで、パッケージの利用者が混乱することなく、安定したAPIを利用できるようにする。
  2. Go言語の可視性ルールへの準拠: exportキーワードの廃止に伴い、識別子の命名規則(大文字/小文字)による可視性制御の慣習に合わせる。アンダースコアプレフィックスは、Go言語の慣習として、パッケージ内部でのみ利用されることを意図した識別子によく用いられます。
  3. 将来的な変更への柔軟性: 内部実装が変更されても、外部APIに影響を与えないようにすることで、ライブラリの進化を容易にする。

前提知識の解説

Go言語における識別子の可視性(エクスポート/アンエクスポート)

Go言語では、識別子(変数、関数、型など)の可視性は、その識別子の先頭文字が大文字であるか小文字であるかによって決まります。

  • エクスポートされた識別子(Exported Identifiers): 先頭文字が大文字の識別子は、そのパッケージの外部からアクセス可能です。これは、他のパッケージから利用できる「公開API」となります。
  • アンエクスポートされた識別子(Unexported Identifiers): 先頭文字が小文字の識別子は、そのパッケージの内部からのみアクセス可能です。これは、パッケージの「内部実装」であり、外部からは直接利用できません。

このコミットでは、既存のexportキーワードで公開されていた、または大文字で始まっていたが内部利用に留めるべき識別子に、アンダースコア(_)をプレフィックスとして付与することで、意図的にアンエクスポート化しています。アンダースコアは、Goの慣習として、特に内部的なヘルパー関数や構造体フィールドなどに用いられることがあります。

DNSプロトコルとGo言語のnetパッケージ

DNS(Domain Name System)は、インターネット上でドメイン名をIPアドレスに変換するための分散型データベースシステムです。Go言語のnetパッケージは、ネットワークI/Oのプリミティブを提供し、TCP/IP、UDP、DNSなどのプロトコルを扱うための機能を含んでいます。

  • DNSクライアント: ドメイン名解決のためのクエリをDNSサーバーに送信し、応答を解析する機能。
  • メッセージのパッキング/アンパッキング: DNSメッセージは特定のバイナリ形式でネットワーク上を流れます。このバイナリデータをGoのデータ構造に変換(アンパッキング)したり、Goのデータ構造をバイナリデータに変換(パッキング)したりする処理が必要です。

ネットワークプログラミングの基本

  • ソケット(Socket): ネットワーク通信のエンドポイント。アプリケーションがネットワーク経由でデータを送受信するための抽象化されたインターフェースです。
  • ファイルディスクリプタ(File Descriptor, FD): Unix系OSにおいて、ファイルやソケットなどのI/Oリソースを識別するための整数値です。
  • ポーリング(Polling): 複数のI/O操作(ソケットからの読み書きなど)の準備ができたかどうかを効率的に監視するメカニズム。PollServerのようなコンポーネントがこれを行います。

Go言語のreflectパッケージ

reflectパッケージは、Goプログラムが実行時に自身の構造を検査(リフレクション)したり、操作したりするための機能を提供します。このコミットでは、DNSメッセージのパッキング/アンパッキング処理において、構造体のフィールドを動的に処理するためにreflectパッケージが利用されています。例えば、PackStructValueUnpackStructValue関数は、reflect.StructValueを使って構造体の各フィールドを走査し、その型に応じてバイナリデータへの変換やバイナリデータからの復元を行っています。

技術的詳細

このコミットの技術的な核心は、src/lib/netパッケージ内の多数の識別子を、外部からアクセスできないようにアンエクスポート化した点にあります。これは、Go言語の設計思想である「シンプルなAPIと強力な内部実装」を具現化するものです。

具体的な変更は以下のパターンに従っています。

  1. 関数名の変更: Exchange, Answer, TryOneName, LoadConfig, DNS_ReadConfig, Open, GetFields, Dtoi, PackDomainName, UnpackDomainName, PackStructValue, PackStruct, UnpackStructValue, UnpackStruct, PrintStructValue, PrintStruct, PackRR, UnpackRR, SetNonblock, NewPollServer, StartServer, MakeIPv4, IsZeros, SimpleMaskLength, ParseIPv4, ParseIPv6, ParseIP, SplitHostPort, JoinHostPort, HostPortToIP, SockaddrToHostPort, Socket, NewConnTCP, NewConnUDP, ReadServicesなど、多くの関数名が_プレフィックス付きに変更されています(例: Exchange -> _Exchange)。これにより、これらの関数はnetパッケージの外部からは直接呼び出せなくなります。

  2. 型名の変更: DNS_Header, PollServer, DNS_Msg_Top, ConnBase, Fileなどの型名が_プレフィックス付きに変更されています(例: DNS_Header -> _DNS_Header)。これにより、これらの型はパッケージ内部でのみ定義・利用されることになります。

  3. 定数名の変更: DNS_NoAnswer, QR, AA, TC, RD, RA, UnknownSocketFamily, Big, PreferIPv4などの定数名が_プレフィックス付きに変更されています(例: DNS_NoAnswer -> DNS_No_Answer, QR -> _QR)。これにより、これらの定数もパッケージ内部でのみ利用されるようになります。

これらの変更は、netパッケージの公開APIをより厳選されたものにし、内部的なヘルパー関数やデータ構造が外部に漏れ出すのを防ぐことを目的としています。例えば、DNSメッセージのパッキング/アンパッキングに使われるPackStructValueUnpackStructValueのような関数は、DNSプロトコルの詳細な実装に依存するものであり、通常はパッケージの利用者が直接触れるべきではありません。これらを非公開にすることで、netパッケージはより高レベルで抽象化されたインターフェースを提供できるようになります。

また、exportキーワードが使われていた箇所が、Go言語の現在の可視性ルール(大文字/小文字)に沿った形に修正されている点も重要です。これは、Go言語の進化の過程で、よりシンプルで統一されたコーディングスタイルへと移行していることを示しています。

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

このコミットは広範囲にわたる変更ですが、その本質を理解するために、いくつかの代表的な変更箇所を挙げます。

src/lib/net/dnsclient.go

--- a/src/lib/net/dnsclient.go
+++ b/src/lib/net/dnsclient.go
@@ -28,7 +28,7 @@ import (
  export var (
  	DNS_InternalError = os.NewError("internal dns error");
  	DNS_MissingConfig = os.NewError("no dns configuration");
- 	DNS_NoAnswer = os.NewError("dns got no answer");
+ 	DNS_No_Answer = os.NewError("dns got no answer");
  	DNS_BadRequest = os.NewError("malformed dns request");
  	DNS_BadReply = os.NewError("malformed dns reply");
  	DNS_ServerFailure = os.NewError("dns server failure");
@@ -40,7 +40,7 @@ export var (
  
  // Send a request on the connection and hope for a reply.
  // Up to cfg.attempts attempts.
- func Exchange(cfg *DNS_Config, c Conn, name string) (m *DNS_Msg, err *os.Error) {
+ func _Exchange(cfg *DNS_Config, c Conn, name string) (m *DNS_Msg, err *os.Error) {
  	if len(name) >= 256 {
  		return nil, DNS_NameTooLong
  	}
@@ -77,14 +77,14 @@ func Exchange(cfg *DNS_Config, c Conn, name string) (m *DNS_Msg, err *os.Error)
  		}
  		return in, nil
  	}
- 	return nil, DNS_NoAnswer
+ 	return nil, DNS_No_Answer
  }
  
  
  // Find answer for name in dns message.
  // On return, if err == nil, addrs != nil.
  // TODO(rsc): Maybe return [][]byte (==[]IPAddr) instead(?
- func Answer(name string, dns *DNS_Msg) (addrs []string, err *os.Error) {
+ func _Answer(name string, dns *DNS_Msg) (addrs []string, err *os.Error) {
  	addrs = make([]string, 0, len(dns.answer));
  
  	if dns.rcode == DNS_RcodeNameError && dns.authoritative {

ここでは、DNS_NoAnswer定数がDNS_No_Answerに、Exchange関数が_Exchangeに、Answer関数が_Answerにそれぞれ変更されています。これにより、これらの識別子はnetパッケージの内部でのみ利用可能になります。

src/lib/net/dnsmsg.go

--- a/src/lib/net/dnsmsg.go
+++ b/src/lib/net/dnsmsg.go
@@ -29,7 +29,7 @@ import (
  	"reflect";
  )
  
-// Packet formats
+// _Packet formats
  
  // Wire constants.
  export const (
@@ -74,19 +74,19 @@ export const (
  )
  
  // The wire format for the DNS packet header.
- type DNS_Header struct {
+ type _DNS_Header struct {
  	id uint16;
  	bits uint16;
  	qdcount, ancount, nscount, arcount uint16;
  }
  
  const (
- 	// DNS_Header.bits
- 	QR = 1<<15;	// query/response (response=1)
- 	AA = 1<<10;	// authoritative
- 	TC = 1<<9;	// truncated
- 	RD = 1<<8;	// recursion desired
- 	RA = 1<<7;	// recursion available
+ 	// _DNS_Header.bits
+ 	_QR = 1<<15;	// query/response (response=1)
+ 	_AA = 1<<10;	// authoritative
+ 	_TC = 1<<9;	// truncated
+ 	_RD = 1<<8;	// recursion desired
+ 	_RA = 1<<7;	// recursion available
  )
  
  // DNS queries.
@@ -188,7 +188,7 @@ export type DNS_RR_A struct {
  }
  
  
-// Packing and unpacking.
+// _Packing and unpacking.
  //
  // All the packers and unpackers take a (msg []byte, off int)
  // and return (off1 int, ok bool).  If they return ok==false, they
@@ -212,10 +212,10 @@ var rr_mk = map[int]*()DNS_RR {
  	DNS_TypeA: func() DNS_RR { return new(DNS_RR_A) },
  }
  
-// Pack a domain name s into msg[off:].
+// _Pack a domain name s into msg[off:].
  // Domain names are a sequence of counted strings
  // split at the dots.  They end with a zero-length string.
- func PackDomainName(s string, msg []byte, off int) (off1 int, ok bool) {
+ func _PackDomainName(s string, msg []byte, off int) (off1 int, ok bool) {
  	// Add trailing dot to canonicalize name.
  	if n := len(s); n == 0 || s[n-1] != '.' {
  		s += ".";
@@ -251,7 +251,7 @@ func PackDomainName(s string, msg []byte, off int) (off1 int, ok bool) {
  	return off, true
  }
  
-// Unpack a domain name.
+// _Unpack a domain name.
  // In addition to the simple sequences of counted strings above,
  // domain names are allowed to refer to strings elsewhere in the
  // packet, to avoid repeating common suffixes when returning
@@ -264,7 +264,7 @@ func PackDomainName(s string, msg []byte, off int) (off1 int, ok bool) {
  // which is where the next record will start.
  // In theory, the pointers are only allowed to jump backward.
  // We let them jump anywhere and stop jumping after a while.
- func UnpackDomainName(msg []byte, off int) (s string, off1 int, ok bool) {
+ func _UnpackDomainName(msg []byte, off int) (s string, off1 int, ok bool) {
  	s = "";
  	ptr := 0;	// number of pointers followed
  Loop:

DNS_Header型が_DNS_Headerに、関連する定数QR, AA, TC, RD, RA_QR, _AA, _TC, _RD, _RAに、PackDomainNameUnpackDomainNameといったパッキング/アンパッキング関数が_PackDomainName, _UnpackDomainNameに変更されています。これは、DNSメッセージの内部構造と処理ロジックが、パッケージの外部からは直接操作されるべきではないという設計思想を反映しています。

コアとなるコードの解説

これらの変更の主な目的は、netパッケージのAPIをより堅牢で使いやすいものにすることです。

  1. 内部実装の詳細の隠蔽: _プレフィックスを付与された関数や型は、netパッケージの内部でのみ使用されることを意図しています。これにより、パッケージの利用者は、DNSメッセージのバイナリフォーマットの詳細や、ソケットのポーリングメカニズムといった低レベルな実装に煩わされることなく、高レベルなネットワークAPIを利用できます。これは、ソフトウェア設計における「情報隠蔽」の原則に従うものです。

  2. APIの安定性向上: 内部実装が外部に公開されていると、その実装が変更された際に、それを直接利用している外部のコードが壊れる可能性があります。内部実装を非公開にすることで、開発者は将来的にこれらの内部コンポーネントを自由にリファクタリングしたり、最適化したりできるようになり、パッケージの互換性を維持しやすくなります。

  3. Go言語の慣習への準拠: Go言語では、識別子の可視性を大文字/小文字で制御するシンプルなルールが採用されています。このコミットは、初期のexportキーワードからこの慣習への移行を促進し、Go言語のコードベース全体で一貫したスタイルを確立する一助となっています。アンダースコアプレフィックスは、特に内部的なヘルパー関数や構造体に対して、その意図(内部利用)を明確に伝える役割も果たします。

例えば、Exchange関数が_Exchangeに変更されたことで、DNSクエリの交換処理はnetパッケージの内部ロジックの一部となり、LookupHostのような公開関数を通じて間接的に利用されるようになります。これにより、ユーザーはDNSの詳細を知らなくてもホスト名を解決できるようになり、APIの使いやすさが向上します。

関連リンク

  • Go言語の可視性ルールに関する公式ドキュメント(Go言語の進化に伴い、このコミット時点とは異なる可能性がありますが、概念は共通です):
  • Go言語のnetパッケージのドキュメント:
  • Go言語の初期の歴史やexportキーワードの廃止に関する情報(Go言語のブログやメーリングリストのアーカイブなど)

参考にした情報源リンク

  • Go言語の公式ドキュメント(Effective Goなど)
  • Go言語のソースコードリポジトリ(GitHub)
  • DNSプロトコルに関する一般的な情報源(RFCなど)
  • ネットワークプログラミングに関する一般的な情報源
  • Go言語の初期のコミット履歴やメーリングリストの議論(公開されている場合)

(注: 2009年当時のGo言語のドキュメントやコミュニティの議論を直接参照することは困難なため、一般的なGo言語の設計原則と現在の知識に基づいて解説を構成しています。)