# Don’t design with interfaces, discover them

### Inti Pesan

Jangan memaksakan desain interface di awal pengembangan, tetapi biarkan interface muncul secara alami melalui refactoring setelah Anda memiliki implementasi konkret yang bekerja.

### Prinsip Dasar

1. **Start with concrete implementations** - Mulai dengan menulis kode konkret terlebih dahulu
2. **Write working code** - Fokus pada kode yang berfungsi
3. **Identify patterns** - Cari pola dan abstraksi yang muncul secara alami
4. **Extract interfaces** - Baru kemudian ekstrak interface dari kode yang sudah ada

### Contoh dalam Go

#### Cara yang Salah (Design dengan Interface di Awal)

```go
// Memaksakan interface di awal tanpa tahu kebutuhan sebenarnya
type Storage interface {
    Save(data []byte) error
    Load(id string) ([]byte, error)
    Delete(id string) error
}

// Harus implementasi semua method bahkan jika tidak perlu
type FileStorage struct{}

func (f *FileStorage) Save(data []byte) error {
    // implementation
}

func (f *FileStorage) Load(id string) ([]byte, error) {
    // implementation
}

func (f *FileStorage) Delete(id string) error {
    // implementation, tapi mungkin tidak pernah digunakan
}
```

#### Cara yang Benar (Discover Interfaces)

```go
// Mulai dengan implementasi konkret
type FileStorage struct {
    basePath string
}

// Hanya tulis method yang benar-benar dibutuhkan
func (f *FileStorage) Save(filename string, data []byte) error {
    return os.WriteFile(filepath.Join(f.basePath, filename), data, 0644)
}

func (f *FileStorage) Load(filename string) ([]byte, error) {
    return os.ReadFile(filepath.Join(f.basePath, filename))
}

// Setelah kode digunakan, baru discover interface yang diperlukan
type FileReader interface {
    Load(filename string) ([]byte, error)
}

type FileWriter interface {
    Save(filename string, data []byte) error
}

// Gunakan interface yang lebih spesifik
func processData(reader FileReader, filename string) {
    data, _ := reader.Load(filename)
    // process data
}
```

### Contoh Lain: Database Operations

```go
// Mulai dengan konkret implementation
type MySQLUserRepository struct {
    db *sql.DB
}

func (r *MySQLUserRepository) CreateUser(user User) error {
    _, err := r.db.Exec("INSERT INTO users (...) VALUES (...)")
    return err
}

func (r *MySQLUserRepository) FindUserByID(id int) (*User, error) {
    row := r.db.QueryRow("SELECT * FROM users WHERE id = ?", id)
    // parse row
}

// Setelah beberapa waktu, discover interface yang dibutuhkan
type UserCreator interface {
    CreateUser(user User) error
}

type UserFinder interface {
    FindUserByID(id int) (*User, error)
}

// Test dengan mock yang lebih sederhana
type MockUserFinder struct {
    Users map[int]*User
}

func (m *MockUserFinder) FindUserByID(id int) (*User, error) {
    return m.Users[id], nil
}
```

### Keuntungan Pendekatan Ini

1. **YAGNI (You Ain't Gonna Need It)** - Hindari membuat interface untuk fitur yang tidak diperlukan
2. **Interface yang lebih kecil** - Interface cenderung lebih fokus dan spesifik
3. **Lebih mudah di-maintain** - Tidak terikat dengan desain interface yang mungkin salah
4. **Better abstraction** - Abstraksi muncul dari pengalaman nyata bukan asumsi

### Kapan Harus Mendesain Interface di Awal?

Hanya ketika:

* Anda sudah sangat familiar dengan domain
* Membuat library untuk konsumsi publik
* Ada requirement yang sangat jelas dan stabil

Quotes ini mengajarkan untuk lebih pragmatis dan membiarkan solusi emerge dari masalah nyata daripada memaksakan abstraksi prematur.
