[インデックス 15081] ファイルの概要
このコミットは、Go言語の標準ライブラリであるbytes
パッケージとstrings
パッケージに、新たにTrimPrefix
およびTrimSuffix
関数を追加するものです。これにより、特定のプレフィックスやサフィックスを文字列またはバイトスライスから効率的かつ意図通りに削除する機能が提供されます。
コミット
commit e515d80d5dfd5621a16f6fc9f08cc3c0958a8414
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date: Fri Feb 1 08:41:25 2013 -0800
bytes, strings: add TrimPrefix and TrimSuffix
Everybody either gets confused and thinks this is
TrimLeft/TrimRight or does this by hand which gets
repetitive looking.
R=rsc, kevlar
CC=golang-dev
https://golang.org/cl/7239044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/e515d80d5dfd5621a16f6fc9f08cc3c0958a8414
元コミット内容
このコミットは、Goのbytes
およびstrings
パッケージにTrimPrefix
とTrimSuffix
という2つの新しい関数を導入します。これらの関数は、与えられた文字列またはバイトスライスが特定のプレフィックスまたはサフィックスで始まる(または終わる)場合に、そのプレフィックスまたはサフィックスを削除した結果を返します。もしプレフィックス/サフィックスが存在しない場合は、元の文字列/スライスがそのまま返されます。
コミットメッセージによると、これらの関数が追加された主な理由は、開発者が既存のTrimLeft
/TrimRight
関数と混同したり、プレフィックス/サフィックスの削除を手動で実装することが多く、それが反復的で読みにくいコードにつながっていたためです。この変更により、より明確で意図が伝わりやすいAPIが提供され、コードの可読性と保守性が向上します。
変更は、bytes/bytes.go
とstrings/strings.go
に新しい関数を追加し、それぞれのテストファイル(bytes_test.go
, strings_test.go
)と例(example_test.go
)を更新しています。また、Goのツールチェイン内の複数のファイル(src/cmd/api/goapi.go
, src/cmd/cgo/gcc.go
, src/cmd/cgo/godefs.go
, src/cmd/fix/typecheck.go
, src/cmd/go/testflag.go
, src/cmd/godoc/dirtrees.go
, src/cmd/godoc/filesystem.go
, src/cmd/godoc/godoc.go
, src/cmd/godoc/main.go
, src/cmd/vet/method.go
, src/pkg/exp/html/parse_test.go
, src/pkg/go/printer/printer.go
, src/pkg/go/types/gcimporter.go
, src/pkg/net/http/pprof/pprof.go
, src/pkg/net/http/response.go
)で、手動で行われていたプレフィックス/サフィックスの削除処理が新しいTrimPrefix
/TrimSuffix
関数に置き換えられています。
変更の背景
Go言語の初期のバージョンでは、文字列やバイトスライスの先頭または末尾から特定の文字列を削除する直接的な関数が存在しませんでした。開発者は通常、以下のいずれかの方法でこの処理を行っていました。
-
strings.HasPrefix
/strings.HasSuffix
とスライス操作の組み合わせ:if strings.HasPrefix(s, prefix) { s = s[len(prefix):] }
この方法は正確ですが、2つの関数呼び出しとスライス操作が必要であり、コードが冗長になりがちでした。特に、多くの場所で同様の処理が必要な場合、コードの重複と可読性の低下を招いていました。
-
strings.TrimLeft
/strings.TrimRight
の誤用:strings.TrimLeft
やstrings.TrimRight
は、引数として与えられたcutset
(文字の集合)に含まれる任意の文字を、文字列の先頭または末尾から削除する関数です。例えば、strings.TrimLeft("banana", "ab")
は"nana"を返します。これは"b"と"a"がcutset
に含まれるため、先頭からそれらの文字がなくなるまで削除されるためです。 しかし、多くの開発者はこれらの関数を、特定の文字列プレフィックスやサフィックスを削除するために使おうとして、意図しない結果(例:strings.TrimLeft("foobar", "foo")
が"bar"ではなく"bar"を返す)に遭遇し、混乱することがありました。これは、TrimLeft
が"f", "o"という文字を個別に削除するためです。
このような背景から、特定の文字列プレフィックスやサフィックスを安全かつ直感的に削除できる専用の関数が求められていました。TrimPrefix
とTrimSuffix
の導入は、このニーズに応え、Goコードの一般的なイディオムを改善することを目的としています。
前提知識の解説
このコミットの理解には、以下のGo言語の基本的な概念と標準ライブラリの知識が役立ちます。
-
string
型と[]byte
型: Go言語では、文字列は不変なバイトのシーケンスとして扱われます。string
型はUTF-8エンコードされたテキストを表し、[]byte
型は任意のバイト列を表します。多くの文字列操作関数は、string
と[]byte
の両方に対応するバージョンがstrings
パッケージとbytes
パッケージにそれぞれ提供されています。 -
スライス操作: Goのスライスは、配列の一部を参照する軽量なデータ構造です。
s[low:high]
という構文で、low
インデックスからhigh-1
インデックスまでの要素を含む新しいスライスを作成できます。この機能は、文字列やバイトスライスの一部を抽出する際によく使用されます。s[len(prefix):]
:s
の先頭からprefix
の長さ分のバイトをスキップした残りの部分。s[:len(s)-len(suffix)]
:s
の末尾からsuffix
の長さ分のバイトをスキップした残りの部分。
-
strings
パッケージとbytes
パッケージ:strings
パッケージ: 文字列(string
型)を操作するためのユーティリティ関数を提供します。例:HasPrefix
,HasSuffix
,Contains
,Index
,Replace
,TrimSpace
,ToLower
,ToUpper
など。bytes
パッケージ: バイトスライス([]byte
型)を操作するためのユーティリティ関数を提供します。strings
パッケージの関数と類似した機能が多く提供されていますが、[]byte
型を直接操作するため、文字列変換のオーバーヘッドがありません。
-
strings.HasPrefix
/bytes.HasPrefix
: 与えられた文字列/バイトスライスが特定のプレフィックスで始まるかどうかを判定する関数です。 -
strings.HasSuffix
/bytes.HasSuffix
: 与えられた文字列/バイトスライスが特定のサフィックスで終わるかどうかを判定する関数です。 -
strings.TrimLeft
/bytes.TrimLeft
およびstrings.TrimRight
/bytes.TrimRight
: これらの関数は、第2引数に与えられたcutset
(文字の集合)に含まれる任意の文字を、文字列/バイトスライスの先頭(TrimLeft
)または末尾(TrimRight
)から削除します。例えば、strings.TrimLeft("abcba", "ab")
は"cba"を返します。これは、cutset
に含まれる'a'と'b'が先頭から削除されるためです。この挙動が、特定の文字列プレフィックスを削除したいという意図と混同されがちでした。
技術的詳細
TrimPrefix
とTrimSuffix
の導入は、Go言語のAPI設計における「明確さ」と「利便性」の原則を反映しています。
TrimPrefix
の設計と実装
TrimPrefix(s, prefix)
は、s
がprefix
で始まる場合にのみprefix
を削除します。実装は非常にシンプルで、まずHasPrefix
でプレフィックスの存在を確認し、存在すればスライス操作でプレフィックス部分を切り取ります。存在しなければ、元のs
をそのまま返します。
strings
パッケージでの実装例:
// TrimPrefix returns s without the provided leading prefix string.
// If s doesn't start with prefix, s is returned unchanged.
func TrimPrefix(s, prefix string) string {
if HasPrefix(s, prefix) { // まずプレフィックスが存在するか確認
return s[len(prefix):] // 存在すれば、プレフィックスの長さ分をスキップしてスライス
}
return s // 存在しなければ、元の文字列をそのまま返す
}
TrimSuffix
の設計と実装
TrimSuffix(s, suffix)
も同様に、s
がsuffix
で終わる場合にのみsuffix
を削除します。実装はHasSuffix
でサフィックスの存在を確認し、存在すればスライス操作でサフィックス部分を切り取ります。存在しなければ、元のs
をそのまま返します。
strings
パッケージでの実装例:
// TrimSuffix returns s without the provided trailing suffix string.
// If s doesn't end with suffix, s is returned unchanged.
func TrimSuffix(s, suffix string) string {
if HasSuffix(s, suffix) { // まずサフィックスが存在するか確認
return s[:len(s)-len(suffix)] // 存在すれば、サフィックスの長さ分を末尾から切り取ってスライス
}
return s // 存在しなければ、元の文字列をそのまま返す
}
既存コードの改善
このコミットでは、Goの標準ライブラリやツールチェイン内の既存コードで、strings.HasPrefix
とスライス操作を組み合わせてプレフィックスを削除していた箇所が、新しいstrings.TrimPrefix
に置き換えられています。これにより、コードの行数が減り、意図がより明確になります。
変更前(例: src/cmd/api/goapi.go
):
if strings.HasPrefix(litType, constDepPrefix) {
dep := litType[len(constDepPrefix):]
w.constDep[ident.Name] = dep
continue
}
変更後(例: src/cmd/api/goapi.go
):
if dep := strings.TrimPrefix(litType, constDepPrefix); dep != litType {
w.constDep[ident.Name] = dep
continue
}
この変更では、TrimPrefix
がプレフィックスを削除した場合にのみdep
がlitType
と異なる値になることを利用して、条件分岐も簡潔にしています。
TrimLeft
/TrimRight
との明確な区別
TrimPrefix
/TrimSuffix
の導入により、TrimLeft
/TrimRight
との役割分担が明確になりました。
TrimLeft
/TrimRight
: 文字の集合(cutset
)に含まれる任意の文字を削除する。TrimPrefix
/TrimSuffix
: 特定の文字列プレフィックス/サフィックスを削除する。
この区別により、開発者は目的に応じて適切な関数を選択できるようになり、混乱が減少します。
コアとなるコードの変更箇所
このコミットの主要な変更は、src/pkg/bytes/bytes.go
とsrc/pkg/strings/strings.go
に新しい関数が追加された点です。
src/pkg/bytes/bytes.go
// TrimPrefix returns s without the provided leading prefix string.
// If s doesn't start with prefix, s is returned unchanged.
func TrimPrefix(s, prefix []byte) []byte {
if HasPrefix(s, prefix) {
return s[len(prefix):]
}
return s
}
// TrimSuffix returns s without the provided trailing suffix string.
// If s doesn't end with suffix, s is returned unchanged.
func TrimSuffix(s, suffix []byte) []byte {
if HasSuffix(s, suffix) {
return s[:len(s)-len(suffix)]
}
return s
}
src/pkg/strings/strings.go
// TrimPrefix returns s without the provided leading prefix string.
// If s doesn't start with prefix, s is returned unchanged.
func TrimPrefix(s, prefix string) string {
if HasPrefix(s, prefix) {
return s[len(prefix):]
}
return s
}
// TrimSuffix returns s without the provided trailing suffix string.
// If s doesn't end with suffix, s is returned unchanged.
func TrimSuffix(s, suffix string) string {
if HasSuffix(s, suffix) {
return s[:len(s)-len(suffix)]
}
return s
}
これらの新しい関数に対応するテストケースと使用例も、それぞれsrc/pkg/bytes/bytes_test.go
, src/pkg/bytes/example_test.go
, src/pkg/strings/strings_test.go
, src/pkg/strings/example_test.go
に追加されています。
また、Goのツールチェイン内の複数のファイルで、手動で行われていたプレフィックス/サフィックスの削除処理が新しいTrimPrefix
/TrimSuffix
関数に置き換えられています。例えば、src/cmd/api/goapi.go
ではstrings.HasPrefix
とスライス操作の組み合わせがstrings.TrimPrefix
に置き換えられています。
コアとなるコードの解説
追加されたTrimPrefix
とTrimSuffix
関数は、Go言語の文字列/バイトスライス操作のイディオムを改善するものです。
TrimPrefix
の解説
TrimPrefix(s, prefix)
は、文字列s
の先頭から特定のprefix
文字列を削除します。
- まず、
HasPrefix(s, prefix)
を呼び出して、s
が実際にprefix
で始まるかどうかを確認します。 - もし
s
がprefix
で始まる場合(HasPrefix
がtrue
を返す場合)、s[len(prefix):]
というスライス操作を行います。これは、s
の先頭からprefix
の長さ分の文字をスキップし、残りの部分を新しい文字列として返します。 - もし
s
がprefix
で始まらない場合(HasPrefix
がfalse
を返す場合)、s
は変更されずにそのまま返されます。
このロジックにより、開発者は「もしこのプレフィックスがあれば削除する」という意図を、冗長なif
文とスライス操作なしに、単一の関数呼び出しで表現できるようになります。
TrimSuffix
の解説
TrimSuffix(s, suffix)
は、文字列s
の末尾から特定のsuffix
文字列を削除します。
- まず、
HasSuffix(s, suffix)
を呼び出して、s
が実際にsuffix
で終わるかどうかを確認します。 - もし
s
がsuffix
で終わる場合(HasSuffix
がtrue
を返す場合)、s[:len(s)-len(suffix)]
というスライス操作を行います。これは、s
の末尾からsuffix
の長さ分の文字を切り取り、残りの部分を新しい文字列として返します。 - もし
s
がsuffix
で終わらない場合(HasSuffix
がfalse
を返す場合)、s
は変更されずにそのまま返されます。
TrimSuffix
もTrimPrefix
と同様に、末尾の特定の文字列を削除する一般的なタスクを簡潔かつ安全に実行するための明確なAPIを提供します。
これらの関数は、Goの標準ライブラリの設計哲学である「シンプルさ」と「明確さ」に合致しており、開発者がより読みやすく、間違いにくいコードを書くのに貢献します。
関連リンク
- Go言語の
strings
パッケージドキュメント: https://pkg.go.dev/strings - Go言語の
bytes
パッケージドキュメント: https://pkg.go.dev/bytes - Go言語のコードレビューシステム (Gerrit) の変更リスト: https://golang.org/cl/7239044
参考にした情報源リンク
- Go言語の公式ドキュメント
- Go言語のソースコード
- コミットメッセージと関連するコードレビューの議論 (Gerrit)