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

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

このコミットは、Go言語のcmd/fixツールにおける冗長なポート番号0の削除に関する変更です。具体的には、net.TCPAddrnet.UDPAddrなどのネットワークアドレス構造体において、ポート番号が0である場合にそのフィールドを明示的に記述するのをやめ、より簡潔な表現に修正するものです。これは、Goのnetパッケージにおけるアドレス表現の慣習と、cmd/fixツールのコード自動修正機能に関連しています。

コミット

commit ae7aa345db7c08c15e621dd567b1666a674ffa1a
Author: Tyler Bunnell <tylerbunnell@gmail.com>
Date:   Thu Mar 7 19:06:19 2013 +0900

    cmd/fix: remove redundant 0 port
    
    Fixes #4505.
    
    R=golang-dev, mikioh.mikioh
    CC=golang-dev
    https://golang.org/cl/7468043

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

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

元コミット内容

cmd/fix: remove redundant 0 port

このコミットは、cmd/fixツールが、ネットワークアドレス構造体(例: net.TCPAddr)の初期化において、ポート番号が0である場合にそのPort: 0という冗長な記述を削除するように修正します。

変更の背景

Go言語のnetパッケージにおけるネットワークアドレス構造体(net.TCPAddr, net.UDPAddrなど)は、IPアドレスとポート番号を保持します。これらの構造体を初期化する際、ポート番号が0である場合、それは「任意の利用可能なポート」を意味します。Goの構造体リテラルでは、フィールドがゼロ値(数値型の場合は0)である場合、そのフィールドを明示的に記述しなくても、デフォルトでゼロ値が設定されます。

しかし、既存のコードベースには、Port: 0のように冗長にポート0を明示している箇所が存在していました。これはコードの可読性を低下させ、不必要に冗長な記述となっていました。

このコミットは、Goの標準ツールであるcmd/fixにこの冗長な記述を自動的に修正する機能を追加することで、コードベース全体の品質と一貫性を向上させることを目的としています。具体的には、Go issue #4505で報告された問題に対応しています。このissueでは、net.TCPAddr{IP: ip4, Port: 0}のような記述が冗長であり、net.TCPAddr{IP: ip4}と書くべきであると指摘されていました。

前提知識の解説

Go言語のnetパッケージ

Go言語のnetパッケージは、ネットワークI/Oのプリミティブ機能を提供します。TCP/IP、UDP/IP、IPアドレス、ポート番号などのネットワーク関連の型や関数が含まれます。

  • net.TCPAddr: TCPネットワークアドレスを表す構造体。IPフィールドとPortフィールドを持つ。
  • net.UDPAddr: UDPネットワークアドレスを表す構造体。IPフィールドとPortフィールドを持つ。
  • 構造体リテラル: Goで構造体の値を初期化する構文。StructType{Field1: Value1, Field2: Value2}のように記述します。フィールドがゼロ値の場合、そのフィールドの記述を省略できます。

Go言語のastパッケージ

go/astパッケージは、Goのソースコードの抽象構文木(AST: Abstract Syntax Tree)を表現するための型を定義しています。Goのツール(コンパイラ、リンター、フォーマッター、go fixなど)は、このASTを解析・操作することで、コードの静的解析や変換を行います。

  • ast.File: Goのソースファイル全体のASTを表す。
  • ast.CompositeLit: 複合リテラル(構造体リテラル、配列リテラル、マップリテラルなど)を表すASTノード。
  • ast.KeyValueExpr: キーと値のペア(例: 構造体リテラルのKey: Value)を表すASTノード。
  • ast.BasicLit: 基本リテラル(数値、文字列、真偽値など)を表すASTノード。

cmd/fixツール

cmd/fixは、Go言語の標準ツールの一つで、古いGoのコードを新しいGoのバージョンや慣習に合わせて自動的に修正する役割を担っています。Goの言語仕様や標準ライブラリの変更に伴い、既存のコードが非推奨になったり、より良い書き方が導入されたりした場合に、go fixコマンドを実行することで、これらの修正を自動的に適用できます。このツールは、go/astパッケージを使用してソースコードのASTを解析し、特定のパターンに合致するコードを変換します。

IPv6 Zone ID

IPv6アドレスには、リンクローカルアドレスやサイトローカルアドレスのように、特定のネットワークインターフェースに紐付けられるものがあります。これらのアドレスは、同じアドレスが複数のインターフェースに存在しうるため、どのインターフェースを指すのかを明確にするために「ゾーンID(Zone ID)」が付加されることがあります。例えば、fe80::1%eth0のように、%の後にインターフェース名が続く形式です。

このコミットが修正しているファイルnetipv6zone.goは、元々IPv6ゾーンIDの処理に関連する修正を行うfixルールを定義しているファイルです。今回の変更は、その既存のルールに、ポート0の冗長な記述を削除する機能を追加するものです。

技術的詳細

このコミットは、src/cmd/fix/netipv6zone.goファイル内のnetipv6zone関数に修正を加えることで、cmd/fixツールの動作を変更しています。netipv6zone関数は、GoのASTを走査し、特定のパターンに合致するコードを見つけて修正を適用します。

変更の核心は、ast.CompositeLit(複合リテラル、ここではnet.TCPAddrnet.UDPAddrなどの構造体リテラルを想定)の要素(Elts)を処理する部分にあります。

元のコードでは、構造体リテラルの要素がast.KeyValueExpr(キーと値のペア、例: Port: e)でない場合、つまりPortフィールドが明示的に指定されていないが、値が直接与えられている場合(例: &net.TCPAddr{ip4}のように、Portフィールドが省略されているが、IPフィールドの後にポート値が続く場合)、その値をPortフィールドの値として扱うようにしていました。

今回の変更では、この処理に条件分岐が追加されました。

  1. もし、Portフィールドの値として扱われる式east.BasicLit(基本リテラル、ここでは数値リテラル)であり、その値が文字列として"0"と等しい場合(つまり、ポート番号が0である場合)、そのKeyValueExprPort: 0)を構造体リテラルの要素リストから削除します。
  2. それ以外の場合(ポート番号が0でない場合)、元のロジック通りにPort: eというKeyValueExprを作成し、構造体リテラルの要素として設定します。

これにより、net.TCPAddr{IP: ip4, Port: 0}のようなコードは、cmd/fixを実行するとnet.TCPAddr{IP: ip4}に自動的に修正されるようになります。

テストファイルsrc/cmd/fix/netipv6zone_test.goも更新され、この新しい修正ルールが正しく適用されることを確認するテストケースが追加されています。具体的には、&net.TCPAddr{ip4, 0}という冗長な記述が、&net.TCPAddr{IP: ip4}という簡潔な記述に修正されることを検証しています。

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

src/cmd/fix/netipv6zone.gonetipv6zone関数内:

--- a/src/cmd/fix/netipv6zone.go
+++ b/src/cmd/fix/netipv6zone.go
@@ -57,10 +57,15 @@ func netipv6zone(f *ast.File) bool {
 							Value: e,
 						}
 					} else {
-						cl.Elts[i] = &ast.KeyValueExpr{
-							Key:   ast.NewIdent("Port"),
-							Value: e,
+						if e.(*ast.BasicLit).Value == "0" {
+							cl.Elts = append(cl.Elts[:i], cl.Elts[i+1:]...)
+						} else {
+							cl.Elts[i] = &ast.KeyValueExpr{
+								Key:   ast.NewIdent("Port"),
+								Value: e,
+							}
 						}
+
 					}
 				}
 				fixed = true

src/cmd/fix/netipv6zone_test.goのテストケース:

--- a/src/cmd/fix/netipv6zone_test.go
+++ b/src/cmd/fix/netipv6zone_test.go
@@ -26,7 +26,8 @@ func f() net.Addr {
 	c := &net.IPAddr{ip1}
 	sub(&net.UDPAddr{ip2, 12345})
 	d := &net.TCPAddr{IP: ip3, Port: 54321}
-	return &net.TCPAddr{ip4}, nil
+	e := &net.TCPAddr{ip4, 0}
+	return &net.TCPAddr{ip5}, nil
 }
 `,
 		Out: `package main
@@ -44,7 +45,8 @@ func f() net.Addr {
 	c := &net.IPAddr{IP: ip1}
 	sub(&net.UDPAddr{IP: ip2, Port: 12345})
 	d := &net.TCPAddr{IP: ip3, Port: 54321}
-	return &net.TCPAddr{IP: ip4}, nil
+	e := &net.TCPAddr{IP: ip4}
+	return &net.TCPAddr{IP: ip5}, nil
 }
 `,
 	},

コアとなるコードの解説

変更されたコードブロックは、netipv6zone関数内でast.CompositeLitの要素を反復処理している部分にあります。

					} else {
						if e.(*ast.BasicLit).Value == "0" {
							cl.Elts = append(cl.Elts[:i], cl.Elts[i+1:]...)
						} else {
							cl.Elts[i] = &ast.KeyValueExpr{
								Key:   ast.NewIdent("Port"),
								Value: e,
							}
						}
					}
  • elseブロック: これは、構造体リテラルの要素がKey: Value形式のKeyValueExprではないが、値eが直接与えられている場合に実行されます。これは、net.TCPAddr{ip4, 0}のように、フィールド名が省略され、位置によって値が割り当てられるケースを指します。
  • if e.(*ast.BasicLit).Value == "0": ここが新しい条件分岐です。
    • e.(*ast.BasicLit): east.BasicLit型(数値リテラルなど)であることをアサートします。
    • .Value == "0": そのリテラルの文字列値が"0"であるかをチェックします。これは、ポート番号が0であることを意味します。
  • cl.Elts = append(cl.Elts[:i], cl.Elts[i+1:]...): もしポート番号が0であれば、現在の要素(Port: 0に相当する部分)をcl.Elts(複合リテラルの要素リスト)から削除します。これにより、冗長なPort: 0の記述が取り除かれます。
  • else { ... }: ポート番号が0でない場合、またはeBasicLitでない場合(このコンテキストではポート番号が0でない数値リテラルを想定)、元のロジックが実行されます。
    • cl.Elts[i] = &ast.KeyValueExpr{Key: ast.NewIdent("Port"), Value: e,}: Port: eというKeyValueExprを明示的に作成し、現在の要素を置き換えます。これは、フィールド名が省略されていた場合に、明示的なPortフィールドを追加する役割も果たします。

この変更により、cmd/fixはGoのコードベースにおいて、Port: 0という冗長な記述を自動的に削除し、より簡潔で慣用的なコードスタイルを促進します。

関連リンク

参考にした情報源リンク

  • Go issue #4505 (上記に同じ)
  • Go CL 7468043 (上記に同じ)
  • Go言語のnetパッケージのドキュメント
  • Go言語のgo/astパッケージのドキュメント
  • cmd/fixツールのドキュメント (Goの公式ドキュメントやソースコード)
  • Go言語の構造体リテラルに関する一般的な情報