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

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

このコミットは、Go言語の標準ライブラリnetパッケージにおけるMACアドレスの解析ロジックを、既存のinterface.goファイルからmac.goという独立したファイルに移動させるリファクタリングを目的としています。これにより、コードのモジュール性、保守性、および可読性が向上します。

コミット

commit 4d355836610a2c675f02cfcf75771504a4b69586
Author: Mikio Hara <mikioh.mikioh@gmail.com>
Date:   Mon Mar 5 07:42:07 2012 +0900

    net: move MAC address parser into distinct file
    
    R=golang-dev, bradfitz
    CC=golang-dev
    https://golang.org/cl/5727054

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

https://github.com/golang/go/commit/4d355836610a2c675f02cfcf75771504a4b69586

元コミット内容

net: move MAC address parser into distinct file

このコミットは、MACアドレスの解析に関連するコードを、src/pkg/net/interface.goからsrc/pkg/net/mac.goという新しいファイルに移動させるものです。これに伴い、関連するテストコードもsrc/pkg/net/interface_test.goからsrc/pkg/net/mac_test.goに移動されています。

変更の背景

Go言語の標準ライブラリは、その設計原則として「シンプルさ」と「モジュール性」を重視しています。初期の段階では、関連する機能が単一のファイルにまとめられることがありますが、プロジェクトの成長とともに、特定の機能が肥大化したり、他の機能との依存関係が複雑になったりすることがあります。

このコミットの背景には、netパッケージ内のinterface.goファイルが、ネットワークインターフェースに関する情報だけでなく、MACアドレスの解析という特定のロジックも含むようになっていたという状況があります。MACアドレスの解析は、ネットワークインターフェースの管理とは異なる、より汎用的なユーティリティ機能と見なすことができます。

このような状況でコードをリファクタリングする主な理由は以下の通りです。

  1. 関心の分離 (Separation of Concerns): MACアドレスの解析ロジックを独立したファイルに分離することで、interface.goは純粋にネットワークインターフェースの管理に特化し、mac.goはMACアドレスの操作に特化します。これにより、各ファイルの責任が明確になり、コードの理解が容易になります。
  2. 保守性の向上: 特定の機能が独立したファイルにまとめられることで、その機能に関する変更やバグ修正が他のコードに与える影響を局所化できます。これにより、将来的なメンテナンスが容易になります。
  3. 再利用性の向上: ParseMACのような汎用的な関数が独立したファイルに存在することで、将来的に他の場所でMACアドレスの解析が必要になった場合に、そのコードをより簡単に再利用できるようになります。
  4. テストの整理: 機能が分離されることで、テストコードもそれに対応して分離され、テストの構造がより論理的になります。

前提知識の解説

このコミットを理解するためには、以下の概念について基本的な知識があると役立ちます。

  • Go言語のパッケージシステム: Go言語では、関連する機能がパッケージとしてまとめられます。パッケージはディレクトリ構造に対応し、他のパッケージからインポートして利用できます。netパッケージは、ネットワーク関連の機能を提供する標準ライブラリの一部です。
  • MACアドレス (Media Access Control Address): ネットワーク上のデバイスを一意に識別するために使用される物理アドレスです。通常、XX:XX:XX:XX:XX:XXのような16進数表記で表されます。IEEE 802 MAC-48、EUI-48、EUI-64などの形式があります。
  • リファクタリング: ソフトウェアの外部的な振る舞いを変更せずに、内部構造を改善するプロセスです。コードの可読性、保守性、拡張性を向上させることを目的とします。
  • bytes.Buffer: Go言語の標準ライブラリbytesパッケージに含まれる型で、可変長のバイトシーケンスを効率的に操作するためのバッファを提供します。文字列の構築などによく使用されます。
  • fmt.Fprintf: Go言語の標準ライブラリfmtパッケージに含まれる関数で、フォーマットされた出力をio.Writerに書き込みます。このコミットではbytes.Bufferに書き込むために使用されています。
  • errors.New: Go言語の標準ライブラリerrorsパッケージに含まれる関数で、新しいエラー値を生成します。
  • goto: Go言語におけるジャンプ文の一つで、指定されたラベルに処理を移します。現代のプログラミングでは、コードの可読性を損なう可能性があるため、gotoの使用は推奨されませんが、特定の最適化やエラーハンドリングのパターンで稀に使用されることがあります。このコミットでは、MACアドレスの解析におけるエラー処理のために使用されています。

技術的詳細

このコミットの技術的な詳細は、主にコードの移動とそれに伴う依存関係の調整にあります。

  1. HardwareAddr型と関連メソッドの移動:

    • type HardwareAddr []byte の定義。
    • func (a HardwareAddr) String() string メソッド(MACアドレスを文字列形式で表現)。 これらがinterface.goからmac.goへ移動しました。
  2. ParseMAC関数の移動:

    • func ParseMAC(s string) (hw HardwareAddr, err error) 関数(文字列からMACアドレスを解析)。 この関数は、様々な形式のMACアドレス文字列(コロン区切り、ハイフン区切り、ドット区切りなど)を解析し、HardwareAddr型に変換するロジックを含んでいます。解析に失敗した場合はエラーを返します。この関数もinterface.goからmac.goへ移動しました。
  3. インポートの調整:

    • interface.goからは、HardwareAddr型やParseMAC関数が不要になったため、それらに関連するbytesfmtパッケージのインポートが削除されました。errorsパッケージは引き続き必要なので残されています。
    • 新しく作成されたmac.goには、HardwareAddr.String()で必要なbytesfmt、そしてParseMACで必要なerrorsパッケージがインポートされています。
  4. テストコードの移動と調整:

    • interface_test.goからmactestsというテストデータ構造と、matchヘルパー関数、そしてTestParseMACテスト関数が削除されました。
    • 新しく作成されたmac_test.goには、これらのテストコードがそのまま移動されました。これにより、MACアドレス解析機能のテストが独立して実行できるようになります。テストコードはreflect.DeepEqualstrings.Containsを使用して、解析結果とエラーが期待通りであるかを検証しています。

この変更は、Go言語の標準ライブラリにおけるコードベースの整理と、特定の機能の独立性を高めるための典型的なリファクタリングパターンを示しています。

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

src/pkg/net/interface.go

  • import文から"bytes""fmt"が削除されました。
  • HardwareAddr型とそのString()メソッドが削除されました。
  • ParseMAC関数が削除されました。
--- a/src/pkg/net/interface.go
+++ b/src/pkg/net/interface.go
@@ -6,11 +6,7 @@
 
 package net
 
-import (
-	"bytes"
-	"errors"
-	"fmt"
-)
+import "errors"
 
 var (
 	errInvalidInterface         = errors.New("net: invalid interface")
@@ -20,77 +16,6 @@ var (
 	errNoSuchMulticastInterface = errors.New("net: no such multicast interface")
 )
 
-// A HardwareAddr represents a physical hardware address.
-type HardwareAddr []byte
-
-func (a HardwareAddr) String() string {
-	var buf bytes.Buffer
-	for i, b := range a {
-		if i > 0 {
-			buf.WriteByte(':')
-		}
-		fmt.Fprintf(&buf, "%02x", b)
-	}
-	return buf.String()
-}
-
-// ParseMAC parses s as an IEEE 802 MAC-48, EUI-48, or EUI-64 using one of the
-// following formats:
-//   01:23:45:67:89:ab
-//   01:23:45:67:89:ab:cd:ef
-//   01-23-45-67-89-ab
-//   01-23-45-67-89-ab-cd-ef
-//   0123.4567.89ab
-//   0123.4567.89ab.cdef
-func ParseMAC(s string) (hw HardwareAddr, err error) {
-	if len(s) < 14 {
-		goto error
-	}
-
-	if s[2] == ':' || s[2] == '-' {
-		if (len(s)+1)%3 != 0 {
-			goto error
-		}
-		n := (len(s) + 1) / 3
-		if n != 6 && n != 8 {
-			goto error
-		}
-		hw = make(HardwareAddr, n)
-		for x, i := 0, 0; i < n; i++ {
-			var ok bool
-			if hw[i], ok = xtoi2(s[x:], s[2]); !ok {
-				goto error
-			}
-			x += 3
-		}
-	} else if s[4] == '.' {
-		if (len(s)+1)%5 != 0 {
-			goto error
-		}
-		n := 2 * (len(s) + 1) / 5
-		if n != 6 && n != 8 {
-			goto error
-		}
-		hw = make(HardwareAddr, n)
-		for x, i := 0, 0; i < n; i += 2 {
-			var ok bool
-			if hw[i], ok = xtoi2(s[x:x+2], 0); !ok {
-				goto error
-			}
-			if hw[i+1], ok = xtoi2(s[x+2:], s[4]); !ok {
-				goto error
-			}
-			x += 5
-		}
-	} else {
-		goto error
-	}
-	return hw, nil
-
-error:
-	return nil, errors.New("invalid MAC address: " + s)
-}
-
 // Interface represents a mapping between network interface name
 // and index.  It also represents network interface facility
 // information.

src/pkg/net/interface_test.go

  • import文から"reflect""strings"が削除されました。
  • mactestsテストデータ、matchヘルパー関数、TestParseMACテスト関数が削除されました。
--- a/src/pkg/net/interface_test.go
+++ b/src/pkg/net/interface_test.go
@@ -6,8 +6,6 @@ package net
 
 import (
 	"bytes"
-	"reflect"
-	"strings"
 	"testing"
 )
 
@@ -96,46 +94,3 @@ func testMulticastAddrs(t *testing.T, ifmat []Addr) {
 		}
 	}
 }\n-\n-var mactests = []struct {\n-\tin  string\n-\tout HardwareAddr\n-\terr string\n-}{\n-\t{\"01:23:45:67:89:AB\", HardwareAddr{1, 0x23, 0x45, 0x67, 0x89, 0xab}, \"\"},\n-\t{\"01-23-45-67-89-AB\", HardwareAddr{1, 0x23, 0x45, 0x67, 0x89, 0xab}, \"\"},\n-\t{\"0123.4567.89AB\", HardwareAddr{1, 0x23, 0x45, 0x67, 0x89, 0xab}, \"\"},\n-\t{\"ab:cd:ef:AB:CD:EF\", HardwareAddr{0xab, 0xcd, 0xef, 0xab, 0xcd, 0xef}, \"\"},\n-\t{\"01.02.03.04.05.06\", nil, \"invalid MAC address\"},\n-\t{\"01:02:03:04:05:06:\", nil, \"invalid MAC address\"},\n-\t{\"x1:02:03:04:05:06\", nil, \"invalid MAC address\"},\n-\t{\"01002:03:04:05:06\", nil, \"invalid MAC address\"},\n-\t{\"01:02003:04:05:06\", nil, \"invalid MAC address\"},\n-\t{\"01:02:03004:05:06\", nil, \"invalid MAC address\"},\n-\t{\"01:02:03:04005:06\", nil, \"invalid MAC address\"},\n-\t{\"01:02:03:04:05006\", nil, \"invalid MAC address\"},\n-\t{\"01-02:03:04:05:06\", nil, \"invalid MAC address\"},\n-\t{\"01:02-03-04-05-06\", nil, \"invalid MAC address\"},\n-\t{\"0123:4567:89AF\", nil, \"invalid MAC address\"},\n-\t{\"0123-4567-89AF\", nil, \"invalid MAC address\"},\n-\t{\"01:23:45:67:89:AB:CD:EF\", HardwareAddr{1, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef}, \"\"},\n-\t{\"01-23-45-67-89-AB-CD-EF\", HardwareAddr{1, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef}, \"\"},\n-\t{\"0123.4567.89AB.CDEF\", HardwareAddr{1, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef}, \"\"},\n-}\n-\n-func match(err error, s string) bool {\n-\tif s == \"\" {\n-\t\treturn err == nil\n-\t}\n-\treturn err != nil && strings.Contains(err.Error(), s)\n-}\n-\n-func TestParseMAC(t *testing.T) {\n-\tfor _, tt := range mactests {\n-\t\tout, err := ParseMAC(tt.in)\n-\t\tif !reflect.DeepEqual(out, tt.out) || !match(err, tt.err) {\n-\t\t\tt.Errorf(\"ParseMAC(%q) = %v, %v, want %v, %v\", tt.in, out, err, tt.out,\n-\t\t\t\ttt.err)\n-\t\t}\n-\t}\n-}\

src/pkg/net/mac.go (新規ファイル)

  • HardwareAddr型とそのString()メソッドが定義されました。
  • ParseMAC関数が定義されました。
  • 必要なbytes, errors, fmtパッケージがインポートされました。
--- /dev/null
+++ b/src/pkg/net/mac.go
@@ -0,0 +1,84 @@
+// Copyright 2011 The Go Authors.  All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// MAC address manipulations
+
+package net
+
+import (
+	"bytes"
+	"errors"
+	"fmt"
+)
+
+// A HardwareAddr represents a physical hardware address.
+type HardwareAddr []byte
+
+func (a HardwareAddr) String() string {
+	var buf bytes.Buffer
+	for i, b := range a {
+		if i > 0 {
+			buf.WriteByte(':')
+		}
+		fmt.Fprintf(&buf, "%02x", b)
+	}
+	return buf.String()
+}
+
+// ParseMAC parses s as an IEEE 802 MAC-48, EUI-48, or EUI-64 using one of the
+// following formats:
+//   01:23:45:67:89:ab
+//   01:23:45:67:89:ab:cd:ef
+//   01-23-45-67-89-ab
+//   01-23-45-67-89-ab-cd-ef
+//   0123.4567.89ab
+//   0123.4567.89ab.cdef
+func ParseMAC(s string) (hw HardwareAddr, err error) {
+	if len(s) < 14 {
+		goto error
+	}
+
+	if s[2] == ':' || s[2] == '-' {
+		if (len(s)+1)%3 != 0 {
+			goto error
+		}
+		n := (len(s) + 1) / 3
+		if n != 6 && n != 8 {
+			goto error
+		}
+		hw = make(HardwareAddr, n)
+		for x, i := 0, 0; i < n; i++ {
+			var ok bool
+			if hw[i], ok = xtoi2(s[x:], s[2]); !ok {
+				goto error
+			}
+			x += 3
+		}
+	} else if s[4] == '.' {
+		if (len(s)+1)%5 != 0 {
+			goto error
+		}
+		n := 2 * (len(s) + 1) / 5
+		if n != 6 && n != 8 {
+			goto error
+		}
+		hw = make(HardwareAddr, n)
+		for x, i := 0, 0; i < n; i += 2 {
+			var ok bool
+			if hw[i], ok = xtoi2(s[x:x+2], 0); !ok {
+				goto error
+			}
+			if hw[i+1], ok = xtoi2(s[x+2:], s[4]); !ok {
+				goto error
+			}
+			x += 5
+		}
+	} else {
+		goto error
+	}
+	return hw, nil
+
+error:
+	return nil, errors.New("invalid MAC address: " + s)
+}

src/pkg/net/mac_test.go (新規ファイル)

  • mactestsテストデータ、matchヘルパー関数、TestParseMACテスト関数が定義されました。
  • 必要なreflect, strings, testingパッケージがインポートされました。
--- /dev/null
+++ b/src/pkg/net/mac_test.go
@@ -0,0 +1,54 @@
+// Copyright 2011 The Go Authors.  All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package net
+
+import (
+	"reflect"
+	"strings"
+	"testing"
+)
+
+var mactests = []struct {
+	in  string
+	out HardwareAddr
+	err string
+}{
+	{"01:23:45:67:89:AB", HardwareAddr{1, 0x23, 0x45, 0x67, 0x89, 0xab}, ""},
+	{"01-23-45-67-89-AB", HardwareAddr{1, 0x23, 0x45, 0x67, 0x89, 0xab}, ""},
+	{"0123.4567.89AB", HardwareAddr{1, 0x23, 0x45, 0x67, 0x89, 0xab}, ""},
+	{"ab:cd:ef:AB:CD:EF", HardwareAddr{0xab, 0xcd, 0xef, 0xab, 0xcd, 0xef}, ""},
+	{"01.02.03.04.05.06", nil, "invalid MAC address"},
+	{"01:02:03:04:05:06:", nil, "invalid MAC address"},
+	{"x1:02:03:04:05:06", nil, "invalid MAC address"},
+	{"01002:03:04:05:06", nil, "invalid MAC address"},
+	{"01:02003:04:05:06", nil, "invalid MAC address"},
+	{"01:02:03004:05:06", nil, "invalid MAC address"},
+	{"01:02:03:04005:06", nil, "invalid MAC address"},
+	{"01:02:03:04:05006", nil, "invalid MAC address"},
+	{"01-02:03:04:05:06", nil, "invalid MAC address"},
+	{"01:02-03-04-05-06", nil, "invalid MAC address"},
+	{"0123:4567:89AF", nil, "invalid MAC address"},
+	{"0123-4567-89AF", nil, "invalid MAC address"},
+	{"01:23:45:67:89:AB:CD:EF", HardwareAddr{1, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef}, ""},
+	{"01-23-45-67-89-AB-CD-EF", HardwareAddr{1, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef}, ""},
+	{"0123.4567.89AB.CDEF", HardwareAddr{1, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef}, ""},
+}
+
+func match(err error, s string) bool {
+	if s == "" {
+		return err == nil
+	}
+	return err != nil && strings.Contains(err.Error(), s)
+}
+
+func TestParseMAC(t *testing.T) {
+	for _, tt := range mactests {
+		out, err := ParseMAC(tt.in)
+		if !reflect.DeepEqual(out, tt.out) || !match(err, tt.err) {
+			t.Errorf("ParseMAC(%q) = %v, %v, want %v, %v", tt.in, out, err, tt.out,
+				tt.err)
+		}
+	}
+}

コアとなるコードの解説

このコミットのコアとなる変更は、MACアドレスの解析機能がinterface.goからmac.goへ完全に移動したことです。

  • HardwareAddr: これは[]byteのエイリアスであり、MACアドレスのバイト表現を保持します。String()メソッドは、このバイト列を一般的なMACアドレスの文字列形式(例: 01:23:45:67:89:ab)に変換するために使用されます。bytes.Bufferを使用して効率的に文字列を構築しています。
  • ParseMAC関数: この関数は、入力された文字列sを解析し、HardwareAddr型に変換します。
    • フォーマットのサポート: IEEE 802 MAC-48、EUI-48、EUI-64の形式をサポートしており、コロン区切り (:), ハイフン区切り (-), ドット区切り (.) のいずれの形式にも対応しています。
    • 長さのチェック: まず、入力文字列の長さがMACアドレスとして妥当な最小長(14文字)を満たしているかを確認します。
    • 区切り文字による分岐: 文字列の3文字目(インデックス2)がコロンまたはハイフンであるか、あるいは5文字目(インデックス4)がドットであるかによって、解析ロジックを分岐させます。
    • バイト変換: 各セグメント(例: 01, 23)を16進数として解析し、バイト値に変換します。xtoi2という内部ヘルパー関数(このコミットの差分には含まれていませんが、netパッケージ内に存在するはずです)がこの変換を担当していると推測されます。
    • エラーハンドリング: 解析中に無効なフォーマットや文字が検出された場合、goto error文を使用してエラー処理セクションにジャンプし、"invalid MAC address"というエラーを返します。gotoの使用はGo言語では一般的ではありませんが、このようなエラーパスの早期終了には有効な場合があります。

このリファクタリングにより、netパッケージの内部構造がより整理され、各ファイルの役割が明確になりました。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Go言語のソースコード(src/pkg/netディレクトリ)
  • 一般的なソフトウェアリファクタリングの原則に関する情報