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

[インデックス 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パッケージにTrimPrefixTrimSuffixという2つの新しい関数を導入します。これらの関数は、与えられた文字列またはバイトスライスが特定のプレフィックスまたはサフィックスで始まる(または終わる)場合に、そのプレフィックスまたはサフィックスを削除した結果を返します。もしプレフィックス/サフィックスが存在しない場合は、元の文字列/スライスがそのまま返されます。

コミットメッセージによると、これらの関数が追加された主な理由は、開発者が既存のTrimLeft/TrimRight関数と混同したり、プレフィックス/サフィックスの削除を手動で実装することが多く、それが反復的で読みにくいコードにつながっていたためです。この変更により、より明確で意図が伝わりやすいAPIが提供され、コードの可読性と保守性が向上します。

変更は、bytes/bytes.gostrings/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言語の初期のバージョンでは、文字列やバイトスライスの先頭または末尾から特定の文字列を削除する直接的な関数が存在しませんでした。開発者は通常、以下のいずれかの方法でこの処理を行っていました。

  1. strings.HasPrefix/strings.HasSuffixとスライス操作の組み合わせ:

    if strings.HasPrefix(s, prefix) {
        s = s[len(prefix):]
    }
    

    この方法は正確ですが、2つの関数呼び出しとスライス操作が必要であり、コードが冗長になりがちでした。特に、多くの場所で同様の処理が必要な場合、コードの重複と可読性の低下を招いていました。

  2. strings.TrimLeft/strings.TrimRightの誤用: strings.TrimLeftstrings.TrimRightは、引数として与えられたcutset(文字の集合)に含まれる任意の文字を、文字列の先頭または末尾から削除する関数です。例えば、strings.TrimLeft("banana", "ab")は"nana"を返します。これは"b"と"a"がcutsetに含まれるため、先頭からそれらの文字がなくなるまで削除されるためです。 しかし、多くの開発者はこれらの関数を、特定の文字列プレフィックスやサフィックスを削除するために使おうとして、意図しない結果(例: strings.TrimLeft("foobar", "foo")が"bar"ではなく"bar"を返す)に遭遇し、混乱することがありました。これは、TrimLeftが"f", "o"という文字を個別に削除するためです。

このような背景から、特定の文字列プレフィックスやサフィックスを安全かつ直感的に削除できる専用の関数が求められていました。TrimPrefixTrimSuffixの導入は、このニーズに応え、Goコードの一般的なイディオムを改善することを目的としています。

前提知識の解説

このコミットの理解には、以下のGo言語の基本的な概念と標準ライブラリの知識が役立ちます。

  1. string型と[]byte: Go言語では、文字列は不変なバイトのシーケンスとして扱われます。string型はUTF-8エンコードされたテキストを表し、[]byte型は任意のバイト列を表します。多くの文字列操作関数は、string[]byteの両方に対応するバージョンがstringsパッケージとbytesパッケージにそれぞれ提供されています。

  2. スライス操作: Goのスライスは、配列の一部を参照する軽量なデータ構造です。s[low:high]という構文で、lowインデックスからhigh-1インデックスまでの要素を含む新しいスライスを作成できます。この機能は、文字列やバイトスライスの一部を抽出する際によく使用されます。

    • s[len(prefix):]sの先頭からprefixの長さ分のバイトをスキップした残りの部分。
    • s[:len(s)-len(suffix)]sの末尾からsuffixの長さ分のバイトをスキップした残りの部分。
  3. stringsパッケージとbytesパッケージ:

    • stringsパッケージ: 文字列(string型)を操作するためのユーティリティ関数を提供します。例: HasPrefix, HasSuffix, Contains, Index, Replace, TrimSpace, ToLower, ToUpperなど。
    • bytesパッケージ: バイトスライス([]byte型)を操作するためのユーティリティ関数を提供します。stringsパッケージの関数と類似した機能が多く提供されていますが、[]byte型を直接操作するため、文字列変換のオーバーヘッドがありません。
  4. strings.HasPrefix / bytes.HasPrefix: 与えられた文字列/バイトスライスが特定のプレフィックスで始まるかどうかを判定する関数です。

  5. strings.HasSuffix / bytes.HasSuffix: 与えられた文字列/バイトスライスが特定のサフィックスで終わるかどうかを判定する関数です。

  6. strings.TrimLeft / bytes.TrimLeft および strings.TrimRight / bytes.TrimRight: これらの関数は、第2引数に与えられたcutset(文字の集合)に含まれる任意の文字を、文字列/バイトスライスの先頭(TrimLeft)または末尾(TrimRight)から削除します。例えば、strings.TrimLeft("abcba", "ab")は"cba"を返します。これは、cutsetに含まれる'a'と'b'が先頭から削除されるためです。この挙動が、特定の文字列プレフィックスを削除したいという意図と混同されがちでした。

技術的詳細

TrimPrefixTrimSuffixの導入は、Go言語のAPI設計における「明確さ」と「利便性」の原則を反映しています。

TrimPrefixの設計と実装

TrimPrefix(s, prefix)は、sprefixで始まる場合にのみ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)も同様に、ssuffixで終わる場合にのみ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がプレフィックスを削除した場合にのみdeplitTypeと異なる値になることを利用して、条件分岐も簡潔にしています。

TrimLeft/TrimRightとの明確な区別

TrimPrefix/TrimSuffixの導入により、TrimLeft/TrimRightとの役割分担が明確になりました。

  • TrimLeft/TrimRight: 文字の集合(cutset)に含まれる任意の文字を削除する。
  • TrimPrefix/TrimSuffix: 特定の文字列プレフィックス/サフィックスを削除する。

この区別により、開発者は目的に応じて適切な関数を選択できるようになり、混乱が減少します。

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

このコミットの主要な変更は、src/pkg/bytes/bytes.gosrc/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に置き換えられています。

コアとなるコードの解説

追加されたTrimPrefixTrimSuffix関数は、Go言語の文字列/バイトスライス操作のイディオムを改善するものです。

TrimPrefixの解説

TrimPrefix(s, prefix)は、文字列sの先頭から特定のprefix文字列を削除します。

  • まず、HasPrefix(s, prefix)を呼び出して、sが実際にprefixで始まるかどうかを確認します。
  • もしsprefixで始まる場合(HasPrefixtrueを返す場合)、s[len(prefix):]というスライス操作を行います。これは、sの先頭からprefixの長さ分の文字をスキップし、残りの部分を新しい文字列として返します。
  • もしsprefixで始まらない場合(HasPrefixfalseを返す場合)、sは変更されずにそのまま返されます。

このロジックにより、開発者は「もしこのプレフィックスがあれば削除する」という意図を、冗長なif文とスライス操作なしに、単一の関数呼び出しで表現できるようになります。

TrimSuffixの解説

TrimSuffix(s, suffix)は、文字列sの末尾から特定のsuffix文字列を削除します。

  • まず、HasSuffix(s, suffix)を呼び出して、sが実際にsuffixで終わるかどうかを確認します。
  • もしssuffixで終わる場合(HasSuffixtrueを返す場合)、s[:len(s)-len(suffix)]というスライス操作を行います。これは、sの末尾からsuffixの長さ分の文字を切り取り、残りの部分を新しい文字列として返します。
  • もしssuffixで終わらない場合(HasSuffixfalseを返す場合)、sは変更されずにそのまま返されます。

TrimSuffixTrimPrefixと同様に、末尾の特定の文字列を削除する一般的なタスクを簡潔かつ安全に実行するための明確なAPIを提供します。

これらの関数は、Goの標準ライブラリの設計哲学である「シンプルさ」と「明確さ」に合致しており、開発者がより読みやすく、間違いにくいコードを書くのに貢献します。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Go言語のソースコード
  • コミットメッセージと関連するコードレビューの議論 (Gerrit)