Teknik Penanganan Kesalahan Go [ditutup]

108

Saya baru saja mulai menggunakan Go. Kode saya mulai memiliki banyak ini:

   if err != nil {
      //handle err
   }

atau ini

  if err := rows.Scan(&some_column); err != nil {
      //handle err
  }

Apakah ada beberapa idiom / strategi / praktik terbaik untuk memeriksa dan menangani kesalahan di Go?

EDIT untuk mengklarifikasi: Saya tidak bersungut-sungut atau menyarankan agar tim Go membuat sesuatu yang lebih baik. Saya bertanya apakah saya melakukannya dengan benar atau apakah saya melewatkan beberapa teknik yang dibuat komunitas. Terima kasih semuanya.

gmoore
sumber
4
Tidak, sebenarnya tidak. Itu topik yang sering dibicarakan, dan masuk akal. Ada banyak proposal evolusi juga. Jawaban tim tampaknya tidak menjadi masalah dalam kode yang ditulis dengan baik.
Denys Séguret
Perhatikan bahwa pertanyaan terkait ini sebenarnya tidak sama dengan pertanyaan ini. Jawabannya terlalu spesifik.
Denys Séguret
Ada juga alasan untuk gangguan ini: membuat lebih sulit untuk menulis program dengan cepat, tetapi juga membuat lebih sulit untuk membuat bug hanya dengan menampilkan ulang kesalahan.
Denys Séguret
Anda dapat menemukan Andrew Gerrand dan Brad Fitzpatrick menulis permulaan klien HTTP / 2 di Go dengan cara yang kurang lebih mirip youtube.com/watch?v=yG-UaBJXZ80
Supreet Sethi

Jawaban:

61

Kode Anda idiomatis dan menurut saya ini adalah praktik terbaik yang tersedia. Beberapa pasti tidak setuju, tapi saya berpendapat bahwa ini adalah gaya yang terlihat di seluruh perpustakaan standar di Golang . Dengan kata lain, penulis Go menulis penanganan kesalahan dengan cara ini.

zzzz
sumber
12
"Pergi penulis tulis penanganan kesalahan dengan cara ini." Terdengar bagus untukku.
gmoore
"Beberapa pasti tidak setuju" : Saya tidak yakin seseorang akan mengatakan itu bukan praktik terbaik yang tersedia saat ini. Beberapa meminta gula sintaks atau perubahan lain tetapi hari ini saya tidak berpikir ada pembuat kode yang serius akan memeriksa kesalahan jika tidak.
Denys Séguret
@dystroy: Oke, beberapa mengatakan " it sux ", yang lain menyebutnya "kesalahan ditangani dalam nilai yang dikembalikan. Gaya 70-an." , dan seterusnya ;-)
zzzz
2
@jnml Menangani kesalahan dengan cara ini adalah masalah desain bahasa, yang merupakan topik yang sangat kontroversial. Untungnya, ada lusinan bahasa yang dapat dipilih.
fuz
4
Apa yang baru saja membunuh saya adalah bagaimana pola yang sama digunakan untuk setiap panggilan fungsi. Ini membuat kode agak berisik di beberapa tempat dan hanya berteriak meminta gula sintaksis untuk menyederhanakan kode tanpa kehilangan informasi apa pun, yang pada dasarnya adalah definisi keringkasan (yang menurut saya merupakan atribut superior daripada verbositas, tetapi itu bisa dibilang kontroversial. titik). Prinsipnya adalah suara, tetapi sintaksnya meninggalkan banyak IMHO yang diinginkan. Namun mengeluh dilarang, jadi saya akan minum kool-aid saya sekarang ;-)
Thomas
30

Enam bulan setelah pertanyaan ini diajukan, Rob Pike menulis entri blog berjudul Errors are Values .

Di sana dia berpendapat bahwa Anda tidak perlu memprogram dengan cara yang disajikan oleh OP, dan menyebutkan beberapa tempat di perpustakaan standar di mana mereka menggunakan pola yang berbeda.

Tentu saja pernyataan umum yang melibatkan nilai kesalahan adalah untuk menguji apakah nilainya nihil, tetapi ada banyak hal lain yang dapat dilakukan dengan nilai kesalahan, dan penerapan beberapa hal lain itu dapat membuat program Anda lebih baik, menghilangkan banyak boilerplate yang muncul jika setiap kesalahan diperiksa dengan pernyataan hafalan if.

...

Gunakan bahasa untuk menyederhanakan penanganan kesalahan Anda.

Tapi ingat: Apa pun yang Anda lakukan, selalu periksa kesalahan Anda!

Bacaan yang bagus.

Michael Deardeuff
sumber
Terima kasih! Akan memeriksanya.
gmoore
Artikel ini luar biasa, pada dasarnya memperkenalkan sebuah objek yang bisa dalam keadaan gagal, dan jika itu hanya akan mengabaikan semua yang Anda lakukan dengannya dan tetap dalam keadaan gagal. Bagi saya kedengarannya hampir monad.
Waterlink
@ Waterlink Pernyataan Anda tidak ada artinya. Segala sesuatu yang memiliki keadaan hampir monad, jika Anda sedikit menyipitkan mata. Membandingkannya dengan en.wikipedia.org/wiki/Null_Object_pattern lebih berguna, menurut saya.
pengguna7610
@ user7610, Terima kasih atas umpan baliknya. Saya hanya bisa setuju.
Waterlink
2
Pike: "Tapi ingat: Apa pun yang Anda lakukan, selalu periksa kesalahan Anda!" - ini tahun 80-an. Kesalahan dapat terjadi di mana saja, hentikan membebani pemrogram, dan gunakan pengecualian demi Pete.
Slawomir
22

Saya setuju dengan jawaban jnml bahwa keduanya adalah kode idiomatik, dan menambahkan yang berikut:

Contoh pertama Anda:

if err != nil {
      //handle err
}

lebih idiomatis saat berurusan dengan lebih dari satu nilai pengembalian. sebagai contoh:

val, err := someFunc()
if err != nil {
      //handle err
}
//do stuff with val

Contoh kedua Anda adalah singkatan yang bagus ketika hanya berurusan dengan errnilai. Ini berlaku jika fungsi hanya mengembalikan error, atau jika Anda sengaja mengabaikan nilai yang dikembalikan selain error. Sebagai contoh, ini terkadang digunakan dengan fungsi Readerdan Writeryang mengembalikan intjumlah byte tertulis (terkadang informasi yang tidak perlu) dan error:

if _, err := f.Read(file); err != nil {
      //handle err
}
//do stuff with f

Bentuk kedua disebut menggunakan pernyataan inisialisasi if .

Jadi, berkenaan dengan praktik terbaik, sejauh yang saya tahu (kecuali untuk menggunakan "kesalahan" paket untuk membuat kesalahan baru saat Anda membutuhkannya) Anda telah membahas hampir semua yang perlu Anda ketahui tentang kesalahan di Go!

EDIT: Jika Anda merasa benar - benar tidak dapat hidup tanpa pengecualian, Anda dapat menirunya dengan defer, panic&recover .

Intermernet
sumber
4

Saya membuat perpustakaan untuk menangani kesalahan yang efisien dan menyalurkan melalui antrian fungsi Go.

Anda dapat menemukannya di sini: https://github.com/go-on/queue

Ini memiliki varian sintaksis kompak dan verbose. Berikut adalah contoh sintaks pendek:

import "github.com/go-on/queue/q"

func SaveUser(w http.ResponseWriter, rq *http.Request) {
    u := &User{}
    err := q.Q(                      
        ioutil.ReadAll, rq.Body,  // read json (returns json and error)
    )(
        // q.V pipes the json from the previous function call
        json.Unmarshal, q.V, u,   // unmarshal json from above  (returns error)
    )(
        u.Validate,               // validate the user (returns error)
    )(
        u.Save,                   // save the user (returns error)
    )(
        ok, w,                    // send the "ok" message (returns no error)
    ).Run()

    if err != nil {
       switch err {
         case *json.SyntaxError:
           ...
       }
    }
}

Perlu diketahui bahwa ada sedikit beban kinerja, karena ini memanfaatkan refleksi.

Juga ini bukan kode go idiomatik, jadi Anda akan ingin menggunakannya dalam proyek Anda sendiri, atau jika tim Anda setuju untuk menggunakannya.

metakeule
sumber
3
Hanya karena Anda bisa melakukan ini, bukan berarti itu ide yang bagus. Ini terlihat seperti pola Rantai Tanggung Jawab , kecuali mungkin lebih sulit untuk dibaca (opini). Saya akan menyarankan itu bukan "Go idiomatik". Menarik sekali.
Steven Soroka
2

Sebuah "strategi" untuk menangani kesalahan di golang dan dalam bahasa lain adalah terus-menerus menyebarkan kesalahan ke tumpukan panggilan sampai Anda cukup tinggi dalam tumpukan panggilan untuk menangani kesalahan itu. Jika Anda mencoba menangani kesalahan itu terlalu dini, kemungkinan besar Anda akan mengulangi kode. Jika Anda menanganinya terlambat, maka Anda akan merusak sesuatu dalam kode Anda. Golang membuat proses ini sangat mudah karena membuatnya sangat jelas apakah Anda menangani kesalahan di lokasi tertentu atau memperbanyaknya.

Jika Anda akan mengabaikan kesalahan, _ sederhana akan mengungkapkan fakta ini dengan sangat jelas. Jika Anda menanganinya, maka kasus kesalahan mana yang Anda tangani sudah jelas karena Anda akan memeriksanya di pernyataan if.

Seperti kata orang di atas, error sebenarnya hanyalah nilai normal. Ini memperlakukannya seperti itu.

himanshu ojha
sumber
2

Dewa Go telah menerbitkan sebuah "rancangan rancangan" untuk penanganan kesalahan di Go 2. Ini bertujuan untuk mengubah idiom kesalahan:

Gambaran Umum dan Desain

Mereka menginginkan umpan balik dari pengguna!

Umpan balik wiki

Secara singkat, ini terlihat seperti:

func f() error {
   handle err { fmt.Println(err); return err }
   check mayFail()
   check canFail()
}

PEMBARUAN: Desain draf telah menerima banyak kritik, jadi saya menyusun Persyaratan untuk Dipertimbangkan untuk Penanganan Kesalahan Go 2 dengan menu kemungkinan untuk solusi akhirnya.

Liam
sumber
1

Sebagian besar di industri, mengikuti aturan standar yang disebutkan dalam dokumentasi Golang Penanganan Error dan Go . Dan itu juga membantu pembuatan dokumen untuk proyek.

pschilakanti.dll
sumber
Ini pada dasarnya adalah jawaban hanya tautan. Saya akan menyarankan Anda menambahkan beberapa konten ke jawaban sehingga jika link menjadi tidak valid, jawaban Anda akan tetap berguna.
Neo
terima kasih atas komentarnya yang berharga.
pschilakanti
0

Di bawah ini adalah pendapat saya untuk mengurangi penanganan kesalahan pada Go, contoh untuk saat mendapatkan parameter HTTP URL:

(Pola desain berasal dari https://blog.golang.org/errors-are-values )

type HTTPAdapter struct {
    Error *common.AppError
}

func (adapter *HTTPAdapter) ReadUUID(r *http.Request, param string, possibleError int) uuid.UUID {
    requestUUID := uuid.Parse(mux.Vars(r)[param])
    if requestUUID == nil { 
        adapter.Error = common.NewAppError(fmt.Errorf("parameter %v is not valid", param),
            possibleError, http.StatusBadRequest)
    }
    return requestUUID
}

memanggilnya untuk beberapa parameter yang mungkin adalah seperti di bawah ini:

    adapter := &httphelper.HTTPAdapter{}
    viewingID := adapter.ReadUUID(r, "viewingID", common.ErrorWhenReadingViewingID)
    messageID := adapter.ReadUUID(r, "messageID", common.ErrorWhenReadingMessadeID)
    if adapter.Error != nil {
        return nil, adapter.Error
    }

Ini bukan peluru perak, sisi negatifnya adalah jika Anda memiliki banyak kesalahan, Anda hanya bisa mendapatkan kesalahan terakhir.

Tetapi dalam hal ini relatif berulang dan berisiko rendah, oleh karena itu saya hanya bisa mendapatkan kemungkinan kesalahan terakhir.

mel3kings
sumber
-1

Anda bisa membersihkan kode penanganan kesalahan Anda untuk kesalahan serupa (karena kesalahan adalah nilai yang harus Anda perhatikan di sini) dan menulis fungsi yang Anda panggil dengan kesalahan yang diteruskan untuk menangani kesalahan. Anda tidak perlu menulis "if err! = Nil {}" setiap saat. Sekali lagi, ini hanya akan menghasilkan pembersihan kode, tetapi saya tidak berpikir itu adalah cara idiomatik dalam melakukan sesuatu.

Sekali lagi, hanya karena Anda bisa tidak berarti Anda harus melakukannya .

20kLeagues
sumber
-1

goerr memungkinkan untuk menangani kesalahan dengan fungsi

package main

import "github.com/goerr/goerr"
import "fmt"

func ok(err error) {
    if err != nil {
        goerr.Return(err)
        // returns the error from do_somethingN() to main()
        // sequence() is terminated
    }
}

func sequence() error {
    ok(do_something1())
    ok(do_something2())
    ok(do_something3())

    return nil /// 1,2,3 succeeded
}
func do_something1() error { return nil }
func do_something2() error { return fmt.Errorf("2") }
func do_something3() error {
    fmt.Println("DOING 3")
    return nil
}

func main() {
    err_do_something := goerr.OR1(sequence)

    // handle errors

    fmt.Println(err_do_something)
}
Anlhord Smithson
sumber
Ick. Rumit / menyembunyikan kesalahan penanganan logika seperti ini bukan ide yang bagus IMO. Kode yang dihasilkan (yang membutuhkan pra-pemrosesan sumber oleh goerr) lebih sulit dibaca / dipikirkan daripada kode Go idiomatik.
Dave C
-4

Jika Anda ingin kontrol kesalahan yang tepat, ini mungkin bukan solusinya, tetapi bagi saya, sebagian besar waktu, kesalahan apa pun adalah penghenti acara.

Jadi, saya menggunakan fungsi sebagai gantinya.

func Err(err error) {
    if err!=nil {
        fmt.Println("Oops", err)
        os.Exit(1)
    }
}

fi, err := os.Open("mmm.txt")
Err(err)
Gon
sumber
Pesan semacam itu harus ditujukan ke stderrdaripada stdout, jadi gunakan saja log.Fatal(err)atau log.Fatalln("some message:", err). Karena hampir tidak ada selain yang mainharus membuat keputusan seperti itu untuk mengakhiri seluruh program (yaitu mengembalikan kesalahan dari fungsi / metode, jangan batalkan) dalam kasus yang jarang terjadi, inilah yang ingin Anda lakukan itu lebih bersih dan lebih baik untuk melakukannya secara eksplisit (mis. if err := someFunc(); err != nil { log.Fatal(err) }) daripada melalui fungsi "pembantu" yang tidak jelas tentang apa yang dilakukannya (nama "Err" tidak baik, tidak ada indikasi bahwa ia dapat menghentikan program).
Dave C
Mempelajari hal baru! Terima kasih @DaveC
Gon