[インデックス 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
関数は、以下のようなロジックでした。
isAlreadyLowerCase
というブール型のフラグをtrue
で初期化します。- 入力文字列
in
を1文字ずつループし、大文字('A'〜'Z')が含まれているかをチェックします。 - もし大文字が見つかった場合、
isAlreadyLowerCase
をfalse
に設定し、ループを中断します。 - ループ終了後、
isAlreadyLowerCase
がtrue
のまま(つまり、大文字が一つも含まれていなかった)であれば、元の文字列in
をそのまま返します。 isAlreadyLowerCase
がfalse
であれば、入力文字列in
を[]byte
スライスに変換し、out
に格納します。out
スライスを再度ループし、各バイトc
が大文字であれば、c += 'a' - 'A'
という演算で小文字に変換します。- 変換後の
out
スライスをstring
型に戻して返します。
このロジックは、大文字が含まれていない場合に無駄な変換処理を避けるという意図はありますが、大文字が含まれている場合には文字列を二度走査することになります。また、isAlreadyLowerCase
フラグとその後の条件分岐が、やや冗長に見える可能性がありました。
変更後のtoLower
関数は、以下のようなロジックに改善されました。
- 入力文字列
in
を1文字ずつループします。 - ループ中に大文字('A'〜'Z')が見つかった場合、その時点で文字列の変換が必要であると判断します。
- 変換が必要と判断されたら、入力文字列
in
を[]byte
スライスに変換し、out
に格納します。 out
スライスをfor i := 0; i < len(in); i++
という形式のループで走査します。このループは、元の文字列の長さに基づいてインデックスi
を使用し、out[i]
の各バイトをチェックします。- 各バイト
c
が大文字であれば、c += 'a' - 'A'
という演算で小文字に変換し、out[i]
に代入します。 - 変換が完了したら、
out
スライスをstring
型に戻して即座に返します。 - 最初のループが最後まで実行され、一度も大文字が見つからなかった場合(つまり、文字列が完全に小文字であるか、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)
参考にした情報源リンク
- GitHubのコミットページ: https://github.com/golang/go/commit/bccf029fc09c4b993f6a638b16c5fe33f0102828
- Go言語のコードレビューシステム (Gerrit) の変更リスト: https://golang.org/cl/43670044 (現在はGitHubに移行しているため、このリンクは直接アクセスできない可能性がありますが、コミットメッセージに記載されています。)
- Go言語のソースコード:
src/pkg/net/lookup_plan9.go
(Go言語のリポジトリ内)