Go言語入門:効果的なGo -Names:Interface names-

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

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

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

スポンサーリンク

背景

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

Go言語の命名規則(Names)

命名の重要性

Go言語において、名前は他の言語と同様に重要です。名前は単なる識別子以上の意味を持ち、セマンティック(意味論的)な効果も持ちます。パッケージ外部からの可視性は、名前の最初の文字が大文字かどうかで決まります。そのため、Go プログラムにおける命名規則について時間をかけて議論する価値があります。

Go言語のインターフェイス名

基本的な命名規則

慣習により、単一メソッドのインターフェイスは、メソッド名に -er サフィックス(接尾辞)または同様の修飾を加えて行為者名詞(agent noun)を構築することで命名されます。

標準的なパターン

メソッド名インターフェイス名意味
ReadReader読み取り機能を提供する
WriteWriter書き込み機能を提供する
FormatFormatterフォーマット機能を提供する
CloseNotifyCloseNotifierクローズ通知機能を提供する

標準ライブラリの例

// io パッケージから
type Reader interface {
    Read([]byte) (int, error)
}

type Writer interface {
    Write([]byte) (int, error)
}

type Closer interface {
    Close() error
}

// fmt パッケージから
type Stringer interface {
    String() string
}

確立されたメソッドシグネチャ

多くの標準的な名前が存在し、それらとそれらが表現する機能名を尊重することは生産的です。

以下のメソッドには標準的なシグネチャと意味があります:

  • Read
  • Write
  • Close
  • Flush
  • String
  • など

重要な原則

1. 同じ名前を使う場合の注意

混乱を避けるため、同じシグネチャと意味を持たない限り、これらの名前の一つをメソッドに付けないでください。

// ❌ 悪い例:標準的な Read と異なるシグネチャ
type BadReader interface {
    Read() string  // 標準的な Read([]byte) (int, error) と異なる
}

// ✅ 良い例:標準に従う
type GoodReader interface {
    Read([]byte) (int, error)  // 標準的なシグネチャ
}

2. 同じ意味の場合は同じ名前を使う

逆に、あなたの型がよく知られた型のメソッドと同じ意味のメソッドを実装する場合は、同じ名前とシグネチャを付けてください。

// ✅ 正しい例:文字列変換メソッド
type Person struct {
    Name string
    Age  int
}

func (p Person) String() string {  // ToString ではない
    return fmt.Sprintf("%s (%d)", p.Name, p.Age)
}

// ❌ 間違った例
func (p Person) ToString() string {  // Go では慣用的でない
    return fmt.Sprintf("%s (%d)", p.Name, p.Age)
}

カスタムインターフェイスの作成

// 単一メソッドインターフェイスの例

// Validator - Validate メソッドを持つ
type Validator interface {
    Validate() error
}

// Serializer - Serialize メソッドを持つ
type Serializer interface {
    Serialize() ([]byte, error)
}

// Processor - Process メソッドを持つ
type Processor interface {
    Process(data []byte) error
}

// Notifier - Notify メソッドを持つ
type Notifier interface {
    Notify(message string) error
}

複合インターフェイス

// 複数のインターフェイスを組み合わせる
type ReadWriter interface {
    Reader
    Writer
}

type ReadWriteCloser interface {
    Reader
    Writer
    Closer
}

具象型での実装

type File struct {
    name string
    data []byte
    pos  int
}

// 標準的な Reader インターフェイスを実装
func (f *File) Read(p []byte) (n int, err error) {
    if f.pos >= len(f.data) {
        return 0, io.EOF
    }
    n = copy(p, f.data[f.pos:])
    f.pos += n
    return n, nil
}

// 標準的な Writer インターフェイスを実装
func (f *File) Write(p []byte) (n int, err error) {
    f.data = append(f.data, p...)
    return len(p), nil
}

// 標準的な Closer インターフェイスを実装
func (f *File) Close() error {
    f.pos = 0
    return nil
}

// 標準的な Stringer インターフェイスを実装
func (f *File) String() string {
    return fmt.Sprintf("File{name: %s, size: %d}", f.name, len(f.data))
}

-er パターンの変形

// 動詞 + -er
type Runner interface {
    Run() error
}

type Starter interface {
    Start() error
}

type Stopper interface {
    Stop() error
}

// 名詞形での命名も可能
type Handler interface {
    Handle(request Request) Response
}

type Manager interface {
    Manage() error
}

機能別グループ化

// ネットワーク関連
type Dialer interface {
    Dial(network, address string) (net.Conn, error)
}

type Listener interface {
    Listen() error
}

// データ処理関連
type Parser interface {
    Parse(input string) (Result, error)
}

type Encoder interface {
    Encode(data interface{}) ([]byte, error)
}

type Decoder interface {
    Decode(data []byte) (interface{}, error)
}

まとめ

重要な原則

  1. 単一メソッドインターフェイスは -er サフィックスを使用
  2. 標準的なメソッド名(Read, Write, Close, String など)は標準シグネチャで使用
  3. 同じ意味なら同じ名前を使用
  4. 混乱を避けるため、異なる意味では標準名を避ける
  5. 簡潔で意味の明確な名前を選択

利点

  • 一貫性: Go コミュニティ全体で統一された命名
  • 相互運用性: 標準ライブラリとの自然な統合
  • 可読性: 名前から機能が推測しやすい
  • 拡張性: インターフェイスの組み合わせが容易

この命名規則に従うことで、Go言語らしい、保守しやすく理解しやすいコードを書くことができます。

まとめ

核心となる概念

  1. “-er”パターン: 単一メソッドのインターフェイスは、動詞に”-er”を付けて「~する者」という行為者名詞にします。これは英語の自然な語形変化を活用した命名法です。
  2. 標準メソッドの尊重: ReadWriteCloseStringなどの標準的なメソッド名には、確立されたシグネチャと意味があります。これらを使う場合は標準に従う必要があります。

一貫性の維持

// ✅ 標準に従った実装
func (t MyType) String() string { ... }

// ❌ 他言語の影響を受けた命名
func (t MyType) ToString() string { ... }

意味の統一

同じ概念には同じ名前を使い、異なる概念には異なる名前を使うことで、Go言語エコシステム全体での一貫性を保ちます。

Go言語の哲学の反映

この命名規則は以下のGo言語の特徴を反映しています:

  • 組み合わせ可能性: 小さなインターフェイスを組み合わせて大きな機能を構築
  • 標準化: コミュニティ全体での一貫した命名
  • 直感性: 名前から機能が容易に推測できる

例えば、io.ReadWriteCloserReaderWriterCloserの組み合わせであることが名前から明確にわかります。

この命名規則を理解し適用することで、Go言語のエコシステムと自然に統合されるコードを書くことができます。

おわりに 

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

よっしー
よっしー

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

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

コメント

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