Go言語入門:効果的なGo -Methods-

スポンサーリンク
Go言語入門:効果的なGo -Methods- ノウハウ
Go言語入門:効果的なGo -Methods-
この記事は約5分で読めます。
よっしー
よっしー

こんにちは。よっしーです(^^)

本日は、Go言語を効果的に使うためのガイドラインについて解説しています。

スポンサーリンク

背景

Go言語を学び始めて、より良いコードを書きたいと思い、Go言語の公式ドキュメント「Effective Go」を知りました。これは、いわば「Goらしいコードの書き方指南書」になります。単に動くコードではなく、効率的で保守性の高いコードを書くためのベストプラクティスが詰まっているので、これを読んだ時の内容を備忘として残しました。

メソッド(Methods)

ポインタ vs 値(Pointers vs. Values)

ByteSizeで見たように、メソッドは任意の名前付き型(ポインタやインターフェースを除く)に対して定義できます。レシーバは構造体である必要はありません。

上記のスライスの議論で、Append関数を書きました。これをスライスのメソッドとして定義することもできます。そのためには、まずメソッドをバインドできる名前付き型を宣言し、次にその型の値をメソッドのレシーバにします。

type ByteSlice []byte

func (slice ByteSlice) Append(data []byte) []byte {
    // 上で定義したAppend関数とまったく同じ本体
}

これでも、メソッドが更新されたスライスを返す必要があります。メソッドがByteSliceへのポインタをレシーバとして受け取るように再定義することで、この不格好さを取り除くことができます。そうすれば、メソッドが呼び出し元のスライスを上書きできます。

func (p *ByteSlice) Append(data []byte) {
    slice := *p
    // 上記と同じ本体、ただしreturnなし
    *p = slice
}

実際、さらに良くすることができます。関数を標準的なWriteメソッドのように修正すると、

func (p *ByteSlice) Write(data []byte) (n int, err error) {
    slice := *p
    // 再び上記と同様
    *p = slice
    return len(data), nil
}

そうすると、*ByteSlice型が標準インターフェースio.Writerを満たすようになり、これは便利です。例えば、これに出力することができます。

var b ByteSlice
fmt.Fprintf(&b, "This hour has %d days\n", 7)

*ByteSliceのみがio.Writerを満たすため、ByteSliceのアドレスを渡します。レシーバにおけるポインタ vs 値の規則は、値メソッドはポインタと値の両方で呼び出せますが、ポインタメソッドはポインタでのみ呼び出せるということです。

この規則が生まれる理由は、ポインタメソッドがレシーバを変更できるからです。値でそれらを呼び出すと、メソッドは値のコピーを受け取ることになり、すべての変更が破棄されてしまいます。そのため、言語はこの間違いを許可しません。ただし、便利な例外があります。値がアドレス可能な場合、言語は自動的にアドレス演算子を挿入することで、値でポインタメソッドを呼び出すという一般的なケースを処理します。私たちの例では、変数bはアドレス可能なので、単にb.WriteでそのWriteメソッドを呼び出すことができます。コンパイラが自動的にそれを(&b).Writeに書き換えてくれます。

ちなみに、バイトのスライスでWriteを使うという考え方は、bytes.Bufferの実装の中核となっています。

解説

1. メソッドの基本

// 独自の型を定義
type ByteSlice []byte

// 値レシーバのメソッド
func (slice ByteSlice) Append(data []byte) []byte {
    // 元のsliceは変更されない
    // 新しいsliceを返す必要がある
}

2. ポインタレシーバの利点

// ポインタレシーバのメソッド
func (p *ByteSlice) Append(data []byte) {
    // *p でポインタが指す実際の値にアクセス
    // 元のsliceを直接変更できる
    // 戻り値が不要
}

3. 重要な規則

  • 値メソッド: ポインタでも値でも呼び出せる
  • ポインタメソッド: ポインタでのみ呼び出せる(原則)

4. 便利な例外

var b ByteSlice
b.Write(data) // これは実際には (&b).Write(data) として処理される

Goコンパイラが自動的にアドレスを取得してくれるので、書きやすくなっています。

5. 実用例

io.Writerインターフェースを実装することで、標準ライブラリの関数(fmt.Fprintfなど)と組み合わせて使えるようになります。

このパターンはbytes.Bufferでも使われており、Goの標準的な設計パターンの一つです。

おわりに 

本日は、Go言語を効果的に使うためのガイドラインについて解説しました。

よっしー
よっしー

何か質問や相談があれば、コメントをお願いします。また、エンジニア案件の相談にも随時対応していますので、お気軽にお問い合わせください。

それでは、また明日お会いしましょう(^^)

コメント

タイトルとURLをコピーしました