Go2 bertujuan untuk mengurangi overhead penanganan kesalahan, tetapi apakah Anda tahu apa yang lebih baik daripada sintaksis yang ditingkatkan untuk penanganan kesalahan?
Tidak perlu menangani kesalahan sama sekali. Saya tidak mengatakan "hapus kode penanganan kesalahan Anda", sebagai gantinya saya menyarankan mengubah kode Anda sehingga Anda tidak memiliki banyak kesalahan untuk ditangani.
Artikel ini mengambil inspirasi dari bab "Define Errors Out of Existence" "dari buku"
A filosofi Desain Perangkat Lunak "oleh John Ousterhout. Saya akan mencoba menerapkan sarannya untuk Go.
Contoh pertama
Berikut adalah fungsi untuk menghitung jumlah baris dalam file:
func CountLines(r io.Reader) (int, error) { var ( br = bufio.NewReader(r) lines int err error ) for { _, err = br.ReadString('\n') lines++ if err != nil { break } } if err != io.EOF { return 0, err } return lines, nil }
Kami membuat bufio.Reader, lalu duduk dalam satu lingkaran, memanggil metode ReadString, menambah penghitung hingga kami mencapai akhir file, dan kemudian mengembalikan jumlah baris yang dibaca. Ini adalah kode yang ingin kami tulis, alih-alih CountLines dipersulit oleh penanganan kesalahan.
Misalnya, ada konstruksi yang aneh:
_, err = br.ReadString('\n') lines++ if err != nil { break }
Kami menambah jumlah baris sebelum memeriksa kesalahan - ini terlihat aneh. Alasan kita harus menuliskannya dengan cara ini adalah karena ReadString akan mengembalikan kesalahan jika menemui akhir file - io.EOF - sebelum menekan karakter baris baru. Ini juga dapat terjadi jika tidak ada baris baru.
Untuk mengatasi masalah ini, kami akan mengatur ulang logika untuk meningkatkan jumlah baris, dan kemudian melihat apakah kami perlu keluar dari loop (logika ini masih tidak benar, dapatkah Anda menemukan kesalahan?).
Namun kami belum selesai memeriksa kesalahan. ReadString akan mengembalikan io.EOF ketika sudah mencapai akhir file. Ini diharapkan, ReadString perlu beberapa cara untuk mengatakan berhenti, tidak ada lagi yang bisa dibaca. Oleh karena itu, sebelum mengembalikan kesalahan ke pemanggil CountLine, kita perlu memeriksa apakah kesalahan io.EOF tidak ditemukan, dan dalam kasus ini mengembalikannya ke pemanggil, jika tidak kita akan mengembalikan nol ketika semuanya baik-baik saja. Inilah sebabnya mengapa fungsi baris terakhir tidak mudah
return lines, err
Saya pikir ini adalah contoh yang baik dari pengamatan Russ Cox bahwa
penanganan kesalahan dapat membuat fungsi lebih sulit . Mari kita lihat versi yang ditingkatkan.
func CountLines(r io.Reader) (int, error) { sc := bufio.NewScanner(r) lines := 0 for sc.Scan() { lines++ } return lines, sc.Err() }
Versi ini meningkatkan transisi dari menggunakan bufio.Reader ke bufio.Scanner. Di bawah tenda, bufio.Scanner menggunakan bufio.Reader, menambahkan lapisan abstraksi yang membantu menghapus penanganan kesalahan, yang menghambat pekerjaan CountLines versi kami sebelumnya (bufio.Scanner dapat memindai template apa pun, secara default ia mencari baris baru).
Metode sc.Scan () mengembalikan true jika pemindai menemukan satu baris teks dan tidak menemukan kesalahan. Dengan demikian, isi loop for kami akan dipanggil hanya ketika ada baris teks di buffer pemindai. Ini berarti bahwa kami me-reset CountLines kami dengan benar menangani case ketika tidak ada karakter baris baru. Juga sekarang kasus ketika file kosong ditangani dengan benar.
Kedua, karena sc.Scan mengembalikan false ketika kesalahan terjadi, loop for kami akan berakhir ketika akhir file tercapai atau kesalahan terjadi. Ketik bufio.Scanner mengingat kesalahan terdeteksi kesalahan pertama, dan kami memperbaiki kesalahan ini setelah keluar dari loop menggunakan metode sc.Err ().
Akhirnya, buffo.Scanner mengurus pemrosesan io.EOF dan mengubahnya menjadi nol jika akhir file tercapai tanpa kesalahan.
Contoh kedua
Contoh kedua saya terinspirasi oleh
Kesalahan Rob Pikes
adalah nilai-nilai posting blog.
Saat bekerja dengan membuka, menulis, dan menutup file, penanganan kesalahan adalah, tetapi tidak terlalu mengesankan, karena operasi dapat disimpulkan pada pembantu seperti ioutil.ReadFile dan ioutil.WriteFile. Namun, ketika bekerja dengan protokol jaringan tingkat rendah, seringkali perlu untuk membangun respons secara langsung menggunakan primitif I / O, sehingga penanganan kesalahan dapat mulai diulang. Pertimbangkan fragmen server HTTP ini yang membuat respons HTTP / 1.1:
type Header struct { Key, Value string } type Status struct { Code int Reason string } func WriteResponse(w io.Writer, st Status, headers []Header, body io.Reader) error { _, err := fmt.Fprintf(w, "HTTP/1.1 %d %s\r\n", st.Code, st.Reason) if err != nil { return err } for _, h := range headers { _, err := fmt.Fprintf(w, "%s: %s\r\n", h.Key, h.Value) if err != nil { return err } } if _, err := fmt.Fprint(w, "\r\n"); err != nil { return err } _, err = io.Copy(w, body) return err }
Pertama kita membuat bilah status menggunakan fmt.Fprintf dan memeriksa kesalahan. Kemudian, untuk setiap tajuk, kami mencatat kunci dan nilai tajuk, setiap kali memeriksa kesalahan. Terakhir, kita akhiri bagian tajuk dengan \ r \ n tambahan, periksa kesalahan, dan salin badan respons ke klien. Akhirnya, meskipun kita tidak perlu memeriksa kesalahan dari io.Copy, kita perlu mengonversinya dari formulir dengan dua nilai kembali, yang io.Copy kembali ke nilai pengembalian tunggal yang diharapkan oleh WriteResponse.
Ini tidak hanya banyak pekerjaan yang berulang, setiap operasi, yang pada dasarnya menulis byte ke io.Writer, memiliki bentuk penanganan kesalahan yang berbeda. Tapi kita bisa membuat tugas kita lebih mudah dengan memperkenalkan pembungkus kecil.
type errWriter struct { io.Writer err error } func (e *errWriter) Write(buf []byte) (int, error) { if e.err != nil { return 0, e.err } var n int n, e.err = e.Writer.Write(buf) return n, nil }
errWriter memenuhi kontrak io.Writer, sehingga dapat digunakan untuk memigrasikan io.Writer yang ada. errWriter mentransfer rekaman ke perekam yang mendasarinya hingga kesalahan terdeteksi. Mulai sekarang, ia membuang semua entri dan mengembalikan kesalahan sebelumnya.
func WriteResponse(w io.Writer, st Status, headers []Header, body io.Reader) error { ew := &errWriter{Writer: w} fmt.Fprintf(ew, "HTTP/1.1 %d %s\r\n", st.Code, st.Reason) for _, h := range headers { fmt.Fprintf(ew, "%s: %s\r\n", h.Key, h.Value) } fmt.Fprint(ew, "\r\n") io.Copy(ew, body) return ew.err }
Menerapkan errWriter ke WriteResponse sangat meningkatkan kejelasan kode. Masing-masing operasi tidak lagi perlu membatasi diri untuk pengecekan kesalahan. Pesan kesalahan bergerak ke akhir fungsi, memeriksa bidang ew.err dan menghindari terjemahan yang mengganggu dari nilai io.Copy yang dikembalikan
Kesimpulan
Saat Anda mengalami penanganan kesalahan yang berlebihan, cobalah mengekstraksi beberapa operasi sebagai jenis pembungkus tambahan.
Tentang penulis
Penulis artikel ini,
Dave Cheney , adalah penulis banyak paket populer untuk Go, misalnya
github.com/pkg/errors dan
github.com/davecheney/httpstat .