For the complete documentation index, see llms.txt. This page is also available as Markdown.

Fatal

Dalam pengembangan aplikasi Go, kita sering melihat log.Fatal digunakan untuk menghentikan program ketika terjadi error. Namun, penggunaan log.Fatal yang tersebar di berbagai tempat dapat membuat kode sulit diuji dan dikelola. Bab ini akan membahas pola penggunaan log.Fatal yang disiplin dan terpusat.

📂 Kode Lengkap Bab Ini: Seluruh kode yang dibahas di bab ini tersedia di GitHub:

🔗 github.com/jacky-htg/workshop/tree/main/07-fatal

7.1 Memahami log.Fatal

Fungsi log.Fatal di Go melakukan dua hal sekaligus:

  1. Mencetak pesan error ke log

  2. Memanggil os.Exit(1) untuk menghentikan program secara paksa

log.Fatal("something went wrong")
// Sama seperti:
// log.Print("something went wrong")
// os.Exit(1)

Karakteristik penting:

  • defer tidak akan dieksekusi setelah log.Fatal

  • Kode setelah log.Fatal tidak akan pernah berjalan

  • Tidak ada kesempatan untuk melakukan cleanup (menutup koneksi database, dll)

7.2 Kapan Menggunakan log.Fatal

log.Fatal sebaiknya hanya digunakan untuk error yang:

  • Terjadi di awal program (belum ada resource yang perlu dibersihkan)

  • Tidak mungkin dipulihkan (unrecoverable)

  • Membuat state program tidak valid untuk melanjutkan eksekusi

Contoh penggunaan yang tepat:

  • File konfigurasi tidak ditemukan

  • Port server sudah digunakan oleh proses lain

  • Koneksi database gagal sama sekali

Lokasi yang tepat untuk log.Fatal:

  • func init() – inisialisasi package-level variable

  • func main() – entry point aplikasi

7.3 Masalah dengan log.Fatal yang Tersebar

Pada bab-bab sebelumnya, kita memiliki log.Fatal di berbagai tempat:

Masalah dengan pendekatan ini:

  • Sulit diuji – log.Fatal akan menghentikan test

  • Cleanup tidak berjalan – defer db.Close() tidak terpanggil

  • Tidak jelas aliran error – bercampur antara return error dan fatal

7.4 Pola: Memisahkan logika dari eksekusi

Solusi yang direkomendasikan adalah memindahkan semua logika ke fungsi run() error, lalu hanya main() yang memanggil log.Fatal jika run() mengembalikan error.

Pola ini memiliki keuntungan:

  • Semua error dikembalikan sebagai nilai biasa (return error)

  • defer tetap berjalan dengan benar

  • Fungsi run() bisa diuji secara unit

  • Hanya satu log.Fatal di seluruh program (di main)

7.5 Implementasi: CLI

Berikut implementasi pola run() error pada cmd/cli/main.go:

Perhatikan perubahan:

  • log.Fatalf dihapus dari dalam run(), diganti dengan return fmt.Errorf(...)

  • Hanya main() yang memiliki log.Fatalf

  • Menggunakan %w untuk wrapping error (mempertahankan rantai error)

7.6 Implementasi: Server

cmd/server/main.go juga mengikuti pola yang sama:

Perhatikan perubahan penting:

  • log.Fatalf di dalam select diubah menjadi return fmt.Errorf(...)

  • Server error sekarang dikembalikan sebagai nilai error dari run()

  • main() tetap hanya memiliki SATU log.Fatalf

7.7 Perbandingan Sebelum dan Sesudah

Aspek
Sebelum
Sesudah

Jumlah log.Fatal

2+ (tersebar)

1 (hanya di main())

Error handling

Campuran (return & fatal)

Konsisten (return error)

Testability

Sulit (fatal hentikan test)

Mudah (run() bisa di-test)

Cleanup (defer)

Tidak jalan setelah fatal

Jalan selalu

Error wrapping

Tidak konsisten

Menggunakan %w

7.8 Error Wrapping dengan %w

Perhatikan penggunaan %w (bukan %v) saat membungkus error:

Dengan %w, kita bisa menggunakan errors.Is() dan errors.As() nantinya untuk memeriksa tipe error tertentu.

Ringkasan Bab 7

Di bab ini kita telah belajar:

  1. Apa itu log.Fatal – Mencetak log + os.Exit(1)

  2. Kapan menggunakannya – Hanya di init() atau main(), untuk error yang tidak bisa dipulihkan

  3. Pola run() error – Memisahkan logika dari eksekusi

  4. Satu log.Fatal – Hanya di main(), memanggil run() dan handle error

  5. Error wrapping – Menggunakan %w untuk mempertahankan rantai error

Manfaat yang kita peroleh:

  • ✅ Semua error ditangani secara konsisten (return error)

  • defer selalu berjalan (koneksi database tertutup dengan benar)

  • ✅ Fungsi run() bisa diuji secara unit

  • ✅ Aliran kode lebih jelas dan mudah dilacak

Yang akan datang:

  • Saat ini run() sudah cukup rapi, tapi masih bisa dikelompokkan lagi

  • Bab selanjutnya: Bootstrap – mengorganisir inisialisasi aplikasi (config, database, dependency injection) dalam satu tempat yang terstruktur

Last updated