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

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

このコミットは、Go言語の標準ライブラリnetパッケージ内のlookup_plan9.goファイルにあるtoLower関数の実装を、より明確にするための変更です。具体的には、文字列を小文字に変換するロジックがリファクタリングされています。

コミット

commit bccf029fc09c4b993f6a638b16c5fe33f0102828
Author: David du Colombier <0intro@gmail.com>
Date:   Tue Dec 17 16:32:27 2013 -0800

    net: rewrite toLower more clearly
    
    Rob suggested this change.
    
    R=golang-dev, r
    CC=golang-dev
    https://golang.org/cl/43670044

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

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

元コミット内容

net: rewrite toLower more clearly

このコミットは、netパッケージ内のtoLower関数をより明確に書き直すことを目的としています。この変更はRob Pike氏によって提案されたものです。

変更の背景

netパッケージは、ネットワーク関連の機能を提供するGo言語の標準ライブラリです。lookup_plan9.goファイルは、Plan 9オペレーティングシステムに特化したネットワークルックアップ(例えば、プロトコル名の解決など)に関連するコードを含んでいます。

toLower関数は、文字列内のASCII大文字を対応する小文字に変換するためのヘルパー関数です。元の実装では、文字列が既に小文字であるかどうかを判断するために一度ループし、もし大文字が含まれていれば、再度ループして変換を行うという二段階の処理を行っていました。

このコミットの背景には、コードの可読性と保守性の向上が挙げられます。Rob Pike氏からの提案があったことからも、既存のtoLower関数のロジックが、より簡潔で理解しやすい形に改善されるべきだと考えられたことが伺えます。特に、文字列の小文字化処理は頻繁に行われる操作であり、その実装が明確であることは重要です。

前提知識の解説

  • Go言語の文字列とバイトスライス: Go言語において、文字列は不変のバイトスライスとして扱われます。文字列を操作(例えば、文字の変更)するには、一度[]byte型に変換し、操作後に再度string型に戻す必要があります。
  • ASCII文字コード: このtoLower関数は、ASCII文字セットに限定して大文字・小文字変換を行います。ASCII文字では、大文字('A'〜'Z')と小文字('a'〜'z')の間には固定のオフセット('a' - 'A')が存在し、これを利用して変換が行われます。
  • Plan 9オペレーティングシステム: Plan 9はベル研究所で開発された分散オペレーティングシステムです。Go言語はPlan 9の設計思想から多くの影響を受けており、Goの初期のコードベースにはPlan 9に特化した実装がしばしば見られます。lookup_plan9.goはこの文脈で、Plan 9環境でのネットワークプロトコル名などのルックアップ処理に利用される可能性があります。
  • コードの可読性: プログラミングにおいて、コードがどれだけ読みやすく、理解しやすいかは非常に重要です。特に、条件分岐やループが複雑になると、バグの原因になったり、将来の変更が困難になったりします。このコミットは、まさにこの可読性の向上を目指しています。

技術的詳細

変更前のtoLower関数は、以下のようなロジックでした。

  1. isAlreadyLowerCaseというブール型のフラグをtrueで初期化します。
  2. 入力文字列inを1文字ずつループし、大文字('A'〜'Z')が含まれているかをチェックします。
  3. もし大文字が見つかった場合、isAlreadyLowerCasefalseに設定し、ループを中断します。
  4. ループ終了後、isAlreadyLowerCasetrueのまま(つまり、大文字が一つも含まれていなかった)であれば、元の文字列inをそのまま返します。
  5. isAlreadyLowerCasefalseであれば、入力文字列in[]byteスライスに変換し、outに格納します。
  6. outスライスを再度ループし、各バイトcが大文字であれば、c += 'a' - 'A'という演算で小文字に変換します。
  7. 変換後のoutスライスをstring型に戻して返します。

このロジックは、大文字が含まれていない場合に無駄な変換処理を避けるという意図はありますが、大文字が含まれている場合には文字列を二度走査することになります。また、isAlreadyLowerCaseフラグとその後の条件分岐が、やや冗長に見える可能性がありました。

変更後のtoLower関数は、以下のようなロジックに改善されました。

  1. 入力文字列inを1文字ずつループします。
  2. ループ中に大文字('A'〜'Z')が見つかった場合、その時点で文字列の変換が必要であると判断します。
  3. 変換が必要と判断されたら、入力文字列in[]byteスライスに変換し、outに格納します。
  4. outスライスをfor i := 0; i < len(in); i++という形式のループで走査します。このループは、元の文字列の長さに基づいてインデックスiを使用し、out[i]の各バイトをチェックします。
  5. 各バイトcが大文字であれば、c += 'a' - 'A'という演算で小文字に変換し、out[i]に代入します。
  6. 変換が完了したら、outスライスをstring型に戻して即座に返します。
  7. 最初のループが最後まで実行され、一度も大文字が見つからなかった場合(つまり、文字列が完全に小文字であるか、ASCIIアルファベットを含まない場合)、ループの後に到達し、元の文字列inをそのまま返します。

この新しいロジックは、大文字が見つかった瞬間に変換モードに入り、その後の処理を一つのブロックにまとめることで、コードのフローがより直線的で理解しやすくなっています。二度目のループは、大文字が見つかった場合にのみ実行されるため、全体的な効率は同等か、場合によっては改善される可能性があります。

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

src/pkg/net/lookup_plan9.goファイルのtoLower関数が変更されています。

--- a/src/pkg/net/lookup_plan9.go
+++ b/src/pkg/net/lookup_plan9.go
@@ -73,23 +73,21 @@ func queryDNS(addr string, typ string) (res []string, err error) {
 // ASCII is sufficient to handle the IP protocol names and allow
 // us to not depend on the strings and unicode packages.
 func toLower(in string) string {
-	isAlreadyLowerCase := true
 	for _, c := range in {
 		if 'A' <= c && c <= 'Z' {
-			isAlreadyLowerCase = false
-			break
-		}
-	}
-	if isAlreadyLowerCase {
-		return in
-	}
-	out := []byte(in)
-	for i, c := range out {
-		if 'A' <= c && c <= 'Z' {
-			out[i] += 'a' - 'A'
+			// Has upper case; need to fix.
+			out := []byte(in)
+			for i := 0; i < len(in); i++ {
+				c := in[i]
+				if 'A' <= c && c <= 'Z' {
+					c += 'a' - 'A'
+				}
+				out[i] = c
+			}
+			return string(out)
 		}
 	}
-	return string(out)
+	return in
 }
 
 // lookupProtocol looks up IP protocol name and returns

コアとなるコードの解説

変更の核心は、toLower関数内の条件分岐とループの構造です。

変更前:

func toLower(in string) string {
	isAlreadyLowerCase := true // (1) フラグの初期化
	for _, c := range in {     // (2) 最初のループ:大文字の有無をチェック
		if 'A' <= c && c <= 'Z' {
			isAlreadyLowerCase = false
			break
		}
	}
	if isAlreadyLowerCase { // (3) フラグによる条件分岐
		return in
	}
	out := []byte(in)      // (4) バイトスライスへの変換
	for i, c := range out { // (5) 二度目のループ:変換処理
		if 'A' <= c && c <= 'Z' {
			out[i] += 'a' - 'A'
		}
	}
	return string(out)
}

このコードでは、まず文字列全体を走査して大文字が含まれているかを確認し((2))、その結果に基づいて変換処理を行うか((5))を決定していました。大文字が含まれている場合は、文字列を二度走査することになります。

変更後:

func toLower(in string) string {
	for _, c := range in { // (1) 最初のループ:大文字の有無をチェック
		if 'A' <= c && c <= 'Z' { // (2) 大文字が見つかった場合
			// Has upper case; need to fix.
			out := []byte(in) // (3) バイトスライスへの変換
			for i := 0; i < len(in); i++ { // (4) 変換処理のループ
				c := in[i]
				if 'A' <= c && c <= 'Z' {
					c += 'a' - 'A'
				}
				out[i] = c
			}
			return string(out) // (5) 変換後、即座にリターン
		}
	}
	return in // (6) 大文字が見つからなかった場合、元の文字列をリターン
}

新しいコードでは、最初のループ((1))で大文字が見つかった瞬間に((2))、その場でバイトスライスへの変換((3))と小文字化処理のループ((4))を開始し、処理が完了次第、関数からリターンします((5))。これにより、isAlreadyLowerCaseフラグが不要になり、コードのフローがより直接的になりました。大文字が一つも含まれていない場合は、最初のループが最後まで実行され、最後に元の文字列が返されます((6))。

この変更により、コードの意図がより明確になり、「大文字が見つかったら変換し、そうでなければ元の文字列を返す」というロジックが直感的に理解できるようになりました。

関連リンク

  • Go言語のnetパッケージに関する公式ドキュメント: https://pkg.go.dev/net
  • Go言語の文字列とバイトスライスに関する解説: https://go.dev/blog/strings (Go公式ブログのStrings, bytes, runes and characters in Go)

参考にした情報源リンク