Penanganan Kesalahan saat Go

Halo, warga Habrovsk! Kursus Pengembang Golang sudah dimulai di OTUS hari ini , dan kami menganggap ini kesempatan yang bagus untuk berbagi pos lain yang bermanfaat tentang topik tersebut. Hari ini mari kita bicara tentang pendekatan Go untuk kesalahan. Ayo mulai!



Menguasai penanganan kesalahan pragmatis dalam kode Go Anda




Posting ini adalah bagian dari seri Sebelum Memulai, di mana kami menjelajahi dunia Golang, berbagi kiat dan ide yang harus Anda ketahui saat menulis kode di Go sehingga Anda tidak perlu mengisi benjolan Anda sendiri.

Saya berasumsi bahwa Anda sudah memiliki setidaknya pengalaman dasar dengan Go, tetapi jika Anda merasa bahwa pada suatu saat Anda menemukan bahan diskusi yang tidak dikenal, jangan ragu untuk berhenti sebentar, jelajahi topiknya dan kembali.

Sekarang setelah kita membersihkan jalan kita, ayo pergi!

Pendekatan Go untuk penanganan kesalahan adalah salah satu fitur yang paling kontroversial dan disalahgunakan. Dalam artikel ini, Anda akan mempelajari pendekatan Go untuk kesalahan, dan memahami cara kerjanya "di bawah tenda." Anda akan mempelajari beberapa pendekatan berbeda, lihat kode sumber Go dan perpustakaan standar untuk mengetahui bagaimana kesalahan ditangani dan bagaimana cara mengatasinya. Anda akan belajar mengapa Ketegasan Jenis memainkan peran penting dalam menanganinya, dan Anda akan melihat perubahan mendatang untuk penanganan kesalahan yang Anda rencanakan untuk diperkenalkan di Go 2.



Entri


Hal pertama yang pertama: kesalahan di Go tidak terkecuali. Dave Cheney menulis posting blog epik tentang ini, jadi saya merujuk Anda untuk itu dan meringkas: dalam bahasa lain Anda tidak dapat memastikan apakah suatu fungsi dapat melempar pengecualian atau tidak. Alih-alih melempar pengecualian, fungsi Go mendukung beberapa nilai pengembalian , dan dengan konvensi fitur ini biasanya digunakan untuk mengembalikan hasil fungsi bersama dengan variabel kesalahan.



Jika karena alasan tertentu fungsi Anda mungkin gagal, Anda mungkin harus mengembalikan jenis error dinyatakan sebelumnya dari itu. Dengan konvensi, mengembalikan kesalahan memberi sinyal kepada penelepon tentang masalah tersebut, dan mengembalikan nol tidak dianggap sebagai kesalahan. Dengan demikian, Anda akan membuat penelepon memahami bahwa masalah telah muncul, dan dia perlu mengatasinya: siapa pun yang memanggil fungsi Anda, ia tahu bahwa ia tidak boleh mengandalkan hasil sebelum memeriksa kesalahan. Jika kesalahan tidak nihil, ia berkewajiban untuk memeriksa dan memprosesnya (mencatat, mengembalikan, memelihara, memanggil beberapa jenis coba lagi / mekanisme pembersihan, dll.).


(3 // penanganan kesalahan
5 // lanjutan)

Cuplikan ini sangat umum di Go, dan beberapa menganggapnya sebagai kode boilerplate. Kompiler memperlakukan variabel yang tidak digunakan sebagai kesalahan kompilasi, jadi jika Anda tidak akan memeriksa kesalahan, Anda harus menetapkannya ke pengidentifikasi kosong . Tapi betapapun nyamannya, kesalahan tidak boleh diabaikan.


(4 // mengabaikan kesalahan tidak aman, dan Anda tidak boleh mengandalkan hasilnya sebelum memeriksa kesalahan)
hasilnya tidak dapat dipercaya sampai memeriksa kesalahan

Kesalahan kembali bersama dengan hasilnya, bersama dengan sistem tipe Go ketat, sangat menyulitkan penulisan kode tag. Anda harus selalu berasumsi bahwa nilai fungsi rusak, kecuali jika Anda telah memeriksa kesalahan yang dikembalikan, dan dengan menetapkan kesalahan ke pengidentifikasi kosong, Anda secara eksplisit mengabaikan bahwa nilai fungsi Anda mungkin rusak.


ID kosong gelap dan penuh kengerian.

Go memang memiliki mekanisme panic dan recover , yang juga dijelaskan dalam posting blog Go yang detail lainnya . Tetapi mereka tidak dimaksudkan untuk mensimulasikan pengecualian. Menurut Dave, "Ketika kamu panik di Go, kamu benar-benar panik: ini bukan masalah orang lain, ini sudah seorang gamer." Mereka berakibat fatal dan menyebabkan crash pada program Anda. Rob Pike datang dengan mengatakan "Jangan panik," yang berbicara sendiri: Anda mungkin harus menghindari mekanisme ini dan mengembalikan kesalahan sebagai gantinya.

"Kesalahan adalah artinya."
"Jangan hanya memeriksa kesalahan, tapi tangani dengan elegan."
"Jangan panik"
semua ucapan Rob Pike

Di bawah tenda


Antarmuka galat

Di bawah tenda, jenis kesalahan adalah antarmuka sederhana dengan satu metode , dan jika Anda tidak terbiasa dengan itu, saya sangat merekomendasikan melihat posting ini di blog Go resmi.


antarmuka kesalahan dari sumber

Membuat kesalahan sendiri tidak sulit. Ada berbagai pendekatan untuk struktur pengguna yang menerapkan metode string Error() . Setiap struktur yang mengimplementasikan metode tunggal ini dianggap sebagai nilai kesalahan yang valid dan dapat dikembalikan seperti itu.

Mari kita lihat beberapa pendekatan ini.

Struktur errorString bawaan


Implementasi antarmuka kesalahan yang paling umum digunakan dan tersebar luas adalah struktur errorString . Ini adalah implementasi termudah yang dapat Anda pikirkan.


Sumber: Go source code

Anda dapat melihat implementasinya yang disederhanakan di sini . Yang dilakukannya hanyalah berisi string , dan string ini dikembalikan oleh metode Error . Kesalahan string ini dapat diformat oleh kami berdasarkan beberapa data, katakanlah, menggunakan fmt.Sprintf . Tapi selain itu, tidak mengandung fitur lain. Jika Anda menerapkan kesalahan. Baru atau fmt.Errorf , maka Anda sudah menggunakannya .


(13 // output :)

coba

github.com/pkg/errors


Contoh sederhana lainnya adalah paket pkg / kesalahan . Tidak perlu bingung dengan paket errors bawaan yang Anda pelajari sebelumnya, paket ini menyediakan fitur-fitur penting tambahan, seperti pembungkus kesalahan, ekspansi, pemformatan, dan perekaman jejak jejak. Anda dapat menginstal paket dengan menjalankan go get github.com/pkg/errors .



Dalam kasus di mana Anda perlu melampirkan jejak tumpukan atau informasi debug yang diperlukan untuk kesalahan Anda, menggunakan fungsi New atau Errorf paket ini memberikan kesalahan yang sudah ditulis ke jejak tumpukan Anda, dan Anda juga dapat melampirkan metadata sederhana menggunakannya kemampuan memformat. Errorf mengimplementasikan antarmuka fmt.Formatter , mis. Anda dapat memformatnya menggunakan rune dari paket fmt ( %s , %v , %+v , dll.).


(// 6 atau alternatif)

Paket ini juga memperkenalkan fungsi errors.Wrap dan errors.Wrapf . Fungsi-fungsi ini menambah konteks kesalahan menggunakan pesan dan jejak tumpukan di tempat mereka dipanggil. Jadi, alih-alih mengembalikan kesalahan, Anda bisa membungkusnya dengan konteks dan data debug penting.



Pembungkus kesalahan oleh kesalahan lain mendukung metode Cause() error , yang mengembalikan kesalahan internal mereka. Selain itu, mereka dapat digunakan dengan fungsi errors.Cause(err error) error , yang mengekstrak kesalahan internal utama dalam kesalahan pembungkus.

Menangani kesalahan


Ketikkan persetujuan


Ketik asersi memainkan peran penting ketika berhadapan dengan kesalahan. Anda akan menggunakannya untuk mengekstraksi informasi dari nilai antarmuka, dan karena penanganan kesalahan dikaitkan dengan implementasi pengguna dari antarmuka error , penerapan pernyataan error adalah alat yang sangat nyaman.

Sintaksnya sama untuk semua keperluannya - x.(T) jika x memiliki tipe antarmuka. x.(T) menyatakan bahwa x tidak nil dan bahwa nilai yang disimpan dalam x adalah tipe T Dalam beberapa bagian berikutnya, kita akan melihat dua cara untuk menggunakan pernyataan tipe - dengan tipe T tertentu dan dengan antarmuka tipe T


(2 // steno sintaks melewatkan variabel boolean ok
3 // panik: konversi antarmuka: antarmuka {} adalah nihil, bukan string
6 // sintaks yang diperluas dengan boolean ok
8 // tidak panik, sebaliknya set ok false ketika pernyataan itu salah
9 // sekarang kita bisa menggunakan s sebagai string dengan aman)

sandbox: panik dengan sintaks yang diperpendek , sintaks yang diperluas aman

Catatan sintaks tambahan: pernyataan tipe dapat digunakan baik dengan sintaks singkat (yang panik ketika pernyataan gagal) atau sintaks diperpanjang (yang menggunakan nilai logis OK untuk menunjukkan keberhasilan atau kegagalan). Saya selalu merekomendasikan mengambil memanjang bukannya diperpendek, karena saya lebih suka memeriksa variabel OK, dan tidak berurusan dengan panik.


Persetujuan Tipe T


Pernyataan tipe x.(T) dengan antarmuka tipe T mengonfirmasi bahwa x mengimplementasikan antarmuka T Dengan demikian, Anda dapat menjamin bahwa nilai antarmuka mengimplementasikan antarmuka, dan hanya jika demikian, Anda dapat menggunakan metode-metodenya.


(5 ... // klaim bahwa x mengimplementasikan antarmuka resolver
6 ... // di sini kita sudah bisa menggunakan metode ini dengan aman)

Untuk memahami bagaimana ini dapat digunakan, mari kita lihat lagi pkg/errors . Anda sudah mengetahui paket kesalahan ini, jadi mari kita errors.Cause(err error) error fungsi errors.Cause(err error) error . errors.Cause(err error) error .

Fungsi ini menerima kesalahan dan mengekstrak kesalahan paling dalam yang dideritanya (salah satu yang tidak lagi berfungsi sebagai pembungkus untuk kesalahan lain). Ini mungkin terlihat primitif, tetapi ada banyak hal hebat yang dapat Anda pelajari dari implementasi ini:


sumber: pkg / kesalahan

Fungsi menerima nilai kesalahan, dan tidak dapat mengasumsikan bahwa argumen err yang diterimanya adalah kesalahan pembungkus (didukung oleh metode Cause ). Karena itu, sebelum memanggil metode Cause , Anda harus memastikan bahwa Anda berurusan dengan kesalahan yang mengimplementasikan metode ini. Dengan melakukan pernyataan tipe di setiap iterasi dari for loop, Anda dapat memastikan bahwa variabel cause mendukung metode Cause , dan dapat terus mengekstrak kesalahan internal hingga Anda menemukan kesalahan yang tidak memiliki Cause .

Dengan membuat antarmuka lokal sederhana yang hanya berisi metode-metode yang Anda butuhkan, dan menerapkan pernyataan tegas, kode Anda dipisahkan dari dependensi lainnya. Argumen yang Anda terima tidak harus menjadi struktur yang diketahui, itu hanya harus menjadi kesalahan. Jenis apa pun yang mengimplementasikan metode Error dan Cause akan dilakukan. Dengan demikian, jika Anda menerapkan metode Cause dalam jenis kesalahan Anda, Anda dapat menggunakan fungsi ini tanpa memperlambat.

Namun, ada satu kelemahan kecil yang perlu diingat: antarmuka dapat berubah, jadi Anda harus hati-hati menjaga kode sehingga pernyataan Anda tidak dilanggar. Jangan lupa untuk mendefinisikan antarmuka Anda di mana Anda menggunakannya, untuk membuatnya tetap ramping dan rapi, dan Anda akan baik-baik saja.

Akhirnya, jika Anda hanya membutuhkan satu metode, terkadang lebih mudah untuk membuat pernyataan pada antarmuka anonim yang hanya berisi metode yang Anda andalkan, yaitu v, ok := x.(interface{ F() (int, error) }) . Menggunakan antarmuka anonim dapat membantu memisahkan kode Anda dari kemungkinan dependensi dan melindunginya dari kemungkinan perubahan pada antarmuka.

Tipe T dan Persetujuan Tipe Switch



Saya mengawali bagian ini dengan memperkenalkan dua pola penanganan kesalahan serupa yang menderita beberapa kekurangan dan perangkap. Ini tidak berarti bahwa mereka tidak umum. Keduanya dapat menjadi alat yang mudah digunakan dalam proyek-proyek kecil, tetapi mereka tidak skala dengan baik.

Yang pertama adalah versi kedua dari pernyataan tipe: pernyataan tipe x.(T) dengan tipe T tertentu dilakukan. Dia mengklaim bahwa nilai x adalah tipe T , atau dapat dikonversi ke tipe T


(2 // kita bisa menggunakan v sebagai mypkg.SomeErrorType)

Lainnya adalah pola Type Switch . Tipe Switch menggabungkan pernyataan switch dengan pernyataan tipe menggunakan kata kunci type dipesan. Mereka sangat umum dalam penanganan kesalahan, di mana mengetahui tipe dasar dari kesalahan variabel bisa sangat berguna.


(3 // memproses ...
5 // memproses ...)

Kelemahan besar dari kedua pendekatan ini adalah keduanya mengarah pada pengikatan kode dengan dependensi mereka. Kedua contoh harus terbiasa dengan struktur SomeErrorType (yang jelas harus diekspor) dan harus mengimpor paket mypkg .
Dalam kedua pendekatan, saat menangani kesalahan Anda, Anda harus terbiasa dengan jenis dan mengimpor paketnya. Situasi bertambah buruk ketika Anda berurusan dengan kesalahan dalam pembungkus, di mana penyebab kesalahan mungkin kesalahan yang timbul dari ketergantungan internal yang Anda tidak tahu dan tidak perlu tahu.


(7 // memproses ...
9 // memproses ...)

Jenis Switch membedakan antara *MyStruct dan MyStruct . Oleh karena itu, jika Anda tidak yakin apakah Anda berurusan dengan pointer atau contoh aktual dari suatu struktur, Anda harus memberikan kedua opsi. Selain itu, seperti dalam kasus switch biasa, kasus-kasus di Type Switch tidak gagal, tetapi tidak seperti Switch Type biasa, penggunaan fallthrough dilarang di Type Switch, jadi Anda harus menggunakan koma dan memberikan kedua opsi, yang mudah dilupakan.



Untuk meringkas


Itu saja! Anda sekarang terbiasa dengan kesalahan dan harus siap untuk memperbaiki kesalahan yang aplikasi Go Anda dapat melempar (atau benar-benar kembali) ke jalan Anda!
Kedua paket errors menyediakan pendekatan sederhana namun penting untuk kesalahan di Go, dan jika mereka memenuhi kebutuhan Anda, mereka adalah pilihan yang bagus. Anda dapat dengan mudah mengimplementasikan struktur kesalahan Anda sendiri dan memanfaatkan penanganan kesalahan Go dengan menggabungkannya dengan pkg/errors .

Saat Anda skala kesalahan sederhana, penggunaan yang benar dari pernyataan tipe bisa menjadi alat yang hebat untuk menangani berbagai kesalahan. Baik menggunakan Type Switch, atau dengan memvalidasi perilaku kesalahan dan memeriksa antarmuka yang diterapkannya.

Apa selanjutnya


Penanganan kesalahan di Go sekarang sangat relevan. Sekarang setelah Anda memiliki dasar-dasarnya, Anda mungkin bertanya-tanya apa yang ada di depan bagi kami untuk menangani kesalahan Go!

Versi Go 2 berikutnya sangat memperhatikan hal ini, dan Anda sudah dapat melihat versi draft . Selain itu, selama dotGo 2019, Marcel van Lojuizen melakukan percakapan luar biasa tentang topik yang saya tidak bisa merekomendasikan - "Nilai kesalahan GO 2 hari ini" .

Jelas, ada banyak lagi pendekatan, tips dan trik, dan saya tidak bisa memasukkan semuanya dalam satu posting! Meskipun demikian, saya harap Anda menikmatinya, dan saya akan melihat Anda di episode Sebelum Memulai !

Dan sekarang secara tradisional menunggu komentar Anda.

Source: https://habr.com/ru/post/id453806/


All Articles