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 kesalahanKesalahan 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 galatDi 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 sumberMembuat 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 codeAnda 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 :)cobagithub.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 amanCatatan 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 / kesalahanFungsi 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.