Pergi yang luar biasa

Konsep desain penanganan kesalahan baru di Go 2 baru-baru ini telah diterbitkan. Sangat menyenangkan bahwa bahasa tidak berdiri di satu tempat - berkembang dan setiap tahun tumbuh lebih cantik.


Hanya sekarang, sementara Go 2 hanya terlihat di cakrawala, sangat menyakitkan dan sedih untuk menunggu. Karena itu, kami menangani masalah kami sendiri. Sedikit pembuatan kode, sedikit kerja dengan ast, dan dengan sedikit gerakan tangan, panik berubah, panik berubah ... menjadi pengecualian elegan!



Dan segera saya ingin membuat pernyataan yang sangat penting dan sangat serius.
Keputusan ini bersifat menghibur dan pedagogis secara eksklusif .
Maksud saya hanya 4 kesenangan. Ini umumnya merupakan bukti konsep, dalam kebenaran. Saya peringatkan :)

Jadi apa yang terjadi?


Hasilnya adalah generator kode perpustakaan kecil seperti itu. Dan pembuat kode, seperti semua orang tahu, membawa kebaikan dan keanggunan di dalam diri mereka. Sebenarnya tidak, tetapi di dunia Go mereka cukup populer.


Kami mengatur generator kode seperti itu di go-raw. Dia mem-parsingnya untuk bantuan modul standar go/ast fungsi, melakukan beberapa tidak transformasi licik, hasilnya ditulis di sebelah file, menambahkan akhiran _jex.go . File yang dihasilkan ingin runtime kecil berfungsi.


Dengan cara sederhana ini, kami menambahkan pengecualian ke Go.


Kami menggunakan


Kami menghubungkan generator ke file, di header (sebelum package ) kami menulis


 //+build jex //go:generate jex 

Jika Anda sekarang menjalankan perintah go generate -tags jex , utilitas jex akan dieksekusi. Dia mengambil nama file dari os.Getenv("GOFILE") , memakannya, mencernanya dan menulis {file}_jex.go . File baru lahir sudah memiliki //+build !jex di header (tag dibalik), jadi go build , dan di kompartemen dengannya, perintah lain, seperti go test atau go install , hanya memperhitungkan file baru yang benar. Lepota ...


Sekarang dot-import github.com/anjensan/jex .
Ya, sementara impor melalui suatu titik adalah wajib. Di masa depan direncanakan untuk pergi sama saja.


 import . "github.com/anjensan/jex" 

Hebat, sekarang Anda dapat memasukkan panggilan ke fungsi rintisan. TRY , THROW , EX dalam kode. Untuk semua ini, kode tersebut tetap valid secara sintaksis, dan bahkan dikompilasi dalam bentuk yang tidak diproses (tidak berfungsi), jadi pelengkapan otomatis tersedia dan linter tidak benar-benar bersumpah. Editor juga akan menunjukkan dokumentasi untuk fungsi-fungsi ini, jika saja mereka punya satu.


Lempar pengecualian


 THROW(errors.New("error name")) 

Tangkap pengecualian


 if TRY() { //   } else { fmt.Println(EX()) } 

Fungsi anonim dihasilkan di bawah tenda. Dan di dalamnya defer . Dan itu memiliki satu fungsi lagi. Dan di dalamnya recover ... Yah, masih ada sedikit ast-magic untuk menangani return dan defer .


Dan ya, omong-omong, mereka didukung!


Selain itu, ada ERR variabel makro khusus. Jika Anda menetapkan kesalahan, pengecualian dilemparkan. Lebih mudah memanggil fungsi yang masih mengembalikan error dengan cara lama


 file, ERR := os.Open(filename) 

Selain itu, ada beberapa tas utilitas kecil ex dan must , tetapi tidak ada banyak yang perlu dibicarakan.


Contohnya


Berikut adalah contoh kode Go yang benar dan idiomatis


 func CopyFile(src, dst string) error { r, err := os.Open(src) if err != nil { return fmt.Errorf("copy %s %s: %v", src, dst, err) } defer r.Close() w, err := os.Create(dst) if err != nil { return fmt.Errorf("copy %s %s: %v", src, dst, err) } if _, err := io.Copy(w, r); err != nil { w.Close() os.Remove(dst) return fmt.Errorf("copy %s %s: %v", src, dst, err) } if err := w.Close(); err != nil { os.Remove(dst) return fmt.Errorf("copy %s %s: %v", src, dst, err) } } 

Kode ini tidak begitu bagus dan elegan. Ngomong-ngomong, ini bukan hanya pendapatku!
Tapi jex akan membantu kita meningkatkannya.


 func CopyFile_(src, dst string) { defer ex.Logf("copy %s %s", src, dst) r, ERR := os.Open(src) defer r.Close() w, ERR := os.Create(dst) if TRY() { ERR := io.Copy(w, r) ERR := w.Close() } else { w.Close() os.Remove(dst) THROW() } } 

Tapi misalnya, program berikut


 func main() { hex, err := ioutil.ReadAll(os.Stdin) if err != nil { log.Fatal(err) } data, err := parseHexdump(string(hex)) if err != nil { log.Fatal(err) } os.Stdout.Write(data) } 

dapat ditulis ulang sebagai


 func main() { if TRY() { hex, ERR := ioutil.ReadAll(os.Stdin) data, ERR := parseHexdump(string(hex)) os.Stdout.Write(data) } else { log.Fatal(EX()) } } 

Berikut adalah contoh lain untuk merasakan ide yang diusulkan lebih baik. Kode asli


 func printSum(a, b string) error { x, err := strconv.Atoi(a) if err != nil { return err } y, err := strconv.Atoi(b) if err != nil { return err } fmt.Println("result:", x + y) return nil } 

dapat ditulis ulang sebagai


 func printSum_(a, b string) { x, ERR := strconv.Atoi(a) y, ERR := strconv.Atoi(b) fmt.Println("result:", x + y) } 

atau bahkan itu


 func printSum_(a, b string) { fmt.Println("result:", must.Int_(strconv.Atoi(a)) + must.Int_(strconv.Atoi(b))) } 

Pengecualian


Intinya adalah struktur pembungkus sederhana di atas contoh error .


 type exception struct { //  ,   err error //  ^W , ,    log []interface{} //      ,    suppress []*exception } 

Poin penting adalah bahwa serangan panik biasa tidak dianggap sebagai pengecualian. Jadi, semua kesalahan standar seperti runtime.TypeAssertionError tidak terkecuali. Ini sejalan dengan praktik terbaik yang diterima di Go - jika kita memiliki, katakanlah, nihil-dereferensi, maka kita membatalkan seluruh proses dengan riang dan riang. Dapat diandalkan dan dapat diprediksi. Meskipun saya tidak yakin, mungkin ada baiknya meninjau momen ini dan menangkap kesalahan seperti itu. Mungkin opsional?


Dan di sini adalah contoh rantai pengecualian


 func one_() { THROW(errors.New("one")) } func two_() { THROW(errors.New("two") } func three() { if TRY() { one_() } else { two_() } } 

Di sini kita dengan tenang menangani satu pengecualian, karena tiba-tiba bam ... dan two pengecualian dilemparkan. Jadi, sumber yang one suppress melampirkannya di bidang suppress . Tidak ada yang akan hilang, semuanya akan masuk ke log. Oleh karena itu, tidak ada kebutuhan khusus untuk mendorong seluruh rantai kesalahan langsung ke teks pesan menggunakan pola fmt.Errorf("blabla: %v", err) sangat populer fmt.Errorf("blabla: %v", err) . Meskipun tidak seorang pun, tentu saja, tidak melarang penggunaannya di sini, jika Anda benar-benar menginginkannya.


Saat lupa menangkap


Ah, poin lain yang sangat penting. Untuk meningkatkan keterbacaan, ada pemeriksaan tambahan: jika suatu fungsi dapat melempar pengecualian, maka namanya harus diakhiri dengan _ . Nama yang sengaja dibengkokkan yang akan memberi tahu programmer, "Tuan, di sini di program Anda ada yang tidak beres, harap berhati-hati dan rajin!"


Pemeriksaan secara otomatis mulai untuk file yang diubah, ditambah itu juga dapat dimulai secara manual dalam proyek menggunakan perintah jex-check . Mungkin masuk akal untuk menjalankannya sebagai bagian dari proses pembangunan bersama dengan linter lainnya.


Pengecekan komentar //jex:nocheck . Omong-omong, ini adalah satu-satunya cara untuk membuang pengecualian dari fungsi anonim.


Tentu saja, ini bukan obat mujarab untuk semua masalah. Pemeriksa akan melewatkan ini


 func bad_() { THROW(errors.New("ups")) } func worse() { f := bad_ f() } 

Di sisi lain, itu tidak jauh lebih buruk daripada pemeriksaan standar untuk err declared and not used , yang sangat mudah untuk dihindari.


 func worse() { a, err := foo() if err != nil { return err } b, err := bar() //  ,    ok... go vet, ? } 

Secara umum, pertanyaan ini agak filosofis, apa yang lebih baik untuk dilakukan ketika Anda lupa memproses kesalahan - diam-diam mengabaikannya, atau membuang kepanikan ... Ngomong-ngomong, hasil terbaik dari tes dapat dicapai dengan menerapkan dukungan pengecualian di kompiler, tetapi ini jauh di luar cakupan artikel ini .


Beberapa orang mungkin mengatakan bahwa, meskipun ini adalah solusi yang luar biasa, itu bukan lagi pengecualian, karena sekarang pengecualian berarti implementasi yang sangat spesifik. Nah, di sana, karena tumpukan jejak tidak melekat pada pengecualian, atau ada linter terpisah untuk memeriksa nama fungsi, atau bahwa fungsi dapat diakhiri dengan _ tetapi tidak membuang pengecualian, atau tidak ada dukungan langsung dalam sintaks, atau bahwa itu benar-benar panik, dan kepanikan bukan pengecualian sama sekali, karena gladiol ... Spora bisa sepanas tidak berharga dan tidak ada gunanya. Oleh karena itu, saya akan meninggalkan mereka di belakang papan artikel, dan saya akan terus memanggil solusi yang dijelaskan secara tidak selektif yang disebut "pengecualian."


Tentang stackraces


Seringkali pengembang, untuk menyederhanakan debugging, menempel jejak stack untuk implementasi error kustom. Bahkan ada beberapa perpustakaan populer untuk ini. Tetapi, untungnya, dengan pengecualian, ini tidak memerlukan tindakan tambahan karena satu fitur menarik dari Go - selama panik, blok defer dieksekusi dalam konteks tumpukan kode yang melemparkan panik. Karena itu di sini


 func foo_() { THROW(errors.New("ups")) } func bar() { if TRY() { foo_() } else { debug.PrintStack() } } 

Jejak tumpukan lengkap akan dicetak, meskipun sedikit verbose (saya memotong nama file)


  runtime/debug.Stack runtime/debug.PrintStack main.bar.func2 github.com/anjensan/jex/runtime.TryCatch.func1 panic main.foo_ main.bar.func1 github.com/anjensan/jex/runtime.TryCatch main.bar main.main 

Tidak ada salahnya membuat helper Anda sendiri untuk memformat / mencetak jejak stack, dengan mempertimbangkan fungsi pengganti, menyembunyikannya agar mudah dibaca. Saya pikir ide yang bagus, tulis di.


Atau Anda dapat mengambil tumpukan dan melampirkannya ke pengecualian menggunakan ex.Log() . Kemudian, pengecualian seperti itu diizinkan untuk ditransfer ke horoutin lain - strextraces tidak hilang.


 func foobar_() { e := make(chan error, 1) go func() { defer close(e) if TRY() { checkZero_() } else { EX().Log(debug.Stack()) //   e <- EX().Wrap() //     } }() ex.Must_(<-e) //  ,  ,  } 

Sayangnya


Eh ... tentu saja, sesuatu seperti itu akan terlihat jauh lebih baik


  try { throw io.EOF, "some comment" } catch e { fmt.Printf("exception: %v", e) } 

Tapi sayangnya, ah, sintaksis Go tidak dapat diperluas.
[serius] Meskipun, mungkin, ini menjadi lebih baik ...


Bagaimanapun, Anda harus memutarbalikkan. Salah satu ide alternatif adalah membuat


  TRY; { THROW(io.EOF, "some comment") }; CATCH; { fmt.Printf("exception: %v", EX) } 

Tapi kode seperti itu terlihat agak bodoh setelah go fmt . Dan kompiler bersumpah ketika melihat return di kedua cabang. Tidak ada masalah dengan if-TRY .


Akan lebih keren untuk mengganti ERR macro dengan fungsi MUST (lebih baik dari sekadar). Untuk menulis


  return MUST(strconv.Atoi(a)) + MUST(strconv.Atoi(b)) 

Pada prinsipnya, ini masih layak, ketika menganalisis Anda, Anda dapat memperoleh jenis ekspresi, karena semua jenis jenis menghasilkan fungsi pembungkus sederhana, seperti yang dinyatakan dalam paket must , dan kemudian ganti MUST dengan nama fungsi pengganti yang sesuai. Ini tidak sepenuhnya sepele, tetapi sepenuhnya mungkin ... Hanya editor / ide yang tidak akan dapat memahami kode tersebut. Bagaimanapun, tanda tangan dari fungsi rintisan MUST tidak dapat diungkapkan dalam sistem tipe Go. Dan karena itu tidak ada pelengkapan otomatis.


Di bawah tenda


Impor baru ditambahkan ke semua file yang diproses.


  import _jex "github.com/anjensan/jex/runtime" 

Panggilan panic(_jex.NewException(...)) diganti dengan panic(_jex.NewException(...)) . EX() juga diganti dengan nama variabel lokal yang berisi pengecualian yang ditangkap.


Tetapi if TRY() {..} else {..} diproses sedikit lebih rumit. Pertama, pemrosesan khusus terjadi untuk semua return dan defer . Kemudian cabang diproses jika ditempatkan dalam fungsi anonim. Dan kemudian fungsi-fungsi ini diteruskan ke _jex.TryCatch(..) . Ini dia


 func test(a int) (int, string) { fmt.Println("before") if TRY() { if a == 0 { THROW(errors.New("a == 0")) } defer fmt.Printf("a = %d\n", a) return a + 1, "ok" } else { fmt.Println("fail") } return 0, "hmm" } 

berubah menjadi sesuatu seperti ini (saya menghapus //line komentar):


 func test(a int) (_jex_r0 int, _jex_r1 string) { var _jex_ret bool fmt.Println("before") var _jex_md2502 _jex.MultiDefer defer _jex_md2502.Run() _jex.TryCatch(func() { if a == 0 { panic(_jex.NewException(errors.New("a == 0"))) } { _f, _p0, _p1 := fmt.Printf, "a = %d\n", a _jex_md2502.Defer(func() { _f(_p0, _p1) }) } _jex_ret, _jex_r0, _jex_r1 = true, a+1, "ok" return }, func(_jex_ex _jex.Exception) { defer _jex.Suppress(_jex_ex) fmt.Println("fail") }) if _jex_ret { return } return 0, "hmm" } 

Banyak, tidak cantik, tetapi berhasil. Oke, tidak semua dan tidak selalu. Misalnya, Anda tidak dapat melakukan defer-recover di dalam TRY, karena panggilan fungsi berubah menjadi lambda tambahan.


Juga, ketika menampilkan pohon ast, opsi "simpan komentar" ditunjukkan. Jadi, secara teori, go/printer harus mencetaknya ... Apa yang dia jujur, kebenarannya sangat, sangat bengkok =) Saya tidak akan memberikan contoh, hanya bengkok. Pada prinsipnya, masalah seperti itu sepenuhnya dapat dipecahkan jika Anda dengan hati-hati menentukan posisi untuk semua ast-node (sekarang mereka kosong), tetapi ini jelas tidak termasuk dalam daftar hal-hal yang diperlukan untuk prototipe.


Coba


Karena penasaran, saya menulis tolok ukur kecil.


Kami memiliki implementasi qsort kayu yang memeriksa duplikat dalam beban. Ditemukan - kesalahan. Satu versi hanya melempar melalui return err , yang lain mengklarifikasi kesalahan dengan memanggil fmt.Errorf . Dan satu lagi menggunakan pengecualian. Kami mengurutkan irisan dengan ukuran yang berbeda, baik tanpa duplikat sama sekali (tidak ada kesalahan, irisan disortir sepenuhnya), atau dengan satu pengulangan (pengurutan terputus sekitar setengah, itu dapat dilihat oleh timing).


Hasil
 ~ > cat /proc/cpuinfo | grep 'model name' | head -1 model name : Intel(R) Core(TM) i7-6700K CPU @ 4.00GHz ~ > go version go version go1.11 linux/amd64 ~ > go test -bench=. github.com/anjensan/jex/demo goos: linux goarch: amd64 pkg: github.com/anjensan/jex/demo BenchmarkNoErrors/_____10/exception-8 10000000 236 ns/op BenchmarkNoErrors/_____10/return_err-8 5000000 255 ns/op BenchmarkNoErrors/_____10/fmt.errorf-8 5000000 287 ns/op BenchmarkNoErrors/____100/exception-8 500000 3119 ns/op BenchmarkNoErrors/____100/return_err-8 500000 3194 ns/op BenchmarkNoErrors/____100/fmt.errorf-8 500000 3533 ns/op BenchmarkNoErrors/___1000/exception-8 30000 42356 ns/op BenchmarkNoErrors/___1000/return_err-8 30000 42204 ns/op BenchmarkNoErrors/___1000/fmt.errorf-8 30000 44465 ns/op BenchmarkNoErrors/__10000/exception-8 3000 525864 ns/op BenchmarkNoErrors/__10000/return_err-8 3000 524781 ns/op BenchmarkNoErrors/__10000/fmt.errorf-8 3000 561256 ns/op BenchmarkNoErrors/_100000/exception-8 200 6309181 ns/op BenchmarkNoErrors/_100000/return_err-8 200 6335135 ns/op BenchmarkNoErrors/_100000/fmt.errorf-8 200 6687197 ns/op BenchmarkNoErrors/1000000/exception-8 20 76274341 ns/op BenchmarkNoErrors/1000000/return_err-8 20 77806506 ns/op BenchmarkNoErrors/1000000/fmt.errorf-8 20 78019041 ns/op BenchmarkOneError/_____10/exception-8 2000000 712 ns/op BenchmarkOneError/_____10/return_err-8 5000000 268 ns/op BenchmarkOneError/_____10/fmt.errorf-8 2000000 799 ns/op BenchmarkOneError/____100/exception-8 500000 2296 ns/op BenchmarkOneError/____100/return_err-8 1000000 1809 ns/op BenchmarkOneError/____100/fmt.errorf-8 500000 3529 ns/op BenchmarkOneError/___1000/exception-8 100000 21168 ns/op BenchmarkOneError/___1000/return_err-8 100000 20747 ns/op BenchmarkOneError/___1000/fmt.errorf-8 50000 24560 ns/op BenchmarkOneError/__10000/exception-8 10000 242077 ns/op BenchmarkOneError/__10000/return_err-8 5000 242376 ns/op BenchmarkOneError/__10000/fmt.errorf-8 5000 251043 ns/op BenchmarkOneError/_100000/exception-8 500 2753692 ns/op BenchmarkOneError/_100000/return_err-8 500 2824116 ns/op BenchmarkOneError/_100000/fmt.errorf-8 500 2845701 ns/op BenchmarkOneError/1000000/exception-8 50 33452819 ns/op BenchmarkOneError/1000000/return_err-8 50 33374000 ns/op BenchmarkOneError/1000000/fmt.errorf-8 50 33705994 ns/op PASS ok github.com/anjensan/jex/demo 64.008s 

Jika kesalahan belum dilemparkan (kode stabil dan diperkuat beton), maka jaminan dengan lemparan pengecualian kira-kira sebanding dengan return err dan fmt.Errorf . Terkadang sedikit lebih cepat. Tetapi jika kesalahan dilemparkan, maka pengecualian berada di posisi kedua. Tetapi itu semua tergantung pada rasio "pekerjaan bermanfaat / kesalahan" dan kedalaman tumpukan. Untuk irisan kecil, return err berjalan di depan celah, untuk irisan menengah dan besar, pengecualian sudah sama dengan penerusan manual.


Singkatnya, jika kesalahan sangat jarang terjadi, pengecualian bahkan dapat mempercepat kode sedikit. Jika seperti orang lain, maka itu akan menjadi sesuatu seperti itu. Tetapi jika sangat sering ... maka pengecualian lambat jauh dari masalah yang paling penting, yang patut dikhawatirkan.


Sebagai ujian, saya memigrasikan perpustakaan gosh nyata untuk pengecualian.


Saya sangat menyesal, tidak berhasil menulis ulang 1-in-1

Lebih tepatnya, itu akan berubah, tetapi ini pasti terganggu.


Jadi, misalnya, fungsi rpc2XML tampaknya mengembalikan error ... ya, itu tidak pernah mengembalikannya. Jika Anda mencoba membuat serialisasi tipe data yang tidak didukung - tidak ada kesalahan, cukup kosongkan output. Mungkin ini yang dimaksud? Tidak, hati nurani tidak membiarkannya seperti itu. Ditambahkan oleh


  default: THROW(fmt.Errorf("unsupported type %T", value)) 

Tetapi ternyata fungsi ini digunakan dengan cara khusus


 func rpcParams2XML(rpc interface{}) (string, error) { var err error buffer := "<params>" for i := 0; i < reflect.ValueOf(rpc).Elem().NumField(); i++ { var xml string buffer += "<param>" xml, err = rpc2XML(reflect.ValueOf(rpc).Elem().Field(i).Interface()) buffer += xml buffer += "</param>" } buffer += "</params>" return buffer, err } 

Di sini kita menjalankan daftar parameter, membuat serial semua, tetapi mengembalikan kesalahan hanya untuk yang terakhir. Kesalahan yang tersisa diabaikan. Perilaku aneh menjadi lebih mudah


 func rpcParams2XML_(rpc interface{}) string { buffer := "<params>" for i := 0; i < reflect.ValueOf(rpc).Elem().NumField(); i++ { buffer += "<param>" buffer += rpc2XML_(reflect.ValueOf(rpc).Elem().Field(i).Interface()) buffer += "</param>" } buffer += "</params>" return buffer } 

Jika setidaknya satu bidang tidak berhasil membuat cerita bersambung - kesalahan. Ya, itu lebih baik. Tetapi ternyata fungsi ini juga digunakan dengan cara khusus .


 xmlstr, _ = rpcResponse2XML(response) 

sekali lagi, untuk kode sumber ini tidak begitu penting, karena ada kesalahan yang diabaikan. Saya mulai menebak mengapa beberapa programmer menyukai penanganan kesalahan secara eksplisit if err != nil ... Tapi dengan pengecualian itu masih lebih mudah untuk diteruskan atau diproses daripada mengabaikan


 xmlstr = rpcResponse2XML_(response) 

Dan saya tidak mulai menghapus "rantai kesalahan." Ini kode aslinya


 func DecodeClientResponse(r io.Reader, reply interface{}) error { rawxml, err := ioutil.ReadAll(r) if err != nil { return FaultSystemError } return xml2RPC(string(rawxml), reply) } 

di sini adalah yang ditulis ulang


 func DecodeClientResponse_(r io.Reader, reply interface{}) { var rawxml []byte if TRY() { rawxml, ERR = ioutil.ReadAll(r) } else { THROW(FaultSystemError) } xml2RPC_(string(rawxml), reply) } 

Di sini kesalahan awal (yang mana ioutil.ReadAll dikembalikan) tidak akan hilang, itu akan dilampirkan ke pengecualian di bidang suppress . Sekali lagi, ini dapat dilakukan seperti pada yang asli, tetapi harus dibuat bingung ...


Saya menulis ulang tes, menggantikan if err != nil { log.Error(..) } dengan lemparan pengecualian sederhana. Ada poin negatif - tes jatuh pada kesalahan pertama, tidak terus bekerja "baik, setidaknya entah bagaimana." Menurut pikiran, akan perlu untuk membaginya menjadi sub-tes ... Apa, secara umum, layak dilakukan dalam hal apapun. Tetapi sangat mudah untuk mendapatkan stackrace yang benar


 func errorReporter(t testing.TB) func(error) { return func(e error) { t.Log(string(debug.Stack())) t.Fatal(e) } } func TestRPC2XMLConverter_(t *testing.T) { defer ex.Catch(errorReporter(t)) // ... xml := rpcRequest2XML_("Some.Method", req) } 

Secara umum, kesalahan sangat mudah untuk diabaikan. Dalam kode asli


 func fault2XML(fault Fault) string { buffer := "<methodResponse><fault>" xml, _ := rpc2XML(fault) buffer += xml buffer += "</fault></methodResponse>" return buffer } 

di sini kesalahan dari rpc2XML sekali lagi diabaikan dengan tenang. Sudah menjadi seperti ini


 func fault2XML(fault Fault) string { buffer := "<methodResponse><fault>" if TRY() { buffer += rpc2XML_(fault) } else { fmt.Printf("ERR: %v", EX()) buffer += "<nil/>" } buffer += "</fault></methodResponse>" return buffer } 

Menurut perasaan pribadi saya, lebih mudah untuk mengembalikan hasil "setengah jadi" dengan kesalahan.
Misalnya, respons setengah dibangun. Pengecualian lebih rumit, karena fungsi mengembalikan hasil yang sukses atau tidak menghasilkan apa-apa sama sekali. Semacam atomicity. Di sisi lain, pengecualian lebih sulit untuk diabaikan atau kehilangan akar penyebab dalam rantai pengecualian. Bagaimanapun, Anda masih harus secara khusus mencoba melakukan ini. Dengan kesalahan, ini terjadi dengan mudah dan alami.


Alih-alih sebuah kesimpulan


Saat menulis artikel ini, tidak ada gopher yang terluka.


Terima kasih atas foto http://migranov.ru dari goffer-alcoholic


Saya tidak dapat memilih antara hub "Pemrograman" dan "Pemrograman abnormal".
Pilihan yang sangat sulit, ditambahkan ke keduanya.

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


All Articles