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

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

このコミットは、Go言語の標準ライブラリであるflagパッケージに、より複雑な使用例を示すためのテストファイルsrc/pkg/flag/example_test.goを追加するものです。これにより、flagパッケージの機能、特にカスタムフラグ型の定義方法や、複数のフラグで単一の変数を共有する方法などが、具体的なコード例として示されることになります。

コミット

commit 07e887f43323027175b22db247c50c73f0fc152f
Author: Rob Pike <r@golang.org>
Date:   Thu Mar 22 11:15:43 2012 +1100

    flag: add examples
    
    R=golang-dev, gri
    CC=golang-dev
    https://golang.org/cl/5867049

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

https://github.com/golang/go/commit/07e887f43323027175b22db247c50c73f0fc152f

元コミット内容

flag: add examples

このコミットメッセージは簡潔に「flagパッケージに例を追加する」と述べています。これは、flagパッケージの利用方法をより明確にするためのコード例が追加されたことを示唆しています。

変更の背景

Go言語の標準ライブラリは、その設計思想として「シンプルさ」と「実用性」を重視しています。flagパッケージはコマンドライン引数をパースするための基本的な機能を提供しますが、その高度な利用方法、例えばカスタム型のフラグを定義する方法や、複数のフラグが同じ変数を参照するように設定する方法などは、ドキュメントだけでは理解しにくい場合があります。

このコミットが行われた2012年3月は、Go言語がまだ比較的新しい時期であり、多くの標準ライブラリが成熟していく過程にありました。開発者がflagパッケージをより効果的に活用できるよう、具体的な使用例を提供することで、ライブラリの使いやすさと理解度を向上させることが目的であったと考えられます。特に、flag.Valueインターフェースを実装して独自のフラグ型を作成する機能は強力ですが、その実装パターンを示す例は、ユーザーにとって非常に有用です。

前提知識の解説

Go言語のflagパッケージ

Go言語のflagパッケージは、コマンドライン引数をパースするための機能を提供します。主な機能は以下の通りです。

  • フラグの定義: flag.String(), flag.Int(), flag.Bool()などの関数を使って、文字列、整数、真偽値などの型のフラグを定義できます。これらの関数は、フラグの値へのポインタを返します。
  • 変数を指定してフラグを定義: flag.StringVar(), flag.IntVar(), flag.BoolVar()などの関数を使って、既存の変数にフラグの値をバインドできます。
  • フラグのパース: flag.Parse()関数を呼び出すことで、コマンドライン引数をパースし、定義されたフラグ変数に値を設定します。通常、main関数の冒頭で呼び出されます。
  • カスタムフラグ型: flag.Valueインターフェースを実装することで、独自のデータ型をコマンドラインフラグとして扱えるようになります。flag.Valueインターフェースは以下の2つのメソッドを要求します。
    • String() string: フラグの現在の値を文字列として返します。診断メッセージなどで使用されます。
    • Set(string) error: コマンドラインから渡された文字列をパースし、フラグの値を設定します。パースエラーが発生した場合はエラーを返します。
  • init()関数: Go言語のinit()関数は、パッケージがインポートされた際に、main()関数が実行される前に自動的に実行される特殊な関数です。パッケージの初期化処理や、グローバル変数の設定などに利用されます。flagパッケージのフラグ定義は、init()関数内で行われることもあります。

Go言語のテストと_test.goファイル

Go言語では、テストコードは通常、テスト対象のソースファイルと同じディレクトリに_test.goというサフィックスを持つファイルとして配置されます。例えば、foo.goのテストはfoo_test.goに書かれます。

  • Example関数: Exampleというプレフィックスを持つ関数は、Goのドキュメンテーションツールgo docによって特別な方法で扱われます。これらの関数は、パッケージの使用例としてドキュメントに表示され、またテストの一部として実行され、その出力が期待される出力と一致するかどうかが検証されます。これにより、ドキュメントとコード例が常に同期していることが保証されます。

技術的詳細

このコミットで追加されたexample_test.goファイルは、flagパッケージの3つの異なる使用シナリオを示しています。

  1. 単一の文字列フラグ:

    • flag.String("species", "gopher", "the species we are studying") を使用して、speciesという名前の文字列フラグを定義しています。
    • デフォルト値は "gopher" で、使用方法の説明も提供されています。
    • これはflagパッケージの最も基本的な使用方法です。
  2. 複数のフラグで単一の変数を共有(ショートハンド):

    • gopherTypeというグローバル文字列変数を定義し、init()関数内でこの変数に2つのフラグをバインドしています。
    • flag.StringVar(&gopherType, "gopher_type", defaultGopher, usage) で長い形式のフラグを定義し、
    • flag.StringVar(&gopherType, "g", defaultGopher, usage+" (shorthand)") で短い形式(ショートハンド)のフラグを定義しています。
    • 両方のフラグが同じgopherType変数を参照するため、どちらかのフラグがコマンドラインで指定されると、その値がgopherTypeに設定されます。
    • init()関数内で定義されているのは、グローバル変数の初期化順序が不定であるため、フラグの定義が確実にmain関数実行前に完了するようにするためです。
  3. ユーザー定義フラグ型(flag.Valueインターフェースの実装):

    • intervalという[]time.Duration型のカスタム型を定義しています。
    • このinterval型はflag.Valueインターフェースを実装しています。
      • String() stringメソッドは、fmt.Sprint(*i)を使用してintervalスライスの文字列表現を返します。
      • Set(value string) errorメソッドは、カンマ区切りの文字列(例: "10s,5m")をパースし、time.Durationのスライスに変換してinterval変数に格納します。
      • Setメソッド内には、フラグが複数回設定された場合にエラーを返すロジックが含まれており、これはフラグの累積を許可しない場合の一般的なパターンを示しています。
    • init()関数内でflag.Var(&intervalFlag, "deltaT", "comma-separated list of intervals to use between events") を使用して、このカスタム型のフラグdeltaTを定義しています。
    • この例は、Goのflagパッケージがいかに柔軟で、開発者が独自の複雑なデータ型をコマンドライン引数として扱えるように設計されているかを示しています。

Example()関数は、flag.Parse()の呼び出しがmain関数で行われるべきであり、テストスイートでは既にパースされているため、この例では実行しないことをコメントで説明しています。これは、Example関数がテストとして実行される際の注意点を示しています。

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

このコミットでは、src/pkg/flag/example_test.goという新しいファイルが追加されています。

--- /dev/null
+++ b/src/pkg/flag/example_test.go
@@ -0,0 +1,83 @@
+// Copyright 2012 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.
+
+// These examples demonstrate more intricate uses of the flag package.
+package flag_test
+
+import (
+	"errors"
+	"flag"
+	"fmt"
+	"strings"
+	"time"
+)
+
+// Example 1: A single string flag called "species" with default value "gopher".
+var species = flag.String("species", "gopher", "the species we are studying")
+
+// Example 2: Two flags sharing a variable, so we can have a shorthand.
+// The order of initialization is undefined, so make sure both use the
+// same default value. They must be set up with an init function.
+var gopherType string
+
+func init() {
+	const (
+		defaultGopher = "pocket"
+		usage         = "the variety of gopher"
+	)
+	flag.StringVar(&gopherType, "gopher_type", defaultGopher, usage)
+	flag.StringVar(&gopherType, "g", defaultGopher, usage+" (shorthand)")
+}
+
+// Example 3: A user-defined flag type, a slice of durations.
+type interval []time.Duration
+
+// String is the method to format the flag's value, part of the flag.Value interface.
+// The String method's output will be used in diagnostics.
+func (i *interval) String() string {
+	return fmt.Sprint(*i)
+}
+
+// Set is the method to set the flag value, part of the flag.Value interface.
+// Set's argument is a string to be parsed to set the flag.
+// It's a comma-separated list, so we split it.
+func (i *interval) Set(value string) error {
+	// If we wanted to allow the flag to be set multiple times,
+	// accumulating values, we would delete this if statement.
+	// That would permit usages such as
+	//	-deltaT 10s -deltaT 15s
+	// and other combinations.
+	if len(*i) > 0 {
+		return errors.New("interval flag already set")
+	}
+	for _, dt := range strings.Split(value, ",") {
+		duration, err := time.ParseDuration(dt)
+		if err != nil {
+			return err
+		}
+		*i = append(*i, duration)
+	}
+	return nil
+}
+
+// Define a flag to accumulate durations. Because it has a special type,
+// we need to use the Var function and therefore create the flag during
+// init.
+
+var intervalFlag interval
+
+func init() {
+	// Tie the command-line flag to the intervalFlag variable and
+	// set a usage message.
+	flag.Var(&intervalFlag, "deltaT", "comma-separated list of intervals to use between events")
+}
+
+func Example() {
+	// All the interesting pieces are with the variables declared above, but
+	// to enable the flag package to see the flags defined there, one must
+	// execute, typically at the start of main (not init!):
+	//	flag.Parse()
+	// We don't run it here because this is not a main function and
+	// the testing suite has already parsed the flags.
+}

コアとなるコードの解説

追加されたexample_test.goファイルは、flagパッケージの高度な利用方法を3つの主要な例で示しています。

  1. speciesフラグの定義:

    • var species = flag.String("species", "gopher", "the species we are studying")
    • これは最も単純なフラグの定義方法で、flag.String関数が文字列型のフラグを定義し、その値へのポインタを返します。このポインタを通じて、パース後にフラグの値にアクセスできます。
  2. gopherTypeフラグとショートハンド:

    • var gopherType string でグローバル変数を宣言。
    • init()関数内で、flag.StringVarを使って"gopher_type""g"という2つのフラグをgopherType変数にバインドしています。
    • これにより、ユーザーは--gopher_type=valueまたは-g=valueのどちらでも同じgopherType変数を設定できるようになります。init()関数を使用しているのは、グローバル変数の初期化順序の不確定性を考慮し、フラグの定義が確実にmain関数実行前に行われるようにするためです。
  3. カスタムフラグ型interval:

    • type interval []time.Durationtime.Durationのスライスを基盤とする新しい型を定義。
    • このinterval型にString()Set()メソッドを実装することで、flag.Valueインターフェースを満たしています。
      • String()メソッドは、interval型の値を文字列として表現する方法を提供します。これは、フラグの現在の値を表示する際などに使用されます。
      • Set(value string) errorメソッドは、コマンドラインから渡された文字列(例: "10s,5m,1h")をパースし、time.ParseDurationを使って個々の期間をtime.Durationに変換し、intervalスライスに追加します。この実装では、フラグが一度設定されたらそれ以上累積しないように、if len(*i) > 0でチェックしています。
    • init()関数内でflag.Var(&intervalFlag, "deltaT", "comma-separated list of intervals to use between events") を使用して、このカスタム型のフラグdeltaTを定義しています。flag.Varは、flag.Valueインターフェースを実装したカスタム型のフラグを登録するために使用されます。

Example()関数は、flag.Parse()の呼び出しがmain関数で行われるべきであるという重要な注意点をコメントで示しています。これは、Example関数がテストスイートによって実行される際に、既にフラグがパースされている状況を考慮したものです。

これらの例は、flagパッケージが提供する柔軟性と、Go言語のインターフェースの強力な活用方法を具体的に示しており、開発者がより複雑なコマンドラインインターフェースを構築する際の参考になります。

関連リンク

参考にした情報源リンク