1 Pengenalan fitur defer dalam Golang

Dalam bahasa Go, pernyataan defer menunda eksekusi pemanggilan fungsi yang mengikutinya hingga fungsi yang berisi pernyataan defer hampir selesai dieksekusi. Anda dapat menganggapnya sebagai blok finally dalam bahasa pemrograman lain, namun penggunaan defer lebih fleksibel dan unik.

Manfaat menggunakan defer adalah dapat digunakan untuk melakukan tugas-tugas pembersihan, seperti menutup file, membuka kunci mutex, atau hanya mencatat waktu keluar dari suatu fungsi. Hal ini dapat membuat program lebih andal dan mengurangi jumlah pekerjaan pemrograman dalam penanganan pengecualian. Dalam filosofi desain Go, penggunaan defer direkomendasikan karena membantu menjaga kode yang ringkas dan mudah dibaca saat menangani kesalahan, pembersihan sumber daya, dan operasi-operasi berikutnya.

2 Prinsip kerja defer

2.1 Prinsip kerja dasar

Prinsip kerja dasar dari defer adalah menggunakan tumpukan (prinsip terakhir masuk, pertama keluar) untuk menyimpan setiap fungsi tertunda yang akan dieksekusi. Ketika pernyataan defer muncul, bahasa Go tidak segera mengeksekusi fungsi yang mengikutinya. Sebagai gantinya, ia memasukkannya ke dalam tumpukan yang didedikasikan. Hanya ketika fungsi luar hampir selesai mengembalikan nilai, fungsi-fungsi tertunda ini akan dieksekusi sesuai urutan tumpukan, dengan fungsi dalam pernyataan defer yang terakhir dinyatakan dieksekusi terlebih dahulu.

Selain itu, perlu dicatat bahwa parameter dalam fungsi-fungsi yang mengikuti pernyataan defer dihitung dan ditetapkan pada saat pernyataan defer dinyatakan, bukan pada saat eksekusi sebenarnya.

func contoh() {
    defer fmt.Println("dunia") // tertunda
    fmt.Println("halo")
}

func utama() {
    contoh()
}

Kode di atas akan menghasilkan output:

halo
dunia

dunia dicetak sebelum fungsi contoh keluar, meskipun muncul sebelum halo dalam kode.

2.2 Urutan eksekusi dari beberapa pernyataan defer

Ketika sebuah fungsi memiliki beberapa pernyataan defer, mereka akan dieksekusi dengan prinsip terakhir masuk, pertama keluar. Hal ini sering sangat penting untuk memahami logika pembersihan yang kompleks. Berikut adalah contoh urutan eksekusi dari beberapa pernyataan defer:

func beberapaDefer() {
    defer fmt.Println("tunda pertama")
    defer fmt.Println("tunda kedua")
    defer fmt.Println("tunda ketiga")

    fmt.Println("tubuh fungsi")
}

func utama() {
    beberapaDefer()
}

Output dari kode ini akan menjadi:

tubuh fungsi
tunda ketiga
tunda kedua
tunda pertama

Karena defer mengikuti prinsip terakhir masuk, pertama keluar, meskipun "tunda pertama" adalah yang pertama ditunda, ia akan dieksekusi terakhir.

3 Aplikasi defer dalam berbagai skenario

3.1 Pelepasan sumber daya

Dalam bahasa Go, pernyataan defer umumnya digunakan untuk menangani logika pelepasan sumber daya, seperti operasi-operasi file dan koneksi basis data. defer memastikan bahwa setelah eksekusi fungsi, sumber daya yang sesuai akan dilepaskan dengan benar tanpa memandang alasan keluar dari fungsi.

Contoh operasi file:

func BacaFile(namafile string) {
    file, err := os.Open(namafile)
    if err != nil {
        log.Fatal(err)
    }
    // Gunakan defer untuk memastikan file ditutup
    defer file.Close()

    // Lakukan operasi pembacaan file...
}

Pada contoh ini, begitu os.Open berhasil membuka file, pernyataan defer file.Close() yang mengikuti memastikan bahwa sumber daya file akan ditutup dengan benar dan sumber daya handle file akan dilepaskan ketika fungsi berakhir.

Contoh Koneksi Basis Data:

func TanyaBasisData(query string) {
    db, err := sql.Open("mysql", "pengguna:katasandi@/namabasisdata")
    if err != nil {
        log.Fatal(err)
    }
    // Pastikan koneksi basis data ditutup menggunakan defer
    defer db.Close()

    // Lakukan operasi kueri basis data...
}

Demikian pula, defer db.Close() memastikan bahwa koneksi basis data akan ditutup ketika keluar dari fungsi TanyaBasisData, tanpa memandang alasan (pengembalian normal atau pengecualian dilemparkan).

3.2 Operasi Kunci pada Pemrograman Bersamaan

Dalam pemrograman bersamaan, menggunakan defer untuk menangani pelepasan kunci mutex adalah praktik yang baik. Hal ini memastikan bahwa kunci dilepaskan dengan benar setelah eksekusi kode bagian kritis, sehingga menghindari deadlock.

Contoh Penggunaan Kunci Mutex:

var mutex sync.Mutex

func updateSharedResource() {
    mutex.Lock()
    // Gunakan defer untuk memastikan bahwa kunci dilepaskan
    defer mutex.Unlock()

    // Lakukan modifikasi pada sumber daya bersama...
}

Tidak peduli apakah modifikasi sumber daya bersama berhasil atau terjadi panic di antaranya, defer akan memastikan bahwa Unlock() dipanggil, memungkinkan goroutine lain yang menunggu kunci untuk mendapatkannya.

Tip: Penjelasan terperinci tentang kunci mutex akan dibahas dalam bab-bab berikutnya. Pemahaman tentang skenario aplikasi defer sudah cukup pada saat ini.

3 Kesalahan Umum dan Pertimbangan untuk defer

Saat menggunakan defer, meskipun keterbacaan dan kebermaintannya kode sangat ditingkatkan, ada juga beberapa kesalahan umum dan pertimbangan yang perlu diingat.

3.1 Parameter fungsi yang ditunda dievaluasi segera

func printValue(v int) {
    fmt.Println("Nilai:", v)
}

func main() {
    nilai := 1
    defer printValue(nilai)
    // Memodifikasi nilai `nilai` tidak akan mempengaruhi parameter yang sudah dilewatkan ke defer
    nilai = 2
}
// Output akan menjadi "Nilai: 1"

Meskipun nilai nilai berubah setelah pernyataan defer, parameter yang dilewatkan ke printValue dalam defer sudah dievaluasi dan tetap, sehingga outputnya tetap "Nilai: 1".

3.2 Hati-hati ketika menggunakan defer di dalam loop

Menggunakan defer di dalam loop dapat menyebabkan sumber daya tidak dilepaskan sebelum loop berakhir, yang dapat mengakibatkan kebocoran sumber daya atau kehabisan sumber daya.

3.3 Hindari "lepas setelah digunakan" dalam pemrograman konkuren

Dalam program konkuren, saat menggunakan defer untuk melepaskan sumber daya, penting untuk memastikan bahwa semua goroutine tidak akan mencoba mengakses sumber daya setelah sumber daya tersebut dilepaskan, untuk mencegah race condition.

4. Perhatikan urutan eksekusi pernyataan defer

Pernyataan defer mengikuti prinsip Last-In-First-Out (LIFO), di mana defer yang terakhir dinyatakan akan dieksekusi terlebih dahulu.

Solusi dan Praktik Terbaik:

  • Selalu sadari bahwa parameter fungsi dalam pernyataan defer dievaluasi pada saat deklarasi.
  • Saat menggunakan defer di dalam loop, pertimbangkan menggunakan fungsi anonim atau secara eksplisit memanggil pelepasan sumber daya.
  • Dalam lingkungan konkuren, pastikan bahwa semua goroutine telah menyelesaikan operasinya sebelum menggunakan defer untuk melepaskan sumber daya.
  • Saat menulis fungsi yang berisi beberapa pernyataan defer, pertimbangkan dengan hati-hati urutan eksekusi dan logikanya.

Dengan mengikuti praktik terbaik ini, Anda dapat menghindari sebagian besar masalah yang dihadapi saat menggunakan defer dan memastikan penulisan kode Go yang lebih tangguh dan mudah dipelihara.