- Verbosity penanganan error di bahasa Go sudah lama menjadi salah satu keluhan teratas dari pengguna
- Berbagai usulan perbaikan sintaksis (misalnya
check/handle, try, operator ?, dll.) telah dibahas dan diuji, tetapi semuanya ditolak tanpa konsensus komunitas yang memadai
- Dampak luas perubahan bahasa terhadap kode, alat, dokumentasi, dan lain-lain, serta prinsip Go untuk mempertahankan kesederhanaan khasnya, menjadi pertimbangan utama
- Karena kejelasan, kemudahan debugging dari cara saat ini dan juga preferensi sebagian pengguna, alasan untuk memaksakan perubahan sintaks dinilai lemah
- Tidak ada rencana perubahan sintaks penanganan error untuk masa depan yang terlihat, dan semua usulan terkait akan ditutup tanpa penyelidikan tambahan
Mengangkat masalah verbosity penanganan error di Go
- Salah satu keluhan lama terhadap Go adalah bahwa sintaks penanganan error terlalu bertele-tele
- Pola seperti
if err != nil secara khas muncul berulang kali di dalam kode
- Semakin banyak pemanggilan API yang dibutuhkan sebuah program, semakin menonjol pola ini, hingga kode penanganan error bisa lebih banyak daripada logika utamanya
- Dalam survei pengguna tahunan, keluhan ini terus muncul di peringkat atas
Diskusi dengan komunitas dan usulan awal
- Tim Go terus meneliti usulan perbaikan penanganan error dengan menekankan pentingnya masukan komunitas
- Dalam diskusi proyek Go 2 pada 2018, Russ Cox secara resmi merangkum inti persoalan penanganan error
- Muncul rancangan mekanisme
check dan handle yang diusulkan Marcel van Lohuizen
- Termasuk perbandingan dengan bahasa-bahasa serupa dan peninjauan berbagai alternatif
- Pendekatan ini memang membuat kode lebih ringkas, tetapi tidak diadopsi karena menambah kompleksitas
Usulan try dan setelahnya
- Pada 2019, diajukan usulan fungsi bawaan
try yang jauh lebih sederhana
- Hanya mengkodekan fungsi
check, sementara handle dihilangkan
- Usulan ini dikritik karena menyembunyikan alur kontrol dan akhirnya dibatalkan di tengah penolakan komunitas
- Dari pengalaman ini muncul pemahaman tentang risiko usulan yang terlalu matang tanpa umpan balik yang cukup
- Untuk usulan perubahan besar, penting mengumpulkan pendapat lebih luas sejak tahap desain awal
Upaya tambahan dan beragam usulan
- Banyak variasi dan pendekatan alternatif untuk penanganan error terus bermunculan dari komunitas
- Kondisinya dirangkum dalam umbrella issue oleh Ian Lance Taylor, sementara contoh-contoh terus dikumpulkan di Go Wiki, blog, dan tempat lain
- Pada 2024, muncul usulan menerapkan operator
? yang dipinjam dari Rust
- Dalam uji kegunaan skala kecil, ada masukan bahwa pendekatan ini terasa intuitif, tetapi tetap tidak mencapai konsensus di tengah beragam pendapat
Kebuntuan diskusi dan kesimpulan
- Meski ada lebih dari tiga usulan resmi maupun tidak resmi, dan ratusan usulan dari komunitas, semuanya ditolak karena kurangnya kesepahaman/konsensus yang memadai
- Bahkan kelompok arsitek internal Go sendiri belum sepakat soal arahnya
- Sampai ada perubahan situasi atau terbentuk kesepahaman khusus, diputuskan untuk menghentikan upaya perubahan sintaks penanganan error itu sendiri
Argumen utama untuk mempertahankan cara saat ini
- Jika gula sintaksis sudah dimasukkan sejak desain awal bahasa, mungkin tidak akan menimbulkan kontroversi, tetapi sekarang ekosistem sudah terbiasa dengan cara yang dipakai selama 15 tahun
- Jika sintaks baru diperkenalkan, akan ada kekhawatiran tentang jarak gaya penulisan kode antara pengguna lama dan baru serta runtuhnya konsistensi
- Ini juga sejalan dengan filosofi desain Go (tidak melakukan hal yang sama dengan banyak cara) dan prinsip yang menekankan kesederhanaan/konsistensi
- Izin redeklarasi pada deklarasi variabel singkat (
:=) juga merupakan perubahan sampingan yang lahir dari penanganan error
- Sintaks penanganan error yang eksplisit (melalui
if) punya kekuatan intuitif untuk membaca kode, melakukan debugging, dan memasang breakpoint
- Perubahan bahasa juga merupakan beban besar dari sisi cakupan perubahan nyata (kode, dokumentasi, alat, dll.) dan biayanya
Perbaikan alternatif dan arah ke depan
- Penguatan fitur pustaka standar (misalnya penambahan
cmp.Or) dapat mengurangi sebagian kode berulang
- Dengan code folding, autocomplete, pemanfaatan LLM di IDE dan alat pengembangan, verbosity bisa sampai taraf tertentu diatasi dalam praktik
- Di kelompok pengguna Go utama (misalnya peserta acara Google Cloud Next), pandangan yang menolak perlunya perubahan bahasa lebih dominan
- Semakin lama memakai Go, masalah verbosity ini justru makin jarang terasa secara nyata
Argumen yang mendukung perlunya perbaikan sintaks
- Berdasarkan masukan pengguna, tuntutan untuk memperbaiki sintaks penanganan error masih tetap ada
- Sintaks penanganan error yang bukan sekadar mengurangi jumlah karakter, tetapi juga meningkatkan kejelasan mungkin dapat membantu meningkatkan kualitas/keamanan kode
- Diperlukan penelitian yang lebih mendalam terhadap penanganan error yang benar-benar menjalankan peran nyata, bukan sekadar pemeriksaan error sederhana
Kesimpulan akhir dan kebijakan ke depan
- Dengan mengakui bahwa hingga kini belum ada konsensus maupun perubahan nyata, dinyatakan bahwa untuk masa depan yang terlihat, semua diskusi dan usulan perubahan bahasa sintaktis untuk penanganan error akan dihentikan
- Proses diskusi dan penelitian yang ada sejauh ini tetap memberi kontribusi tidak langsung pada perbaikan ekosistem dan proses Go
- Jika di masa depan muncul definisi masalah yang lebih jelas dan konsensus yang lebih kuat, diskusi bisa dibuka kembali
- Untuk sementara, fokus kebijakan adalah mempertahankan ketangguhan dan kesederhanaan Go, bukan mencoba pendekatan baru
1 komentar
Komentar Hacker News
Jika ingin dengan mudah menyarankan bahwa tim Go seharusnya bisa memilih alternatif lain, saya sangat berharap Anda memeriksa tautan wiki Go2ErrorHandlingFeedback atau pencarian issue GitHub. Hampir semua ide yang diajukan sebenarnya sudah pernah dibahas dengan serius, dan sebagai pengguna yang menghargai pendekatan transparan tim Go, saya sangat menikmati memakai Go setiap hari
Dokumen rancangan awal menyebut C++, Rust, dan Swift, tetapi saya sulit menemukan do-notation/for-comprehensions/monadic-let dari bahasa fungsional seperti Haskell/Scala/OCaml yang saya cari. Tim Go tampak seperti master dalam desain bahasa, tetapi pada akhirnya terlihat mentok pada keterbatasan static typing tanpa polimorfisme parametrik seperti Java, sehingga tidak mampu menemukan jawaban untuk masalah error handling. Menurut saya ini berasal dari desain fundamental bahasanya
Meski dokumen itu ditulis oleh orang-orang yang cerdas dan berpengalaman, sangat aneh bahwa solusi seperti monad Maybe/Either milik Haskell dan operator bind (do-notation) tidak disebut sama sekali. Padahal itu sama sekali tidak sesulit atau seakademis yang dibayangkan, dan merupakan cara yang sangat elegan serta terbukti untuk meneruskan error dengan aman. Saya tidak tahu kenapa komunitas Go tidak mengadopsikan ini. Saya bersyukur halaman ini ada, tetapi sulit memahami kenapa solusi seterkenal ini dilewatkan
Hampir semua bahasa menawarkan berbagai pendekatan yang lebih baik, jadi saya penasaran kenapa hanya di Go masalah ini begitu menonjol. Apakah sekadar tidak ada konsensus, atau memang ada karakteristik khusus Go yang membuat solusi dari bahasa lain tidak cocok?
Hal yang sering terlihat dalam kritik terhadap Go adalah kecenderungan orang yang relatif non-ahli berasumsi bahwa para pengembang Go pasti lebih tidak paham bahasa dibanding mereka. Padahal, dalam kebanyakan kasus, pengembang Go justru jauh lebih berpengalaman dan jauh lebih tahu. Kaum non-ahli menganggap bahasa dengan lebih banyak fitur pasti lebih baik, tetapi mengabaikan bahwa yang penting sebenarnya adalah menjaga keseimbangan keseluruhan dengan baik
Saya rasa pengguna diuntungkan oleh sikap konservatif Go yang berhati-hati dalam menambahkan fitur bahasa baru. Dalam kasus Swift, perubahan fitur terlalu banyak sehingga sulit dipelajari, dan bahkan di Mac terbaru pun kadang ada pengalaman tidak bisa membangun satu proyek sederhana sekalipun. Karena keyword terus bertambah dan berubah, Swift kurang berkelanjutan untuk dipakai, sedangkan kekuatan Go ada pada konsistensinya
Pernah ada situasi pengecualian ketika sebuah fungsi Go mengharapkan fungsi internalnya menghasilkan error, dan jika fungsi internal itu justru tidak menghasilkan error, fungsi luarnya malah harus dianggap gagal. Dalam struktur yang tidak umum seperti itu saya harus bercabang dengan
if err == nil, tetapi karena kebiasaan saya menulisif err != nil, saya sempat salah menulisnya dan butuh waktu lama menemukan kesalahannya karena terlalu terbiasa dengan pola yang biasa dipakai. Saya jadi berpikir, andaikan ada dukungan di level bahasa untuk membedakan secara sintaksis antaraif err != nilyang sering dipakai danif err == nilyang jarang dipakai, mungkin kesalahan seperti ini bisa dikurangiif err == nil, saya menambahkan komentar// inverteduntuk menegaskan polanya. Akan bagus kalau bahasa bisa menanganinya secara otomatis, tetapi untuk saat ini cara seperti ini setidaknya bisa membuat perbedaannya lebih jelasif err == nil { return ... }yang sering dipakai malah bisa terlihat lebih canggung di dalam kode. Ada pendapat bahwa cara penanganan error Go saat ini jelas dan mudah dibaca, sehingga banyak orang menyukainyaif fruit != "Apple", jadi secara esensial ini bukan cuma masalah error handling melainkan masalah percabangan kondisi secara umum. Error pada akhirnya juga diperlakukan seperti nilai status lainif err != nilseperti simbol khusus agar menyatu secara alami di latar belakang (kurang menonjol), lalu hanyaif err == nilyang berbeda dibuat lebih menonjolif err … {dalam bentuk yang diringkasSaya menyukai pendekatan penanganan error Go yang eksplisit. Saya cukup memahaminya secara sederhana: fungsi selalu berhasil (minimal error) atau bisa gagal. Fungsi yang berpotensi gagal harus ditangani dulu sebelum bisa lanjut ke tahap berikutnya. Banyak bahasa melempar error lewat exception yang naik melalui stack sampai tertangkap, sehingga sering kali hanya memberi tahu di mana error terjadi tanpa banyak petunjuk yang benar-benar berguna. Di Go, kita bisa dengan jelas memilih opsi berikut: 1) mengabaikan error 2) langsung return saat error terjadi 3) me-wrap error untuk menambahkan informasi berguna 4) menafsirkan error tertentu lalu bercabang menanganinya (misalnya mengubahnya menjadi 404). Di Go2 saya ingin mencoba menambahkan tipe
Result<Value, Failure>atau tipe error yang lebih spesifik dan dapat dienumerasi. Menurut saya lebih tepat memperkenalkannya di Go 2 demi kompatibilitas dengan Go 1Awalnya saya kurang suka cara Go menangani error, tetapi setelah membaca posting blog errors-are-values dan mulai memakai
panic(err)di tempat yang tepat, saya justru jadi sangat puas. Untuk kondisi abnormal yang memang tidak seharusnya ditangani langsung oleh kode induk, memakai panic bisa sangat mengurangi percabangan error yang berantakan di dalam kode. Cara mengelola error seperti ini sangat membantu dalam pekerjaan nyata saya-etry/catch/finallydi C#, itu terasa segar, tetapi sekarang saya justru lebih menyukai logika sederhana seperti Go. Banyaknya baris kode (Loc) menurut saya tetap punya keunggulan karena alur kodenya lebih jelasSaat ada klaim bahwa kalau error benar-benar ditangani maka verbositasnya cepat tertutupi, saya jadi bertanya-tanya apakah membuat manual stack trace benar-benar termasuk ‘menangani’. Kalau mengikuti definisi Go, bukankah exception juga termasuk penanganan? Ada sanggahan bernada jenaka seperti itu
Saya tidak suka tulisan ini membahas masalah error handling Go hanya sebagai “sintaksnya verbose”. Menurut saya masalah yang sebenarnya adalah 1) error mudah terlewat diam-diam atau terabaikan tanpa sengaja 2) hasil fungsi tidak bisa dengan mudah diteruskan atau disimpan seperti nilai biasa 3) error bertingkat seperti
errors.Isterasa janggal dengan sistem tipe 4) sulit melakukan switching pada error 5) sentinel value banyak dipakai di standard library 6) kurang cocok dengan generics sehingga perlu paket tambahan, dan masih ada berbagai masalah lainDi Elixir (dan Erlang), fungsi umumnya mengembalikan tuple
{:ok, result}atau{:error, description}. Berkat sintakswithdi Elixir, penanganan error bisa dikumpulkan di bagian bawah blok sehingga keterbacaannya jauh lebih baik. Jika Go juga memperkenalkan sesuatu yang miripwith, keterbacaan mungkin bisa ditingkatkan, misalnya dengan menjalankan rangkaian instruksi hanya saat error bernilai nil, lalu menaruh blok handler error di bagian paling bawahSaya tidak mengerti kenapa Go tidak langsung mengikuti gaya Rust. Apalagi sekarang sudah ada generics, implementasi yang mirip pun semestinya cepat dibuat. Saya juga tidak setuju dengan logika yang mengkritik operator
?milik Rust sebagai sesuatu yang mendorong pengabaian error. Dalam praktiknya, Go justru sering membiarkan nilai return error diabaikan tanpa error kompilasi. Pencegahan kesalahan baru benar-benar mungkin jika pengembalian tipeResultdipaksa seperti gaya Rust. Kalau ini diperdebatkan atas nama kenyamanan, bukankah seharusnya panic juga dilarang? Itu argumen yang cukup keras?” akan membuat orang “tidak lagi memakai wrapped error”, ada bantahan bahwa justru fitur semacam itu bisa dirancang untuk mendorong wrappingMenurut saya bahasa tidak seharusnya dibahas dengan model mencentang fitur satu per satu seperti Rust, melainkan harus dirancang dalam konsistensi keseluruhan. Hanya karena semua item di daftar fitur sudah dicentang, bukan berarti itu otomatis cocok dengan hakikat bahasa tersebut