Day 3: インターフェース - 解説

本日のまとめ

インターフェースの重要ポイント:

概念 説明 重要度
暗黙的実装 明示的な宣言不要 ★★★
型アサーション 安全な型変換 ★★★
型スイッチ 複数の型を扱う ★★☆
空インターフェース 任意の型を保持 ★★☆
デザインパターン Strategy, Adapter, Decorator, DI ★★★
テスト モックで依存を注入 ★★★

---

なぜGoのインターフェースは特別なのか

暗黙的実装の威力

// 外部パッケージの型にインターフェースを適用できる
// 標準ライブラリの型も、自分で定義したインターフェースを満たす

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

// os.File, bytes.Buffer, strings.Builder など
// すべてMyWriterを満たす(それらを修正せずに)

Duck Typing vs 暗黙的実装

# Python: Duck Typing(動的、実行時チェック)
def process(obj):
    obj.write(b"hello")  # 実行時にエラーの可能性

// Go: 暗黙的実装(静的、コンパイル時チェック)
func process(w io.Writer) {
    w.Write([]byte("hello"))  // コンパイル時にチェック
}

---

インターフェースの内部実装

インターフェース値の構造

// インターフェース値は2つのワードで構成される
type iface struct {
    tab  *itab   // 型情報とメソッドテーブル
    data unsafe.Pointer  // 実際の値へのポインタ
}

nil インターフェースの注意点

func main() {
    var w io.Writer
    fmt.Println(w == nil)  // true

    var buf *bytes.Buffer = nil
    w = buf
    fmt.Println(w == nil)  // false! (型情報がある)

    // これは危険
    if w != nil {
        w.Write([]byte("hello"))  // panic: buf is nil
    }
}

---

型アサーションのパフォーマンス

// 型アサーションは高速(O(1))
if s, ok := i.(string); ok {
    // ...
}

// 型スイッチも高速
switch v := i.(type) {
case string:
    // ...
}

// リフレクションは遅い
reflect.TypeOf(i)  // 型アサーションより10倍以上遅い

---

インターフェース設計のベストプラクティス

1. 消費者側でインターフェースを定義

// 悪い例:提供者側でインターフェースを定義
package storage

type Storage interface {
    Get(key string) ([]byte, error)
    Set(key string, value []byte) error
    // 多くのメソッド...
}

// 良い例:消費者側で必要なものだけ定義
package userservice

type UserGetter interface {
    Get(key string) ([]byte, error)
}

func GetUser(store UserGetter, id string) (*User, error) {
    // ...
}

2. インターフェースを返さない

// 悪い例
func NewStorage() Storage {
    return &MemoryStorage{}
}

// 良い例
func NewMemoryStorage() *MemoryStorage {
    return &MemoryStorage{}
}

3. 最小限のインターフェース

// 悪い例
type Repository interface {
    GetByID(id int) (*User, error)
    GetByName(name string) (*User, error)
    GetByEmail(email string) (*User, error)
    Create(*User) error
    Update(*User) error
    Delete(id int) error
    List() ([]*User, error)
    Count() (int, error)
}

// 良い例
type UserGetter interface {
    GetByID(id int) (*User, error)
}

type UserCreator interface {
    Create(*User) error
}

// 必要な場所で組み合わせる

---

よくある間違い

1. インターフェースの乱用

// 悪い例:すべてにインターフェースを作る
type StringProcessor interface {
    Process(s string) string
}

// 良い例:必要な時だけインターフェースを作る
func Process(s string) string {
    return strings.ToUpper(s)
}

2. 空インターフェースの過度な使用

// 悪い例
func Store(data interface{}) {}

// 良い例:ジェネリクスを使う(Go 1.18+)
func Store[T any](data T) {}

// または具体的な型を使う
func StoreUser(user *User) {}

---

メンタルモデル:インターフェースの思考法

1. 「契約」として考える

インターフェースは実装の詳細ではなく、契約です。

// 契約: 「私は書き込むことができます」
type Writer interface {
    Write([]byte) (int, error)
}

// 契約を守る実装
type FileWriter struct { /* ... */ }
func (w *FileWriter) Write(p []byte) (int, error) { /* ... */ }

type NetworkWriter struct { /* ... */ }
func (w *NetworkWriter) Write(p []byte) (int, error) { /* ... */ }

// 契約を信頼するコード
func SaveData(w Writer, data []byte) error {
    // Writerの具体的な実装は知らない、契約だけを信頼
    _, err := w.Write(data)
    return err
}

2. 「振る舞い」で考える

型ではなく、何ができるかで考えます。

// 悪い思考: 「Userオブジェクトを保存する」
type UserRepository struct {
    // User固有の実装...
}

// 良い思考: 「エンティティを保存できる」
type Repository[T any] interface {
    Save(entity T) error
    Find(id string) (T, error)
}

// 任意の型で使える
var userRepo Repository[User]
var productRepo Repository[Product]

3. 「構成」で考える

継承ではなく、構成で機能を組み立てます。

// 小さなインターフェースを定義
type Reader interface {
    Read(p []byte) (n int, err error)
}

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

type Closer interface {
    Close() error
}

// 組み合わせて大きな概念を表現
type ReadWriteCloser interface {
    Reader
    Writer
    Closer
}

// 必要な機能だけを要求
func ProcessFile(r Reader) error {
    // ReadだけできればOK
}

---

設計原則:SOLID再訪(インターフェース編)

Interface Segregation Principle の深掘り

クライアントは使わないメソッドに依存すべきではありません。

// 現実的な例: ドキュメント管理システム

// 悪い設計:全機能を1つのインターフェースに
type DocumentManager interface {
    Create(doc *Document) error
    Read(id string) (*Document, error)
    Update(doc *Document) error
    Delete(id string) error
    Search(query string) ([]*Document, error)
    Index(doc *Document) error
    Backup() error
    Restore(backup *Backup) error
    Audit(action string) error
}

// 問題:
// - 読み取り専用クライアントも全メソッドを知る必要がある
// - モックが複雑になる
// - 変更の影響範囲が大きい

// 良い設計: 役割で分離
type DocumentReader interface {
    Read(id string) (*Document, error)
    Search(query string) ([]*Document, error)
}

type DocumentWriter interface {
    Create(doc *Document) error
    Update(doc *Document) error
    Delete(id string) error
}

type DocumentIndexer interface {
    Index(doc *Document) error
}

type DocumentBackup interface {
    Backup() error
    Restore(backup *Backup) error
}

// クライアントは必要な機能だけを要求
type ReportGenerator struct {
    reader DocumentReader  // 読み取りだけ必要
}

type AdminPanel struct {
    reader DocumentReader
    writer DocumentWriter
    backup DocumentBackup
}

Dependency Inversion の実践例

// レイヤードアーキテクチャでの DIP

// プレゼンテーション層
package handler

// ビジネスロジックへの依存(インターフェース)
type UserService interface {
    GetUser(ctx context.Context, id string) (*User, error)
    CreateUser(ctx context.Context, user *User) error
}

type UserHandler struct {
    service UserService  // 抽象に依存
}

// ビジネスロジック層
package service

// データアクセスへの依存(インターフェース)
type UserRepository interface {
    FindByID(ctx context.Context, id string) (*User, error)
    Save(ctx context.Context, user *User) error
}

type userService struct {
    repo UserRepository  // 抽象に依存
}

// データアクセス層
package repository

type postgresUserRepository struct {
    db *sql.DB  // 具象型(外部ライブラリ)
}

// 依存関係の流れ:
// Handler -> Service (interface) <- serviceImpl
//                ↓
//           Repository (interface) <- repositoryImpl
//
// すべてのレイヤーが抽象(インターフェース)に依存

---

さらなる探求:高度なインターフェーステクニック

1. 型パラメータ付きインターフェース(Go 1.18+)

// ジェネリックなリポジトリインターフェース
type Repository[T any] interface {
    FindByID(ctx context.Context, id string) (T, error)
    Save(ctx context.Context, entity T) error
    Delete(ctx context.Context, id string) error
    List(ctx context.Context) ([]T, error)
}

// 具体的な型で使用
type UserRepository Repository[User]
type ProductRepository Repository[Product]

// 実装
type InMemoryRepository[T any] struct {
    items map[string]T
}

func (r *InMemoryRepository[T]) FindByID(ctx context.Context, id string) (T, error) {
    item, ok := r.items[id]
    if !ok {
        var zero T
        return zero, errors.New("not found")
    }
    return item, nil
}

// 使用例
userRepo := &InMemoryRepository[User]{items: make(map[string]User)}
productRepo := &InMemoryRepository[Product]{items: make(map[string]Product)}

2. インターフェースの制約(Constraints)

// 数値型の制約
type Number interface {
    ~int | ~int8 | ~int16 | ~int32 | ~int64 |
    ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 |
    ~float32 | ~float64
}

// 比較可能な型の制約
type Comparable interface {
    comparable
}

// メソッドを持つ制約
type Stringer interface {
    String() string
}

// 組み合わせ
func Max[T Number](a, b T) T {
    if a > b {
        return a
    }
    return b
}

func PrintAll[T Stringer](items []T) {
    for _, item := range items {
        fmt.Println(item.String())
    }
}

3. インターフェースのエンベッディング

// 基本インターフェース
type Reader interface {
    Read(p []byte) (n int, err error)
}

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

// エンベッディングで組み合わせ
type ReadWriter interface {
    Reader  // Readメソッドを持つ
    Writer  // Writeメソッドを持つ
}

// メソッドのオーバーライドはできない(これはエラー)
type BrokenInterface interface {
    Reader
    Read(p []byte) (n int, err error)  // エラー: 重複定義
}

// 実装
type Buffer struct {
    data []byte
}

func (b *Buffer) Read(p []byte) (int, error) {
    // 実装
}

func (b *Buffer) Write(p []byte) (int, error) {
    // 実装
}

// BufferはReadWriterを自動的に実装
var _ ReadWriter = (*Buffer)(nil)

4. インターフェースのnil問題の深掘り

package main

import "fmt"

type MyError struct {
    msg string
}

func (e *MyError) Error() string {
    if e == nil {
        return "no error"
    }
    return e.msg
}

// ケース1: nil具象型を返す
func returnNilConcrete() *MyError {
    return nil  // *MyError型のnil
}

// ケース2: nilインターフェースを返す
func returnNilInterface() error {
    return nil  // error型のnil
}

// ケース3: nil具象型をインターフェースに変換して返す
func returnConvertedNil() error {
    var err *MyError = nil
    return err  // 危険!
}

func main() {
    // ケース1: nil具象型
    err1 := returnNilConcrete()
    fmt.Printf("err1 == nil: %v\n", err1 == nil)  // true

    // ケース2: nilインターフェース
    err2 := returnNilInterface()
    fmt.Printf("err2 == nil: %v\n", err2 == nil)  // true

    // ケース3: 変換されたnil(落とし穴!)
    err3 := returnConvertedNil()
    fmt.Printf("err3 == nil: %v\n", err3 == nil)  // false!

    // err3は型情報を持つため、nilではない
    // しかし、err3.Error()を呼ぶとpanicする可能性がある
}

// 正しいパターン
func correctPattern() error {
    var err *MyError = nil
    if err != nil {
        return err  // errがnilでない場合のみ返す
    }
    return nil  // 明示的にnilを返す
}

---

ビジュアル図解:インターフェースの理解

インターフェース値のメモリ構造

インターフェース値 (16 bytes on 64-bit)
┌─────────────────────────────┐
│  Type Pointer (8 bytes)     │ → 型情報(itab)
├─────────────────────────────┤
│  Data Pointer (8 bytes)     │ → 実際の値
└─────────────────────────────┘

例1: var w io.Writer = &FileWriter{path: "test.txt"}

┌─────────────────────────────┐
│  *itab (FileWriter → Writer)│ → メソッドテーブル
├─────────────────────────────┤
│  *FileWriter                │ → FileWriterインスタンス
└─────────────────────────────┘

例2: var w io.Writer = nil

┌─────────────────────────────┐
│  nil (0x0)                  │
├─────────────────────────────┤
│  nil (0x0)                  │
└─────────────────────────────┘

例3: var e error = (*MyError)(nil)  // 危険!

┌─────────────────────────────┐
│  *itab (MyError → error)    │ → 型情報あり!
├─────────────────────────────┤
│  nil (0x0)                  │ → データはnil
└─────────────────────────────┘
この場合、e == nil は false になる!

型アサーションのフロー

var i interface{} = "hello"

s, ok := i.(string)

ステップ1: 型情報を取得
    i の Type Pointer を確認

ステップ2: 型を比較
    Type Pointer が string を指しているか?

    Yes → ステップ3へ
    No  → ok = false, s = ""(ゼロ値)

ステップ3: データを取得
    Data Pointer から値をコピー
    s = "hello", ok = true

パフォーマンス: O(1) (ハッシュテーブルルックアップ)

型スイッチの評価順序

func process(i interface{}) {
    switch v := i.(type) {
    case int:           // ケース1
        // ...
    case string:        // ケース2
        // ...
    case io.Reader:     // ケース3
        // ...
    case interface{ Close() error }:  // ケース4
        // ...
    default:            // ケース5
        // ...
    }
}

評価フロー:
1. i の型を取得
2. 各caseを順番に比較
3. 最初にマッチしたcaseを実行
4. どれもマッチしなければdefault

注意: ケースの順序が重要!
     より具体的な型を先に書く

---

プロダクション事例:実際のアンチパターンとその修正

実例1: エラーインターフェースの誤用

// 悪いコード(実際のプロジェクトから)
type ValidationError struct {
    Errors []string
}

func (e *ValidationError) Error() string {
    return strings.Join(e.Errors, ", ")
}

func ValidateUser(user *User) error {
    var err ValidationError

    if user.Name == "" {
        err.Errors = append(err.Errors, "name is required")
    }

    if user.Email == "" {
        err.Errors = append(err.Errors, "email is required")
    }

    // 落とし穴!
    return &err  // errが空でもnilではない
}

func main() {
    err := ValidateUser(&User{Name: "Alice", Email: "alice@example.com"})
    if err != nil {  // true になる!
        fmt.Println("Validation failed:", err)
    }
}

// 修正版
func ValidateUser(user *User) error {
    var errors []string

    if user.Name == "" {
        errors = append(errors, "name is required")
    }

    if user.Email == "" {
        errors = append(errors, "email is required")
    }

    if len(errors) > 0 {
        return &ValidationError{Errors: errors}  // エラーがある場合のみ返す
    }

    return nil  // 明示的にnilを返す
}

実例2: インターフェースのレイヤー汚染

// 悪いコード
package repository

// インターフェースを提供者側で定義
type UserRepository interface {
    GetUser(id string) (*User, error)
    SaveUser(user *User) error
}

package service

import "myapp/repository"

type UserService struct {
    repo repository.UserRepository  // repository に依存
}

// 問題:
// - serviceパッケージがrepositoryパッケージに依存
// - repositoryの変更がserviceに影響
// - テストが困難

// 修正版
package service

// サービス層でインターフェースを定義
type UserRepository interface {
    GetUser(id string) (*User, error)
    SaveUser(user *User) error
}

type UserService struct {
    repo UserRepository  // 自分のインターフェースに依存
}

package repository

import "myapp/service"

type PostgresUserRepository struct {
    db *sql.DB
}

// service.UserRepository を実装(暗黙的)
func (r *PostgresUserRepository) GetUser(id string) (*User, error) { /* ... */ }
func (r *PostgresUserRepository) SaveUser(user *User) error { /* ... */ }

// メリット:
// - 依存関係が逆転(DIP)
// - repository の変更が service に影響しない
// - モックテストが容易

---

明日の予習

Day 4: 並行処理の基礎では、以下を学びます:

  • ゴルーチンの基本とCSPモデル
  • sync.WaitGroupによる同期
  • sync.Mutex / RWMutexによる排他制御
  • atomic操作と無ロック並行処理
  • 並行処理のパターンとアンチパターン
  • データ競合の検出とデバッグ

予習課題

以下のコードに潜むバグを見つけてください:

package main

import (
    "fmt"
    "sync"
)

type Counter struct {
    value int
}

func (c *Counter) Increment() {
    c.value++
}

func (c *Counter) Value() int {
    return c.value
}

func main() {
    counter := &Counter{}
    var wg sync.WaitGroup

    for i := 0; i < 1000; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            counter.Increment()
        }()
    }

    wg.Wait()
    fmt.Println("Final count:", counter.Value())
}

質問:

  • このコードを実行すると、Final count は常に 1000 になるでしょうか?
  • もしならない場合、その理由は何でしょうか?
  • どのように修正すればよいでしょうか?

---

参考リソース

必読ドキュメント

推奨記事

  • "How to use interfaces in Go" by Jordan Orelli
  • "Accept Interfaces, Return Structs" by Jack Lindamood
  • "Go Interfaces Are Beautiful" by Dave Cheney

オープンソースプロジェクトの事例

  • io.Reader / io.Writer: 標準ライブラリの最高の例
  • database/sql.Driver: プラグイン可能なドライバーシステム
  • net/http.Handler: ミドルウェアパターンの典型

---

インターフェースはGoの最も強力な機能です。小さく保ち、組み合わせて使うことで、柔軟で保守性の高いコードが書けます!

Remember: "The bigger the interface, the weaker the abstraction." - Rob Pike