Interface #
Interface di Go adalah fitur yang paling membingungkan bagi developer yang datang dari Java atau C# — bukan karena susah, tapi karena terlalu sederhana dibanding ekspektasi. Di Java, kamu harus mendeklarasikan implements Runnable secara eksplisit. Di Go, tidak ada deklarasi seperti itu sama sekali. Sebuah tipe otomatis memenuhi interface jika ia memiliki semua method yang interface syaratkan. Ini bukan kelemahan desain — ini adalah kekuatan terbesar interface Go, yang membuat decoupling antar komponen menjadi sangat alami dan membuat testing menjadi jauh lebih mudah.
Interface Mendefinisikan Kontrak Perilaku #
Interface di Go hanya mendefinisikan apa yang bisa dilakukan — bukan apa yang dimiliki. Tidak ada field, tidak ada implementasi, hanya daftar method signature:
// Interface mendefinisikan kontrak: "apapun yang bisa di-write"
type Writer interface {
Write(p []byte) (n int, err error)
}
// Interface mendefinisikan kontrak: "apapun yang bisa di-close"
type Closer interface {
Close() error
}
// Interface mendefinisikan kontrak: "apapun yang punya representasi string"
type Stringer interface {
String() string
}
// Interface yang lebih kaya
type Shape interface {
Area() float64
Perimeter() float64
String() string
}
Perhatikan tidak ada keyword public, abstract, atau virtual. Hanya daftar method dan tipe-nya.
Implicit Implementation — Kekuatan Terbesar Go #
Di Java: class MyWriter implements Writer { ... } — kamu harus menyatakan bahwa kamu mengimplementasikan interface.
Di Go: tidak ada deklarasi apapun. Compiler memeriksa sendiri apakah sebuah tipe memenuhi semua method yang diminta interface:
type Writer interface {
Write(p []byte) (n int, err error)
}
// FileWriter mengimplementasikan Writer — tanpa "implements Writer"!
type FileWriter struct {
path string
f *os.File
}
func (fw *FileWriter) Write(p []byte) (int, error) {
return fw.f.Write(p)
}
// NetworkWriter juga mengimplementasikan Writer — sama, tanpa deklarasi
type NetworkWriter struct {
conn net.Conn
}
func (nw *NetworkWriter) Write(p []byte) (int, error) {
return nw.conn.Write(p)
}
// Fungsi ini menerima APAPUN yang bisa di-write
func saveData(w Writer, data []byte) error {
_, err := w.Write(data)
return err
}
func main() {
fw := &FileWriter{path: "output.txt"}
nw := &NetworkWriter{}
saveData(fw, []byte("data ke file")) // ✓
saveData(nw, []byte("data ke network")) // ✓ — polimorfisme!
}
Mengapa Implicit Implementation Sangat Kuat #
Bayangkan kamu menggunakan library pihak ketiga yang mendefinisikan struct ExternalDB. Library itu tidak tahu tentang interface UserRepository yang kamu buat. Tapi selama ExternalDB memiliki method yang interface-mu butuhkan, ia otomatis memenuhinya — tanpa kamu perlu mengubah library tersebut, tanpa perlu wrapper class.
// Interface-mu di package aplikasi
type UserRepository interface {
FindByID(id int) (*User, error)
Save(user *User) error
}
// Library pihak ketiga — kamu tidak bisa mengubah kode ini
// tapi ia memiliki method FindByID dan Save
type ThirdPartyDB struct { ... }
func (db *ThirdPartyDB) FindByID(id int) (*User, error) { ... }
func (db *ThirdPartyDB) Save(user *User) error { ... }
// ThirdPartyDB otomatis memenuhi UserRepository
// tanpa perlu mengubah ThirdPartyDB!
var repo UserRepository = &ThirdPartyDB{}
Interface Value — Dua Komponen Internal #
Penting dipahami: sebuah variabel interface menyimpan dua hal sekaligus — tipe konkret dan nilai konkretnya. Ini mempengaruhi bagaimana nil interface bekerja:
// Representasi internal interface value:
// ┌──────────────┬───────────────┐
// │ type (T) │ value (V) │
// └──────────────┴───────────────┘
var w Writer // type=nil, value=nil → interface nil
var fw *FileWriter = nil
w = fw // type=*FileWriter, value=nil → interface TIDAK nil!
fmt.Println(w == nil) // false! — meski fw adalah nil pointer
Ini adalah gotcha yang sangat terkenal di Go — nil interface berbeda dari interface yang berisi nil pointer:
// ANTI-PATTERN: fungsi ini tidak berperilaku seperti yang diharapkan
func getWriter(useFile bool) Writer {
var fw *FileWriter // nil pointer
if useFile {
fw = openFile()
}
return fw // mengembalikan Writer yang berisi (*FileWriter, nil)
// bukan nil Writer!
}
func main() {
w := getWriter(false)
if w == nil {
fmt.Println("tidak ada writer") // TIDAK pernah tercetak!
}
// w != nil meski fw adalah nil pointer
// Memanggil w.Write() akan panic karena fw adalah nil
}
// BENAR: return nil interface secara eksplisit
func getWriter(useFile bool) Writer {
if useFile {
return openFile() // *FileWriter yang valid
}
return nil // nil interface yang sesungguhnya
}
Jangan pernah return variabel interface yang mungkin nil dari fungsi. Selalu returnnilsecara eksplisit jika tidak ada implementasi. Mengembalikan(*ConcreteType)(nil)dikemas dalam interface akan menghasilkan interface yang tidak nil, menyebabkan pengecekanif result == nilselalu false.
Interface Kecil Lebih Kuat #
Salah satu prinsip terpenting di komunitas Go: interface kecil lebih berguna dari interface besar. io.Reader hanya punya satu method — tapi ia dipakai di ribuan tempat di ekosistem Go:
// io.Reader — satu method, ribuan implementasi
type Reader interface {
Read(p []byte) (n int, err error)
}
// Semua ini mengimplementasikan io.Reader:
// - *os.File
// - *bytes.Buffer
// - *strings.Reader
// - net.Conn
// - *http.Request.Body
// - *gzip.Reader
// - *zip.Reader
// ...dan ratusan lainnya
// Fungsi yang menerima io.Reader bekerja dengan SEMUA implementasi di atas
func countLines(r io.Reader) (int, error) {
scanner := bufio.NewScanner(r)
count := 0
for scanner.Scan() {
count++
}
return count, scanner.Err()
}
// Bisa dipakai dengan file, string, network connection, dll
lines, _ := countLines(os.Stdin)
lines, _ = countLines(strings.NewReader("baris 1\nbaris 2\n"))
lines, _ = countLines(httpResp.Body)
Panduan ukuran interface:
1-2 method ✓ Sangat bagus — bisa diimplementasikan oleh banyak tipe
3-5 method ✓ Masih OK — kontrak yang jelas dan terbatas
6-10 method ⚠ Mulai berat — pertimbangkan pecah jadi interface kecil
10+ method ✗ Hampir pasti terlalu besar — sulit di-mock, sulit dipakai
Komposisi Interface #
Interface bisa di-embed ke interface lain untuk membentuk kontrak yang lebih besar, persis seperti struct embedding:
// Interface atomik — masing-masing satu kemampuan
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 Seeker interface {
Seek(offset int64, whence int) (int64, error)
}
// Komposisi — dibuat dari interface yang lebih kecil
type ReadWriter interface {
Reader
Writer
}
type ReadWriteCloser interface {
Reader
Writer
Closer
}
type ReadWriteSeeker interface {
Reader
Writer
Seeker
}
// Tipe yang mengimplementasikan ReadWriteCloser otomatis
// juga mengimplementasikan Reader, Writer, dan Closer secara terpisah
Komposisi ini memungkinkan kamu memilih kontrak yang tepat untuk setiap fungsi — berikan hanya apa yang dibutuhkan, tidak lebih:
func processInput(r Reader) { ... } // hanya butuh Read
func writeOutput(w Writer) { ... } // hanya butuh Write
func handleConn(rwc ReadWriteCloser) { ... } // butuh ketiganya
any dan interface{}
#
interface{} (atau alias any sejak Go 1.18) adalah interface kosong — memenuhi semua tipe karena tidak mensyaratkan method apapun:
// any dan interface{} adalah identik — any hanyalah alias
var v any = 42
v = "hello"
v = []int{1, 2, 3}
v = struct{ X int }{X: 10}
// Berguna untuk generic containers sebelum generics (Go 1.18)
type Stack struct {
items []any
}
func (s *Stack) Push(item any) {
s.items = append(s.items, item)
}
func (s *Stack) Pop() (any, bool) {
if len(s.items) == 0 {
return nil, false
}
item := s.items[len(s.items)-1]
s.items = s.items[:len(s.items)-1]
return item, true
}
Gunakan
anydengan sangat hemat. Setiap kali kamu menggunakanany, kamu kehilangan type safety yang Go berikan — compiler tidak bisa membantu mendeteksi error tipe saat kompilasi. Sejak Go 1.18, gunakan generics sebagai alternatif yang type-safe untuk container generik:// ANTI-PATTERN: kehilangan type safety func contains(slice []any, item any) bool { ... } items := []any{1, 2, 3} // BENAR sejak Go 1.18: type-safe dengan generics func contains[T comparable](slice []T, item T) bool { for _, v := range slice { if v == item { return true } } return false } fmt.Println(contains([]int{1, 2, 3}, 2)) // true, type-safe fmt.Println(contains([]string{"a", "b"}, "c")) // false, type-safe
Type Assertion #
Type assertion mengekstrak nilai konkret dari variabel interface:
var i interface{} = "Hello, Go!"
// Safe form — selalu gunakan ini
str, ok := i.(string)
if ok {
fmt.Println(str) // Hello, Go!
fmt.Println(len(str)) // 10
} else {
fmt.Println("bukan string")
}
// Unsafe form — PANIC jika tipe tidak sesuai
str2 := i.(string) // OK karena i memang string
num := i.(int) // PANIC: interface conversion: interface {} is string, not int
Type assertion sangat berguna untuk mengecek kemampuan tambahan dari nilai yang diterima sebagai interface:
type Writer interface {
Write([]byte) (int, error)
}
// Cek apakah writer juga bisa di-close
func writeAndClose(w Writer, data []byte) error {
if _, err := w.Write(data); err != nil {
return err
}
// Type assertion untuk cek kemampuan Close
if closer, ok := w.(io.Closer); ok {
return closer.Close() // panggil Close jika ada
}
return nil // tidak apa-apa jika tidak ada Close
}
// Cek apakah error punya informasi tambahan
func handleError(err error) {
// Cek apakah error adalah tipe tertentu
var netErr *net.OpError
if errors.As(err, &netErr) {
fmt.Println("network error pada operasi:", netErr.Op)
return
}
var pathErr *os.PathError
if errors.As(err, &pathErr) {
fmt.Println("path error pada:", pathErr.Path)
return
}
fmt.Println("error umum:", err)
}
Type Switch #
Type switch adalah cara elegan untuk menangani berbagai kemungkinan tipe dalam satu blok — jauh lebih bersih dari serangkaian type assertion:
func formatValue(v any) string {
switch val := v.(type) {
case nil:
return "<nil>"
case bool:
if val {
return "true"
}
return "false"
case int:
return strconv.Itoa(val)
case int64:
return strconv.FormatInt(val, 10)
case float64:
return strconv.FormatFloat(val, 'f', -1, 64)
case string:
return fmt.Sprintf("%q", val)
case []byte:
return fmt.Sprintf("bytes(%d)", len(val))
case error:
return "error: " + val.Error()
case fmt.Stringer:
// Tipe yang mengimplementasikan Stringer
return val.String()
default:
return fmt.Sprintf("%T(%v)", val, val)
}
}
func main() {
fmt.Println(formatValue(nil)) // <nil>
fmt.Println(formatValue(true)) // true
fmt.Println(formatValue(42)) // 42
fmt.Println(formatValue(3.14)) // 3.14
fmt.Println(formatValue("hello")) // "hello"
fmt.Println(formatValue([]byte{1,2})) // bytes(2)
}
Interface untuk Dependency Injection dan Testing #
Ini adalah penggunaan terpenting interface di kode produksi. Dengan mendefinisikan dependency sebagai interface, kamu bisa:
- Mengganti implementasi tanpa mengubah kode yang menggunakannya
- Menyuntikkan mock saat testing tanpa library eksternal apapun
// Definisikan dependency sebagai interface di sisi konsumer
type EmailSender interface {
Send(to, subject, body string) error
}
type SMSSender interface {
Send(to, message string) error
}
type UserRepository interface {
FindByID(id int) (*User, error)
Save(user *User) error
}
// Service bergantung pada interface, bukan implementasi konkret
type UserService struct {
repo UserRepository
email EmailSender
sms SMSSender
}
func NewUserService(repo UserRepository, email EmailSender, sms SMSSender) *UserService {
return &UserService{repo: repo, email: email, sms: sms}
}
func (s *UserService) Register(name, emailAddr, phone string) error {
user := &User{Name: name, Email: emailAddr, Phone: phone}
if err := s.repo.Save(user); err != nil {
return fmt.Errorf("gagal menyimpan user: %w", err)
}
// Kirim notifikasi — tidak peduli implementasinya
if err := s.email.Send(emailAddr, "Selamat Datang!", "Akun kamu berhasil dibuat."); err != nil {
return fmt.Errorf("gagal kirim email: %w", err)
}
return nil
}
// ── Implementasi nyata untuk produksi ────────────────────────
type SMTPEmailSender struct {
host string
port int
}
func (s *SMTPEmailSender) Send(to, subject, body string) error {
// kirim email via SMTP
fmt.Printf("[SMTP] Mengirim ke %s: %s\n", to, subject)
return nil
}
type TwilioSMSSender struct {
apiKey string
}
func (t *TwilioSMSSender) Send(to, message string) error {
// kirim SMS via Twilio API
return nil
}
// ── Mock untuk testing — tanpa framework apapun! ─────────────
type MockEmailSender struct {
SentEmails []struct{ To, Subject, Body string }
ShouldFail bool
}
func (m *MockEmailSender) Send(to, subject, body string) error {
if m.ShouldFail {
return errors.New("mock: email gagal dikirim")
}
m.SentEmails = append(m.SentEmails, struct{ To, Subject, Body string }{to, subject, body})
return nil
}
type MockUserRepo struct {
Users map[int]*User
SaveErr error
}
func (r *MockUserRepo) FindByID(id int) (*User, error) {
user, ok := r.Users[id]
if !ok {
return nil, fmt.Errorf("user %d tidak ditemukan", id)
}
return user, nil
}
func (r *MockUserRepo) Save(user *User) error {
if r.SaveErr != nil {
return r.SaveErr
}
if r.Users == nil {
r.Users = make(map[int]*User)
}
r.Users[len(r.Users)+1] = user
return nil
}
Interface di Sisi Konsumer, Bukan Produsen #
Ini adalah prinsip desain paling penting yang sering dilanggar:
// ANTI-PATTERN: interface didefinisikan di sisi PRODUSEN
// package userservice
type UserServiceInterface interface {
CreateUser(name, email string) (*User, error)
GetUser(id int) (*User, error)
UpdateUser(id int, data UpdateData) error
DeleteUser(id int) error
ListUsers(filter Filter) ([]*User, error)
// ... 10 method lagi
}
type UserService struct { ... }
// UserService mengimplementasikan UserServiceInterface
// Masalah: konsumer yang hanya butuh GetUser terpaksa bergantung
// pada interface raksasa ini, dan mock-nya harus mengimplementasikan
// semua 15 method meski hanya 1 yang dipakai
// ─────────────────────────────────────────────────────────────
// BENAR: interface didefinisikan di sisi KONSUMER
// package handler — hanya definisikan yang dibutuhkan
type UserGetter interface {
GetUser(id int) (*User, error)
}
type ProfileHandler struct {
users UserGetter // interface kecil, mudah di-mock
}
// package ordersvc — kebutuhan berbeda, interface berbeda
type UserValidator interface {
GetUser(id int) (*User, error)
}
type OrderService struct {
users UserValidator
}
Hasilnya: UserService (struct nyata) otomatis memenuhi UserGetter dan UserValidator karena keduanya hanya butuh satu method yang sama. Setiap konsumer mendapat interface sekecil yang mereka butuhkan.
Method Set — Value vs Pointer #
Ada aturan penting tentang method set yang menentukan kapan tipe value dan pointer bisa memenuhi interface:
type Animal interface {
Sound() string
Move()
}
type Dog struct{ Name string }
func (d Dog) Sound() string { return "Woof" } // value receiver
func (d *Dog) Move() { fmt.Println(d.Name, "berlari") } // pointer receiver
func main() {
// *Dog mengimplementasikan Animal — pointer memiliki SEMUA method
var a Animal = &Dog{Name: "Buddy"} // ✓
a.Sound()
a.Move()
// Dog TIDAK mengimplementasikan Animal — value tidak punya method pointer
// var b Animal = Dog{Name: "Buddy"} // ✗ compile error:
// Dog does not implement Animal (Move method has pointer receiver)
}
Aturan Method Set:
Tipe T memiliki method:
→ Semua method dengan VALUE receiver (T)
Tipe *T memiliki method:
→ Semua method dengan VALUE receiver (T)
→ Semua method dengan POINTER receiver (*T)
Implikasi untuk interface:
→ Jika interface memiliki method dengan pointer receiver,
hanya *T yang bisa memenuhi interface, bukan T
→ Gunakan pointer (&value) saat assign ke interface jika
ada method dengan pointer receiver
Contoh Program Lengkap #
Program berikut membangun sistem notifikasi multi-channel yang mendemonstrasikan dependency injection via interface:
package main
import (
"fmt"
"strings"
"time"
)
// ── Interface Definitions ─────────────────────────────────────
type Notifier interface {
Send(recipient, message string) error
Name() string
}
type NotificationStore interface {
Save(n Notification) error
FindByRecipient(recipient string) []Notification
}
// ── Domain Types ──────────────────────────────────────────────
type Priority int
const (
PriorityLow Priority = iota
PriorityNormal
PriorityHigh
PriorityCritical
)
func (p Priority) String() string {
switch p {
case PriorityLow: return "Low"
case PriorityNormal: return "Normal"
case PriorityHigh: return "High"
case PriorityCritical: return "Critical"
default: return "Unknown"
}
}
type Notification struct {
ID int
Recipient string
Message string
Channel string
Priority Priority
SentAt time.Time
Success bool
Error string
}
// ── Concrete Notifier Implementations ────────────────────────
type EmailNotifier struct {
SMTPHost string
From string
sentCount int
}
func (e *EmailNotifier) Send(recipient, message string) error {
// Simulasi pengiriman email
e.sentCount++
fmt.Printf(" 📧 [EMAIL] To: %s\n %s\n", recipient, message)
return nil
}
func (e *EmailNotifier) Name() string { return "Email" }
type SlackNotifier struct {
WebhookURL string
Channel string
}
func (s *SlackNotifier) Send(recipient, message string) error {
fmt.Printf(" 💬 [SLACK] #%s @%s: %s\n", s.Channel, recipient, message)
return nil
}
func (s *SlackNotifier) Name() string { return "Slack" }
type SMSNotifier struct {
APIKey string
FromNum string
}
func (s *SMSNotifier) Send(recipient, message string) error {
// SMS biasanya dibatasi panjangnya
if len(message) > 160 {
message = message[:157] + "..."
}
fmt.Printf(" 📱 [SMS] To: %s | %s\n", recipient, message)
return nil
}
func (s *SMSNotifier) Name() string { return "SMS" }
// ── In-Memory Store ───────────────────────────────────────────
type InMemoryStore struct {
notifications []Notification
nextID int
}
func (s *InMemoryStore) Save(n Notification) error {
s.nextID++
n.ID = s.nextID
s.notifications = append(s.notifications, n)
return nil
}
func (s *InMemoryStore) FindByRecipient(recipient string) []Notification {
var result []Notification
for _, n := range s.notifications {
if n.Recipient == recipient {
result = append(result, n)
}
}
return result
}
// ── Notification Service ──────────────────────────────────────
type NotificationService struct {
notifiers map[string]Notifier
store NotificationStore
}
func NewNotificationService(store NotificationStore) *NotificationService {
return &NotificationService{
notifiers: make(map[string]Notifier),
store: store,
}
}
func (ns *NotificationService) Register(notifier Notifier) {
ns.notifiers[notifier.Name()] = notifier
}
func (ns *NotificationService) Notify(
recipient, message string,
priority Priority,
channels ...string,
) {
// Tentukan channel berdasarkan priority jika tidak ditentukan
if len(channels) == 0 {
switch priority {
case PriorityCritical:
channels = []string{"Email", "SMS", "Slack"}
case PriorityHigh:
channels = []string{"Email", "Slack"}
default:
channels = []string{"Email"}
}
}
fmt.Printf("\n[%s] Kirim ke %s (Priority: %s):\n",
time.Now().Format("15:04:05"), recipient, priority)
for _, ch := range channels {
notifier, ok := ns.notifiers[ch]
if !ok {
fmt.Printf(" ⚠ Channel %q tidak terdaftar\n", ch)
continue
}
n := Notification{
Recipient: recipient,
Message: message,
Channel: ch,
Priority: priority,
SentAt: time.Now(),
}
err := notifier.Send(recipient, message)
if err != nil {
n.Error = err.Error()
fmt.Printf(" ✗ Gagal kirim via %s: %v\n", ch, err)
} else {
n.Success = true
}
_ = ns.store.Save(n)
}
}
func (ns *NotificationService) History(recipient string) {
notifications := ns.store.FindByRecipient(recipient)
if len(notifications) == 0 {
fmt.Printf("\nTidak ada riwayat notifikasi untuk %s\n", recipient)
return
}
fmt.Printf("\n=== Riwayat Notifikasi: %s ===\n", recipient)
for _, n := range notifications {
status := "✓"
if !n.Success {
status = "✗"
}
fmt.Printf(" [%s] %s via %-6s | %s\n",
status,
n.SentAt.Format("15:04:05"),
n.Channel,
truncate(n.Message, 50),
)
}
}
func truncate(s string, n int) string {
if len(s) <= n {
return s
}
return s[:n-3] + "..."
}
// ── Main ──────────────────────────────────────────────────────
func main() {
// Setup — dependency injection via interface
store := &InMemoryStore{}
svc := NewNotificationService(store)
// Register notifiers — semua mengimplementasikan Notifier
svc.Register(&EmailNotifier{SMTPHost: "smtp.example.com", From: "[email protected]"})
svc.Register(&SlackNotifier{WebhookURL: "https://hooks.slack.com/...", Channel: "alerts"})
svc.Register(&SMSNotifier{APIKey: "twilio-key", FromNum: "+62800000000"})
// Kirim berbagai notifikasi
svc.Notify("[email protected]", "Selamat datang di platform kami!", PriorityNormal)
svc.Notify("[email protected]",
"Transaksi Rp 5.000.000 berhasil diproses",
PriorityHigh)
svc.Notify("[email protected]",
"CRITICAL: Server CPU usage mencapai 98%! Segera periksa!",
PriorityCritical)
svc.Notify("[email protected]",
"Laporan bulanan kamu sudah siap diunduh",
PriorityLow,
"Email") // override channel
// Lihat riwayat
svc.History("[email protected]")
svc.History("[email protected]")
// Demonstrasi type assertion — cek kapabilitas tambahan
fmt.Println("\n=== Info Notifier ===")
for name, notifier := range svc.notifiers {
info := fmt.Sprintf("%-10s", name)
// Type assertion untuk cek apakah EmailNotifier
if emailNotifier, ok := notifier.(*EmailNotifier); ok {
info += fmt.Sprintf(" | SMTP: %s | Terkirim: %d",
emailNotifier.SMTPHost,
emailNotifier.sentCount)
}
// Type switch untuk info spesifik per tipe
switch n := notifier.(type) {
case *SlackNotifier:
info += fmt.Sprintf(" | Channel: #%s", n.Channel)
case *SMSNotifier:
info += fmt.Sprintf(" | From: %s", n.FromNum)
}
fmt.Printf(" %s\n", info)
}
// Demonstrasi: interface kecil di sisi konsumer
var channels []string
for name := range svc.notifiers {
channels = append(channels, name)
}
fmt.Printf("\nChannel tersedia: %s\n", strings.Join(channels, ", "))
}
Ringkasan #
- Implicit implementation — tidak ada
implements; tipe otomatis memenuhi interface jika memiliki semua method yang disyaratkan.- Interface = kontrak perilaku — hanya method signature, tidak ada field atau implementasi.
- Interface value menyimpan (type, value) — variabel interface yang berisi nil pointer tidak sama dengan nil interface; selalu return
nileksplisit.- Interface kecil lebih kuat —
io.Readerdengan satu method dipakai ribuan kali; hindari interface dengan 10+ method.- Komposisi interface — embed interface ke interface lain untuk kontrak yang lebih kaya.
any/interface{}kehilangan type safety — gunakan generics sejak Go 1.18 untuk container generik yang type-safe.- Type assertion safe (
val, ok := i.(Type)) — selalu gunakan two-value form untuk menghindari panic.- Type switch untuk menangani banyak kemungkinan tipe dengan elegan.
- Definisikan interface di sisi konsumer — setiap konsumer mendefinisikan interface sekecil yang dibutuhkan, bukan satu interface besar di sisi produsen.
- Method set:
*Tmemiliki method value dan pointer receiver;Thanya memiliki method value receiver — gunakan pointer saat assign ke interface jika ada pointer receiver.