Go: Proposal: Fungsi pemeriksaan kesalahan Go bawaan, "coba"

Dibuat pada 5 Jun 2019  ·  808Komentar  ·  Sumber: golang/go

Proposal: Fungsi pemeriksaan kesalahan Go bawaan, try

Proposal ini telah ditutup .

Sebelum berkomentar, harap baca dokumen desain terperinci dan lihat ringkasan diskusi pada 6 Juni , ringkasan pada 10 Juni , dan _yang terpenting saran untuk tetap fokus_. Pertanyaan atau saran Anda mungkin sudah dijawab atau dibuat. Terima kasih.

Kami mengusulkan fungsi bawaan baru yang disebut try , yang dirancang khusus untuk menghilangkan pernyataan boilerplate if yang biasanya terkait dengan penanganan kesalahan di Go. Tidak ada perubahan bahasa lain yang disarankan. Kami menganjurkan menggunakan pernyataan defer yang ada dan fungsi perpustakaan standar untuk membantu menambah atau membungkus kesalahan. Pendekatan minimal ini membahas skenario paling umum sambil menambahkan sedikit kerumitan pada bahasa. try built-in mudah dijelaskan, mudah diterapkan, ortogonal dengan konstruksi bahasa lain, dan sepenuhnya kompatibel ke belakang. Ini juga membuka jalan untuk memperluas mekanisme, jika kita ingin melakukannya di masa depan.

[Teks di bawah ini telah diedit untuk mencerminkan dokumen desain dengan lebih akurat.]

Fungsi try mengambil satu ekspresi sebagai argumen. Ekspresi harus mengevaluasi ke n+1 nilai (di mana n mungkin nol) di mana nilai terakhir harus bertipe error . Ini mengembalikan nilai n pertama (jika ada) jika argumen kesalahan (akhir) adalah nihil, jika tidak, ia kembali dari fungsi terlampir dengan kesalahan itu. Misalnya, kode seperti

f, err := os.Open(filename)
if err != nil {
    return …, err  // zero values for other results, if any
}

dapat disederhanakan menjadi

f := try(os.Open(filename))

try hanya dapat digunakan dalam fungsi yang mengembalikan hasil error , dan hasil itu harus menjadi parameter hasil terakhir dari fungsi terlampir.

Proposal ini mengurangi draf desain asli yang dipresentasikan di GopherCon tahun lalu menjadi esensinya. Jika augmentasi atau pembungkusan kesalahan diinginkan, ada dua pendekatan: Tetap dengan pernyataan if yang telah dicoba dan benar, atau, sebagai alternatif, "deklarasikan" penangan kesalahan dengan pernyataan defer :

defer func() {
    if err != nil { // no error may have occurred - check for it
        err = … // wrap/augment error
    }
}()

Di sini, err adalah nama hasil kesalahan dari fungsi terlampir. Dalam praktiknya, fungsi helper yang sesuai akan mengurangi deklarasi error handler menjadi one-liner. Contohnya

defer fmt.HandleErrorf(&err, "copy %s %s", src, dst)

(di mana fmt.HandleErrorf menghiasi *err ) terbaca dengan baik dan dapat diimplementasikan tanpa memerlukan fitur bahasa baru.

Kelemahan utama dari pendekatan ini adalah bahwa parameter hasil kesalahan perlu diberi nama, mungkin mengarah ke API yang kurang cantik. Pada akhirnya ini adalah masalah gaya, dan kami yakin kami akan beradaptasi dengan mengharapkan gaya baru, sama seperti kami beradaptasi dengan tidak memiliki titik koma.

Singkatnya, try mungkin tampak tidak biasa pada awalnya, tetapi ini hanyalah gula sintaksis yang dibuat khusus untuk satu tugas tertentu, penanganan kesalahan dengan lebih sedikit boilerplate, dan untuk menangani tugas itu dengan cukup baik. Karena itu sangat cocok dengan filosofi Go. try tidak dirancang untuk mengatasi _semua_ situasi penanganan kesalahan; ini dirancang untuk menangani kasus _paling umum_ dengan baik, untuk menjaga desain tetap sederhana dan jelas.

kredit

Usulan ini sangat dipengaruhi oleh masukan yang kami terima selama ini. Secara khusus, ia meminjam ide dari:

Dokumen desain terperinci

https://github.com/golang/proposal/blob/master/design/32437-try-builtin.md

tryhard alat untuk menjelajahi dampak try

https://github.com/griesemer/tryhard

Go2 LanguageChange Proposal error-handling

Komentar yang paling membantu

Halo semuanya,

Tujuan kami dengan proposal seperti ini adalah untuk mengadakan diskusi di seluruh komunitas tentang implikasi, pengorbanan, dan bagaimana melanjutkan, dan kemudian menggunakan diskusi itu untuk membantu memutuskan jalan ke depan.

Berdasarkan tanggapan masyarakat yang luar biasa dan diskusi ekstensif di sini, kami menandai proposal ini ditolak lebih cepat dari jadwal .

Sejauh umpan balik teknis, diskusi ini telah membantu mengidentifikasi beberapa pertimbangan penting yang kami lewatkan, terutama implikasi untuk menambahkan cetakan debug dan menganalisis cakupan kode.

Lebih penting lagi, kami telah mendengar dengan jelas banyak orang yang berpendapat bahwa proposal ini tidak menargetkan masalah yang berharga. Kami masih percaya bahwa penanganan kesalahan di Go tidak sempurna dan dapat ditingkatkan secara berarti, tetapi jelas bahwa kami sebagai komunitas perlu berbicara lebih banyak tentang aspek spesifik apa dari penanganan kesalahan yang menjadi masalah yang harus kami atasi.

Sejauh membahas masalah yang harus dipecahkan, kami mencoba untuk memaparkan visi masalah kami Agustus lalu di " Ikhtisar masalah penanganan kesalahan Go 2 , " tetapi dalam retrospeksi kami tidak cukup menarik perhatian ke bagian itu dan tidak cukup mendorong diskusi tentang apakah masalah khusus itu benar. Proposal try mungkin merupakan solusi yang bagus untuk masalah yang diuraikan di sana, tetapi bagi banyak dari Anda itu bukan masalah untuk dipecahkan. Di masa depan kita perlu melakukan pekerjaan yang lebih baik dengan menarik perhatian pada pernyataan masalah awal ini dan memastikan bahwa ada kesepakatan luas tentang masalah yang perlu dipecahkan.

(Mungkin juga bahwa pernyataan masalah penanganan kesalahan sepenuhnya dikalahkan dengan menerbitkan draf desain generik pada hari yang sama.)

Pada topik yang lebih luas tentang apa yang harus ditingkatkan tentang penanganan kesalahan Go, kami akan sangat senang melihat laporan pengalaman tentang aspek penanganan kesalahan apa di Go yang paling bermasalah bagi Anda di basis kode dan lingkungan kerja Anda sendiri dan seberapa besar pengaruh solusi yang baik akan miliki dalam pengembangan Anda sendiri. Jika Anda memang menulis laporan seperti itu, harap pasang tautan di laman Go2ErrorHandlingFeedback .

Terima kasih kepada semua orang yang berpartisipasi dalam diskusi ini, di sini dan di tempat lain. Seperti yang telah ditunjukkan oleh Russ Cox sebelumnya, diskusi di seluruh komunitas seperti ini adalah yang terbaik dari sumber terbuka . Kami sangat menghargai bantuan semua orang untuk memeriksa proposal khusus ini dan secara lebih umum dalam membahas cara terbaik untuk meningkatkan status penanganan kesalahan di Go.

Robert Griesemer, untuk Komite Peninjau Proposal.

Semua 808 komentar

Saya setuju ini adalah cara terbaik ke depan: memperbaiki masalah paling umum dengan desain sederhana.

Saya tidak ingin bersepeda (jangan ragu untuk menunda percakapan ini), tetapi Rust pergi ke sana dan akhirnya menyelesaikan dengan ? operator postfix daripada fungsi bawaan, untuk meningkatkan keterbacaan.

Proposal gophercon mengutip ? dalam gagasan yang dipertimbangkan dan memberikan tiga alasan mengapa itu dibuang: yang pertama ("transfer aliran kontrol sebagai aturan umum disertai dengan kata kunci") dan yang ketiga ("penangan lebih didefinisikan secara alami dengan kata kunci, jadi cek juga harus") tidak berlaku lagi. Yang kedua adalah gaya: ia mengatakan bahwa, bahkan jika operator postfix bekerja lebih baik untuk rantai, masih dapat membaca lebih buruk dalam beberapa kasus seperti:

check io.Copy(w, check newReader(foo))

daripada:

io.Copy(w, newReader(foo)?)?

tapi sekarang kita akan memiliki:

try(io.Copy(w, try(newReader(foo))))

yang menurut saya jelas lebih buruk dari ketiganya, karena bahkan tidak jelas lagi yang merupakan fungsi utama yang dipanggil.

Jadi inti dari komentar saya adalah bahwa ketiga alasan yang disebutkan dalam proposal gophercon untuk tidak menggunakan ? tidak berlaku untuk proposal try ini; ? ringkas, sangat mudah dibaca, tidak mengaburkan struktur pernyataan (dengan hierarki panggilan fungsi internal), dan dapat dirantai. Ini menghilangkan lebih banyak kekacauan dari tampilan, sementara tidak mengaburkan aliran kontrol lebih dari yang sudah diusulkan try() .

Untuk memperjelas:

Melakukan

func f() (n int, err error) {
  n = 7
  try(errors.New("x"))
  // ...
}

kembali (0, "x") atau (7, "x")? Saya akan berasumsi yang terakhir.

Apakah pengembalian kesalahan harus diberi nama jika tidak ada dekorasi atau penanganan (seperti dalam fungsi pembantu internal)? Saya akan berasumsi tidak.

Contoh Anda mengembalikan 7, errors.New("x") . Ini harus jelas dalam dokumen lengkap yang akan segera dikirimkan (https://golang.org/cl/180557).

Parameter hasil kesalahan tidak perlu diberi nama untuk menggunakan try . Itu hanya perlu diberi nama jika fungsi perlu merujuknya ke fungsi yang ditangguhkan atau di tempat lain.

Saya benar-benar tidak senang dengan _function_ bawaan yang memengaruhi aliran kontrol pemanggil. Saya menghargai ketidakmungkinan menambahkan kata kunci baru di Go 1, tetapi mengatasi masalah itu dengan fungsi bawaan ajaib sepertinya salah bagi saya. Membayangi built-in lainnya tidak memiliki hasil yang tidak terduga seperti perubahan aliran kontrol.

Saya tidak suka tampilan postfix ? , tapi saya pikir itu masih bagus try() .

Sunting: Yah, saya benar-benar lupa bahwa kepanikan itu ada dan bukan kata kunci.

Proposal terperinci sekarang ada di sini (peningkatan pemformatan yang tertunda, akan segera hadir) dan mudah-mudahan akan menjawab banyak pertanyaan.

@dominikh Proposal terperinci membahas ini secara panjang lebar, tetapi harap dicatat bahwa panic dan recover adalah dua bawaan yang juga memengaruhi aliran kontrol.

Satu klarifikasi / saran untuk perbaikan:

if the last argument supplied to try, of type error, is not nil, the enclosing function’s error result variable (...) is set to that non-nil error value before the enclosing function returns

Bisakah ini mengatakan is set to that non-nil error value and the enclosing function returns ? (s/sebelum/dan)

Pada pembacaan pertama, before the enclosing function returns sepertinya akan _akhirnya_ menetapkan nilai kesalahan di beberapa titik di masa depan tepat sebelum fungsi dikembalikan - mungkin di baris selanjutnya. Interpretasi yang benar adalah bahwa try dapat menyebabkan fungsi saat ini kembali. Itu perilaku yang mengejutkan untuk bahasa saat ini, jadi teks yang lebih jelas akan disambut.

Saya pikir ini hanya gula, dan sejumlah kecil lawan vokal menggoda golang tentang penggunaan berulang mengetik if err != nil ... dan seseorang menganggapnya serius. Saya tidak berpikir itu masalah. Satu-satunya hal yang hilang adalah dua bawaan ini:

https://github.com/purpleidea/mgmt/blob/a235b760dc3047a0d66bb0b9d63c25bc746ed274/util/errwrap/errwrap.go#L26

Tidak yakin mengapa ada orang yang menulis fungsi seperti ini, tetapi untuk apa output yang diharapkan

try(foobar())

Jika foobar mengembalikan (error, error)

Saya menarik kembali kekhawatiran saya sebelumnya tentang aliran kontrol dan saya tidak lagi menyarankan menggunakan ? . Saya minta maaf atas tanggapan spontan (meskipun saya ingin menunjukkan ini tidak akan terjadi seandainya masalah diajukan _setelah_ proposal lengkap tersedia).

Saya tidak setuju dengan perlunya penanganan kesalahan yang disederhanakan, tetapi saya yakin itu adalah pertempuran yang kalah. try seperti yang tercantum dalam proposal tampaknya merupakan cara yang paling tidak buruk untuk melakukannya.

@webermaster Hanya hasil error terakhir yang khusus untuk ekspresi yang diteruskan ke try , seperti yang dijelaskan dalam dokumen proposal.

Seperti @dominikh , saya juga tidak setuju dengan perlunya penanganan kesalahan yang disederhanakan.

Ini memindahkan kompleksitas vertikal ke kompleksitas horizontal yang jarang merupakan ide bagus.

Jika saya benar-benar harus memilih antara menyederhanakan proposal penanganan kesalahan, ini akan menjadi proposal pilihan saya.

Akan sangat membantu jika ini dapat disertai (pada beberapa tahap penerimaan) dengan alat untuk mengubah kode Go untuk menggunakan try di beberapa subset fungsi pengembalian kesalahan di mana transformasi seperti itu dapat dengan mudah dilakukan tanpa mengubah semantik. Tiga manfaat terjadi pada saya:

  • Saat mengevaluasi proposal ini, ini akan memungkinkan orang dengan cepat memahami bagaimana try dapat digunakan dalam basis kode mereka.
  • Jika try masuk ke versi Go yang akan datang, orang mungkin ingin mengubah kode mereka untuk menggunakannya. Memiliki alat untuk mengotomatiskan kasus mudah akan banyak membantu.
  • Memiliki cara untuk mengubah basis kode besar dengan cepat untuk menggunakan try akan memudahkan untuk memeriksa efek implementasi dalam skala besar. (Kebenaran, kinerja, dan ukuran kode, katakanlah.) Implementasinya mungkin cukup sederhana untuk membuat pertimbangan ini diabaikan.

Saya hanya ingin mengungkapkan bahwa menurut saya try(foo()) yang sebenarnya keluar dari fungsi panggilan menghilangkan dari kita isyarat visual bahwa aliran fungsi dapat berubah tergantung pada hasilnya.

Saya merasa saya dapat bekerja dengan try jika sudah cukup terbiasa, tetapi saya juga merasa kita akan membutuhkan dukungan IDE tambahan (atau semacamnya) untuk menyorot try untuk mengenali aliran implisit dalam tinjauan kode secara efisien /sesi debug

Hal yang paling saya khawatirkan adalah kebutuhan untuk menamai nilai pengembalian hanya agar pernyataan penangguhan senang.

Saya pikir masalah penanganan kesalahan keseluruhan yang dikeluhkan komunitas adalah kombinasi dari boilerplate if err != nil DAN menambahkan konteks ke kesalahan. FAQ dengan jelas menyatakan bahwa yang terakhir sengaja ditinggalkan sebagai masalah terpisah, tetapi saya merasa ini menjadi solusi yang tidak lengkap, tetapi saya akan bersedia memberikannya kesempatan setelah memikirkan 2 hal ini:

  1. Deklarasikan err di awal fungsi.
    Apakah ini bekerja? Saya ingat masalah dengan penundaan & hasil yang tidak disebutkan namanya. Jika tidak proposal perlu mempertimbangkan ini.
func sample() (string, error) {
  var err error
  defer fmt.HandleErrorf(&err, "whatever")
  s := try(f())
  return s, nil
}
  1. Tetapkan nilai seperti yang kita lakukan sebelumnya, tetapi gunakan fungsi helper wrapf yang memiliki if err != nil boilerplate.
func sample() (string, error) {
  s, err := f()
  try(wrapf(err, "whatever"))
  return s, nil
}
func wrapf(err error, format string, ...v interface{}) error {
  if err != nil {
    // err = wrapped error
  }
  return err
}

Jika salah satu bekerja, saya bisa menghadapinya.

func sample() (string, error) {
  var err error
  defer fmt.HandleErrorf(&err, "whatever")
  s := try(f())
  return s, nil
}

Ini tidak akan berhasil. Penangguhan akan memperbarui variabel err lokal, yang tidak terkait dengan nilai kembalian.

func sample() (string, error) {
  s, err := f()
  try(wrapf(err, "whatever"))
  return s, nil
}
func wrapf(err error, format string, ...v interface{}) error {
  if err != nil {
    // err = wrapped error
  }
  return err
}

Itu harus bekerja. Itu akan memanggil wrapf bahkan pada kesalahan nihil.
Ini juga akan (terus) berfungsi, dan IMO jauh lebih jelas:

func sample() (string, error) {
  s, err := f()
  if err != nil {
      return "", wrap(err)
  }
  return s, nil
}

Tidak ada yang akan membuat Anda menggunakan try .

Tidak yakin mengapa ada orang yang menulis fungsi seperti ini, tetapi untuk apa output yang diharapkan

try(foobar())

Jika foobar mengembalikan (error, error)

Mengapa Anda mengembalikan lebih dari satu kesalahan dari suatu fungsi? Jika Anda mengembalikan lebih dari satu kesalahan dari fungsi, mungkin fungsi harus dipecah menjadi dua yang terpisah sejak awal, masing-masing hanya mengembalikan satu kesalahan.

Bisakah Anda menguraikan dengan sebuah contoh?

@cespare : Seharusnya seseorang dapat menulis go fix yang menulis ulang kode yang ada cocok untuk try sehingga menggunakan try . Mungkin berguna untuk merasakan bagaimana kode yang ada dapat disederhanakan. Kami tidak mengharapkan perubahan signifikan dalam ukuran atau kinerja kode, karena try hanyalah gula sintaksis, menggantikan pola umum dengan bagian kode sumber yang lebih pendek yang pada dasarnya menghasilkan kode keluaran yang sama. Perhatikan juga bahwa kode yang menggunakan try akan terikat untuk menggunakan versi Go yang setidaknya merupakan versi di mana try diperkenalkan.

@lestrrat : Setuju bahwa seseorang harus belajar bahwa try dapat mengubah aliran kontrol. Kami menduga bahwa IDE dapat menyorotinya dengan cukup mudah.

@Goodwine : Seperti yang sudah ditunjukkan oleh @randall77 , saran pertama Anda tidak akan berhasil. Salah satu opsi yang telah kami pikirkan (tetapi tidak dibahas dalam dokumen) adalah kemungkinan memiliki beberapa variabel yang dideklarasikan sebelumnya yang menunjukkan hasil error (jika ada di tempat pertama). Itu akan menghilangkan kebutuhan untuk menamai hasil itu agar dapat digunakan dalam defer . Tapi itu akan lebih ajaib; tampaknya tidak dibenarkan. Masalah dengan penamaan hasil pengembalian pada dasarnya kosmetik, dan yang paling penting adalah dalam API yang dibuat secara otomatis yang dilayani oleh go doc dan teman-teman. Akan mudah untuk mengatasi ini di alat-alat itu (lihat juga FAQ dokumen desain terperinci tentang hal ini).

@nictuku : Mengenai saran Anda untuk klarifikasi (s/before/and/): Saya pikir kode tepat sebelum paragraf yang Anda maksud menjelaskan apa yang sebenarnya terjadi, tapi saya mengerti maksud Anda, s/before/and/ may membuat prosa lebih jelas. Aku akan membuat perubahan.

Lihat CL 180637 .

Saya sebenarnya sangat menyukai proposal ini. Namun, saya punya satu kritik. Titik keluar dari fungsi di Go selalu ditandai dengan return . Kepanikan juga merupakan titik keluar, namun itu adalah kesalahan besar yang biasanya tidak dimaksudkan untuk ditemui.

Membuat titik keluar dari fungsi yang bukan return , dan dimaksudkan untuk menjadi biasa, dapat menyebabkan kode yang jauh lebih mudah dibaca. Saya telah mendengar tentang ini dalam sebuah ceramah dan sulit untuk melupakan keindahan bagaimana kode ini disusun:

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 mungkin terlihat seperti kekacauan besar, dan _dimaksudkan_ oleh draft penanganan kesalahan, tetapi mari kita bandingkan dengan hal yang sama dengan try .

func CopyFile(src, dst string) error {
    defer func() {
        err = fmt.Errorf("copy %s %s: %v", src, dst, err)
    }()
    r, err := try(os.Open(src))
    defer r.Close()

    w, err := try(os.Create(dst))

    defer w.Close()
    defer os.Remove(dst)
    try(io.Copy(w, r))
    try(w.Close())

    return nil
}

Anda mungkin melihat ini pada pandangan pertama dan berpikir itu terlihat lebih baik, karena ada lebih sedikit kode yang diulang. Namun, sangat mudah untuk menemukan semua titik yang dikembalikan oleh fungsi pada contoh pertama. Mereka semua menjorok dan dimulai dengan return , diikuti dengan spasi. Ini karena fakta bahwa semua pengembalian bersyarat _harus_ berada di dalam blok bersyarat, sehingga diindentasi oleh standar gofmt . return juga, seperti yang dinyatakan sebelumnya, satu-satunya cara untuk meninggalkan fungsi tanpa mengatakan bahwa kesalahan besar telah terjadi. Pada contoh kedua, hanya ada satu return , jadi sepertinya satu-satunya hal yang harus dikembalikan oleh fungsi _ever_ adalah nil . Dua panggilan try terakhir mudah dilihat, tetapi dua panggilan pertama sedikit lebih sulit, dan akan lebih sulit lagi jika panggilan tersebut bersarang di suatu tempat, misalnya proc := try(os.FindProcess(try(strconv.Atoi(os.Args[1])))) .

Kembali dari suatu fungsi tampaknya merupakan hal yang "suci" untuk dilakukan, itulah sebabnya saya pribadi berpikir bahwa semua titik keluar dari suatu fungsi harus ditandai dengan return .

Seseorang telah menerapkan ini 5 tahun yang lalu. Jika Anda tertarik, Anda bisa
coba fitur ini

https://news.ycombinator.com/item?id=20101417

Saya menerapkan try() di Go lima tahun lalu dengan preprosesor AST dan menggunakannya dalam proyek nyata, itu cukup bagus: https://github.com/lunixbochs/og

Berikut adalah beberapa contoh saya menggunakannya dalam fungsi error-check-heavy: https://github.com/lunixbochs/poxd/blob/master/tls.go#L13

Saya menghargai upaya yang dilakukan untuk ini. Saya pikir ini adalah solusi paling menarik yang pernah saya lihat sejauh ini. Tapi saya pikir itu memperkenalkan banyak pekerjaan saat debugging. Membuka bungkus mencoba dan menambahkan blok if setiap kali saya men-debug dan membungkusnya kembali ketika saya selesai itu membosankan. Dan saya juga merasa ngeri tentang variabel err ajaib yang perlu saya pertimbangkan. Saya tidak pernah terganggu oleh pemeriksaan kesalahan eksplisit jadi mungkin saya orang yang salah untuk bertanya. Itu selalu menurut saya "siap untuk debug".

@griesemer
Masalah saya dengan penggunaan penangguhan yang Anda usulkan sebagai cara untuk menangani pembungkusan kesalahan adalah bahwa perilaku dari cuplikan yang saya tunjukkan (diulangi di bawah) tidak terlalu umum AFAICT, dan karena sangat jarang maka saya dapat membayangkan orang menulis ini berpikir itu berhasil ketika tidak.

Seperti .. seorang pemula tidak akan tahu ini, jika mereka memiliki bug karena ini mereka tidak akan pergi "tentu saja, saya perlu pengembalian bernama", mereka akan stres karena seharusnya berhasil dan tidak.

var err error
defer fmt.HandleErrorf(err);

try sudah terlalu ajaib sehingga Anda sebaiknya melanjutkan dan menambahkan nilai kesalahan implisit itu. Pikirkan pemula, bukan pada mereka yang tahu semua nuansa Go. Jika tidak cukup jelas, saya pikir itu bukan solusi yang tepat.

Atau... Tidak menyarankan menggunakan penangguhan seperti ini, coba cara lain yang lebih aman tetapi tetap dapat dibaca.

@deanveloper Memang benar bahwa proposal ini (dan dalam hal ini, proposal apa pun yang mencoba mencoba hal yang sama) akan menghapus pernyataan return yang terlihat secara eksplisit dari kode sumber - itulah inti dari proposal, bukan? Untuk menghapus boilerplate dari pernyataan if dan returns yang semuanya sama. Jika Anda ingin menyimpan return , jangan gunakan try .

Kami terbiasa segera mengenali pernyataan return (dan panic ) karena begitulah cara aliran kontrol ini diekspresikan dalam Go (dan banyak bahasa lainnya). Tampaknya tidak berlebihan bahwa kita juga akan mengenali try sebagai aliran kontrol yang berubah setelah beberapa orang terbiasa, seperti yang kita lakukan untuk return . Saya tidak ragu bahwa dukungan IDE yang baik akan membantu dalam hal ini juga.

Saya memiliki dua kekhawatiran:

  • pengembalian bernama sangat membingungkan, dan ini mendorong mereka dengan kasus penggunaan baru dan penting
  • ini akan mencegah menambahkan konteks ke kesalahan

Dalam pengalaman saya, menambahkan konteks ke kesalahan segera setelah setiap situs panggilan sangat penting untuk memiliki kode yang dapat dengan mudah di-debug. Dan pengembalian bernama telah menyebabkan kebingungan bagi hampir setiap pengembang Go yang saya kenal di beberapa titik.

Masalah gaya yang lebih kecil adalah sangat disayangkan berapa banyak baris kode yang sekarang akan dibungkus try(actualThing()) . Saya dapat membayangkan melihat sebagian besar baris dalam basis kode yang dibungkus try() . Itu terasa disayangkan.

Saya pikir masalah ini akan diatasi dengan tweak:

a, b, err := myFunc()
check(err, "calling myFunc on %v and %v", a, b)

check() akan berperilaku seperti try() , tetapi akan menghilangkan perilaku melewati nilai pengembalian fungsi secara umum, dan sebagai gantinya akan memberikan kemampuan untuk menambahkan konteks. Itu masih akan memicu pengembalian.

Ini akan mempertahankan banyak keuntungan dari try() :

  • itu adalah bawaan
  • itu mengikuti aliran kontrol yang ada WRT untuk menunda
  • itu selaras dengan praktik yang ada untuk menambahkan konteks ke kesalahan dengan baik
  • itu sejajar dengan proposal dan pustaka saat ini untuk pembungkusan kesalahan, seperti errors.Wrap(err, "context message")
  • itu menghasilkan situs panggilan yang bersih: tidak ada boilerplate di baris a, b, err := myFunc()
  • menjelaskan kesalahan dengan defer fmt.HandleError(&err, "msg") masih dimungkinkan, tetapi tidak perlu didorong.
  • tanda tangan check sedikit lebih sederhana, karena tidak perlu mengembalikan sejumlah argumen dari fungsi yang dibungkusnya.

@s4n-gt Terima kasih untuk tautan ini. Saya tidak menyadarinya.

@Goodwine Poin diambil. Alasan untuk tidak memberikan lebih banyak dukungan penanganan kesalahan langsung dibahas dalam dokumen desain secara rinci. Ini juga merupakan fakta bahwa selama satu tahun atau lebih (sejak rancangan desain diterbitkan di Gophercon tahun lalu) tidak ada solusi yang memuaskan untuk penanganan kesalahan eksplisit yang muncul. Itulah sebabnya proposal ini sengaja mengabaikannya (dan sebaliknya menyarankan untuk menggunakan defer ). Proposal ini masih membuka pintu untuk perbaikan di masa depan dalam hal itu.

Proposal menyebutkan mengubah pengujian paket untuk memungkinkan pengujian dan tolok ukur mengembalikan kesalahan. Meskipun tidak akan menjadi "perubahan perpustakaan sederhana", kami dapat mempertimbangkan untuk menerima func main() error juga. Itu akan membuat menulis skrip kecil jauh lebih bagus. Semantik akan setara dengan:

func main() {
  if err := newmain(); err != nil {
    println(err.Error())
    os.Exit(1)
  }
}

Satu kritik terakhir. Sebenarnya bukan kritik terhadap proposal itu sendiri, melainkan kritik terhadap tanggapan bersama terhadap argumen tandingan "fungsi pengontrol aliran".

Tanggapan untuk "Saya tidak suka bahwa suatu fungsi mengendalikan aliran" adalah bahwa " panic juga mengontrol aliran program!". Namun, ada beberapa alasan mengapa panic lebih baik melakukan ini yang tidak berlaku untuk try .

  1. panic ramah untuk pemrogram pemula karena apa yang dilakukannya intuitif, terus membuka bungkus tumpukan. Seseorang bahkan tidak perlu mencari cara kerja panic untuk memahami apa yang dilakukannya. Pemrogram pemula bahkan tidak perlu khawatir tentang recover , karena pemula biasanya tidak membangun mekanisme pemulihan panik, terutama karena mereka hampir selalu kurang menguntungkan daripada sekadar menghindari kepanikan sejak awal.

  2. panic adalah nama yang mudah dilihat. Ini membawa kekhawatiran, dan itu perlu. Jika seseorang melihat panic dalam basis kode, mereka harus segera memikirkan cara _menghindari_ kepanikan, meskipun itu sepele.

  3. Membonceng dari poin terakhir, panic tidak dapat disarangkan dalam panggilan, membuatnya lebih mudah untuk dilihat.

Tidak apa-apa panik untuk mengontrol aliran program karena sangat mudah dikenali, dan intuitif untuk apa yang dilakukannya.

Fungsi try tidak memenuhi salah satu dari poin ini.

  1. Seseorang tidak dapat menebak apa yang dilakukan try tanpa melihat dokumentasinya. Banyak bahasa menggunakan kata kunci dengan cara yang berbeda, sehingga sulit untuk memahami apa artinya di Go.

  2. try tidak menarik perhatian saya, terutama jika itu adalah sebuah fungsi. _Terutama_ ketika penyorotan sintaks akan menyorotnya sebagai suatu fungsi. _TERUTAMA_ setelah berkembang dalam bahasa seperti Java, di mana try dipandang sebagai boilerplate yang tidak perlu (karena pengecualian yang diperiksa).

  3. try dapat digunakan dalam argumen untuk panggilan fungsi, seperti contoh saya di komentar saya sebelumnya proc := try(os.FindProcess(try(strconv.Atoi(os.Args[1])))) . Ini membuatnya semakin sulit dikenali.

Mata saya mengabaikan fungsi try , bahkan ketika saya secara khusus mencarinya. Mata saya akan melihatnya, tetapi langsung beralih ke panggilan os.FindProcess atau strconv.Atoi . try adalah pengembalian bersyarat. Aliran kontrol DAN pengembalian keduanya ditahan di atas tumpuan di Go. Semua aliran kontrol dalam suatu fungsi diindentasi, dan semua pengembalian dimulai dengan return . Menggabungkan kedua konsep ini bersama-sama menjadi panggilan fungsi yang mudah terlewatkan terasa agak aneh.


Komentar ini dan komentar terakhir saya adalah satu-satunya kritik nyata saya terhadap ide tersebut. Saya pikir saya mungkin tidak menyukai proposal ini, tetapi saya masih berpikir bahwa ini adalah kemenangan keseluruhan untuk Go. Solusi ini masih terasa lebih mirip Go daripada solusi lainnya. Jika ini ditambahkan saya akan senang, namun saya pikir itu masih bisa ditingkatkan, saya tidak yakin bagaimana caranya.

@buchanae menarik. Namun, seperti yang tertulis, ini memindahkan pemformatan gaya fmt dari sebuah paket ke dalam bahasa itu sendiri, yang membuka sekaleng worm.

Namun, seperti yang tertulis, ini memindahkan pemformatan gaya fmt dari sebuah paket ke dalam bahasa itu sendiri, yang membuka sekaleng worm.

Poin bagus. Contoh yang lebih sederhana:

a, b, err := myFunc()
check(err, "calling myFunc")

@buchanae Kami telah mempertimbangkan untuk membuat penanganan kesalahan eksplisit lebih terhubung langsung dengan try - silakan lihat dokumen desain terperinci, khususnya bagian tentang iterasi Desain. Saran spesifik Anda tentang check hanya akan memungkinkan untuk menambah kesalahan melalui sesuatu seperti fmt.Errorf seperti API (sebagai bagian dari check ), jika saya mengerti dengan benar. Secara umum, orang mungkin ingin melakukan segala macam hal dengan kesalahan, tidak hanya membuat yang baru yang merujuk ke yang asli melalui string kesalahannya.

Sekali lagi, proposal ini tidak berusaha untuk menyelesaikan semua situasi penanganan kesalahan. Saya menduga dalam banyak kasus try masuk akal untuk kode yang sekarang terlihat seperti ini:

a, b, c, ... err := try(someFunctionCall())
if err != nil {
   return ..., err
}

Ada banyak sekali kode yang terlihat seperti ini. Dan tidak setiap bagian dari kode yang terlihat seperti ini membutuhkan lebih banyak penanganan kesalahan. Dan di mana defer tidak benar, seseorang masih dapat menggunakan pernyataan if .

Saya tidak mengikuti baris ini:

defer fmt.HandleErrorf(&err, “foobar”)

Ini menjatuhkan kesalahan masuk di lantai, yang tidak biasa. Apakah itu dimaksudkan untuk digunakan sesuatu yang lebih seperti ini?

defer fmt.HandleErrorf(&err, “foobar: %v”, err)

Duplikasi err agak gagap-y. Ini tidak benar-benar langsung sesuai dengan proposal, hanya komentar sampingan tentang doc.

Saya berbagi dua masalah yang diangkat oleh @buchanae , re: bernama pengembalian dan kesalahan kontekstual.

Saya menemukan pengembalian bernama agak merepotkan seperti itu; Saya pikir mereka hanya benar-benar bermanfaat sebagai dokumentasi. Bersandar pada mereka lebih berat adalah kekhawatiran. Maaf untuk menjadi sangat kabur, meskipun. Saya akan lebih memikirkan hal ini dan memberikan beberapa pemikiran yang lebih konkret.

Saya pikir ada kekhawatiran nyata bahwa orang akan berusaha keras untuk menyusun kode mereka sehingga try dapat digunakan, dan oleh karena itu menghindari menambahkan konteks ke kesalahan. Ini adalah waktu yang sangat aneh untuk memperkenalkan ini, mengingat kami baru saja menyediakan cara yang lebih baik untuk menambahkan konteks ke kesalahan melalui fitur pembungkusan kesalahan resmi.

Saya benar-benar berpikir bahwa try seperti yang diusulkan membuat beberapa kode secara signifikan lebih baik. Inilah fungsi yang saya pilih kurang lebih secara acak dari basis kode proyek saya saat ini, dengan beberapa nama diubah. Saya sangat terkesan dengan cara kerja try saat menetapkan bidang struct. (Itu dengan asumsi pembacaan proposal saya benar, dan ini berhasil?)

Kode yang ada:

func NewThing(thingy *foo.Thingy, db *sql.DB, client pb.Client) (*Thing, error) {
        err := dbfile.RunMigrations(db, dbMigrations)
        if err != nil {
                return nil, err
        }
        t := &Thing{
                thingy: thingy,
        }
        t.scanner, err = newScanner(thingy, db, client)
        if err != nil {
                return nil, err
        }
        t.initOtherThing()
        return t, nil
}

Dengan try :

func NewThing(thingy *foo.Thingy, db *sql.DB, client pb.Client) (*Thing, error) {
        try(dbfile.RunMigrations(db, dbMigrations))
        t := &Thing{
                thingy:  thingy,
                scanner: try(newScanner(thingy, db, client)),
        }
        t.initOtherThing()
        return t, nil
}

Tidak ada kehilangan keterbacaan, kecuali mungkin bahwa newScanner mungkin gagal. Tapi kemudian di dunia dengan try programmer Go akan lebih sensitif terhadap kehadirannya.

@josharian Mengenai main mengembalikan error : Bagi saya tampaknya hanya fungsi pembantu kecil Anda yang diperlukan untuk mendapatkan efek yang sama. Saya tidak yakin mengubah tanda tangan main dibenarkan.

Mengenai contoh "foobar": Itu hanya contoh yang buruk. Saya mungkin harus mengubahnya. Terima kasih telah mengangkatnya.

defer fmt.HandleErrorf(&err, “foobar: %v”, err)

Sebenarnya itu tidak benar, karena err akan dievaluasi terlalu dini. Ada beberapa cara untuk mengatasi ini, tetapi tidak ada yang sebersih HandleErrorf asli (saya pikir cacat). Saya pikir akan lebih baik untuk memiliki satu atau dua contoh fungsi pembantu yang lebih realistis.

EDIT: bug evaluasi awal ini ada dalam contoh
menjelang akhir doc:

defer fmt.HandleErrorf(&err, "copy %s %s: %v", src, dst, err)

@adg Ya, try dapat digunakan seperti yang Anda gunakan dalam contoh Anda. Saya membiarkan komentar Anda kembali: pengembalian bernama berdiri apa adanya.

orang mungkin ingin melakukan segala macam hal dengan kesalahan, tidak hanya membuat yang baru yang merujuk ke yang asli melalui string kesalahannya.

try tidak mencoba menangani semua jenis kesalahan yang ingin dilakukan orang, hanya kesalahan yang dapat kami temukan cara praktis untuk membuatnya lebih sederhana. Saya percaya contoh check saya berjalan di jalur yang sama.

Dalam pengalaman saya, bentuk paling umum dari kode penanganan kesalahan adalah kode yang pada dasarnya menambahkan jejak tumpukan, terkadang dengan konteks tambahan. Saya telah menemukan bahwa jejak tumpukan sangat penting untuk debugging, di mana saya mengikuti pesan kesalahan melalui kode.

Tapi, mungkin proposal lain akan menambahkan jejak tumpukan ke semua kesalahan? Aku kehilangan jejak.

Dalam contoh yang diberikan @adg , ada dua potensi kegagalan tetapi tidak ada konteks. Jika newScanner dan RunMigrations sendiri tidak memberikan pesan yang memberi petunjuk Anda ke mana yang salah, maka Anda dibiarkan menebak-nebak.

Dalam contoh yang diberikan @adg , ada dua potensi kegagalan tetapi tidak ada konteks. Jika newScanner dan RunMigrations sendiri tidak memberikan pesan yang memberi tahu Anda mana yang salah, maka Anda tidak perlu menebak-nebak.

Itu benar, dan itulah pilihan desain yang kami buat di bagian kode khusus ini. Kami melakukan banyak kesalahan bungkus di bagian lain dari kode.

Saya berbagi keprihatinan sebagai @deanveloper dan yang lainnya bahwa itu mungkin membuat debugging lebih sulit. Memang benar bahwa kita dapat memilih untuk tidak menggunakannya, tetapi gaya dependensi pihak ketiga tidak berada di bawah kendali kita.
Jika if err := ... { return err } yang kurang berulang adalah poin utama, saya ingin tahu apakah "pengembalian bersyarat" sudah cukup, seperti yang diusulkan https://github.com/golang/go/issues/27794 .

        return nil, err if f, err := os.Open(...)
        return nil, err if _, err := os.Write(...)

Saya pikir ? akan lebih cocok daripada try , dan selalu harus mengejar defer untuk kesalahan juga akan rumit.

Ini juga menutup gerbang untuk memiliki pengecualian menggunakan try/catch selamanya.

Ini juga menutup gerbang untuk memiliki pengecualian menggunakan try/catch selamanya.

Saya _lebih_ dari baik-baik saja dengan ini.

Saya setuju dengan beberapa masalah yang diangkat di atas mengenai menambahkan konteks ke kesalahan. Saya perlahan mencoba untuk beralih dari hanya mengembalikan kesalahan menjadi selalu menghiasinya dengan konteks dan kemudian mengembalikannya. Dengan proposal ini, saya harus sepenuhnya mengubah fungsi saya untuk menggunakan params pengembalian bernama (yang saya rasa aneh karena saya jarang menggunakan pengembalian telanjang).

Seperti yang dikatakan @griesemer :

Sekali lagi, proposal ini tidak berusaha untuk menyelesaikan semua situasi penanganan kesalahan. Saya menduga dalam banyak kasus coba masuk akal untuk kode yang sekarang pada dasarnya terlihat seperti ini:
a, b, c, ... err := coba(someFunctionCall())
jika salah != nihil {
kembali..., err
}
Ada banyak sekali kode yang terlihat seperti ini. Dan tidak setiap bagian dari kode yang terlihat seperti ini membutuhkan lebih banyak penanganan kesalahan. Dan di mana penangguhan tidak benar, seseorang masih dapat menggunakan pernyataan if.

Ya, tetapi seharusnya tidak baik, kode idiomatik selalu membungkus/menghias kesalahannya? Saya percaya itu sebabnya kami memperkenalkan mekanisme penanganan kesalahan yang disempurnakan untuk menambahkan kesalahan konteks/bungkus di stdlib. Seperti yang saya lihat, proposal ini sepertinya hanya mempertimbangkan kasus penggunaan paling dasar.

Selain itu, proposal ini hanya membahas kasus membungkus/mendekorasi beberapa kemungkinan situs pengembalian kesalahan di _satu tempat_, menggunakan parameter bernama dengan panggilan penangguhan.

Tetapi itu tidak melakukan apa pun untuk kasus ketika seseorang perlu menambahkan konteks yang berbeda ke kesalahan yang berbeda dalam satu fungsi. Misalnya, sangat penting untuk mendekorasi kesalahan DB untuk mendapatkan informasi lebih lanjut tentang dari mana asalnya (dengan asumsi tidak ada jejak tumpukan)

Ini adalah contoh kode nyata yang saya miliki -

func (p *pgStore) DoWork() error {
    tx, err := p.handle.Begin()
    if err != nil {
        return err
    }
    var res int64
    err = tx.QueryRow(`INSERT INTO table (...) RETURNING c1`, ...).Scan(&res)
    if err != nil {
        tx.Rollback()
        return fmt.Errorf("insert table: %w", err)
    }

    _, err = tx.Exec(`INSERT INTO table2 (...) VALUES ($1)`, res)
    if err != nil {
        tx.Rollback()
        return fmt.Errorf("insert table2: %w", err)
    }
    return tx.Commit()
}

Menurut usulan:

Jika augmentasi atau pembungkusan kesalahan diinginkan, ada dua pendekatan: Tetap dengan pernyataan if yang dicoba-dan-benar, atau, sebagai alternatif, "deklarasikan" penangan kesalahan dengan pernyataan penangguhan:

Saya pikir ini akan termasuk dalam kategori "tetap dengan pernyataan if yang sudah dicoba dan benar". Saya berharap proposal dapat ditingkatkan untuk mengatasi hal ini juga.

Saya sangat menyarankan tim Go memprioritaskan obat generik , karena di situlah Go paling banyak mendengar kritik, dan menunggu penanganan kesalahan. Teknik hari ini tidak terlalu menyakitkan (tapi go fmt harus membiarkannya duduk di satu baris).

Konsep try() memiliki semua masalah check dari check/handle:

  1. Itu tidak membaca seperti Go. Orang menginginkan sintaks penugasan, tanpa tes nil berikutnya, seperti yang terlihat seperti Go. Tiga belas tanggapan terpisah untuk diperiksa/ditangani menyarankan hal ini; lihat _Tema Berulang_ di sini:
    https://github.com/golang/go/wiki/Go2ErrorHandlingFeedback#recurring -themes

    f, #      := os.Open(...) // return on error
    f, #panic := os.Open(...) // panic on error
    f, #hname := os.Open(...) // invoke named handler on error
    // # is any available symbol or unambiguous pair
    
  2. Penyatuan panggilan fungsi yang mengembalikan kesalahan mengaburkan urutan operasi, dan menghalangi debugging. Keadaan saat kesalahan terjadi, dan oleh karena itu urutan panggilan, harus jelas, tetapi ini bukan:
    try(step4(try(step1()), try(step3(try(step2())))))
    Sekarang ingat bahwa bahasa melarang:
    f(t ? a : b) dan f(a++)

  3. Akan sepele untuk mengembalikan kesalahan tanpa konteks. Alasan utama pemeriksaan/pegangan adalah untuk mendorong kontekstualisasi.

  4. Itu terkait dengan mengetik error dan nilai pengembalian terakhir. Jika kami perlu memeriksa nilai/tipe pengembalian lain untuk status luar biasa, kami kembali ke: if errno := f(); errno != 0 { ... }

  5. Itu tidak menawarkan banyak jalur. Kode yang memanggil API penyimpanan atau jaringan menangani kesalahan tersebut secara berbeda dari yang disebabkan oleh input yang salah atau keadaan internal yang tidak terduga. Kode saya melakukan salah satu dari ini jauh lebih sering daripada return err :

    • log. Fatal()
    • panic() untuk kesalahan yang seharusnya tidak pernah muncul
    • log pesan dan coba lagi

@gopherbot tambahkan Go2, LanguageChange

Bagaimana kalau hanya menggunakan ? untuk membuka hasil seperti rust

Alasan kami ragu untuk memanggil try() mungkin karena dua ikatan implisit. Kami tidak dapat melihat pengikatan untuk kesalahan nilai pengembalian dan argumen untuk try(). Untuk tentang try(), kita dapat membuat aturan bahwa kita harus menggunakan try() dengan fungsi argumen yang memiliki kesalahan dalam mengembalikan nilai. Tetapi mengikat untuk mengembalikan nilai tidak. Jadi saya pikir lebih banyak ekspresi diperlukan bagi pengguna untuk memahami apa yang dilakukan kode ini.

func doSomething() (int, %error) {
  f := try(foo())
  ...
}
  • Kami tidak dapat menggunakan try() jika doSomething tidak memiliki nilai kembalian %error .
  • Kami tidak dapat menggunakan try() jika foo() tidak memiliki kesalahan pada nilai pengembalian terakhir.

Sulit untuk menambahkan persyaratan/fitur baru ke sintaks yang ada.

Sejujurnya, saya pikir foo() juga harus memiliki %error.

Tambahkan 1 aturan lagi

  • %error hanya dapat menjadi satu dalam daftar nilai kembalian suatu fungsi.

Dalam dokumen desain terperinci saya perhatikan bahwa dalam iterasi sebelumnya disarankan untuk meneruskan penangan kesalahan ke fungsi try builtin. Seperti ini:

handler := func(err error) error {
        return fmt.Errorf("foo failed: %v", err)  // wrap error
}

f := try(os.Open(filename), handler)  

atau bahkan lebih baik, seperti ini:

f := try(os.Open(filename), func(err error) error {
        return fmt.Errorf("foo failed: %v", err)  // wrap error
})  

Meskipun, seperti yang dinyatakan dokumen, bahwa ini menimbulkan beberapa pertanyaan, saya pikir proposal ini akan jauh lebih diinginkan dan berguna jika tetap mempertahankan kemungkinan ini untuk secara opsional menentukan fungsi atau penutupan penangan kesalahan seperti itu.

Kedua, saya tidak keberatan bahwa bawaan yang dapat menyebabkan fungsi kembali, tetapi, untuk sedikit bersepeda, nama 'coba' terlalu pendek untuk menunjukkan bahwa itu dapat menyebabkan pengembalian. Jadi nama yang lebih panjang, seperti attempt tampaknya lebih baik bagi saya.

EDIT: Ketiga, idealnya, bahasa go harus mendapatkan obat generik terlebih dahulu, di mana kasus penggunaan yang penting adalah kemampuan untuk mengimplementasikan fungsi coba ini sebagai generik, sehingga pelepasan sepeda dapat berakhir, dan semua orang bisa mendapatkan penanganan kesalahan yang mereka sukai.

Berita peretas memiliki beberapa poin: try tidak berperilaku seperti fungsi normal (dapat kembali) sehingga tidak baik untuk memberikan sintaks seperti fungsi. Sintaks return atau defer akan lebih tepat:

func CopyFile(src, dst string) (err error) {
        r := try os.Open(src)
        defer r.Close()

        w := try os.Create(dst)
        defer func() {
                w.Close()
                if err != nil {
                        os.Remove(dst) // only if a “try” fails
                }
        }()

        try io.Copy(w, r)
        try w.Close()
        return nil
}

@sheerun argumen tandingan umum untuk ini adalah bahwa panic juga merupakan fungsi bawaan yang mengubah aliran kontrol. Saya pribadi tidak setuju dengan itu, namun itu benar.

  1. Menggemakan @deanveloper di atas , serta komentar serupa lainnya, saya sangat khawatir kami meremehkan biaya penambahan yang baru, agak halus, dan—terutama ketika dimasukkan dalam panggilan fungsi lain—kata kunci yang mudah diabaikan yang mengelola kontrol tumpukan panggilan mengalir. panic(...) adalah pengecualian yang relatif jelas (permainan kata-kata tidak dimaksudkan) untuk aturan bahwa return adalah satu-satunya jalan keluar dari suatu fungsi. Saya tidak berpikir kita harus menggunakan keberadaannya sebagai pembenaran untuk menambahkan yang ketiga.
  2. Proposal ini akan mengkanonisasi pengembalian kesalahan yang tidak terbungkus sebagai perilaku default, dan menghapus kesalahan pembungkusan sebagai sesuatu yang Anda harus ikut serta, dengan upacara tambahan. Tapi, dalam pengalaman saya, itu justru mundur ke praktik yang baik. Saya berharap proposal di ruang ini akan mempermudah, atau setidaknya tidak lebih sulit, untuk menambahkan informasi kontekstual ke kesalahan di situs kesalahan.

mungkin kita bisa menambahkan varian dengan fungsi tambahan opsional seperti tryf dengan semantik ini:

func tryf(t1 T1, t1 T2, … tn Tn, te error, fn func(error) error) (T1, T2, … Tn)

terjemahkan ini

x1, x2, … xn = tryf(f(), func(err error) { return fmt.Errorf("foobar: %q", err) })

ke dalam ini

t1, … tn, te := f()
if te != nil {
    if fn != nil {
        te = fn(te)
    }
    err = te
    return
}

karena ini adalah pilihan eksplisit (daripada menggunakan try ) kita dapat menemukan jawaban yang masuk akal atas pertanyaan-pertanyaan dalam versi sebelumnya dari desain ini. misalnya jika fungsi augmentasi nihil, jangan lakukan apa-apa dan kembalikan saja kesalahan aslinya.

Saya khawatir try akan menggantikan penanganan kesalahan tradisional, dan akibatnya membuat anotasi jalur kesalahan menjadi lebih sulit.

Kode yang menangani kesalahan dengan mencatat pesan dan memperbarui penghitung telemetri akan dianggap cacat atau tidak tepat oleh linter dan pengembang yang mengharapkan try semuanya.

a, b, err := doWork()
if err != nil {
  updateCounters()
  writeLogs()
  return err
}

Go adalah bahasa yang sangat sosial dengan idiom umum yang ditegakkan oleh perkakas (fmt, lint, dll). Harap ingat konsekuensi sosial dari ide ini - akan ada kecenderungan untuk ingin menggunakannya di mana-mana.

@politician , maaf, tapi kata yang kamu cari bukan _social_ tapi _opinionated_. Go adalah bahasa pemrograman berpendirian. Selebihnya, saya sebagian besar setuju dengan apa yang Anda maksud.

Alat komunitas @beoran seperti Godep dan berbagai linter menunjukkan bahwa Go memiliki opini dan sosial, dan banyak drama dengan bahasa tersebut berasal dari kombinasi itu. Semoga kita berdua bisa sepakat bahwa try seharusnya tidak menjadi drama selanjutnya.

@politician Terima kasih atas klarifikasinya, saya tidak mengerti seperti itu. Saya tentu setuju bahwa kita harus berusaha menghindari drama.

Saya bingung tentang hal itu.

Dari blog: Kesalahan adalah nilai , dari sudut pandang saya, itu dirancang untuk dihargai bukan untuk diabaikan.

Dan saya percaya apa yang dikatakan Rop Pike, "Nilai dapat diprogram, dan karena kesalahan adalah nilai, kesalahan dapat diprogram.".

Kita tidak boleh menganggap error sebagai exception , ini seperti mengimpor kompleksitas tidak hanya untuk berpikir tetapi juga untuk pengkodean jika kita melakukannya.

"Gunakan bahasa untuk menyederhanakan penanganan kesalahan Anda." --Rob Pike

Dan lebih banyak lagi, kita dapat meninjau slide ini

image

Satu situasi di mana saya menemukan pemeriksaan kesalahan melalui if sangat canggung adalah ketika menutup file (misalnya pada NFS). Saya kira, saat ini kita dimaksudkan untuk menulis yang berikut, jika kesalahan kembali dari .Close() mungkin?

r, err := os.Open(src)
if err != nil {
    return err
}
defer func() {
    // maybe check whether a previous error occured?
    return r.Close()
}()

Bisakah defer try(r.Close()) menjadi cara yang baik untuk memiliki sintaks yang dapat dikelola untuk beberapa cara menangani kesalahan seperti itu? Setidaknya, masuk akal untuk menyesuaikan contoh CopyFile() dalam proposal dalam beberapa cara, untuk tidak mengabaikan kesalahan dari r.Close() dan w.Close() .

@seehuhn Contoh Anda tidak akan dikompilasi karena fungsi yang ditangguhkan tidak memiliki tipe pengembalian.

func doWork() (err error) {
  r, err := os.Open(src)
  if err != nil {
    return err
  }
  defer func() {
    err = r.Close()  // overwrite the return value
  }()
}

Akan bekerja seperti yang Anda harapkan. Kuncinya adalah nilai pengembalian bernama.

Saya suka proposalnya tetapi saya pikir contoh @seehuhn harus ditangani juga:

defer try(w.Close())

akan mengembalikan kesalahan dari Close() hanya jika kesalahan belum disetel.
Pola ini sering digunakan...

Saya setuju dengan kekhawatiran tentang menambahkan konteks ke kesalahan. Saya melihatnya sebagai salah satu praktik terbaik yang membuat pesan kesalahan lebih ramah (dan jelas) dan membuat proses debug lebih mudah.

Hal pertama yang saya pikirkan adalah mengganti fmt.HandleErrorf dengan fungsi tryf , yang mengawali kesalahan dengan konteks tambahan.

func tryf(t1 T1, t1 T2, … tn Tn, te error, ts string) (T1, T2, … Tn)

Misalnya (dari kode nyata yang saya miliki):

func (c *Config) Build() error {
    pkgPath, err := c.load()
    if err != nil {
        return nil, errors.WithMessage(err, "load config dir")
    }
    b := bytes.NewBuffer(nil)
    if err = templates.ExecuteTemplate(b, "main", c); err != nil {
        return nil, errors.WithMessage(err, "execute main template")
    }
    buf, err := format.Source(b.Bytes())
    if err != nil {
        return nil, errors.WithMessage(err, "format main template")
    }
    target := fmt.Sprintf("%s.go", filename(pkgPath))
    if err := ioutil.WriteFile(target, buf, 0644); err != nil {
        return nil, errors.WithMessagef(err, "write file %s", target)
    }
    // ...
}

Dapat diubah menjadi sesuatu seperti:

func (c *Config) Build() error {
    pkgPath := tryf(c.load(), "load config dir")
    b := bytes.NewBuffer(nil)
    tryf(emplates.ExecuteTemplate(b, "main", c), "execute main template")
    buf := tryf(format.Source(b.Bytes()), "format main template")
    target := fmt.Sprintf("%s.go", filename(pkgPath))
    tryf(ioutil.WriteFile(target, buf, 0644), fmt.Sprintf("write file %s", target))
    // ...
}

Atau, jika saya mengambil contoh @agnivade :

func (p *pgStore) DoWork() (err error) {
    tx := tryf(p.handle.Begin(), "begin transaction")
        defer func() {
        if err != nil {
            tx.Rollback()
        }
    }()
    var res int64
    tryf(tx.QueryRow(`INSERT INTO table (...) RETURNING c1`, ...).Scan(&res), "insert table")
    _, = tryf(tx.Exec(`INSERT INTO table2 (...) VALUES ($1)`, res), "insert table2")
    return tryf(tx.Commit(), "commit transaction")
}

Namun, @josharian mengangkat poin bagus yang membuat saya ragu dengan solusi ini:

Namun, seperti yang tertulis, ini memindahkan pemformatan gaya fmt dari sebuah paket ke dalam bahasa itu sendiri, yang membuka sekaleng worm.

Saya sepenuhnya setuju dengan proposal ini dan dapat melihat manfaatnya di sejumlah contoh.

Satu-satunya perhatian saya dengan proposal adalah penamaan try , saya merasa bahwa konotasinya dengan bahasa lain, dapat mengubah persepsi pengembang tentang apa tujuannya ketika berasal dari bahasa lain. Java datang untuk menemukan di sini.

Bagi saya, saya lebih suka builtin disebut pass . Saya merasa ini memberikan representasi yang lebih baik dari apa yang terjadi. Lagi pula, Anda tidak menangani kesalahan - alih-alih meneruskannya kembali untuk ditangani oleh penelepon. try memberikan kesan bahwa kesalahan telah ditangani.

Ini adalah jempol ke bawah dari saya, terutama karena masalah yang ingin diatasi ("pernyataan boilerplate jika biasanya terkait dengan penanganan kesalahan") sama sekali bukan masalah bagi saya. Jika semua pemeriksaan kesalahan hanya if err != nil { return err } maka saya dapat melihat beberapa nilai dalam menambahkan gula sintaksis untuk itu (meskipun Go adalah bahasa yang relatif bebas gula berdasarkan kecenderungan).

Faktanya, apa yang ingin saya lakukan jika terjadi kesalahan non-nihil sangat bervariasi dari satu situasi ke situasi berikutnya. Mungkin saya ingin t.Fatal(err) . Mungkin saya ingin menambahkan pesan dekorasi return fmt.Sprintf("oh no: %v", err) . Mungkin saya hanya mencatat kesalahan dan melanjutkan. Mungkin saya menetapkan tanda kesalahan pada objek SafeWriter saya dan melanjutkan, memeriksa tanda di akhir beberapa urutan operasi. Mungkin saya perlu mengambil tindakan lain. Tak satu pun dari ini dapat diotomatisasi dengan try . Jadi jika argumen untuk try adalah bahwa itu akan menghilangkan semua blok if err != nil , argumen itu tidak berlaku.

Apakah akan menghilangkan _some_ dari mereka? Tentu. Apakah itu tawaran yang menarik bagi saya? meh Aku benar-benar tidak peduli. Bagi saya, if err != nil hanyalah bagian dari Go, seperti kurung kurawal, atau defer . Saya mengerti itu terlihat bertele-tele dan berulang-ulang bagi orang yang baru menggunakan Go, tetapi orang yang baru menggunakan Go tidak cocok untuk membuat perubahan dramatis pada bahasa, karena banyak alasan.

Batasan untuk perubahan signifikan pada Go secara tradisional adalah bahwa perubahan yang diusulkan harus menyelesaikan masalah yang (A) signifikan, (B) mempengaruhi banyak orang, dan (C) diselesaikan dengan baik oleh proposal tersebut. Saya tidak yakin dengan salah satu dari tiga kriteria ini. Saya cukup senang dengan penanganan kesalahan Go apa adanya.

Untuk menggemakan @peterbourgon dan @deanveloper , salah satu hal favorit saya tentang Go adalah aliran kodenya jelas dan panic() tidak diperlakukan seperti mekanisme kontrol aliran standar seperti di Python.

Mengenai perdebatan tentang panic, panic() hampir selalu muncul dengan sendirinya pada sebuah baris karena tidak memiliki nilai. Anda tidak bisa fmt.Println(panic("oops")) . Ini sangat meningkatkan visibilitasnya dan membuatnya jauh lebih sedikit dibandingkan dengan try() daripada yang dibuat orang.

Jika ada konstruksi kontrol aliran lain untuk fungsi, saya akan _far_ lebih suka bahwa itu menjadi pernyataan yang dijamin sebagai item paling kiri pada sebuah baris.

Salah satu contoh dalam proposal membuat masalah bagi saya:

func printSum(a, b string) error {
        fmt.Println(
                "result:",
                try(strconv.Atoi(a)) + try(strconv.Atoi(b)),
        )
        return nil
}

Aliran kontrol benar-benar menjadi kurang jelas dan sangat kabur.

Ini juga bertentangan dengan niat awal Rob Pike bahwa semua kesalahan perlu ditangani secara eksplisit.

Sementara reaksi terhadap ini dapat berupa "maka jangan gunakan", masalahnya adalah -- perpustakaan lain akan menggunakannya, dan men-debug mereka, membacanya, dan menggunakannya, menjadi lebih bermasalah. Ini akan memotivasi perusahaan saya untuk tidak pernah mengadopsi go 2, dan mulai menggunakan hanya perpustakaan yang tidak menggunakan try . Jika saya tidak sendirian dengan ini, ini mungkin mengarah ke divisi a-la python 2/3.

Juga, penamaan try akan secara otomatis menyiratkan bahwa pada akhirnya catch akan muncul di sintaks, dan kita akan kembali menjadi Java.

Jadi, karena semua ini, saya _sangat_ menentang proposal ini.

Saya tidak suka nama try . Ini menyiratkan _percobaan_ dalam melakukan sesuatu dengan risiko kegagalan yang tinggi (saya mungkin memiliki bias budaya terhadap _try_ karena saya bukan penutur asli bahasa Inggris), sedangkan try akan digunakan jika kami mengharapkan kegagalan yang jarang terjadi (motivasi karena ingin mengurangi verbositas penanganan kesalahan) dan optimis. Selain itu try dalam proposal ini sebenarnya _catches_ kesalahan untuk mengembalikannya lebih awal. Saya suka saran pass dari @HiImJC.

Selain namanya, saya merasa canggung memiliki return -pernyataan seperti sekarang tersembunyi di tengah ekspresi. Ini merusak gaya aliran Go. Ini akan membuat tinjauan kode lebih sulit.

Secara umum, saya menemukan bahwa proposal ini hanya akan bermanfaat bagi pemrogram malas yang sekarang memiliki senjata untuk kode yang lebih pendek dan bahkan lebih sedikit alasan untuk melakukan upaya membungkus kesalahan. Karena itu juga akan membuat tinjauan lebih sulit (kembali di tengah ekspresi), saya pikir proposal ini bertentangan dengan tujuan "pemrograman pada skala" dari Go.

Salah satu hal favorit saya tentang Go yang biasanya saya katakan ketika menjelaskan bahasanya adalah bahwa hanya ada satu cara untuk melakukan sesuatu, untuk sebagian besar hal. Proposal ini sedikit bertentangan dengan prinsip itu dengan menawarkan banyak cara untuk melakukan hal yang sama. Saya pribadi berpikir ini tidak perlu dan itu akan menghilangkan, daripada menambah kesederhanaan dan keterbacaan bahasa.

Saya suka proposal ini secara keseluruhan. Interaksi dengan defer tampaknya cukup untuk menyediakan cara ergonomis untuk mengembalikan kesalahan sambil juga menambahkan konteks tambahan. Meskipun akan lebih baik untuk mengatasi halangan yang ditunjukkan @josharian tentang cara memasukkan kesalahan asli ke dalam pesan kesalahan yang dibungkus.

Apa yang hilang adalah cara ergonomis untuk berinteraksi dengan proposal pemeriksaan kesalahan di atas meja. Saya percaya API harus sangat berhati-hati dalam jenis kesalahan apa yang mereka kembalikan, dan defaultnya mungkin "kesalahan yang dikembalikan tidak dapat diperiksa dengan cara apa pun". Maka akan mudah untuk pergi ke keadaan di mana kesalahan dapat diperiksa dengan cara yang tepat, seperti yang didokumentasikan oleh tanda tangan fungsi ("Ini melaporkan kesalahan jenis X dalam keadaan A dan kesalahan jenis Y dalam keadaan B").

Sayangnya, sampai sekarang, proposal ini membuat opsi yang paling ergonomis menjadi yang paling tidak diinginkan (bagi saya); membabi buta melewati jenis kesalahan sewenang-wenang. Saya pikir ini tidak diinginkan karena mendorong untuk tidak memikirkan jenis kesalahan yang Anda kembalikan dan bagaimana pengguna API Anda akan menggunakannya. Kenyamanan tambahan dari proposal ini tentu bagus, tetapi saya khawatir itu akan mendorong perilaku buruk karena kenyamanan yang dirasakan akan lebih besar daripada nilai yang dirasakan dari memikirkan dengan cermat tentang informasi kesalahan apa yang Anda berikan (atau bocorkan).

Sebuah bandaid akan terjadi jika kesalahan yang dikembalikan oleh try diubah menjadi kesalahan yang tidak "tidak dapat dibuka". Sayangnya ini memiliki kelemahan yang cukup parah juga, karena membuat defer tidak dapat memeriksa kesalahan itu sendiri. Selain itu mencegah penggunaan di mana try sebenarnya akan mengembalikan kesalahan dari jenis yang diinginkan (yaitu, kasus penggunaan di mana try digunakan dengan hati-hati daripada sembarangan).

Solusi lain adalah dengan menggunakan kembali gagasan (yang dibuang) memiliki argumen kedua opsional ke try untuk mendefinisikan/memasukkan daftar putih jenis kesalahan yang mungkin dikembalikan dari situs itu. Ini agak merepotkan karena kami memiliki dua cara berbeda untuk mendefinisikan "jenis kesalahan", baik menurut nilai ( io.EOF dll) atau menurut jenis ( *os.PathError , *exec.ExitError ). Sangat mudah untuk menentukan jenis kesalahan yang merupakan nilai sebagai argumen untuk suatu fungsi, tetapi lebih sulit untuk menentukan jenisnya. Tidak yakin bagaimana menangani itu, tetapi membuang ide di luar sana.

Masalah yang ditunjukkan @josharian dapat dihindari dengan menunda evaluasi err:

defer func() { fmt.HandleErrorf(&err, "oops: %v", err) }()

Tidak terlihat bagus, tetapi harus berhasil. Namun saya lebih suka jika ini dapat diatasi dengan menambahkan kata kerja/bendera pemformatan baru untuk penunjuk kesalahan, atau mungkin untuk petunjuk secara umum, yang mencetak nilai dereferensi seperti pada %v biasa. Untuk tujuan contoh, sebut saja %*v :

defer fmt.HandleErrorf(&err, "oops: %*v", &err)

Di samping halangan, saya pikir proposal ini terlihat menjanjikan, tetapi tampaknya penting untuk menjaga ergonomi menambahkan konteks kesalahan di cek.

Sunting:

Pendekatan lain adalah dengan membungkus penunjuk kesalahan dalam sebuah struct yang mengimplementasikan Stringer :

type wraperr struct{ err *error }
func (w wraperr) String() string { return (*w.err).Error() }

...

defer handleErrorf(&err, "oops: %v", wraperr{&err})

Beberapa hal dari sudut pandang saya. Mengapa kita begitu khawatir tentang menyimpan beberapa baris kode? Saya menganggap ini sejalan dengan fungsi Small yang dianggap berbahaya .

Selain itu saya menemukan bahwa proposal semacam itu akan menghapus tanggung jawab menangani kesalahan dengan benar untuk beberapa "keajaiban" yang saya khawatirkan hanya akan disalahgunakan dan mendorong kemalasan yang menghasilkan kode dan bug berkualitas buruk.

Proposal seperti yang dinyatakan juga memiliki sejumlah perilaku yang tidak jelas sehingga ini sudah bermasalah daripada _eksplisit_ ekstra ~3 baris yang lebih jelas.

Saat ini kami menggunakan pola penangguhan dengan hemat di rumah. Ada artikel di sini yang memiliki penerimaan yang sama ketika kami menulisnya - https://bet365techblog.com/better-error-handling-in-go

Namun, kami menggunakannya untuk mengantisipasi kemajuan proposal check / handle .

Periksa/tangani adalah pendekatan yang jauh lebih komprehensif untuk membuat penanganan kesalahan menjadi lebih ringkas. Blok handle -nya mempertahankan cakupan fungsi yang sama seperti yang didefinisikan di dalamnya, sedangkan pernyataan defer adalah konteks baru dengan jumlah, berapa pun, overhead. Ini tampaknya lebih sesuai dengan idiom go, karena jika Anda menginginkan perilaku "kembalikan saja kesalahan ketika itu terjadi" Anda dapat mendeklarasikannya secara eksplisit sebagai handle { return err } .

Penundaan jelas bergantung pada referensi err yang dipertahankan juga, tetapi kami telah melihat masalah muncul dari membayangi referensi kesalahan dengan blok cakupan vars. Jadi itu bukan bukti yang cukup bodoh untuk dianggap sebagai cara standar untuk menangani kesalahan saat berjalan.

try , dalam contoh ini, tampaknya tidak menyelesaikan terlalu banyak dan saya memiliki ketakutan yang sama seperti yang lain bahwa itu hanya akan mengarah pada implementasi yang malas, atau yang menggunakan pola penangguhan secara berlebihan.

Jika penanganan kesalahan berbasis penangguhan akan menjadi A Thing, maka sesuatu seperti ini mungkin harus ditambahkan ke paket kesalahan:

        f := try(os.Create(filename))
        defer errors.Deferred(&err, f.Close)

Mengabaikan kesalahan pernyataan Tutup yang ditangguhkan adalah masalah yang cukup umum. Harus ada alat standar untuk membantunya.

Fungsi bawaan yang mengembalikan adalah penjualan yang lebih sulit daripada kata kunci yang melakukan hal yang sama.
Saya lebih suka jika itu adalah kata kunci seperti di Zig[1].

  1. https://ziglang.org/documentation/master/#try

Fungsi bawaan, yang tanda tangan tipenya tidak dapat diekspresikan menggunakan sistem tipe bahasa, dan perilakunya yang mengacaukan fungsi biasanya, tampak seperti pintu keluar yang dapat digunakan berulang kali untuk menghindari evolusi bahasa yang sebenarnya.

Kami terbiasa segera mengenali pernyataan balik (dan panik) karena begitulah aliran kontrol semacam ini diekspresikan dalam Go (dan banyak bahasa lainnya). Tampaknya tidak terlalu jauh bahwa kita juga akan mengenali try sebagai aliran kontrol yang berubah setelah terbiasa, seperti yang kita lakukan untuk pengembalian. Saya tidak ragu bahwa dukungan IDE yang baik akan membantu dalam hal ini juga.

Saya pikir itu cukup mengada-ada. Dalam kode gofmt'ed, pengembalian selalu cocok dengan /^\t*return / – ini adalah pola yang sangat sepele untuk dilihat, tanpa bantuan apa pun. try , di sisi lain, dapat terjadi di mana saja dalam kode, bersarang secara sewenang-wenang jauh di dalam panggilan fungsi. Tidak ada jumlah pelatihan yang akan membuat kita dapat segera melihat semua aliran kontrol dalam suatu fungsi tanpa bantuan alat.

Lebih lanjut, fitur yang bergantung pada "dukungan IDE yang baik" akan merugikan di semua lingkungan di mana tidak ada dukungan IDE yang baik. Alat peninjauan kode langsung muncul di benak saya – akankah Gerrit menyoroti semua percobaan untuk saya? Bagaimana dengan orang yang memilih untuk tidak menggunakan IDE, atau penyorotan kode yang mewah, karena berbagai alasan? Akankah acme mulai menyoroti try ?

Fitur bahasa harus mudah dipahami dengan sendirinya, tidak bergantung pada dukungan editor.

@kungfusheep saya suka artikel itu. Merawat pembungkus dalam penangguhan saja sudah meningkatkan keterbacaan sedikit tanpa try .

Saya di kamp yang tidak merasa kesalahan dalam Go benar-benar masalah. Meski begitu, if err != nil { return err } bisa menjadi sangat gagap pada beberapa fungsi. Saya telah menulis fungsi yang memerlukan pemeriksaan kesalahan setelah hampir setiap pernyataan dan tidak ada yang memerlukan penanganan khusus selain membungkus dan mengembalikan. Terkadang tidak ada struct Buffer pintar yang akan membuat segalanya lebih baik. Terkadang itu hanya langkah kritis yang berbeda demi satu dan Anda hanya perlu melakukan hubungan pendek jika terjadi kesalahan.

Meskipun try pasti akan membuat kode itu jauh lebih mudah dibaca lebih baik saat sepenuhnya kompatibel, saya setuju bahwa try bukan fitur yang harus dimiliki, jadi jika orang terlalu takut mungkin lebih baik tidak memilikinya.

Semantiknya cukup jelas. Setiap kali Anda melihat try itu mengikuti jalur bahagia, atau kembali. Saya benar-benar tidak bisa lebih sederhana dari itu.

Ini terlihat seperti makro berselubung khusus.

@dominikh try selalu cocok dengan /try\(/ jadi saya tidak tahu apa maksud Anda sebenarnya. Ini sama-sama dapat dicari dan setiap editor yang pernah saya dengar memiliki fitur pencarian.

@qrpnxz Saya pikir poin yang dia coba sampaikan bukanlah bahwa Anda tidak dapat mencarinya secara terprogram, tetapi lebih sulit untuk mencari dengan mata Anda. Regexp hanyalah analogi, dengan penekanan pada /^\t* , menandakan bahwa semua pengembalian jelas menonjol dengan berada di awal baris (mengabaikan spasi putih di depan).

Memikirkannya lagi, seharusnya ada beberapa fungsi pembantu yang umum. Mungkin mereka harus dalam paket yang disebut "ditangguhkan".

Mengatasi proposal untuk check dengan format untuk menghindari penamaan pengembalian, Anda bisa melakukannya dengan fungsi yang memeriksa nil, seperti itu

func Format(err error, message string, args ...interface{}) error {
    if err == nil {
        return nil
    }
    return fmt.Errorf(...)
}

Ini dapat digunakan tanpa pengembalian bernama seperti:

func foo(s string) (int, error) {
    n, err := strconv.Atoi(s)
    try(deferred.Format(err, "bad string %q", s))
    return n, nil
}

fmt.HandleError yang diusulkan dapat dimasukkan ke dalam paket yang ditangguhkan sebagai gantinya dan fungsi pembantu error.Defer saya dapat disebut deferred.Exec dan mungkin ada exec bersyarat untuk prosedur yang dijalankan hanya jika kesalahannya bukan nol.

Menyatukannya, Anda mendapatkan sesuatu seperti

func CopyFile(src, dst string) (err error) {
    defer deferred.Annotate(&err, "copy %s %s", src, dst)

    r := try(os.Open(src))
    defer deferred.Exec(&err, r.Close)

    w := try(os.Create(dst))
    defer deferred.Exec(&err, r.Close)

    defer deferred.Cond(&err, func(){ os.Remove(dst) })
    try(io.Copy(w, r))

    return nil
}

Contoh lain:

func (p *pgStore) DoWork() (err error) {
    tx := try(p.handle.Begin())

    defer deferred.Cond(&err, func(){ tx.Rollback() })

    var res int64 
    err = tx.QueryRow(`INSERT INTO table (...) RETURNING c1`, ...).Scan(&res)
    try(deferred.Format(err, "insert table")

    _, err = tx.Exec(`INSERT INTO table2 (...) VALUES ($1)`, res)
    try(deferred.Format(err, "insert table2"))

    return tx.Commit()
}

Proposal ini membawa kita dari memiliki if err != nil di mana-mana, menjadi memiliki try di mana-mana. Ini menggeser masalah yang diusulkan dan tidak menyelesaikannya.

Meskipun, saya berpendapat bahwa mekanisme penanganan kesalahan saat ini bukanlah masalah untuk memulai. Kami hanya perlu meningkatkan perkakas dan memeriksanya .

Selanjutnya, saya berpendapat bahwa if err != nil sebenarnya lebih mudah dibaca daripada try karena tidak mengacaukan garis bahasa logika bisnis, melainkan berada tepat di bawahnya:

file := try(os.OpenFile("thing")) // less readable than, 

file, err := os.OpenFile("thing")
if err != nil {

}

Dan jika Go ingin lebih ajaib dalam penanganan kesalahannya, mengapa tidak memilikinya sepenuhnya. Misalnya Go dapat secara implisit memanggil try jika pengguna tidak menetapkan kesalahan. Sebagai contoh:

func getString() (string, error) { ... }

func caller() {
  defer func() {
    if err != nil { ... } // whether `err` must be defined or not is not shown in this example. 
  }

  // would call try internally, because a user is not 
  // assigning an error value. Also, it can add a compile error
  // for "defined and not used err value" if the user does not 
  // handle the error. 
  str := getString()
}

Bagi saya, itu benar-benar akan menyelesaikan masalah redundansi dengan mengorbankan sihir dan potensi keterbacaan.

Oleh karena itu, saya mengusulkan agar kami benar-benar menyelesaikan 'masalah' seperti pada contoh di atas atau mempertahankan penanganan kesalahan saat ini tetapi alih-alih mengubah bahasa untuk menyelesaikan redundansi dan pembungkusan, kami tidak mengubah bahasa tetapi kami meningkatkan perkakas dan pemeriksaan kode untuk membuat pengalaman lebih baik.

Misalnya, di VSCode ada cuplikan bernama iferr jika Anda mengetiknya dan menekan enter, itu berkembang menjadi pernyataan penanganan kesalahan penuh ... oleh karena itu, menulisnya tidak pernah terasa melelahkan bagi saya, dan membaca nanti lebih baik .

@josharian

Meskipun ini bukan "perubahan pustaka sederhana", kami dapat mempertimbangkan untuk menerima kesalahan func main() juga.

Masalahnya adalah tidak semua platform memiliki semantik yang jelas tentang apa artinya itu. Penulisan ulang Anda bekerja dengan baik di program Go "tradisional" yang berjalan pada sistem operasi lengkap - tetapi segera setelah Anda menulis firmware mikrokontroler atau bahkan hanya WebAssembly, tidak terlalu jelas apa arti os.Exit(1) . Saat ini, os.Exit adalah panggilan perpustakaan, jadi implementasi Go gratis hanya untuk tidak menyediakannya. Bentuk main adalah masalah bahasa.


Pertanyaan tentang proposal yang mungkin paling baik dijawab oleh "tidak": Bagaimana try berinteraksi dengan argumen variadik? Ini adalah kasus pertama dari fungsi variadic (ish) yang tidak memiliki variadic-nes dalam argumen terakhir. Apakah ini diperbolehkan:

var e []error
try(e...)

Mengesampingkan mengapa Anda pernah melakukan itu. Saya menduga jawabannya adalah "tidak" (jika tidak, tindak lanjutnya adalah "bagaimana jika panjang irisan yang diperluas adalah 0). Cukup angkat sehingga dapat diingat ketika mengucapkan spesifikasi pada akhirnya.

  • Beberapa fitur terbesar yang sedang berjalan adalah bawaan saat ini memastikan aliran kontrol yang jelas, penanganan kesalahan secara eksplisit dan dianjurkan, dan pengembang sangat dilarang menulis kode "ajaib". Proposal try tidak konsisten dengan prinsip dasar ini, karena akan mempromosikan steno dengan mengorbankan keterbacaan aliran kontrol.
  • Jika proposal ini diadopsi, maka mungkin pertimbangkan untuk membuat pernyataan bawaan try alih-alih function . Maka itu lebih konsisten dengan pernyataan aliran kontrol lainnya seperti if . Selain itu, penghapusan tanda kurung bersarang sedikit meningkatkan keterbacaan.
  • Sekali lagi, jika proposal diadopsi maka mungkin mengimplementasikannya tanpa menggunakan defer atau yang serupa. Itu sudah tidak dapat diimplementasikan dalam go murni (seperti yang ditunjukkan oleh orang lain) sehingga mungkin juga menggunakan implementasi yang lebih efisien di bawah tenda.

Saya melihat dua masalah dengan ini:

  1. Ini menempatkan BANYAK kode bersarang di dalam fungsi. Itu menambah banyak beban kognitif ekstra, mencoba mengurai kode di kepala Anda.
  1. Ini memberi kita tempat di mana kode dapat keluar dari tengah pernyataan.

Nomor 2 menurut saya jauh lebih buruk. Semua contoh di sini adalah panggilan sederhana yang mengembalikan kesalahan, tetapi yang lebih berbahaya adalah ini:

func doit(abc string) error {
    a := fmt.Sprintf("value of something: %s\n", try(getValue(abc)))
    log.Println(a)
    return nil
}

Kode ini dapat keluar di tengah sprintf itu, dan akan sangat mudah untuk melewatkan fakta itu.

Suara saya tidak. Ini tidak akan membuat kode go lebih baik. Itu tidak akan membuatnya lebih mudah untuk dibaca. Itu tidak akan membuatnya lebih kuat.

Saya sudah mengatakannya sebelumnya, dan proposal ini mencontohkannya - saya merasa 90% keluhan tentang Go adalah "Saya tidak ingin menulis pernyataan if atau loop" . Ini menghilangkan beberapa pernyataan if yang sangat sederhana, tetapi menambahkan beban kognitif dan memudahkan kehilangan titik keluar untuk suatu fungsi.

Saya hanya ingin menunjukkan bahwa Anda tidak dapat menggunakan ini di main dan mungkin membingungkan pengguna baru atau saat mengajar. Jelas ini berlaku untuk fungsi apa pun yang tidak mengembalikan kesalahan, tetapi saya pikir main itu istimewa karena muncul di banyak contoh..

func main() {
    f := try(os.Open("foo.txt"))
    defer f.Close()
}

Saya tidak yakin membuat try panic di main juga bisa diterima.

Selain itu tidak akan sangat berguna dalam tes ( func TestFoo(t* testing.T) ) yang sangat disayangkan :(

Masalah yang saya miliki dengan ini adalah mengasumsikan Anda selalu ingin mengembalikan kesalahan ketika itu terjadi. Kapan mungkin Anda ingin menambahkan konteks ke kesalahan dan mengembalikannya atau mungkin Anda hanya ingin berperilaku berbeda saat terjadi kesalahan. Mungkin itu tergantung pada jenis kesalahan yang dikembalikan.

Saya lebih suka sesuatu yang mirip dengan try/catch yang mungkin terlihat seperti

Dengan asumsi foo() didefinisikan sebagai

func foo() (int, error) {}

Anda kemudian bisa melakukannya

n := try(foo()) {
    case FirstError:
        // do something based on FirstError
    case OtherError:
        // do something based on OtherError
    default:
        // default behavior for any other error
}

Yang diterjemahkan menjadi

n, err := foo()
if errors.Is(err, FirstError) {
    // do something based on FirstError
if errors.Is(err, OtherError) {
    // do something based on OtherError
} else {
    // default behavior for any other error
}

Bagi saya, penanganan kesalahan adalah salah satu bagian terpenting dari basis kode.
Sudah terlalu banyak kode masuk if err != nil { return err } , mengembalikan kesalahan dari jauh di dalam tumpukan tanpa menambahkan konteks tambahan, atau bahkan (mungkin) lebih buruk menambahkan konteks dengan menutupi kesalahan yang mendasarinya dengan pembungkus fmt.Errorf .

Memberikan kata kunci baru yang merupakan jenis sihir yang tidak melakukan apa-apa selain mengganti if err != nil { return err } sepertinya merupakan jalan yang berbahaya untuk dilalui.
Sekarang semua kode hanya akan dibungkus dalam panggilan untuk mencoba. Ini agak bagus (meskipun keterbacaan menyebalkan) untuk kode yang hanya berurusan dengan kesalahan dalam paket seperti:

func foo() error {
  /// stuff
  try(bar())
  // more stuff
}

Tetapi saya berpendapat bahwa contoh yang diberikan benar-benar mengerikan dan pada dasarnya membuat penelepon mencoba memahami kesalahan yang sangat dalam di tumpukan, seperti penanganan pengecualian.
Tentu saja, ini semua terserah pengembang untuk melakukan hal yang benar di sini, tetapi ini memberi pengembang cara yang bagus untuk tidak peduli dengan kesalahan mereka dengan mungkin "kami akan memperbaikinya nanti" (dan kita semua tahu bagaimana kelanjutannya ).

Saya berharap kita akan melihat masalah dari perspektif yang berbeda dari *"bagaimana kita bisa mengurangi pengulangan" dan lebih banyak tentang "bagaimana kita bisa membuat penanganan kesalahan (yang tepat) lebih sederhana dan pengembang lebih produktif".
Kita harus memikirkan bagaimana ini akan memengaruhi menjalankan kode produksi.

*Catatan: Ini tidak benar-benar mengurangi pengulangan, hanya mengubah apa yang diulang, sambil membuat kode kurang terbaca karena semuanya terbungkus dalam try() .

Satu poin terakhir: Membaca proposal pada awalnya tampaknya bagus, kemudian Anda mulai masuk ke semua gotcha (setidaknya yang terdaftar) dan itu seperti "ok ya ini terlalu banyak".


Saya menyadari banyak dari ini subjektif, tapi itu sesuatu yang saya pedulikan. Semantik ini sangat penting.
Apa yang ingin saya lihat adalah cara untuk membuat penulisan dan pemeliharaan kode tingkat produksi lebih sederhana sehingga Anda mungkin juga melakukan kesalahan "benar" bahkan untuk kode tingkat POC/demo.

Karena konteks kesalahan tampaknya menjadi tema yang berulang...

Hipotesis: sebagian besar fungsi Go mengembalikan (T, error) sebagai lawan dari (T1, T2, T3, error)

Bagaimana jika, alih-alih mendefinisikan try sebagai try(T1, T2, T3, error) (T1, T2, T3) kita mendefinisikannya sebagai
try(func (args) (T1, T2, T3, error))(T1, T2, T3) ? (ini adalah perkiraan)

yang mengatakan bahwa struktur sintaksis dari panggilan try selalu merupakan argumen pertama yang merupakan ekspresi yang mengembalikan banyak nilai, yang terakhir adalah kesalahan.

Kemudian, seperti make , ini membuka pintu ke bentuk panggilan 2-argumen, di mana argumen kedua adalah konteks dari percobaan (misalnya string tetap, string dengan %v , fungsi yang mengambil argumen kesalahan dan mengembalikan kesalahan lain, dll.)

Ini masih memungkinkan rantai untuk kasing (T, error) tetapi Anda tidak dapat lagi menyambungkan beberapa pengembalian yang biasanya tidak diperlukan IMO.

@ cpuguy83 Jika Anda membaca proposal, Anda akan melihat tidak ada yang mencegah Anda membungkus kesalahan. Sebenarnya ada beberapa cara untuk melakukannya saat masih menggunakan try . Banyak orang tampaknya berasumsi bahwa untuk beberapa alasan sekalipun.

if err != nil { return err } sama dengan "kami akan memperbaikinya nanti" sebagai try kecuali lebih mengganggu saat membuat prototipe.

Saya tidak tahu bagaimana hal-hal yang ada di dalam sepasang tanda kurung kurang dapat dibaca daripada langkah-langkah fungsi yang juga setiap empat baris boilerplate.

Akan lebih baik jika Anda menunjukkan beberapa "gotcha" tertentu yang mengganggu Anda karena itulah topiknya.

Keterbacaan tampaknya menjadi masalah tetapi bagaimana dengan go fmt menghadirkan try() sehingga menonjol, seperti:

f := try(
    os.Open("file.txt")
)

@MrTravisB

Masalah yang saya miliki dengan ini adalah mengasumsikan Anda selalu ingin mengembalikan kesalahan ketika itu terjadi.

saya tidak setuju. Ini mengasumsikan bahwa Anda ingin melakukannya cukup sering untuk menjamin singkatan untuk hal itu. Jika tidak, itu tidak menghalangi penanganan kesalahan dengan jelas.

Kapan mungkin Anda ingin menambahkan konteks ke kesalahan dan mengembalikannya atau mungkin Anda hanya ingin berperilaku berbeda saat terjadi kesalahan.

Proposal menjelaskan pola untuk menambahkan konteks seluruh blok ke kesalahan. @josharian menunjukkan bahwa ada kesalahan dalam contoh, dan tidak jelas apa cara terbaik untuk menghindarinya. Saya telah menulis beberapa contoh cara untuk menanganinya.

Untuk konteks kesalahan yang lebih spesifik, sekali lagi, try melakukan sesuatu, dan jika Anda tidak menginginkannya, jangan gunakan try .

@boomlinde Persis poin saya. Proposal ini mencoba memecahkan kasus penggunaan tunggal daripada menyediakan alat untuk memecahkan masalah penanganan kesalahan yang lebih besar. Saya pikir pertanyaan mendasar jika persis apa yang Anda tunjukkan.

Ini mengasumsikan bahwa Anda ingin melakukannya cukup sering untuk menjamin singkatan untuk hal itu.

Menurut pendapat dan pengalaman saya, kasus penggunaan ini adalah minoritas kecil dan tidak memerlukan sintaks steno.

Juga, pendekatan menggunakan defer untuk menangani kesalahan memiliki masalah karena mengasumsikan Anda ingin menangani semua kemungkinan kesalahan dengan cara yang sama. Pernyataan defer tidak dapat dibatalkan.

defer fmt.HandleErrorf(&err, “foobar”)

n := try(foo())

x : try(foo2())

Bagaimana jika saya ingin penanganan kesalahan yang berbeda untuk kesalahan yang mungkin dikembalikan dari foo() vs foo2() ?

@MrTravisB

Bagaimana jika saya ingin penanganan kesalahan yang berbeda untuk kesalahan yang mungkin dikembalikan dari foo() vs foo2()?

Kemudian Anda menggunakan sesuatu yang lain. Itulah poin yang dibuat @boomlinde .

Mungkin Anda secara pribadi tidak sering melihat kasus penggunaan ini, tetapi banyak orang melihatnya, dan menambahkan try tidak terlalu memengaruhi Anda. Faktanya, semakin jarang kasus penggunaan bagi Anda, semakin sedikit hal itu mempengaruhi Anda bahwa try ditambahkan.

@qrpnxz

f := try(os.Open("/foo"))
data := try(ioutil.ReadAll(f))
try(send(data))

(ya saya mengerti ada ReadFile dan contoh khusus ini bukan cara terbaik untuk menyalin data di suatu tempat, bukan intinya)

Ini membutuhkan lebih banyak upaya untuk membaca karena Anda harus menguraikan inline try. Logika aplikasi dibungkus dalam panggilan lain.
Saya juga berpendapat bahwa penangan kesalahan defer di sini tidak akan baik kecuali hanya membungkus kesalahan dengan pesan baru... manusia untuk membaca apa yang terjadi.

Setidaknya operator adalah postfix ( ? ditambahkan ke akhir panggilan) yang tidak memberikan beban tambahan untuk menggali logika yang sebenarnya.

Kontrol aliran berbasis ekspresi

panic mungkin merupakan fungsi pengontrol aliran lainnya, tetapi tidak mengembalikan nilai, menjadikannya pernyataan yang efektif. Bandingkan ini dengan try , yang merupakan ekspresi dan dapat muncul di mana saja.

recover memang memiliki nilai dan mempengaruhi kontrol aliran, tetapi harus muncul dalam pernyataan defer . defer s ini biasanya literal fungsi, recover hanya dipanggil sekali, jadi recover juga muncul secara efektif sebagai pernyataan. Sekali lagi, bandingkan ini dengan try yang dapat terjadi di mana saja.

Saya pikir poin-poin itu berarti bahwa try membuatnya jauh lebih sulit untuk mengikuti aliran kontrol dengan cara yang belum pernah kita miliki sebelumnya, seperti yang telah ditunjukkan sebelumnya, tetapi saya tidak melihat perbedaan antara pernyataan dan ekspresi menunjukkan.


Usulan lain

Izinkan pernyataan seperti

if err != nil {
    return nil, 0, err
}

untuk diformat pada satu baris oleh gofmt ketika blok hanya berisi pernyataan return dan pernyataan itu tidak berisi baris baru. Sebagai contoh:

if err != nil { return nil, 0, err }

Alasan

  • Itu tidak memerlukan perubahan bahasa
  • Aturan pemformatannya sederhana dan jelas
  • Aturan dapat dirancang untuk dipilih di mana gofmt menyimpan baris baru jika sudah ada (seperti struct literal). Keikutsertaan juga memungkinkan penulis untuk menekankan beberapa penanganan kesalahan
  • Jika tidak ikut serta, kode dapat secara otomatis dipindahkan ke gaya baru dengan panggilan ke gofmt
  • Ini hanya untuk pernyataan return , jadi tidak akan disalahgunakan untuk kode golf yang tidak perlu
  • Berinteraksi dengan baik dengan komentar yang menjelaskan mengapa beberapa kesalahan dapat terjadi dan mengapa kesalahan tersebut dikembalikan. Menggunakan banyak ekspresi try bersarang menangani ini dengan buruk
  • Ini mengurangi ruang vertikal penanganan kesalahan sebesar 66%
  • Tidak ada aliran kontrol berbasis ekspresi
  • Kode dibaca jauh lebih sering daripada yang tertulis, sehingga harus dioptimalkan untuk pembaca. Kode berulang yang memakan lebih sedikit ruang sangat membantu pembaca, di mana try lebih condong ke penulis
  • Orang-orang telah mengusulkan try yang ada di beberapa baris. Misalnya komentar ini atau komentar ini yang memperkenalkan gaya seperti
f, err := os.Open(file)
try(maybeWrap(err))
  • Gaya "coba pada barisnya sendiri" menghilangkan ambiguitas tentang nilai err yang dikembalikan. Oleh karena itu, saya menduga formulir ini akan umum digunakan. Mengizinkan satu baris jika blok hampir sama, kecuali itu juga eksplisit tentang apa nilai pengembaliannya
  • Itu tidak mempromosikan penggunaan pengembalian bernama atau pembungkus berbasis defer yang tidak jelas. Keduanya meningkatkan penghalang untuk kesalahan pembungkusan dan yang pertama mungkin memerlukan godoc perubahan
  • Tidak perlu ada diskusi tentang kapan harus menggunakan try versus menggunakan penanganan kesalahan tradisional
  • Tidak menghalangi melakukan try atau sesuatu yang lain di masa depan. Perubahan mungkin positif bahkan jika try diterima
  • Tidak ada interaksi negatif dengan fungsi testing atau main . Faktanya, jika proposal mengizinkan pernyataan bergaris tunggal apa pun alih-alih hanya mengembalikan, itu dapat mengurangi penggunaan pustaka berbasis pernyataan. Mempertimbangkan
value, err := something()
if err != nil { t.Fatal(err) }
  • Tidak ada interaksi negatif dengan pemeriksaan terhadap kesalahan tertentu. Mempertimbangkan
n, err := src.Read(buf)
if err == io.EOF { return nil }
else if err != nil { return err }

Singkatnya, proposal ini memiliki biaya yang kecil, dapat dirancang untuk ikut serta, tidak menghalangi perubahan lebih lanjut karena hanya bergaya, dan mengurangi kesulitan membaca kode penanganan kesalahan verbose sambil menjaga semuanya tetap eksplisit. Saya pikir itu setidaknya harus dipertimbangkan sebagai langkah pertama sebelum masuk semua pada try .


Beberapa contoh porting

Dari https://github.com/golang/go/issues/32437#issuecomment -498941435

Dengan mencoba

func NewThing(thingy *foo.Thingy, db *sql.DB, client pb.Client) (*Thing, error) {
        try(dbfile.RunMigrations(db, dbMigrations))
        t := &Thing{
                thingy:  thingy,
                scanner: try(newScanner(thingy, db, client)),
        }
        t.initOtherThing()
        return t, nil
}

Dengan ini

func NewThing(thingy *foo.Thingy, db *sql.DB, client pb.Client) (*Thing, error) {
        err := dbfile.RunMigrations(db, dbMigrations))
        if err != nil { return nil, fmt.Errorf("running migrations: %v", err) }

        t := &Thing{thingy: thingy}
        t.scanner, err = newScanner(thingy, db, client)
        if err != nil { return nil, fmt.Errorf("creating scanner: %v", err) }

        t.initOtherThing()
        return t, nil
}

Ini kompetitif dalam penggunaan ruang sambil tetap memungkinkan untuk menambahkan konteks ke kesalahan.

Dari https://github.com/golang/go/issues/32437#issuecomment -499007288

Dengan mencoba

func (c *Config) Build() error {
    pkgPath := try(c.load())
    b := bytes.NewBuffer(nil)
    try(emplates.ExecuteTemplate(b, "main", c))
    buf := try(format.Source(b.Bytes()))
    target := fmt.Sprintf("%s.go", filename(pkgPath))
    try(ioutil.WriteFile(target, buf, 0644))
    // ...
}

Dengan ini

func (c *Config) Build() error {
    pkgPath, err := c.load()
    if err != nil { return nil, errors.WithMessage(err, "load config dir") }

    b := bytes.NewBuffer(nil)
    err = templates.ExecuteTemplate(b, "main", c)
    if err != nil { return nil, errors.WithMessage(err, "execute main template") }

    buf, err := format.Source(b.Bytes())
    if err != nil { return nil, errors.WithMessage(err, "format main template") }

    target := fmt.Sprintf("%s.go", filename(pkgPath))
    err = ioutil.WriteFile(target, buf, 0644)
    if err != nil { return nil, errors.WithMessagef(err, "write file %s", target) }
    // ...
}

Komentar asli menggunakan tryf hipotetis untuk melampirkan pemformatan, yang telah dihapus. Tidak jelas cara terbaik untuk menambahkan semua konteks yang berbeda, dan mungkin try bahkan tidak akan berlaku.

@cpuguy83
Bagi saya ini lebih mudah dibaca dengan try . Dalam contoh ini saya membaca "buka file, baca semua byte, kirim data". Dengan penanganan kesalahan biasa saya akan membaca "buka file, periksa apakah ada kesalahan, penanganan kesalahan melakukan ini, lalu baca semua byte, sekarang periksa apakah ada sesuatu yang terjadi..." Saya tahu Anda dapat memindai melalui err != nil s, tetapi bagi saya try lebih mudah karena ketika saya melihatnya saya langsung tahu perilakunya: return if err != nil. Jika Anda memiliki cabang, saya harus melihat apa fungsinya. Itu bisa melakukan apa saja.

Saya juga berpendapat bahwa penangguhan penangan kesalahan di sini tidak akan baik kecuali hanya membungkus kesalahan dengan pesan baru

Saya yakin ada hal lain yang dapat Anda lakukan di penangguhan, tetapi terlepas dari itu, try tetap untuk kasus umum sederhana. Kapan pun Anda ingin melakukan sesuatu yang lebih, selalu ada penanganan kesalahan Go yang baik. Itu tidak akan hilang.

@zeebo Ya, saya menyukainya.
Artikel @kungfusheep menggunakan pemeriksaan kesalahan satu baris seperti itu dan saya keluar untuk mencobanya. Kemudian segera setelah saya simpan, gofmt memperluasnya menjadi tiga baris yang menyedihkan. Banyak fungsi di stdlib didefinisikan dalam satu baris seperti itu sehingga saya terkejut bahwa gofmt akan memperluasnya.

@qrpnxz

Saya kebetulan membaca banyak kode go. Salah satu hal terbaik tentang bahasa ini adalah kemudahan yang berasal dari sebagian besar kode yang mengikuti gaya tertentu (terima kasih gofmt).
Saya tidak ingin membaca banyak kode yang dibungkus try(f()) .
Ini berarti akan ada perbedaan dalam gaya/praktik kode, atau linter seperti "oh Anda seharusnya menggunakan try() di sini" (yang lagi-lagi saya bahkan tidak suka, itulah poin saya dan orang lain berkomentar pada proposal ini).

Secara objektif tidak lebih baik dari if err != nil { return err } , hanya lebih sedikit untuk mengetik.


Satu hal terakhir:

Jika Anda membaca proposal, Anda akan melihat tidak ada yang mencegah Anda dari

Bisakah kita menahan diri dari bahasa seperti itu? Tentu saja saya membaca proposalnya. Kebetulan saya membacanya tadi malam dan kemudian berkomentar pagi ini setelah memikirkannya dan tidak menjelaskan hal-hal kecil dari apa yang saya maksudkan.
Ini adalah nada permusuhan yang luar biasa.

@cpuguy83
Orang cpu saya yang buruk. Aku tidak bermaksud seperti itu.

Dan saya kira Anda harus menunjukkan bahwa kode yang menggunakan try akan terlihat sangat berbeda dari kode yang tidak, jadi saya dapat membayangkan itu akan memengaruhi pengalaman menguraikan kode itu, tetapi saya tidak dapat sepenuhnya setuju bahwa perbedaan itu berarti lebih buruk dalam hal ini, meskipun saya mengerti Anda secara pribadi tidak menyukainya sama seperti saya pribadi menyukainya. Banyak hal di Go yang seperti itu. Mengenai apa yang linter suruh Anda lakukan adalah masalah lain sama sekali, saya pikir.

Tentu itu tidak secara objektif lebih baik. Saya mengungkapkan bahwa itu lebih mudah dibaca bagi saya . Saya dengan hati-hati mengatakan itu.

Sekali lagi, maaf karena terdengar seperti itu. Meskipun ini adalah argumen, saya tidak bermaksud memusuhi Anda.

https://github.com/golang/go/issues/32437#issuecomment -498908380

Tidak ada yang akan membuat Anda menggunakan try.

Mengabaikan glibness, saya pikir itu cara yang cukup bergelombang untuk mengabaikan kritik desain.

Tentu, saya tidak harus menggunakannya. Tetapi siapa pun yang saya gunakan untuk menulis kode dapat menggunakannya dan memaksa saya untuk mencoba menguraikan try(try(try(to()).parse().this)).easily()) . Ini seperti mengatakan

Tidak ada yang akan membuat Anda menggunakan antarmuka kosong{}.

Bagaimanapun, Go cukup ketat tentang kesederhanaan: gofmt membuat semua kode terlihat sama. Jalan bahagia terus ke kiri dan apa pun yang bisa mahal atau mengejutkan adalah eksplisit . try seperti yang diusulkan adalah perubahan 180 derajat dari ini. Kesederhanaan != ringkas.

Setidaknya try harus menjadi kata kunci dengan nilai.

Ini tidak _objectively_ lebih baik dari if err != nil { return err } , hanya lebih sedikit untuk mengetik.

Ada satu perbedaan objektif antara keduanya: try(Foo()) adalah ekspresi. Bagi sebagian orang, perbedaan itu adalah kerugian (kritik try(strconv.Atoi(x))+try(strconv.Atoi(y)) ). Bagi orang lain, perbedaan itu adalah keuntungan untuk alasan yang hampir sama. Masih tidak secara objektif lebih baik atau lebih buruk - tetapi saya juga tidak berpikir perbedaannya harus disapu di bawah karpet dan mengklaim bahwa itu "hanya kurang untuk mengetik" tidak melakukan keadilan proposal.

@elagergren-spideroak sulit untuk mengatakan bahwa try menjengkelkan untuk dilihat dalam satu napas dan kemudian mengatakan bahwa itu tidak eksplisit di napas berikutnya. Anda harus memilih satu.

adalah umum untuk melihat argumen fungsi dimasukkan ke dalam variabel sementara terlebih dahulu. Saya yakin itu akan lebih umum untuk dilihat

this := try(to()).parse().this
that := try(this.easily())

daripada contoh Anda.

try tidak melakukan apa-apa adalah jalan yang menyenangkan, sehingga terlihat seperti yang diharapkan. Di jalan yang tidak bahagia yang dilakukannya hanyalah kembali. Melihat ada try sudah cukup untuk mengumpulkan informasi itu. Tidak ada yang mahal tentang kembali dari suatu fungsi, jadi dari deskripsi itu saya tidak berpikir try melakukan 180

@josharian Mengenai komentar Anda di https://github.com/golang/go/issues/32437#issuecomment -498941854 , saya rasa tidak ada kesalahan evaluasi awal di sini.

defer fmt.HandleErrorf(&err, “foobar: %v”, err)

Nilai err yang tidak dimodifikasi diteruskan ke HandleErrorf , dan pointer ke err diteruskan. Kami memeriksa apakah err adalah nil (menggunakan pointer). Jika tidak, kami memformat string, menggunakan nilai err yang tidak dimodifikasi. Kemudian kita set err ke nilai kesalahan yang diformat, menggunakan pointer.

@Merovius Proposal sebenarnya hanyalah sintaks gula makro, jadi itu akan berakhir tentang apa yang menurut orang terlihat lebih bagus atau akan menyebabkan sedikit masalah. Jika menurut Anda tidak, tolong jelaskan kepada saya. Itu sebabnya saya mendukungnya, secara pribadi. Ini adalah tambahan yang bagus tanpa menambahkan kata kunci apa pun dari sudut pandang saya.

@ianlancetaylor , saya pikir @josharian benar: nilai "tidak dimodifikasi" dari err adalah nilai pada saat defer didorong ke tumpukan, bukan nilai (mungkin dimaksudkan) dari err ditetapkan oleh try sebelum kembali.

Masalah lain yang saya miliki dengan try adalah membuatnya lebih mudah bagi orang untuk membuang lebih banyak dan logika ke dalam satu baris. Ini adalah masalah utama saya dengan sebagian besar bahasa lain, adalah bahwa mereka membuatnya sangat mudah untuk menempatkan seperti 5 ekspresi dalam satu baris, dan saya tidak ingin itu pergi.

this := try(to()).parse().this
that := try(this.easily())

^^ bahkan ini benar-benar mengerikan. Baris pertama, saya harus melompat-lompat melakukan pencocokan paren di kepala saya. Bahkan baris kedua yang sebenarnya cukup sederhana... sangat sulit untuk dibaca.
Fungsi bersarang sulit dibaca.

parser, err := to()
if err != nil {
    return err
}
this := parser.parse().this
that, err := this.easily()
if err != nil {
    return err
}

^^ Ini jauh lebih mudah dan lebih baik IMO. Ini sangat sederhana dan jelas. ya, ini lebih banyak baris kode, saya tidak peduli. Ini sangat jelas.

@bcmils @josharian Ah, tentu saja, terima kasih. Jadi itu harus

defer func() { fmt.HandleErrorf(&err, “foobar: %v”, err) }()

Tidak begitu bagus. Mungkin fmt.HandleErrorf harus secara implisit meneruskan nilai kesalahan sebagai argumen terakhir.

Masalah ini mendapat banyak komentar dengan sangat cepat, dan banyak dari mereka yang menurut saya mengulangi komentar yang telah dibuat. Tentu saja jangan ragu untuk berkomentar, tetapi saya ingin dengan lembut menyarankan bahwa jika Anda ingin menyatakan kembali poin yang telah dibuat, Anda melakukannya dengan menggunakan emoji GitHub, daripada mengulangi poin tersebut. Terima kasih.

@ianlancetaylor jika fmt.HandleErrorf mengirimkan err sebagai argumen pertama setelah format maka implementasinya akan lebih baik dan pengguna akan dapat merujuknya dengan %[1]v selalu.

@natefinch Sangat setuju.

Saya ingin tahu apakah pendekatan gaya karat akan lebih cocok?
Perhatikan ini bukan proposal hanya memikirkannya ...

this := to()?.parse().this
that := this.easily()?

Pada akhirnya saya pikir ini lebih bagus, tetapi (bisa menggunakan ! atau yang lain juga ...), tetapi masih tidak memperbaiki masalah penanganan kesalahan dengan baik.


tentu saja karat juga memiliki try() kurang lebih seperti ini, tapi... gaya karat lainnya.

Ini tidak _objectively_ lebih baik dari if err != nil { return err } , hanya lebih sedikit untuk mengetik.

Ada satu perbedaan objektif antara keduanya: try(Foo()) adalah ekspresi. Bagi sebagian orang, perbedaan itu adalah kerugian (kritik try(strconv.Atoi(x))+try(strconv.Atoi(y)) ). Bagi orang lain, perbedaan itu adalah keuntungan untuk alasan yang hampir sama. Masih tidak secara objektif lebih baik atau lebih buruk - tetapi saya juga tidak berpikir perbedaannya harus disapu di bawah karpet dan mengklaim bahwa itu "hanya kurang untuk mengetik" tidak melakukan keadilan proposal.

Ini adalah salah satu alasan terbesar saya menyukai sintaks ini; itu memungkinkan saya menggunakan fungsi pengembalian kesalahan sebagai bagian dari ekspresi yang lebih besar tanpa harus menyebutkan semua hasil antara. Dalam beberapa situasi, memberi nama mereka mudah, tetapi di situasi lain tidak ada nama yang bermakna atau tidak berlebihan untuk diberikan kepada mereka, dalam hal ini saya lebih suka tidak memberi mereka nama sama sekali.

@MrTravisB

Persis poin saya. Proposal ini mencoba memecahkan kasus penggunaan tunggal daripada menyediakan alat untuk memecahkan masalah penanganan kesalahan yang lebih besar. Saya pikir pertanyaan mendasar jika persis apa yang Anda tunjukkan.

Apa yang saya katakan secara spesifik yang merupakan maksud Anda? Tampaknya bagi saya bahwa Anda pada dasarnya salah memahami maksud saya jika Anda berpikir bahwa kita setuju.

Menurut pendapat dan pengalaman saya, kasus penggunaan ini adalah minoritas kecil dan tidak memerlukan sintaks steno.

Di sumber Go ada ribuan kasus yang dapat ditangani oleh try di luar kotak meskipun tidak ada cara untuk menambahkan konteks ke kesalahan. Jika kecil, itu masih merupakan penyebab umum keluhan.

Juga, pendekatan menggunakan penangguhan untuk menangani kesalahan memiliki masalah karena mengasumsikan Anda ingin menangani semua kemungkinan kesalahan yang sama. pernyataan penangguhan tidak dapat dibatalkan.

Demikian pula, pendekatan menggunakan + untuk menangani aritmatika mengasumsikan bahwa Anda tidak ingin mengurangi, jadi Anda tidak ingin menguranginya. Pertanyaan yang menarik adalah apakah konteks kesalahan di seluruh blok setidaknya mewakili pola umum.

Bagaimana jika saya ingin penanganan kesalahan yang berbeda untuk kesalahan yang mungkin dikembalikan dari foo() vs foo2()

Sekali lagi, maka Anda tidak menggunakan try . Kemudian Anda tidak mendapatkan apa pun dari try , tetapi Anda juga tidak kehilangan apa pun.

@cpuguy83

Saya ingin tahu apakah pendekatan gaya karat akan lebih cocok?

Proposal menyajikan argumen menentang ini.

Pada titik ini, saya pikir memiliki try{}catch{} lebih mudah dibaca :upside_down_face:

  1. Menggunakan impor bernama untuk berkeliling kasus sudut defer tidak hanya buruk untuk hal-hal seperti godoc, tetapi yang paling penting itu sangat rawan kesalahan. Saya tidak peduli saya bisa membungkus semuanya dengan func() lain untuk mengatasi masalah ini, hanya saja lebih banyak hal yang perlu saya ingat, saya pikir itu mendorong "praktik buruk".
  2. Tidak ada yang akan membuat Anda menggunakan try.

    Itu tidak berarti itu adalah solusi yang baik, saya menunjukkan bahwa ide saat ini memiliki kekurangan dalam desain dan saya memintanya untuk ditangani dengan cara yang tidak terlalu rawan kesalahan.

  3. Saya pikir contoh seperti try(try(try(to()).parse().this)).easily()) tidak realistis, ini sudah bisa dilakukan dengan fungsi lain dan saya pikir akan adil bagi mereka yang meninjau kode untuk memintanya untuk dibagi.
  4. Bagaimana jika saya memiliki 3 tempat yang dapat error-out dan saya ingin membungkus setiap tempat secara terpisah? try() membuat ini sangat sulit, sebenarnya try() sudah mengecilkan kesalahan pembungkusan mengingat kesulitannya, tetapi berikut adalah contoh dari apa yang saya maksud:

    func before() error {
      x, err := foo()
      if err != nil {
        wrap(err, "error on foo")
      }
      y, err := bar(x)
      if err != nil {
        wrapf(err, "error on bar with x=%v", x)
      }
      fmt.Println(y)
      return nil
    }
    
    func after() (err error) {
      defer fmt.HandleErrorf(&err, "something failed but I don't know where: %v", err)
      x := try(foo())
      y := try(bar(x))
      fmt.Println(y)
      return nil
    }
    
  5. Sekali lagi, maka Anda tidak menggunakan try . Kemudian Anda tidak mendapatkan apa pun dari try , tetapi Anda juga tidak kehilangan apa pun.

    Katakanlah itu praktik yang baik untuk membungkus kesalahan dengan konteks yang berguna, try() akan dianggap sebagai praktik yang buruk karena tidak menambahkan konteks apa pun. Ini berarti bahwa try() adalah fitur yang tidak ingin digunakan oleh siapa pun dan menjadi fitur yang sangat jarang digunakan sehingga mungkin juga tidak ada.

    Daripada hanya mengatakan "baiklah, jika Anda tidak menyukainya, jangan gunakan dan tutup mulut" (ini adalah bagaimana membaca), saya pikir akan lebih baik untuk mencoba untuk mengatasi apa yang banyak digunakan untuk mempertimbangkan cacat dalam desain. Bisakah kita mendiskusikan apa yang bisa dimodifikasi dari desain yang diusulkan sehingga masalah kita ditangani dengan cara yang lebih baik?

@boomlinde Poin yang kami setujui adalah bahwa proposal ini mencoba menyelesaikan kasus penggunaan kecil dan fakta bahwa "jika Anda tidak membutuhkannya, jangan gunakan itu" adalah argumen utama untuk itu. Seperti yang dinyatakan @elagergren-spideroak, argumen itu tidak berfungsi karena meskipun saya tidak ingin menggunakannya, orang lain akan memaksa saya untuk menggunakannya. Dengan logika argumen Anda, Go juga harus memiliki pernyataan ternary. Dan jika Anda tidak menyukai pernyataan ternary, jangan gunakan itu.

Penafian - Saya pikir Go harus memiliki pernyataan ternary tetapi mengingat bahwa pendekatan Go terhadap fitur bahasa adalah tidak memperkenalkan fitur yang dapat membuat kode lebih sulit dibaca maka seharusnya tidak.

Hal lain terjadi pada saya: Saya melihat banyak kritik berdasarkan gagasan bahwa memiliki try dapat mendorong pengembang untuk menangani kesalahan dengan sembarangan. Tapi menurut saya ini, jika ada, lebih benar dari bahasa saat ini; boilerplate penanganan kesalahan cukup mengganggu sehingga mendorong seseorang untuk menelan atau mengabaikan beberapa kesalahan untuk menghindarinya. Misalnya, saya telah menulis hal-hal seperti ini beberapa kali:

func exists(filename string) bool {
  _, err := os.Stat(filename)
  return err == nil
}

agar dapat menulis if exists(...) { ... } , meskipun kode ini secara diam-diam mengabaikan beberapa kemungkinan kesalahan. Jika saya memiliki try , saya mungkin tidak akan repot-repot melakukannya dan hanya mengembalikan (bool, error) .

Menjadi kacau di sini, saya akan melemparkan ide untuk menambahkan fungsi bawaan kedua yang disebut catch yang akan menerima fungsi yang mengambil kesalahan dan mengembalikan kesalahan yang ditimpa, kemudian jika catch berikutnya disebut itu akan menimpa pawang. Misalnya:

func catch(handler func(err error) error) {
  // .. impl ..
}

Sekarang, fungsi bawaan ini juga akan menjadi fungsi seperti makro yang akan menangani kesalahan berikutnya yang dikembalikan oleh try seperti ini:

func wrapf(format string, ...values interface{}) func(err error) error {
  // user defined
  return func(err error) error {
    return fmt.Errorf(format + ": %v", ...append(values, err))
  }
}
func sample() {
  catch(wrapf("something failed in foo"))
  try(foo()) // "something failed in foo: <error>"
  x := try(foo2()) // "something failed in foo: <error>"
  // Subsequent calls for catch overwrite the handler
  catch(wrapf("something failed in bar with x=%v", x))
  try(bar(x)) // "something failed in bar with x=-1: <error>"
}

Ini bagus karena saya bisa membungkus kesalahan tanpa defer yang bisa rawan kesalahan kecuali kita menggunakan nilai pengembalian bernama atau membungkus dengan fungsi lain, itu juga bagus karena defer akan menambahkan penangan kesalahan yang sama untuk semua kesalahan bahkan jika saya ingin menangani 2 di antaranya secara berbeda. Anda juga dapat menggunakannya sesuai keinginan Anda, misalnya:

func foo(a, b string) (int64, error) {
  return try(strconv.Atoi(a)) + try(strconv.Atoi(b))
}
func withContext(a, b string) (int64, error) {
  catch(func (err error) error {
    return fmt.Errorf("can't parse a: %s, b: %s, err: %v", a, b, err)
  })
  return try(strconv.Atoi(a)) + try(strconv.Atoi(b))
}
func moreExplicitContext(a, b string) (int64, error) {
  catch(func (err error) error {
    return fmt.Errorf("can't parse a: %s, err: %v", a, err)
  })
  x := try(strconv.Atoi(a))
  catch(func (err error) error {
    return fmt.Errorf("can't parse b: %s, err: %v", b, err)
  })
  y := try(strconv.Atoi(b))
  return x + y
}
func withHelperWrapf(a, b string) (int64, error) {
  catch(wrapf("can't parse a: %s", a))
  x := try(strconv.Atoi(a))
  catch(wrapf("can't parse b: %s", b))
  y := try(strconv.Atoi(b))
  return x + y
}
func before(a, b string) (int64, error) {
  x, err := strconv.Atoi(a)
  if err != nil {
    return 0,  fmt.Errorf("can't parse a: %s, err: %v", a, err)
  }
  y, err := strconv.Atoi(b)
  if err != nil {
    return 0,  fmt.Errorf("can't parse b: %s, err: %v", b, err)
  }
  return x + y
}

Dan masih dalam mood yang kacau (untuk membantu Anda berempati) Jika Anda tidak suka catch , Anda tidak perlu menggunakannya.

Sekarang... maksudku bukan kalimat terakhir, tapi rasanya kurang membantu untuk diskusi, IMO agresif sekali.
Namun, jika kita menempuh rute ini, saya pikir kita mungkin juga memiliki try{}catch(error err){} sebagai gantinya :stuck_out_tongue:

Lihat juga #27519 - model kesalahan #id/catch

Tidak ada yang akan membuat Anda menggunakan try.

Mengabaikan glibness, saya pikir itu cara yang cukup bergelombang untuk mengabaikan kritik desain.

Maaf, glib bukan maksud saya.

Apa yang saya coba katakan, adalah bahwa try tidak dimaksudkan sebagai solusi 100%. Ada berbagai paradigma penanganan kesalahan yang tidak ditangani dengan baik oleh try . Misalnya, jika Anda perlu menambahkan konteks yang bergantung pada situs panggilan ke kesalahan. Anda selalu dapat kembali menggunakan if err != nil { untuk menangani kasus yang lebih rumit tersebut.

Ini tentu saja merupakan argumen yang valid bahwa try tidak dapat menangani X, untuk berbagai contoh X. Tetapi seringkali menangani kasus X berarti membuat mekanisme menjadi lebih rumit. Ada tradeoff di sini, menangani X di satu sisi tetapi memperumit mekanisme untuk yang lainnya. Apa yang kita lakukan semua tergantung pada seberapa umum X itu, dan seberapa banyak kerumitan yang diperlukan untuk menangani X.

Jadi dengan "Tidak ada yang akan membuat Anda menggunakan try", maksud saya bahwa contoh yang dimaksud adalah di 10%, bukan 90%. Pernyataan itu tentu saja untuk diperdebatkan, dan saya senang mendengar argumen balasan. Tetapi pada akhirnya kita harus menarik garis di suatu tempat dan berkata "ya, try tidak akan menangani kasus itu. Anda harus menggunakan penanganan kesalahan gaya lama. Maaf.".

Bukannya "coba tidak dapat menangani kasus khusus penanganan kesalahan ini" itu masalahnya, itu "coba mendorong Anda untuk tidak membungkus kesalahan Anda". Ide check-handle memaksa Anda untuk menulis pernyataan pengembalian, jadi menulis pembungkus kesalahan cukup sepele.

Di bawah proposal ini Anda perlu menggunakan pengembalian bernama dengan defer , yang tidak intuitif dan tampaknya sangat retas.

Ide check-handle memaksa Anda untuk menulis pernyataan pengembalian, jadi menulis pembungkus kesalahan cukup sepele.

Itu tidak benar - dalam rancangan desain, setiap fungsi yang mengembalikan kesalahan memiliki penangan default yang hanya mengembalikan kesalahan.

Membangun poin nakal @Goodwine , Anda tidak benar-benar membutuhkan fungsi terpisah seperti HandleErrorf jika Anda memiliki fungsi jembatan tunggal seperti

func handler(err *error, handle func(error) error) {
  // nil handle is treated as the identity
  if *err != nil && handle != nil {
    *err = handle(*err)
  }
}

yang akan Anda gunakan seperti

defer handler(&err, func(err error) error {
  if errors.Is(err, io.EOF) {
    return nil
  }
  return fmt.Errorf("oops: %w", err)
})

Anda dapat menjadikan handler itu sendiri sebagai bawaan semi-ajaib seperti try .

Jika itu ajaib, itu bisa mengambil argumen pertamanya secara implisit—memungkinkannya untuk digunakan bahkan dalam fungsi yang tidak menyebutkan pengembalian error mereka, menghilangkan salah satu aspek yang kurang beruntung dari proposal saat ini sambil membuatnya kurang rewel dan rawan kesalahan untuk menghias kesalahan. Tentu saja, itu tidak mengurangi banyak contoh sebelumnya:

defer handler(func(err error) error {
  if errors.Is(err, io.EOF) {
    return nil
  }
  return fmt.Errorf("oops: %w", err)
})

Jika ajaib dengan cara ini, itu harus menjadi kesalahan waktu kompilasi jika digunakan di mana saja kecuali sebagai argumen untuk defer . Anda bisa melangkah lebih jauh dan membuatnya ditangguhkan secara implisit, tetapi defer handler terbaca dengan cukup baik.

Karena menggunakan defer ia dapat memanggil fungsi handle setiap kali ada kesalahan non-nil yang dikembalikan, membuatnya berguna bahkan tanpa try karena Anda dapat menambahkan

defer handler(wrapErrWithPackageName)

di atas untuk fmt.Errorf("mypkg: %w", err) semuanya.

Itu memberi Anda banyak proposal check / handle yang lebih lama tetapi itu berfungsi dengan penangguhan secara alami (dan secara eksplisit) sambil menghilangkan kebutuhan, dalam banyak kasus, untuk secara eksplisit menamai err kembali. Seperti try ini adalah makro yang relatif mudah yang (saya bayangkan) dapat diimplementasikan sepenuhnya di front end.

Itu tidak benar - dalam rancangan desain, setiap fungsi yang mengembalikan kesalahan memiliki penangan default yang hanya mengembalikan kesalahan.

Buruk saya, Anda benar.

Maksud saya, saya pikir contoh yang dimaksud ada di 10%, bukan 90%. Pernyataan itu tentu saja untuk diperdebatkan, dan saya senang mendengar argumen balasan. Tapi akhirnya kita harus menarik garis di suatu tempat dan berkata "ya, coba tidak akan menangani kasus itu. Anda harus menggunakan penanganan kesalahan gaya lama. Maaf.".

Setuju, pendapat saya adalah bahwa garis ini harus ditarik saat memeriksa EOF atau serupa, bukan saat membungkus. Tetapi mungkin jika kesalahan memiliki lebih banyak konteks, ini tidak akan menjadi masalah lagi.

Bisakah kesalahan bungkus otomatis try() dengan konteks yang berguna untuk debugging? Misalnya jika xerrors menjadi errors , kesalahan harus memiliki sesuatu yang terlihat seperti jejak tumpukan yang dapat ditambahkan oleh try() , bukan? Jika demikian mungkin itu sudah cukup

Jika tujuannya adalah (baca https://github.com/golang/proposal/blob/master/design/32437-try-builtin.md):

  • menghilangkan boilerplate
  • perubahan bahasa minimal
  • mencakup "skenario paling umum"
  • menambahkan sedikit kerumitan pada bahasa

Saya akan mengambil saran memberikan sudut dan mengizinkan migrasi kode "langkah kecil" untuk semua miliaran baris kode di luar sana.

alih-alih yang disarankan:

func printSum(a, b string) error {
        defer fmt.HandleErrorf(&err, "sum %s %s: %v", a,b, err) 
        x := try(strconv.Atoi(a))
        y := try(strconv.Atoi(b))
        fmt.Println("result:", x + y)
        return nil
}

Kita dapat:

func printSum(a, b string) error {
        var err ErrHandler{HandleFunc : twoStringsErr("printSum",a,b)} 
        x, err.Error := strconv.Atoi(a)
        y,err.Error := strconv.Atoi(b)
        fmt.Println("result:", x + y)
        return nil
}

Apa yang akan kita peroleh?
twoStringsErr dapat digarisbawahi ke printSum, atau penangan umum yang tahu cara menangkap kesalahan (dalam hal ini dengan 2 parameter string) - jadi jika saya memiliki tanda tangan fungsi berulang yang sama yang digunakan di banyak fungsi saya, saya tidak perlu menulis ulang penangan masing-masing waktu
dengan cara yang sama, saya dapat membuat tipe ErrHandler diperpanjang dengan cara:

type ioErrHandler ErrHandler
func (i ErrHandler) Handle() ...{

}

atau

type parseErrHandler ErrHandler
func (p parseErrHandler) Handle() ...{

}

atau

type str2IntErrHandler ErrHandler
func (s str2IntErrHandler) Handle() ...{

}

dan gunakan ini di sekitar kode saya:

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

Jadi, kebutuhan sebenarnya adalah mengembangkan pemicu ketika err.Error disetel ke tidak nil
Dengan menggunakan metode ini kita juga dapat:

func (s str2IntErrHandler) Handle() bool{
   **return false**
}

Yang akan memberi tahu fungsi panggilan untuk melanjutkan alih-alih kembali

Dan gunakan penangan kesalahan yang berbeda dalam fungsi yang sama:

func printSum(a, b string) error {
        var pErr str2IntErrHandler 
        var oErr overflowError 
        x, err.Error := strconv.Atoi(a)
        y,err.Error := strconv.Atoi(b)
        fmt.Println("result:", x + y)
        totalAsByte,oErr := sumBytes(x,y)
        sunAsByte,oErr := subtractBytes(x,y)
        return nil
}

dll.

Melewati tujuan lagi

  • hilangkan boilerplate - selesai
  • perubahan bahasa minimal - selesai
  • mencakup "skenario paling umum" - lebih dari yang disarankan IMO
  • menambahkan sedikit kerumitan pada bahasa - sone
    Plus - migrasi kode lebih mudah dari
x, err := strconv.Atoi(a)

ke

x, err.Error := strconv.Atoi(a)

dan sebenarnya - keterbacaan yang lebih baik (IMO, lagi)

@guybrand Anda adalah penganut terbaru untuk tema berulang ini (yang saya suka).

Lihat https://github.com/golang/go/wiki/Go2ErrorHandlingFeedback#recurring -themes

@guybrand Itu sepertinya proposal yang sama sekali berbeda; Saya pikir Anda harus mengajukannya sebagai masalah sendiri sehingga yang ini dapat fokus membahas proposal @griesemer .

@natefinch setuju. Saya pikir ini lebih diarahkan untuk meningkatkan pengalaman saat menulis Go daripada mengoptimalkan untuk membaca. Saya ingin tahu apakah makro atau cuplikan IDE dapat menyelesaikan masalah tanpa ini menjadi fitur bahasa.

@Goodwine

Katakanlah itu praktik yang baik untuk membungkus kesalahan dengan konteks yang berguna, try() akan dianggap sebagai praktik yang buruk karena tidak menambahkan konteks apa pun. Ini berarti bahwa try() adalah fitur yang tidak ingin digunakan oleh siapa pun dan menjadi fitur yang sangat jarang digunakan sehingga mungkin juga tidak ada.

Seperti disebutkan dalam proposal (dan ditunjukkan dengan contoh), try dasarnya tidak mencegah Anda menambahkan konteks. Saya akan mengatakan bahwa cara itu diusulkan, menambahkan konteks ke kesalahan sepenuhnya ortogonal untuk itu. Ini dibahas secara khusus di FAQ proposal.

Saya menyadari bahwa try tidak akan berguna jika dalam satu fungsi jika ada banyak konteks berbeda yang ingin Anda tambahkan ke kesalahan berbeda dari panggilan fungsi. Namun, saya juga percaya bahwa sesuatu dalam nada umum HandleErrorf mencakup area penggunaan yang luas karena hanya menambahkan konteks fungsi ke kesalahan bukanlah hal yang tidak biasa.

Daripada hanya mengatakan "baiklah, jika Anda tidak menyukainya, jangan gunakan dan tutup mulut" (ini adalah bagaimana membaca), saya pikir akan lebih baik untuk mencoba untuk mengatasi apa yang banyak digunakan untuk mempertimbangkan cacat dalam desain.

Jika itu adalah bagaimana membaca saya minta maaf. Maksud saya bukanlah bahwa Anda harus berpura-pura bahwa itu tidak ada jika Anda tidak menyukainya. Jelas bahwa ada kasus di mana try tidak akan berguna dan Anda tidak boleh menggunakannya dalam kasus seperti itu, yang untuk proposal ini saya percaya memberikan keseimbangan yang baik antara KISS dan utilitas umum. Saya tidak berpikir bahwa saya tidak jelas pada saat itu.

Terima kasih semuanya atas umpan balik yang produktif sejauh ini; ini sangat informatif.
Inilah upaya saya pada ringkasan awal, untuk mendapatkan perasaan yang lebih baik untuk umpan balik. Permintaan maaf sebelumnya untuk siapa pun yang saya lewatkan atau salah mengartikan; Saya harap saya mendapatkan inti keseluruhannya dengan benar.

0) Sisi positifnya, @rasky , @adg , @eandre , @dpinela , dan lainnya secara eksplisit menyatakan kebahagiaan atas penyederhanaan kode yang diberikan try .

1) Perhatian yang paling penting tampaknya adalah bahwa try tidak mendorong gaya penanganan kesalahan yang baik tetapi malah mempromosikan "keluar cepat". ( @agnivade , @peterbourgon , @politician , @a8m , @eandre , @prologic , @kungfusheep , @cpuguy , dan lainnya telah menyuarakan keprihatinan mereka tentang ini.)

2) Banyak orang tidak menyukai ide built-in, atau sintaks fungsi yang menyertainya karena menyembunyikan return . Akan lebih baik menggunakan kata kunci. ( @sheerun , @Redundancy , @dolmen , @komuw , @RobertGrantEllis , @elagergren-spideroak). try juga dapat dengan mudah diabaikan (@peterbourgon), terutama karena dapat muncul dalam ekspresi yang mungkin bersarang secara sewenang-wenang. @natefinch khawatir bahwa try membuatnya "terlalu mudah untuk membuang terlalu banyak dalam satu baris", sesuatu yang biasanya kami coba hindari di Go. Juga, dukungan IDE untuk menekankan try mungkin tidak cukup (@dominikh); try perlu "berdiri sendiri".

3) Bagi sebagian orang, status quo dari pernyataan if eksplisit tidak menjadi masalah, mereka senang dengan itu ( @bitfield , @marwan-at-work, @natefinch). Lebih baik hanya memiliki satu cara untuk melakukan sesuatu (@gbbr); dan pernyataan if eksplisit lebih baik daripada pernyataan return implisit ( @DavexPro , @hmage , @prologic , @natefinch).
Sejalan dengan itu, @mattn prihatin tentang "pengikatan implisit" dari hasil kesalahan ke try - koneksi tidak terlihat secara eksplisit dalam kode.

4) Menggunakan try akan mempersulit proses debug kode; misalnya, mungkin perlu untuk menulis ulang ekspresi try kembali ke pernyataan if hanya agar pernyataan debug dapat dimasukkan ( @deanveloper , @typeless , @networkimprov , others).

5) Ada beberapa kekhawatiran tentang penggunaan pengembalian bernama ( @buchanae , @adg).

Beberapa orang telah memberikan saran untuk memperbaiki atau memodifikasi proposal:

6) Beberapa telah mengambil ide penangan kesalahan opsional (@beoran) atau string format yang disediakan untuk try ( @unexge , @a8m , @eandre , @gotwarlost) untuk mendorong penanganan kesalahan yang baik.

7) @pierrec menyarankan bahwa gofmt dapat memformat ekspresi try dengan tepat agar lebih terlihat.
Alternatifnya, seseorang dapat membuat kode yang ada menjadi lebih ringkas dengan mengizinkan gofmt memformat pernyataan if yang memeriksa kesalahan pada satu baris (@zeebo).

8) @marwan-at-work berpendapat bahwa try hanya menggeser penanganan kesalahan dari pernyataan if try ke ekspresi try. Sebaliknya, jika kita ingin benar-benar menyelesaikan masalah, Go harus "memiliki" penanganan kesalahan dengan membuatnya benar-benar implisit. Tujuannya adalah untuk membuat penanganan kesalahan (yang tepat) lebih sederhana dan pengembang lebih produktif (@cpuguy).

9) Akhirnya, beberapa orang tidak suka nama try ( @beoran , @HiImJC , @dolmen) atau lebih suka simbol seperti ? ( @twisted1919 , @leaxoy , others) .

Beberapa komentar tentang umpan balik ini (diberi nomor yang sesuai):

0) Terima kasih atas umpan balik positifnya! :-)

1) Akan lebih baik untuk mempelajari lebih lanjut tentang masalah ini. Gaya pengkodean saat ini menggunakan pernyataan if untuk menguji kesalahan adalah sejelas mungkin. Sangat mudah untuk menambahkan informasi tambahan ke kesalahan, secara individual (untuk setiap if ). Seringkali masuk akal untuk menangani semua kesalahan yang terdeteksi dalam suatu fungsi dengan cara yang seragam, yang dapat dilakukan dengan defer - ini sudah dimungkinkan sekarang. Fakta bahwa kita sudah memiliki semua alat untuk penanganan kesalahan yang baik dalam bahasa, dan masalah konstruksi pengendali yang tidak ortogonal dengan defer , yang membuat kita meninggalkan mekanisme baru hanya untuk menambah kesalahan .

2) Tentu saja ada kemungkinan untuk menggunakan kata kunci atau sintaks khusus alih-alih built-in. Kata kunci baru tidak akan kompatibel ke belakang. Operator baru mungkin, tetapi tampaknya semakin tidak terlihat. Proposal rinci membahas berbagai pro dan kontra panjang lebar. Tapi mungkin kita salah menilai ini.

3) Alasan proposal ini adalah bahwa penanganan kesalahan (khususnya kode boilerplate terkait) disebutkan sebagai masalah signifikan di Go (di samping kurangnya obat generik) oleh komunitas Go. Proposal ini secara langsung membahas masalah boilerplate. Itu tidak lebih dari menyelesaikan kasus paling dasar karena kasus yang lebih kompleks lebih baik ditangani dengan apa yang sudah kita miliki. Jadi, sementara sejumlah besar orang senang dengan status quo, ada (mungkin) kontingen orang yang sama besarnya yang akan menyukai pendekatan yang lebih ramping seperti try , mengetahui dengan baik bahwa ini "adil" gula sintaksis.

4) Titik debugging adalah perhatian yang valid. Jika ada kebutuhan untuk menambahkan kode antara mendeteksi kesalahan dan return , harus menulis ulang ekspresi try menjadi pernyataan if dapat mengganggu.

5) Nilai pengembalian yang dinamai: Dokumen terperinci membahas hal ini secara panjang lebar. Jika ini adalah perhatian utama tentang proposal ini, maka kami berada di tempat yang bagus, saya pikir.

6) Argumen handler opsional ke try : Dokumen rinci membahas ini juga. Lihat bagian tentang iterasi Desain.

7) Menggunakan gofmt untuk memformat ekspresi try sedemikian rupa sehingga lebih terlihat tentu akan menjadi pilihan. Tapi itu akan menghilangkan beberapa manfaat dari try saat digunakan dalam sebuah ekspresi.

8) Kami telah mempertimbangkan untuk melihat masalah dari sudut pandang penanganan kesalahan ( handle ) daripada dari sudut pandang pengujian kesalahan ( try ). Secara khusus, kami secara singkat mempertimbangkan hanya memperkenalkan gagasan penangan kesalahan (mirip dengan draf desain asli yang dipresentasikan di Gophercon tahun lalu). Pemikirannya adalah jika (dan hanya jika) penangan dideklarasikan, dalam penugasan multi-nilai di mana nilai terakhir bertipe error , nilai itu dapat dibiarkan begitu saja dalam penugasan. Kompiler akan secara implisit memeriksa apakah itu bukan nihil, dan jika demikian bercabang ke pawang. Itu akan membuat penanganan kesalahan eksplisit hilang sepenuhnya dan mendorong semua orang untuk menulis penangan sebagai gantinya. Ini tampaknya merupakan pendekatan yang ekstrem karena akan sepenuhnya implisit - fakta bahwa cek terjadi tidak akan terlihat.

9) Bolehkah saya menyarankan agar kita tidak melepaskan nama sepeda pada saat ini. Setelah semua masalah lain diselesaikan adalah waktu yang lebih baik untuk menyempurnakan nama.

Ini bukan untuk mengatakan bahwa kekhawatiran itu tidak valid - jawaban di atas hanya menyatakan pemikiran kita saat ini. Ke depan, akan lebih baik untuk mengomentari masalah baru (atau bukti baru yang mendukung kekhawatiran ini) - hanya menyatakan kembali apa yang telah dikatakan tidak memberi kami informasi lebih lanjut.

Dan akhirnya, tampaknya tidak semua orang yang mengomentari masalah ini telah membaca dokumen terperinci. Harap lakukan sebelum berkomentar untuk menghindari mengulangi apa yang telah dikatakan. Terima kasih.

Ini bukan komentar atas proposal, tapi laporan salah ketik. Itu tidak diperbaiki sejak proposal lengkap diterbitkan, jadi saya pikir saya akan menyebutkannya:

func try(t1 T1, t1 T2, … tn Tn, te error) (T1, T2, … Tn)

seharusnya:

func try(t1 T1, t2 T2, … tn Tn, te error) (T1, T2, … Tn)

Apakah layak untuk menganalisis kode Go yang tersedia secara terbuka untuk pernyataan pemeriksaan kesalahan untuk mencoba dan mencari tahu apakah sebagian besar pemeriksaan kesalahan benar-benar berulang atau jika dalam banyak kasus, beberapa pemeriksaan dalam fungsi yang sama menambahkan informasi kontekstual yang berbeda? Proposal akan sangat masuk akal untuk kasus pertama tetapi tidak akan membantu yang terakhir. Dalam kasus terakhir, orang akan terus menggunakan if err != nil atau menyerah untuk menambahkan konteks tambahan, menggunakan try() dan resor untuk menambahkan konteks kesalahan umum per fungsi yang IMO akan berbahaya. Dengan fitur nilai kesalahan yang akan datang, saya pikir kami berharap orang-orang membungkus kesalahan dengan lebih banyak info lebih sering. Mungkin saya salah memahami proposal tetapi AFAIU, ini membantu mengurangi boilerplate hanya ketika semua kesalahan dari satu fungsi harus dibungkus dengan tepat satu cara dan tidak membantu jika suatu fungsi menangani lima kesalahan yang mungkin perlu dibungkus secara berbeda. Tidak yakin seberapa umum kasus seperti itu di alam liar (cukup umum di sebagian besar proyek saya) tetapi saya khawatir try() mungkin mendorong orang untuk menggunakan pembungkus umum per fungsi bahkan ketika masuk akal untuk membungkus kesalahan yang berbeda berbeda.

Hanya komentar singkat yang didukung dengan data kumpulan sampel kecil:

Kami mengusulkan fungsi bawaan baru yang disebut try, yang dirancang khusus untuk menghilangkan pernyataan boilerplate if yang biasanya terkait dengan penanganan kesalahan di Go

Jika ini adalah masalah inti yang diselesaikan oleh proposal ini, saya menemukan bahwa "boilerplate" ini hanya menyumbang ~1,4% dari kode saya di lusinan proyek sumber terbuka yang tersedia untuk umum dengan total ~60rb SLOC.

Penasaran apakah ada orang lain yang memiliki statistik serupa?

Pada basis kode yang jauh lebih besar seperti Go itu sendiri dengan total sekitar ~1,6 juta SLOC, ini berjumlah sekitar ~0,5% dari basis kode yang memiliki baris seperti if err != nil .

Apakah ini benar-benar masalah yang paling berdampak untuk dipecahkan dengan Go 2?

Terima kasih banyak @griesemer telah meluangkan waktu untuk membahas ide-ide semua orang dan secara eksplisit memberikan pemikiran. Saya pikir itu sangat membantu dengan persepsi bahwa masyarakat didengar dalam prosesnya.

  1. @pierrec menyarankan agar gofmt dapat memformat ekspresi try yang sesuai untuk membuatnya lebih terlihat.
    Sebagai alternatif, seseorang dapat membuat kode yang ada menjadi lebih ringkas dengan mengizinkan gofmt memformat pernyataan if yang memeriksa kesalahan pada satu baris (@zeebo).
  1. Menggunakan gofmt untuk memformat ekspresi try sedemikian rupa sehingga lebih terlihat tentu akan menjadi pilihan. Tapi itu akan menghilangkan beberapa manfaat dari try saat digunakan dalam ekspresi.

Ini adalah pemikiran berharga tentang mengharuskan gofmt untuk memformat try , tapi saya tertarik jika ada pemikiran khusus tentang gofmt mengizinkan pemeriksaan pernyataan if kesalahan menjadi satu baris. Proposal disatukan dengan format try , tapi saya pikir itu adalah hal yang sepenuhnya ortogonal. Terima kasih.

@griesemer terima kasih atas kerja luar biasa melalui semua komentar dan menjawab sebagian besar jika tidak semua umpan balik

Satu hal yang tidak dibahas dalam umpan balik Anda adalah gagasan untuk menggunakan bagian perkakas/pemeriksaan bahasa Go untuk meningkatkan pengalaman penanganan kesalahan, daripada memperbarui sintaks Go.

Misalnya, dengan pendaratan LSP baru ( gopls ), sepertinya ini adalah tempat yang sempurna untuk menganalisis tanda tangan suatu fungsi dan menangani boilerplate penanganan kesalahan untuk pengembang, dengan pembungkusan dan pemeriksaan yang tepat juga.

@griesemer Saya yakin ini tidak dipikirkan dengan matang, tetapi saya mencoba mengubah saran Anda lebih dekat ke sesuatu yang saya rasa nyaman di sini: https://www.reddit.com/r/golang/comments/bwvyhe /proposal_a_builtin_go_error_check_function_try/eq22bqa?utm_source=share&utm_medium=web2x

@zeebo Akan mudah untuk membuat format $ gofmt if err != nil { return ...., err } dalam satu baris. Agaknya itu hanya untuk jenis pola if khusus ini, tidak semua pernyataan if "pendek"?

Sejalan dengan itu, ada kekhawatiran tentang try tidak terlihat karena berada di jalur yang sama dengan logika bisnis. Kami memiliki semua opsi ini:

Gaya saat ini:

a, b, c, ... err := BusinessLogic(...)
if err != nil {
   return ..., err
}

Satu baris if :

a, b, c, ... err := BusinessLogic(...)
if err != nil { return ..., err }

try pada baris terpisah (!):

a, b, c, ... err := BusinessLogic(...)
try(err)

try seperti yang diusulkan:

a, b, c := try(BusinessLogic(...))

Baris pertama dan terakhir tampak paling jelas (bagi saya), terutama setelah digunakan untuk mengenali try apa adanya. Dengan baris terakhir, kesalahan secara eksplisit diperiksa, tetapi karena itu (biasanya) bukan tindakan utama, itu sedikit lebih banyak di latar belakang.

@marwan-at-work Saya tidak yakin apa yang Anda usulkan bahwa alat itu berfungsi untuk Anda. Apakah Anda menyarankan agar mereka menyembunyikan penanganan kesalahan?

@dpinela

@guybrand Itu sepertinya proposal yang sama sekali berbeda; Saya pikir Anda harus mengajukannya sebagai masalah sendiri sehingga yang ini dapat fokus membahas proposal @griesemer .

IMO proposal saya hanya berbeda dalam sintaks, artinya:

  • Tujuan serupa dalam konten dan prioritas.
  • Gagasan menangkap setiap err dalam barisnya sendiri dan karenanya (jika tidak nihil) keluar dari fungsi saat melewati fungsi handler serupa (pseudo asm - ini adalah "jnz" dan "panggilan").
  • Ini bahkan berarti jumlah baris dalam badan fungsi (tanpa penangguhan) dan aliran akan terlihat persis sama (dan karenanya AST mungkin akan menjadi sama juga)

jadi perbedaan utamanya adalah apakah kita membungkus panggilan fungsi asli dengan try(func()) yang akan selalu menganalisis var terakhir ke jnz panggilan atau menggunakan nilai pengembalian aktual untuk melakukannya.

Saya tahu ini terlihat berbeda, tetapi sebenarnya sangat mirip dalam konsep.
Di sisi lain - jika Anda mencoba yang biasa .... tangkap dalam banyak bahasa mirip-c - itu akan menjadi implementasi yang sangat berbeda, keterbacaan yang berbeda, dll.

Namun saya benar-benar berpikir untuk menulis proposal, terima kasih atas idenya.

@griesemer

Saya tidak yakin apa yang Anda usulkan bahwa alat itu berfungsi untuk Anda. Apakah Anda menyarankan agar mereka menyembunyikan penanganan kesalahan?

Justru sebaliknya: Saya menyarankan bahwa gopls secara opsional dapat menulis boilerplate penanganan kesalahan untuk Anda.

Seperti yang Anda sebutkan dalam komentar terakhir Anda:

Alasan proposal ini adalah bahwa penanganan kesalahan (khususnya kode boilerplate terkait) disebutkan sebagai masalah signifikan di Go (di samping kurangnya obat generik) oleh komunitas Go

Jadi inti masalahnya adalah programmer akhirnya menulis banyak kode boilerplate. Jadi masalahnya adalah tentang menulis, bukan membaca. Oleh karena itu, saran saya adalah: biarkan komputer (tooling/gopls) melakukan penulisan untuk programmer dengan menganalisis tanda tangan fungsi dan menempatkan klausa penanganan kesalahan yang tepat.

Sebagai contoh:

// user begins to write this function: 
func openFile(path string) ([]byte, error) {
  file, err := os.Open(path)
  defer file.Close()
  bts, err := ioutil.ReadAll(file)
  return bts, nil
}

Kemudian pengguna mengaktifkan alat, mungkin hanya dengan menyimpan file (mirip dengan cara kerja gofmt/goimports) dan gopls akan melihat fungsi ini, menganalisis tanda tangan kembalinya, dan menambahkan kode menjadi ini:

// user has triggered the tool (by saving the file, or code action)
func openFile(path string) ([]byte, error) {
  file, err := os.Open(path)
  if err != nil {
    return nil, fmt.Errorf("openFile: %w", err)
  }
  defer file.Close()
  bts, err := ioutil.ReadAll(file)
  if err != nil {
    return nil, fmt.Errorf("openFile: %w", err)
  }
  return bts, nil
}

Dengan cara ini, kami mendapatkan yang terbaik dari kedua dunia: kami mendapatkan keterbacaan/eksplisititas sistem penanganan kesalahan saat ini, dan programmer tidak menulis boilerplate penanganan kesalahan apa pun . Lebih baik lagi, pengguna dapat melanjutkan dan memodifikasi blok penanganan kesalahan nanti untuk melakukan perilaku yang berbeda: gopls dapat memahami bahwa blok tersebut ada, dan tidak akan mengubahnya.

Bagaimana alat tahu bahwa saya bermaksud menangani err nanti dalam fungsi alih-alih kembali lebih awal? Meski jarang, tapi kode tetap saya tulis.

Saya minta maaf jika ini telah diangkat sebelumnya, tetapi saya tidak dapat menemukan penyebutan itu.

try(DoSomething()) cocok untuk saya, dan masuk akal: kodenya mencoba melakukan sesuatu. try(err) , OTOH, terasa sedikit aneh, secara semantik: bagaimana cara mencoba kesalahan? Dalam pikiran saya, seseorang dapat _test_ atau _check_ kesalahan, tetapi _mencoba_ tampaknya tidak benar.

Saya menyadari bahwa mengizinkan try(err) penting untuk alasan konsistensi: Saya kira akan aneh jika try(DoSomething()) berhasil, tetapi err := DoSomething(); try(err) tidak. Namun, sepertinya try(err) terlihat sedikit canggung di halaman. Saya tidak dapat memikirkan fungsi bawaan lainnya yang dapat dibuat terlihat aneh ini dengan mudah.

Saya tidak punya saran konkret tentang masalah ini, tetapi saya tetap ingin melakukan pengamatan ini.

@griesemer Terima kasih. Memang, proposal itu hanya untuk return , tetapi saya menduga membiarkan satu pernyataan menjadi satu baris akan baik. Misalnya dalam pengujian, seseorang dapat, tanpa perubahan pada pustaka pengujian, memiliki

if err != nil { t.Fatal(err) }

Baris pertama dan terakhir tampak paling jelas (bagi saya), terutama setelah digunakan untuk mengenali try apa adanya. Dengan baris terakhir, kesalahan secara eksplisit diperiksa, tetapi karena itu (biasanya) bukan tindakan utama, itu sedikit lebih banyak di latar belakang.

Dengan baris terakhir, sebagian biaya disembunyikan. Jika Anda ingin membuat anotasi kesalahan, yang saya yakini secara vokal dikatakan oleh komunitas sebagai praktik terbaik yang diinginkan dan harus didorong, seseorang harus mengubah tanda tangan fungsi untuk memberi nama argumen dan berharap satu defer diterapkan ke setiap keluar di badan fungsi, jika tidak try tidak memiliki nilai; bahkan mungkin negatif karena kemudahannya.

Saya tidak punya lagi untuk menambahkan yang saya yakin belum dikatakan.


Saya tidak melihat bagaimana menjawab pertanyaan ini dari dokumen desain. Apa yang dilakukan kode ini:

func foo() (err error) {
    src := try(getReader())
    if src != nil {
        n, err := src.Read(nil)
        if err == io.EOF {
            return nil
        }
        try(err)
        println(n)
    }
    return nil
}

Pemahaman saya adalah bahwa itu akan berubah menjadi

func foo() (err error) {
    tsrc, te := getReader()
    if err != nil {
        err = te
        return
    }
    src := tsrc

    if src != nil {
        n, err := src.Read(nil)
        if err == io.EOF {
            return nil
        }

        terr := err
        if terr != nil {
            err = terr
            return
        }

        println(n)
    }
    return nil
}

yang gagal dikompilasi karena err dibayangi selama pengembalian telanjang. Apakah ini tidak akan dikompilasi? Jika demikian, itu adalah kegagalan yang sangat halus, dan sepertinya tidak terlalu mungkin terjadi. Jika tidak, maka lebih banyak yang terjadi daripada gula.

@marwan-di-kerja

Seperti yang Anda sebutkan dalam komentar terakhir Anda:

Alasan proposal ini adalah bahwa penanganan kesalahan (khususnya kode boilerplate terkait) disebutkan sebagai masalah signifikan di Go (di samping kurangnya obat generik) oleh komunitas Go

Jadi inti masalahnya adalah programmer akhirnya menulis banyak kode boilerplate. Jadi masalahnya adalah tentang menulis, bukan membaca.

Saya pikir itu sebenarnya sebaliknya - bagi saya gangguan terbesar dengan boilerplate penanganan kesalahan saat ini tidak begitu banyak harus mengetiknya, melainkan bagaimana menyebarkan jalur bahagia fungsi secara vertikal di layar, membuatnya lebih sulit untuk dipahami di sekilas. Efeknya sangat terasa dalam kode I/O-berat, di mana biasanya ada blok boilerplate di antara setiap dua operasi. Bahkan versi sederhana dari CopyFile membutuhkan ~20 baris meskipun sebenarnya hanya melakukan lima langkah: open source, defer close source, open destination, copy source -> destination, close destination.

Masalah lain dengan sintaks saat ini, adalah bahwa, seperti yang saya sebutkan sebelumnya, jika Anda memiliki rantai operasi yang masing-masing dapat mengembalikan kesalahan, sintaks saat ini memaksa Anda untuk memberi nama pada semua hasil antara, bahkan jika Anda lebih suka meninggalkan beberapa anonim. Ketika ini terjadi, itu juga mengganggu keterbacaan karena Anda harus menghabiskan siklus otak untuk menguraikan nama-nama itu, meskipun itu tidak terlalu informatif.

Saya suka try pada baris terpisah.
Dan saya berharap dapat menentukan fungsi handler secara mandiri.

func try(error, optional func(error)error)
func (p *pgStore) DoWork() error {
    tx, err := p.handle.Begin()
    try(err)

    handle := func(err error) error {
        tx.Rollback()
        return err
    }

    var res int64
    _, err = tx.QueryRow(`INSERT INTO table (...) RETURNING c1`, ...).Scan(&res)
    try(err, handle)

    _, err = tx.Exec(`INSERT INTO table2 (...) VALUES ($1)`, res)
    try(err, handle)

    return tx.Commit()
}

@zeebo : Contoh yang saya berikan adalah terjemahan 1:1. Yang pertama (tradisional if ) tidak menangani kesalahan, dan yang lain tidak. Jika yang pertama menangani kesalahan, dan jika ini adalah satu-satunya tempat kesalahan diperiksa dalam suatu fungsi, contoh pertama (menggunakan if ) mungkin merupakan pilihan yang tepat untuk menulis kode. Jika ada beberapa pemeriksaan kesalahan, yang semuanya menggunakan penanganan kesalahan (pembungkusan) yang sama, katakan karena semuanya menambahkan informasi tentang fungsi saat ini, seseorang dapat menggunakan pernyataan defer untuk menangani semua kesalahan di satu tempat. Secara opsional, seseorang dapat menulis ulang if 's menjadi try 's (atau biarkan saja). Jika ada beberapa kesalahan yang harus diperiksa, dan semuanya menangani kesalahan secara berbeda (yang mungkin merupakan tanda bahwa perhatian fungsi terlalu luas dan mungkin perlu dipisahkan), menggunakan if 's adalah jalan untuk pergi. Ya, ada lebih dari satu cara untuk melakukan hal yang sama, dan pilihan yang tepat tergantung pada kode serta selera pribadi. Meskipun kami berusaha keras di Go untuk "satu cara untuk melakukan satu hal", ini tentu saja tidak demikian, terutama untuk konstruksi umum. Misalnya, ketika urutan if - else - if menjadi terlalu panjang, terkadang switch mungkin lebih tepat. Terkadang deklarasi variabel var x int mengekspresikan maksud lebih baik daripada x := 0 , dan seterusnya (meskipun tidak semua orang senang dengan ini).

Mengenai pertanyaan Anda tentang "menulis ulang": Tidak, tidak akan ada kesalahan kompilasi. Perhatikan bahwa penulisan ulang terjadi secara internal (dan mungkin lebih efisien daripada yang disarankan oleh pola kode), dan kompiler tidak perlu mengeluh tentang pengembalian yang dibayangi. Dalam contoh Anda, Anda mendeklarasikan variabel err lokal dalam lingkup bersarang. try masih akan memiliki akses langsung ke variabel hasil err , tentu saja. Penulisan ulang mungkin terlihat lebih seperti ini di balik sampul.

[diedit] PS: Jawaban yang lebih baik adalah: try bukan pengembalian telanjang (meskipun penulisan ulang terlihat seperti itu). Bagaimanapun, seseorang secara eksplisit memberikan try argumen yang berisi (atau merupakan) kesalahan yang dikembalikan jika tidak nil . Kesalahan bayangan untuk pengembalian telanjang adalah kesalahan pada sumbernya (bukan terjemahan yang mendasari sumbernya. Kompilator tidak memerlukan kesalahan tersebut.

Jika tipe pengembalian akhir fungsi menyeluruh bukan dari kesalahan tipe, dapatkah kita panik?

Ini akan membuat builtin lebih fleksibel (seperti memuaskan kekhawatiran saya di #32219)

@pjebs Ini telah dipertimbangkan dan diputuskan. Harap baca dokumen desain terperinci (yang secara eksplisit merujuk pada masalah Anda tentang hal ini).

Saya juga ingin menunjukkan bahwa try() diperlakukan sebagai ekspresi meskipun berfungsi sebagai pernyataan pengembalian. Ya, saya tahu try adalah makro bawaan tetapi sebagian besar pengguna akan menggunakan ini seperti pemrograman fungsional, saya kira.

func doSomething() (error, error, error, error, error) {
   ...
}
try(try(try(try(try(doSomething)))))

Desainnya mengatakan Anda menjelajahi menggunakan panic alih-alih kembali dengan kesalahan.

Saya menyoroti perbedaan halus:

Lakukan persis seperti yang dinyatakan proposal Anda saat ini, kecuali hapus batasan bahwa fungsi menyeluruh harus memiliki tipe pengembalian akhir dari tipe error .

Jika tidak memiliki tipe pengembalian akhir error => panic
Jika menggunakan coba untuk deklarasi variabel tingkat paket => panik (menghapus kebutuhan untuk konvensi MustXXX( ) )

Untuk pengujian unit, perubahan bahasa sederhana.

@mattn , saya sangat ragu ada banyak orang yang akan menulis kode seperti itu.

@pjebs , semantik itu - panik jika tidak ada hasil kesalahan dalam fungsi saat ini - persis seperti yang dibahas oleh dokumen desain di https://github.com/golang/proposal/blob/master/design/32437-try-builtin. md#diskusi.

Selanjutnya, dalam upaya untuk membuat try berguna tidak hanya di dalam fungsi dengan hasil kesalahan, semantik try bergantung pada konteksnya: Jika try digunakan pada tingkat paket, atau jika dipanggil di dalam fungsi tanpa hasil kesalahan, try akan panik saat menemui kesalahan. (Selain itu, karena properti itu, built-in disebut must daripada try dalam proposal itu.) Memiliki try (atau must) berperilaku dalam cara yang peka konteks ini tampak alami dan juga cukup berguna: Ini akan memungkinkan penghapusan banyak fungsi penolong yang harus ditentukan pengguna yang saat ini digunakan dalam ekspresi inisialisasi variabel tingkat paket. Itu juga akan membuka kemungkinan menggunakan try in unit test melalui paket pengujian.

Namun, sensitivitas konteks dari try dianggap penuh: Misalnya, perilaku fungsi yang berisi panggilan try dapat berubah secara diam-diam (dari mungkin panik menjadi tidak panik, dan sebaliknya) jika hasil kesalahan ditambahkan atau dihapus dari tanda tangan. Tampaknya ini properti yang terlalu berbahaya. Solusi yang jelas adalah dengan membagi fungsionalitas try menjadi dua fungsi terpisah, must dan try (sangat mirip dengan apa yang disarankan oleh masalah #31442). Tapi itu akan membutuhkan dua fungsi built-in baru, dengan hanya mencoba terhubung langsung ke kebutuhan mendesak untuk dukungan penanganan kesalahan yang lebih baik.

@pjebs Itulah _persis_ yang kami pertimbangkan dalam proposal sebelumnya (lihat dokumen terperinci, bagian tentang iterasi Desain, paragraf ke-4):

Selanjutnya, dalam upaya untuk membuat try berguna tidak hanya di dalam fungsi dengan hasil kesalahan, semantik try bergantung pada konteksnya: Jika try digunakan pada tingkat paket, atau jika dipanggil di dalam fungsi tanpa hasil kesalahan, try akan panik saat menemui kesalahan. (Sebagai tambahan, karena properti itu, built-in dipanggil harus daripada mencoba dalam proposal itu.)

Konsensus (Go Team internal) adalah bahwa akan membingungkan jika try bergantung pada konteks dan bertindak secara berbeda. Misalnya, menambahkan hasil kesalahan ke suatu fungsi (atau menghapusnya) dapat secara diam-diam mengubah perilaku fungsi dari panik menjadi tidak panik (atau sebaliknya).

@griesemer Terima kasih atas klarifikasi tentang penulisan ulang. Saya senang itu akan dikompilasi.

Saya mengerti bahwa contohnya adalah terjemahan yang tidak menjelaskan kesalahannya. Saya mencoba berargumen bahwa try mempersulit untuk melakukan anotasi kesalahan yang baik dalam situasi umum, dan anotasi kesalahan itu sangat penting bagi komunitas. Sebagian besar komentar sejauh ini telah mengeksplorasi cara untuk menambahkan dukungan anotasi yang lebih baik ke try .

Tentang harus menangani kesalahan secara berbeda, saya tidak setuju bahwa itu adalah tanda bahwa perhatian fungsi terlalu luas. Saya telah menerjemahkan beberapa contoh kode nyata yang diklaim dari komentar dan menempatkannya di dropdown di bagian bawah komentar asli saya, dan contoh di https://github.com/golang/go/issues/32437#issuecomment - 499007288 Saya pikir menunjukkan kasus umum dengan baik:

func (c *Config) Build() error {
    pkgPath, err := c.load()
    if err != nil { return nil, errors.WithMessage(err, "load config dir") }

    b := bytes.NewBuffer(nil)
    err = templates.ExecuteTemplate(b, "main", c)
    if err != nil { return nil, errors.WithMessage(err, "execute main template") }

    buf, err := format.Source(b.Bytes())
    if err != nil { return nil, errors.WithMessage(err, "format main template") }

    target := fmt.Sprintf("%s.go", filename(pkgPath))
    err = ioutil.WriteFile(target, buf, 0644)
    if err != nil { return nil, errors.WithMessagef(err, "write file %s", target) }
    // ...
}

Tujuan fungsi itu adalah untuk mengeksekusi template pada beberapa data ke dalam file. Saya tidak percaya itu perlu dipecah, dan akan sangat disayangkan jika semua kesalahan itu baru saja mendapatkan garis yang mereka buat dari penangguhan. Itu mungkin baik-baik saja untuk pengembang, tetapi itu kurang berguna bagi pengguna.

Saya pikir itu juga sedikit sinyal betapa halusnya bug defer wrap(&err, "message: %v", err) dan bagaimana mereka tersandung bahkan oleh programmer Go yang berpengalaman.


Untuk meringkas argumen saya : Saya pikir anotasi kesalahan lebih penting daripada pemeriksaan kesalahan berbasis ekspresi, dan kita bisa mendapatkan sedikit pengurangan kebisingan dengan mengizinkan pemeriksaan kesalahan berbasis pernyataan menjadi satu baris, bukan tiga. Terima kasih.

@griesemer maaf saya membaca bagian berbeda yang membahas kepanikan dan tidak melihat diskusi tentang bahaya.

@zeebo Terima kasih untuk contoh ini. Sepertinya menggunakan pernyataan if adalah pilihan yang tepat dalam kasus ini. Tapi intinya, memformat if menjadi satu baris dapat sedikit menyederhanakan ini.

Saya ingin sekali lagi mengemukakan gagasan tentang handler sebagai argumen kedua try , tetapi dengan tambahan bahwa argumen handler menjadi _required_, tetapi nihil-mampu. Ini menjadikan penanganan kesalahan sebagai default, bukan pengecualian. Dalam kasus di mana Anda benar-benar ingin meneruskan kesalahan tanpa perubahan, cukup berikan nilai nil ke pawang dan try akan berperilaku seperti di proposal asli, tetapi argumen nil akan bertindak sebagai isyarat visual bahwa kesalahan tidak ditangani. Akan lebih mudah ditangkap selama peninjauan kode.

file := try(os.Open("my_file.txt"), nil)

Apa yang harus terjadi jika pawang disediakan tetapi nihil? Haruskah mencoba panik atau memperlakukannya sebagai penangan kesalahan yang tidak ada?

Seperti disebutkan di atas, try akan berperilaku sesuai dengan proposal asli. Tidak akan ada yang namanya penangan kesalahan yang tidak ada, hanya yang nihil.

Bagaimana jika pawang dipanggil dengan kesalahan non-nil dan kemudian mengembalikan hasil nihil? Apakah ini berarti kesalahannya "dibatalkan"? Atau haruskah fungsi terlampir kembali dengan kesalahan nihil?

Saya percaya bahwa fungsi terlampir akan kembali dengan kesalahan nihil. Akan sangat membingungkan jika try terkadang dapat melanjutkan eksekusi bahkan setelah menerima nilai kesalahan non-nihil. Ini akan memungkinkan penangan untuk "mengurus" kesalahan dalam beberapa keadaan. Perilaku ini dapat berguna dalam fungsi gaya "dapatkan atau buat", misalnya.

func getOrCreateObject(obj *object) error {
    defaultObjectHandler := func(err error) error {
        if err == ObjectDoesNotExistErr {
            *obj = object{}
            return nil
        }
        return fmt.Errorf("getting or creating object: %v", err)
    }

    *obj = try(db.getObject(), defaultObjectHandler)
}

Juga tidak jelas apakah mengizinkan penangan kesalahan opsional akan membuat pemrogram mengabaikan penanganan kesalahan yang tepat sama sekali. Juga akan mudah untuk melakukan penanganan kesalahan yang tepat di mana-mana tetapi melewatkan satu kejadian percobaan. Dan seterusnya.

Saya percaya bahwa kedua masalah ini diringankan dengan menjadikan pawang sebagai argumen yang diperlukan dan tidak ada habisnya. Ini membutuhkan pemrogram untuk membuat keputusan yang sadar dan eksplisit bahwa mereka tidak akan menangani kesalahan mereka.

Sebagai bonus, saya pikir membutuhkan penangan kesalahan juga membuat try s sangat bersarang karena kurang singkat. Beberapa orang mungkin melihat ini sebagai kerugian, tapi saya pikir ini adalah keuntungan.

@velovix Saya suka idenya, tetapi mengapa penangan kesalahan harus diperlukan? Tidak bisakah nil secara default? Mengapa kita membutuhkan "petunjuk visual"?

@griesemer Bagaimana jika ide @velovix diadopsi tetapi dengan builtin berisi fungsi yang telah ditentukan sebelumnya yang mengubah err menjadi panik DAN Kami menghapus persyaratan bahwa fungsi over-arching memiliki nilai pengembalian kesalahan?

Idenya adalah, jika fungsi menyeluruh tidak mengembalikan kesalahan, menggunakan try tanpa penangan kesalahan adalah kesalahan waktu kompilasi.

Penangan kesalahan juga dapat digunakan untuk membungkus kesalahan yang akan segera dikembalikan menggunakan berbagai pustaka dll di lokasi kesalahan, alih-alih defer di bagian atas yang memodifikasi kesalahan yang dikembalikan bernama.

@pjebs

mengapa penangan kesalahan harus diperlukan? Tidak bisakah itu menjadi nihil secara default? Mengapa kita membutuhkan "petunjuk visual"?

Ini untuk mengatasi kekhawatiran bahwa

  1. Proposal try seperti sekarang dapat membuat orang enggan memberikan konteks pada kesalahan mereka karena melakukannya tidak begitu mudah.

Memiliki penangan di tempat pertama membuat penyediaan konteks lebih mudah, dan menjadikan penangan sebagai argumen yang diperlukan mengirim pesan: Kasus umum yang direkomendasikan adalah menangani atau mengontekstualisasikan kesalahan dalam beberapa cara, tidak hanya meneruskannya ke tumpukan. Ini sejalan dengan rekomendasi umum dari komunitas Go.

  1. Kekhawatiran dari dokumen proposal asli. Saya mengutipnya di komentar pertama saya:

Juga tidak jelas apakah mengizinkan penangan kesalahan opsional akan membuat pemrogram mengabaikan penanganan kesalahan yang tepat sama sekali. Juga akan mudah untuk melakukan penanganan kesalahan yang tepat di mana-mana tetapi melewatkan satu kejadian percobaan. Dan seterusnya.

Harus melewati nil eksplisit membuat lebih sulit untuk lupa menangani kesalahan dengan benar. Anda harus secara eksplisit memutuskan untuk tidak menangani kesalahan alih-alih melakukannya secara implisit dengan meninggalkan argumen.

Memikirkan lebih jauh tentang pengembalian bersyarat yang disebutkan secara singkat di https://github.com/golang/go/issues/32437#issuecomment -498947603.
Kelihatannya
return if f, err := os.Open("/my/file/path"); err != nil
akan lebih sesuai dengan tampilan if Go yang ada.

Jika kita menambahkan aturan untuk pernyataan return if bahwa
ketika ekspresi kondisi terakhir (seperti err != nil ) tidak ada,dan variabel terakhir dari deklarasi dalam pernyataan return if bertipe error ,maka nilai dari variabel terakhir akan secara otomatis dibandingkan dengan nil sebagai kondisi implisit.

Maka pernyataan return if dapat disingkat menjadi:
return if f, err := os.Open("my/file/path")

Yang sangat dekat dengan rasio signal-noise yang disediakan oleh try .
Jika kita mengubah return if menjadi try , menjadi
try f, err := os.Open("my/file/path")
Sekali lagi menjadi mirip dengan variasi lain yang diusulkan dari try di utas ini, setidaknya secara sintaksis.
Secara pribadi, saya masih lebih suka return if daripada try dalam hal ini karena ini membuat titik keluar dari suatu fungsi menjadi sangat eksplisit. Misalnya, ketika debugging saya sering menyorot kata kunci return di dalam editor untuk mengidentifikasi semua titik keluar dari fungsi besar.

Sayangnya, tampaknya tidak cukup membantu dengan ketidaknyamanan memasukkan debug logging juga.
Kecuali kami juga mengizinkan blok body untuk return if , seperti
Asli:

        return if f, err := os.Open("my/path") 

Saat men-debug:

-       return if f, err := os.Open("my/path") 
+       return if f, err := os.Open("my/path") {
+               fmt.Printf("DEBUG: os.Open: %s\n", err)
+       }

Arti dari blok tubuh return if sudah jelas, saya berasumsi. Ini akan dieksekusi sebelum defer dan kembali.

Yang mengatakan, saya tidak memiliki keluhan dengan pendekatan penanganan kesalahan yang ada di Go.
Saya lebih prihatin tentang bagaimana penambahan penanganan kesalahan baru akan berdampak pada kebaikan Go saat ini.

@velovix Kami sangat menyukai ide try dengan fungsi handler eksplisit sebagai argumen ke-2. Tetapi ada terlalu banyak pertanyaan yang tidak memiliki jawaban yang jelas, seperti yang dinyatakan oleh dokumen desain. Anda telah menjawab beberapa dari mereka dengan cara yang tampaknya masuk akal bagi Anda. Sangat mungkin (dan itu adalah pengalaman kami di dalam Tim Go), bahwa orang lain berpikir bahwa jawaban yang benar adalah jawaban yang sangat berbeda. Misalnya, Anda menyatakan bahwa argumen handler harus selalu diberikan, tetapi dapat berupa nil , untuk membuatnya eksplisit, kami tidak peduli dengan penanganan kesalahan. Sekarang apa yang terjadi jika seseorang memberikan nilai fungsi (bukan nil literal), dan nilai fungsi itu (disimpan dalam variabel) ternyata nihil? Dengan analogi dengan nilai nil eksplisit, tidak diperlukan penanganan. Tetapi orang lain mungkin berpendapat bahwa ini adalah bug dalam kode. Atau, sebagai alternatif, seseorang dapat mengizinkan argumen penangan bernilai nihil, tetapi kemudian suatu fungsi mungkin secara tidak konsisten menangani kesalahan dalam beberapa kasus dan tidak dalam kasus lain, dan tidak selalu jelas dari kode yang mana, karena tampaknya penangan selalu ada . Argumen lain adalah bahwa lebih baik memiliki deklarasi tingkat atas dari penangan kesalahan karena itu membuatnya sangat jelas bahwa fungsi memang menangani kesalahan. Oleh karena itu defer . Mungkin ada lebih.

Akan lebih baik untuk mempelajari lebih lanjut tentang kekhawatiran ini. Gaya pengkodean saat ini menggunakan pernyataan if untuk menguji kesalahan adalah sejelas mungkin. Sangat mudah untuk menambahkan informasi tambahan ke kesalahan, secara individual (untuk masing-masing jika). Seringkali masuk akal untuk menangani semua kesalahan yang terdeteksi dalam suatu fungsi dengan cara yang seragam, yang dapat dilakukan dengan penundaan - ini sudah dimungkinkan sekarang. Fakta bahwa kita sudah memiliki semua alat untuk penanganan kesalahan yang baik dalam bahasa, dan masalah konstruk penangan yang tidak ortogonal untuk ditunda, yang membuat kita meninggalkan mekanisme baru semata-mata untuk menambah kesalahan.

@griesemer - IIUC, Anda mengatakan bahwa untuk konteks kesalahan yang bergantung pada situs panggilan, pernyataan if saat ini baik-baik saja. Padahal, fungsi try baru ini berguna untuk kasus-kasus di mana menangani banyak kesalahan di satu tempat berguna.

Saya percaya kekhawatirannya adalah bahwa, walaupun hanya melakukan if err != nil { return err} mungkin baik-baik saja untuk beberapa kasus, biasanya disarankan untuk menghias kesalahan sebelum kembali. Dan proposal ini tampaknya membahas yang sebelumnya dan tidak berbuat banyak untuk yang terakhir. Yang pada dasarnya berarti orang akan didorong untuk menggunakan pola pengembalian yang mudah.

@agnivade Anda benar, proposal ini sama sekali tidak membantu dengan dekorasi kesalahan (tetapi untuk merekomendasikan penggunaan defer ). Salah satu alasannya adalah bahwa mekanisme bahasa untuk ini sudah ada. Segera setelah dekorasi kesalahan diperlukan, terutama atas dasar kesalahan individu, jumlah tambahan teks sumber untuk kode dekorasi membuat if kurang memberatkan dibandingkan. Ini adalah kasus di mana tidak ada dekorasi yang diperlukan, atau di mana dekorasi selalu sama, di mana boilerplate menjadi gangguan yang terlihat dan kemudian mengurangi kode penting.

Orang-orang sudah didorong untuk menggunakan pola pengembalian yang mudah, try atau tidak try , hanya ada sedikit untuk ditulis. Kalau dipikir-pikir, _satu-satunya cara untuk mendorong dekorasi kesalahan adalah membuatnya wajib_, karena apa pun dukungan bahasa yang tersedia, kesalahan dekorasi akan membutuhkan lebih banyak pekerjaan.

Salah satu cara untuk mempermanis kesepakatan adalah dengan hanya mengizinkan sesuatu seperti try (atau notasi pintasan analog lainnya) _jika_ handler eksplisit (mungkin kosong) disediakan di suatu tempat (perhatikan bahwa desain draf asli tidak memiliki persyaratan, baik).

Saya tidak yakin kami ingin melangkah sejauh ini. Biarkan saya menyatakan kembali bahwa banyak kode yang sangat bagus, katakanlah internal perpustakaan, tidak perlu menghiasi kesalahan di mana-mana. Tidak apa-apa untuk menyebarkan kesalahan dan menghiasnya tepat sebelum mereka meninggalkan titik masuk API, misalnya. (Faktanya, mendekorasinya di mana-mana hanya akan menyebabkan kesalahan yang terlalu didekorasi, dengan penyebab sebenarnya tersembunyi, mempersulit untuk menemukan kesalahan penting; sangat mirip dengan pencatatan yang terlalu bertele-tele dapat mempersulit untuk melihat apa yang sebenarnya terjadi).

Saya pikir kita juga bisa menambahkan fungsi tangkap , yang akan menjadi pasangan yang bagus, jadi:

func a() int {
  x := randInt()
  // let's assume that this is what recruiters should "fix" for us
  // or this happens in 3rd-party package.
  if x % 1337 != 0 {
    panic("not l33t enough")
  }
  return x
}

func b() error {
  // if a() panics, then x = 0, err = error{"not l33t enough"}
  x, err := catch(a())
  if err != nil {
    return err
  }
  sendSomewhereElse(x)
  return nil
}

// which could be simplified even further

func c() error {
  x := try(catch(a()))
  sendSomewhereElse(x)
  return nil
}

dalam contoh ini, catch() akan recover() panik dan return ..., panicValue .
tentu saja, kami memiliki kasus sudut yang jelas di mana kami memiliki fungsi, yang juga mengembalikan kesalahan. dalam hal ini saya pikir akan lebih mudah untuk hanya melewati nilai kesalahan.

jadi, pada dasarnya, Anda kemudian dapat menggunakan catch() untuk benar-benar memulihkan() panik dan mengubahnya menjadi kesalahan.
ini terlihat cukup lucu bagi saya, karena Go sebenarnya tidak memiliki pengecualian, tetapi dalam kasus ini kami memiliki pola try()-catch() yang cukup rapi, yang juga tidak akan meledakkan seluruh basis kode Anda dengan sesuatu seperti Java ( catch(Throwable) di Utama + throws LiterallyAnything ). Anda dapat dengan mudah memproses kepanikan seseorang seperti itu adalah kesalahan biasa. Saat ini saya memiliki sekitar 6mln+ LoC di Go dalam proyek saya saat ini, dan saya pikir ini akan menyederhanakan hal-hal setidaknya untuk saya.

@griesemer Terima kasih atas rekap diskusi Anda.

Saya melihat satu hal yang hilang di sana: beberapa orang berpendapat bahwa kita harus menunggu dengan fitur ini sampai kita memiliki obat generik, yang diharapkan akan memungkinkan kita untuk memecahkan masalah ini dengan cara yang lebih elegan.

Selain itu, saya juga menyukai saran @velovix , dan meskipun saya menghargai bahwa ini menimbulkan beberapa pertanyaan seperti yang dijelaskan dalam spesifikasi, saya pikir ini dapat dengan mudah dijawab dengan cara yang masuk akal, seperti yang sudah dilakukan oleh @velovix .

Sebagai contoh:

  • Apa yang terjadi jika seseorang memberikan nilai fungsi (bukan literal nihil), dan nilai fungsi itu (disimpan dalam variabel) ternyata nihil? => Jangan menangani kesalahan, titik. Ini berguna jika penanganan kesalahan bergantung pada konteks dan variabel penangan diatur tergantung pada apakah penanganan kesalahan diperlukan atau tidak. Ini bukan bug, melainkan fitur. :)

  • Argumen lain adalah bahwa lebih baik memiliki deklarasi tingkat atas dari penangan kesalahan karena itu membuatnya sangat jelas bahwa fungsi memang menangani kesalahan. => Jadi, tentukan pengendali kesalahan di bagian atas fungsi sebagai fungsi penutupan bernama dan gunakan itu, jadi juga sangat jelas bahwa kesalahan harus ditangani. Ini bukan masalah serius, lebih merupakan persyaratan gaya.

Apa kekhawatiran lain yang ada? Saya cukup yakin mereka semua dapat dijawab dengan cara yang sama dengan cara yang masuk akal.

Akhirnya, seperti yang Anda katakan, "salah satu cara untuk mempermanis kesepakatan adalah dengan hanya mengizinkan sesuatu seperti try (atau notasi pintasan analog apa pun) jika penangan eksplisit (mungkin kosong) disediakan di suatu tempat". Saya pikir jika kita ingin melanjutkan proposal ini, kita harus benar-benar mengambilnya "sejauh ini", untuk mendorong penanganan kesalahan yang tepat, "eksplisit lebih baik daripada implisit".

@griesemer

Sekarang apa yang terjadi jika seseorang memberikan nilai fungsi (bukan literal nihil), dan nilai fungsi itu (disimpan dalam variabel) ternyata nihil? Dengan analogi dengan nilai nil eksplisit, penanganan tidak diperlukan. Tetapi orang lain mungkin berpendapat bahwa ini adalah bug dalam kode.

Secara teori ini memang tampak seperti gotcha potensial, meskipun saya mengalami kesulitan mengkonseptualisasikan situasi yang masuk akal di mana seorang pawang akhirnya menjadi nihil secara tidak sengaja. Saya membayangkan bahwa penangan paling sering berasal dari fungsi utilitas yang didefinisikan di tempat lain, atau sebagai penutupan yang ditentukan dalam fungsi itu sendiri. Tak satu pun dari ini cenderung menjadi nihil secara tak terduga. Anda secara teoritis dapat memiliki skenario di mana fungsi handler diedarkan sebagai argumen untuk fungsi lain, tetapi bagi saya tampaknya agak tidak masuk akal. Mungkin ada pola seperti ini yang tidak saya sadari.

Argumen lain adalah bahwa lebih baik memiliki deklarasi tingkat atas dari penangan kesalahan karena itu membuatnya sangat jelas bahwa fungsi memang menangani kesalahan. Oleh karena itu defer .

Seperti yang disebutkan @beoran , mendefinisikan handler sebagai penutupan di dekat bagian atas fungsi akan terlihat sangat mirip dalam gaya, dan itulah yang secara pribadi saya harapkan orang akan menggunakan handler paling umum. Meskipun saya menghargai kejelasan yang dimenangkan oleh fakta bahwa semua fungsi yang menangani kesalahan akan menggunakan defer , itu mungkin menjadi kurang jelas ketika suatu fungsi perlu berputar dalam strategi penanganan kesalahannya di tengah fungsi. Kemudian, akan ada dua defer untuk dilihat dan pembaca harus mempertimbangkan bagaimana mereka akan berinteraksi satu sama lain. Ini adalah situasi di mana saya percaya argumen handler akan lebih jelas dan ergonomis, dan saya pikir ini akan menjadi skenario _relatif_ umum.

Apakah mungkin untuk membuatnya bekerja tanpa tanda kurung?

Yaitu sesuatu seperti:
a := try func(some)

@Cyberax - Seperti yang telah disebutkan di atas, sangat penting bagi Anda untuk membaca dokumen desain dengan cermat sebelum memposting. Karena ini adalah masalah lalu lintas tinggi, dengan banyak orang berlangganan.

Doc membahas operator vs fungsi secara rinci.

Saya suka ini lebih dari yang saya suka versi agustus.

Saya pikir banyak umpan balik negatif, yang tidak langsung bertentangan dengan pengembalian tanpa kata kunci return , dapat diringkas dalam dua poin:

  1. orang tidak suka parameter hasil bernama, yang akan diperlukan dalam banyak kasus
  2. itu mencegah menambahkan konteks terperinci ke kesalahan

Lihat misalnya:

Adapun bantahan atas kedua keberatan tersebut masing-masing adalah:

  1. "kami memutuskan bahwa [parameter hasil bernama] baik-baik saja"
  2. "Tidak ada yang akan membuat Anda menggunakan try " / itu tidak akan sesuai untuk 100% kasus

Saya tidak punya apa-apa untuk dikatakan tentang 1 (saya tidak merasa kuat tentang itu). Tetapi mengenai 2 saya perhatikan bahwa proposal agustus tidak memiliki masalah ini, sebagian besar proposal tandingan juga tidak memiliki masalah ini.

Secara khusus baik tryf counter-proposal (yang telah diposting secara independen dua kali di utas ini) maupun try(X, handlefn) counter-proposal (yang merupakan bagian dari iterasi desain) tidak memiliki masalah ini.

Saya pikir sulit untuk membantah bahwa try , sebagaimana adanya, akan mendorong orang menjauh dari kesalahan dekorasi dengan konteks yang relevan dan menuju dekorasi kesalahan per fungsi generik tunggal.

Karena alasan ini, saya pikir ada baiknya mencoba mengatasi masalah ini dan saya ingin mengusulkan solusi yang memungkinkan:

  1. Saat ini parameter defer hanya dapat berupa pemanggilan fungsi atau metode. Izinkan defer juga memiliki nama fungsi atau literal fungsi, mis
defer func(...) {...}
defer packageName.functionName
  1. Ketika panik atau penangguhan menghadapi jenis penangguhan ini, mereka akan memanggil fungsi yang melewati nilai nol untuk semua parameternya

  2. Izinkan try memiliki lebih dari satu parameter

  3. Ketika try bertemu dengan tipe penangguhan baru, ia akan memanggil fungsi yang meneruskan pointer ke nilai kesalahan sebagai parameter pertama diikuti oleh semua parameter try sendiri, kecuali yang pertama.

Misalnya, diberikan:

func errorfn() error {
    return errors.New("an error")
}


func f(fail bool) {
    defer func(err *error, a, b, c int) {
        fmt.Printf("a=%d b=%d c=%d\n", a, b, c)
    }
    if fail {
        try(errorfn, 1, 2, 3)
    }
}

berikut akan terjadi:

f(false)        // prints "a=0 b=0 c=0"
f(true)         // prints "a=1 b=2 c=3"

Kode di https://github.com/golang/go/issues/32437#issuecomment -499309304 oleh @zeebo kemudian dapat ditulis ulang sebagai:

func (c *Config) Build() error {
    defer func(err *error, msg string, args ...interface{}) {
        if *err == nil || msg == "" {
            return
        }
        *err = errors.WithMessagef(err, msg, args...)
    }
    pkgPath := try(c.load(), "load config dir")

    b := bytes.NewBuffer(nil)
    try(templates.ExecuteTemplate(b, "main", c), "execute main template")

    buf := try(format.Source(b.Bytes()), "format main template")

    target := fmt.Sprintf("%s.go", filename(pkgPath))
    try(ioutil.WriteFile(target, buf, 0644), "write file %s", target)
    // ...
}

Dan mendefinisikan ErrorHandlef sebagai:

func HandleErrorf(err *error, format string, args ...interface{}) {
        if *err != nil && format != "" {
                *err = fmt.Errorf(format + ": %v", append(args, *err)...)
        }
}

akan memberikan tryf yang banyak dicari semua orang secara gratis, tanpa menarik string format fmt -style ke dalam bahasa inti.

Fitur ini kompatibel ke belakang karena defer tidak mengizinkan ekspresi fungsi sebagai argumennya. Itu tidak memperkenalkan kata kunci baru.
Perubahan yang perlu dilakukan untuk mengimplementasikannya, selain yang diuraikan di https://github.com/golang/proposal/blob/master/design/32437-try-builtin.md , adalah:

  1. ajari parser tentang jenis penangguhan baru
  2. ubah pemeriksa tipe untuk memeriksa bahwa di dalam suatu fungsi semua penangguhan yang memiliki fungsi sebagai parameter (bukan panggilan) juga memiliki tanda tangan yang sama
  3. ubah pemeriksa tipe untuk memeriksa apakah parameter yang diteruskan ke try cocok dengan tanda tangan fungsi yang diteruskan ke defer
  4. ubah backend (?) untuk menghasilkan panggilan deferproc yang sesuai
  5. ubah implementasi try untuk menyalin argumennya ke dalam argumen dari panggilan yang ditangguhkan ketika menemukan panggilan yang ditangguhkan oleh jenis penangguhan yang baru.

Setelah kerumitan desain draf check/handle , saya terkejut melihat proposal yang jauh lebih sederhana dan pragmatis ini mendarat meskipun saya kecewa karena ada begitu banyak penolakan terhadapnya.

Diakui banyak penolakan datang dari orang-orang yang cukup senang dengan verbositas saat ini (posisi yang sangat masuk akal untuk diambil) dan yang mungkin tidak akan benar-benar menerima proposal apa pun untuk meringankannya. Bagi kita semua, saya pikir proposal ini tepat sasaran karena sederhana dan mirip Go, tidak mencoba melakukan terlalu banyak dan menyesuaikan diri dengan baik dengan teknik penanganan kesalahan yang ada di mana Anda selalu dapat mundur jika try tidak melakukan persis seperti yang Anda inginkan.

Mengenai beberapa poin tertentu:

  1. Satu-satunya hal yang saya tidak suka tentang proposal adalah kebutuhan untuk memiliki parameter pengembalian kesalahan bernama ketika defer digunakan tetapi, karena itu, saya tidak dapat memikirkan solusi lain yang tidak akan bertentangan dengan cara kerja bahasa lainnya. Jadi saya pikir kita hanya harus menerima ini jika proposal diadopsi.

  2. Sangat disayangkan bahwa try tidak cocok dengan paket pengujian untuk fungsi yang tidak mengembalikan nilai kesalahan. Solusi pilihan saya sendiri untuk ini adalah memiliki fungsi bawaan kedua (mungkin ptry atau must ) yang selalu panik daripada kembali saat menemui kesalahan non-nihil dan yang karenanya dapat menjadi digunakan dengan fungsi yang disebutkan di atas (termasuk main ). Meskipun ide ini telah ditolak dalam iterasi proposal saat ini, saya membentuk kesan bahwa itu adalah 'panggilan dekat' dan karena itu mungkin memenuhi syarat untuk dipertimbangkan kembali.

  3. Saya pikir akan sulit bagi orang-orang untuk memahami apa yang dilakukan go try(f) atau defer try(f) dan karena itu yang terbaik adalah melarang mereka sama sekali.

  4. Saya setuju dengan mereka yang berpikir bahwa teknik penanganan kesalahan yang ada akan terlihat kurang bertele-tele jika go fmt tidak menulis ulang satu baris pernyataan if . Secara pribadi, saya lebih suka aturan sederhana bahwa ini akan diizinkan untuk _any_ pernyataan tunggal if apakah berkaitan dengan penanganan kesalahan atau tidak. Sebenarnya saya tidak pernah bisa mengerti mengapa ini saat ini tidak diizinkan saat menulis fungsi satu baris di mana tubuh ditempatkan pada baris yang sama dengan deklarasi yang diizinkan.

Dalam kasus kesalahan dekorasi

func myfunc()( err error){
try(thing())
defer func(){
err = errors.Wrap(err,"more context")
}()
}

Ini terasa jauh lebih bertele-tele dan menyakitkan daripada paradigma yang ada, dan tidak sesingkat check/handle. Varian try() non-wrapping lebih ringkas, tetapi rasanya orang akan berakhir menggunakan campuran try, dan kesalahan biasa kembali. Saya tidak yakin saya menyukai ide pencampuran coba dan pengembalian kesalahan sederhana, tetapi saya benar-benar menjual kesalahan dekorasi (dan menantikan Is/As). Buat saya berpikir bahwa meskipun ini secara sintaksis rapi, saya tidak yakin saya ingin benar-benar menggunakannya. check/handle merasakan sesuatu yang akan saya peluk dengan lebih seksama.

Saya sangat menyukai kesederhanaan ini dan pendekatan "lakukan satu hal dengan baik". Dalam penerjemah GoAWK saya, ini akan sangat membantu -- saya memiliki sekitar 100 if err != nil { return nil } konstruksi yang akan disederhanakan dan dirapikan, dan itu ada dalam basis kode yang cukup kecil.

Saya telah membaca pembenaran proposal untuk menjadikannya sebagai bawaan daripada kata kunci, dan intinya tidak harus menyesuaikan pengurai. Tetapi bukankah itu sedikit menyusahkan bagi penulis kompiler dan perkakas, sedangkan memiliki parens tambahan dan masalah keterbacaan yang terlihat-seperti-fungsi-tetapi-bukan-akan menjadi sesuatu yang semua pembuat kode dan kode Go- pembaca harus bertahan. Menurut pendapat saya argumen (alasan? :-) bahwa "tetapi panic() mengontrol aliran" tidak memotongnya, karena kepanikan dan pemulihan pada dasarnya adalah, luar biasa , sedangkan try() akan menjadi penanganan kesalahan normal dan aliran kontrol.

Saya pasti akan menghargainya bahkan jika ini berjalan apa adanya, tetapi preferensi kuat saya adalah agar aliran kontrol normal menjadi jelas, yaitu, dilakukan melalui kata kunci.

Saya mendukung proposal ini. Itu menghindari reservasi terbesar saya tentang proposal sebelumnya: non-ortogonalitas handle sehubungan dengan defer .

Saya ingin menyebutkan dua aspek yang menurut saya tidak disorot di atas.

Pertama, meskipun proposal ini tidak memudahkan untuk menambahkan teks kesalahan khusus konteks ke kesalahan, ini _memudahkan untuk menambahkan informasi pelacakan kesalahan bingkai tumpukan ke kesalahan: https://play.golang.org/p /YL1MoqR08E6

Kedua, try bisa dibilang merupakan solusi yang adil untuk sebagian besar masalah yang mendasari https://github.com/golang/go/issues/19642. Untuk mengambil contoh dari masalah itu, Anda dapat menggunakan try untuk menghindari penulisan semua nilai yang dikembalikan setiap kali. Ini juga berpotensi berguna saat mengembalikan tipe struct berdasarkan nilai dengan nama panjang.

func (f *Font) viewGlyphData(b *Buffer, x GlyphIndex) (buf []byte, offset, length uint32, err error) {
    xx := int(x)
    if f.NumGlyphs() <= xx {
        try(ErrNotFound)
    }
    i := f.cached.locations[xx+0]
    j := f.cached.locations[xx+1]
    if j < i {
        try(errInvalidGlyphDataLength)
    }
    if j-i > maxGlyphDataLength {
        try(errUnsupportedGlyphDataLength)
    }
    buf, err = b.view(&f.src, int(i), int(j-i))
    return buf, i, j - i, err
}

Saya juga menyukai proposal ini.

Dan saya punya permintaan.

Seperti make , dapatkah kita mengizinkan try untuk mengambil sejumlah variabel parameter

  • coba (p):
    seperti di atas.
    nilai kesalahan pengembalian adalah wajib (sebagai parameter pengembalian terakhir).
    MODEL PENGGUNAAN PALING UMUM
  • coba (f, doPanic bool):
    seperti di atas, tetapi jika doPanic, maka panic(err) alih-alih kembali.
    Dalam mode ini, nilai kesalahan pengembalian tidak diperlukan.
  • coba (p, fn):
    seperti di atas, tetapi panggil fn(err) sebelum kembali.
    Dalam mode ini, nilai kesalahan pengembalian tidak diperlukan.

Dengan cara ini, ini adalah salah satu bawaan yang dapat menangani semua kasus penggunaan, sambil tetap eksplisit. Keuntungannya:

  • selalu eksplisit - tidak perlu menyimpulkan apakah akan panik atau mengatur kesalahan dan kembali
  • mendukung penangan khusus konteks (tetapi tidak ada rantai penangan)
  • mendukung kasus penggunaan di mana tidak ada variabel pengembalian kesalahan
  • mendukung must(...) semantik

Meskipun if err !=nil { return ... err } berulang-ulang jelas merupakan kegagapan yang buruk, saya setuju dengan itu
yang menganggap proposal try() sangat rendah dalam keterbacaan dan agak tidak eksplisit.
Penggunaan pengembalian bernama juga bermasalah.

Jika penataan seperti ini diperlukan, mengapa tidak try(err) sebagai gula sintaksis untuk
if err !=nil { return err } :

file, err := os.Open("file.go")
try(err)

untuk

file, err := os.Open("file.go")
if err != nil {
   return err
}

Dan jika ada lebih dari satu nilai kembalian, try(err) dapat return t1, ... tn, err
di mana t1, ... tn adalah nilai nol dari nilai balik lainnya.

Saran ini dapat meniadakan kebutuhan akan nilai pengembalian bernama dan menjadi,
menurut saya, lebih mudah dipahami dan lebih mudah dibaca.

Bahkan lebih baik, saya pikir akan menjadi:

file, try(err) := os.Open("file.go")

Atau bahkan

file, err? := os.Open("file.go")

Yang terakhir ini kompatibel ke belakang (? Saat ini tidak diizinkan di pengidentifikasi).

(Saran ini terkait dengan https://github.com/golang/go/wiki/Go2ErrorHandlingFeedback#recurring-themes. Tetapi contoh tema yang berulang tampak berbeda karena itu pada tahap ketika pegangan eksplisit masih dibahas alih-alih pergi itu untuk menunda.)

Terima kasih kepada tim go atas proposal yang menarik dan hati-hati ini.

@rogpeppe berkomentar jika try secara otomatis menambahkan bingkai tumpukan, bukan saya, saya setuju dengan itu tidak menambah konteks.

@aarzilli - Jadi menurut proposal Anda, apakah klausa penangguhan wajib setiap kali kami memberikan parameter tambahan ke tryf ?

Apa yang terjadi jika saya melakukannya?

try(ioutil.WriteFile(target, buf, 0644), "write file %s", target)

dan tidak menulis fungsi penangguhan?

@agnivade

Apa yang terjadi jika saya melakukan (...) dan tidak menulis fungsi penangguhan?

kesalahan pengetikan.

Menurut pendapat saya, menggunakan try untuk menghindari penulisan semua nilai yang dikembalikan sebenarnya hanyalah pemogokan lain terhadapnya.

func (f *Font) viewGlyphData(b *Buffer, x GlyphIndex) (buf []byte, offset, length uint32, err error) {
    xx := int(x)
    if f.NumGlyphs() <= xx {
        try(ErrNotFound)
    }
    //...

Saya sepenuhnya memahami keinginan untuk menghindari keharusan menulis return nil, 0, 0, ErrNotFound , tetapi saya lebih suka menyelesaikannya dengan cara lain.

Kata try tidak berarti "kembali". Dan begitulah yang digunakan di sini. Saya sebenarnya lebih suka proposal berubah sehingga try tidak dapat mengambil nilai error secara langsung, karena saya tidak pernah ingin ada orang yang menulis kode seperti itu ^^ . Itu salah baca. Jika Anda menunjukkan kode itu kepada seorang pemula, mereka tidak akan tahu apa yang coba dilakukan.

Jika kita menginginkan cara untuk mengembalikan default dan nilai kesalahan dengan mudah, mari kita selesaikan secara terpisah. Mungkin bawaan lain seperti

return default(ErrNotFound)

Setidaknya itu terbaca dengan semacam logika.

Tapi jangan menyalahgunakan try untuk menyelesaikan beberapa masalah lain.

@natefinch jika try builtin bernama check seperti dalam proposal asli, itu akan menjadi check(err) yang terbaca jauh lebih baik, imo.

Mengesampingkan itu, saya tidak tahu apakah menulis try(err) benar-benar merupakan penyalahgunaan. Itu keluar dari definisi dengan bersih. Tetapi, di sisi lain, itu juga berarti bahwa ini sah:

a, b := try(1, f(), err)

Saya kira masalah utama saya dengan try adalah bahwa itu benar-benar hanya panic yang hanya naik satu tingkat... kecuali bahwa tidak seperti panik, ini adalah ekspresi, bukan pernyataan, sehingga Anda dapat menyembunyikan itu di tengah-tengah pernyataan di suatu tempat. Itu hampir membuatnya lebih buruk daripada panik.

@natefinch Jika Anda mengonsepnya seperti kepanikan yang naik satu tingkat dan kemudian melakukan hal-hal lain, tampaknya sangat berantakan. Namun, saya mengkonseptualisasikannya secara berbeda. Fungsi yang mengembalikan kesalahan di Go secara efektif mengembalikan Hasil, untuk secara longgar meminjam dari terminologi Rust. try adalah utilitas yang membongkar hasil dan mengembalikan "hasil kesalahan" jika error != nil , atau membongkar bagian T dari hasil jika error == nil .

Tentu saja, di Go kita sebenarnya tidak memiliki objek hasil, tetapi secara efektif polanya sama dan try tampak seperti kodifikasi alami dari pola itu. Saya percaya bahwa solusi apa pun untuk masalah ini harus mengkodifikasi beberapa aspek penanganan kesalahan, dan try s mengambilnya tampaknya masuk akal bagi saya. Saya dan orang lain menyarankan untuk sedikit memperluas kemampuan try agar lebih sesuai dengan pola penanganan kesalahan Go yang ada, tetapi konsep dasarnya tetap sama.

@ugorji Varian try(f, bool) yang Anda usulkan terdengar seperti must dari #32219.

@ugorji Varian try(f, bool) yang Anda usulkan terdengar seperti must dari #32219.

Ya itu. Saya hanya merasa ketiga kasus dapat ditangani dengan fungsi bawaan tunggal, dan memenuhi semua kasus penggunaan dengan elegan.

Karena try() sudah ajaib, dan menyadari nilai pengembalian kesalahan, dapatkah itu ditambah untuk juga mengembalikan pointer ke nilai itu ketika dipanggil dalam bentuk nullary (argumen nol)? Itu akan menghilangkan kebutuhan untuk pengembalian bernama, dan saya percaya, membantu untuk menghubungkan secara visual dari mana kesalahan diharapkan berasal dalam pernyataan penangguhan. Sebagai contoh:

func foo() error {
  defer fmt.HandleErrorf(try(), "important foo context info")
  try(bar())
  try(baz())
  try(etc())
}

@ugorji
Saya pikir boolean pada try(f, bool) akan membuatnya sulit dibaca dan mudah dilewatkan. Saya suka proposal Anda, tetapi untuk kasus panik, saya pikir itu bisa ditinggalkan bagi pengguna untuk menulisnya di dalam handler dari poin ketiga Anda, misalnya try(f(), func(err error) { panic('at the disco'); }) , ini membuatnya lebih eksplisit bagi pengguna daripada try(f(), true) tersembunyi

@ugorji
Saya pikir boolean pada try(f, bool) akan membuatnya sulit dibaca dan mudah dilewatkan. Saya suka proposal Anda, tetapi untuk kasus panik, saya pikir itu bisa ditinggalkan bagi pengguna untuk menulisnya di dalam handler dari poin ketiga Anda, misalnya try(f(), func(err error) { panic('at the disco'); }) , ini membuatnya lebih eksplisit bagi pengguna daripada try(f(), true) tersembunyi

Kalau dipikir-pikir lebih jauh, saya cenderung setuju dengan posisi dan alasan Anda, dan tetap terlihat elegan sebagai one-liner.

@patrick-nyt masih merupakan pendukung _assignment syntax_ untuk memicu tes nihil, di https://github.com/golang/go/issues/32437#issuecomment -499533464

Konsep ini muncul dalam 13 tanggapan terpisah untuk memeriksa/menangani proposal
https://github.com/golang/go/wiki/Go2ErrorHandlingFeedback#recurring -themes

f, ?return := os.Open(...)
f, ?panic  := os.Open(...)

Mengapa? Karena terbaca seperti Go 1, sedangkan try() dan check tidak.

Satu keberatan untuk try tampaknya adalah ekspresi. Misalkan ada pernyataan postfix unary ? yang berarti return jika tidak nil. Berikut adalah contoh kode standar (dengan asumsi bahwa paket tertunda yang saya usulkan ditambahkan):

func CopyFile(src, dst string) error {
    var err error // Don't need a named return because err is explicitly named
    defer deferred.Annotate(&err, "copy %s %s", src, dst)

    r, err := os.Open(src)
    err?
    defer deferred.AnnotatedExec(&err, r.Close)

    w, err := os.Create(dst)
    err?
    defer deferred.AnnotatedExec(&err, r.Close)

    defer deferred.Cond(&err, func(){ os.Remove(dst) })
    _, err = io.Copy(w, r)

    return err
}

Contoh pgStore:

func (p *pgStore) DoWork() error {
    tx, err := p.handle.Begin()
    err?

    defer deferred.Cond(&err, func(){ tx.Rollback() })

    var res int64 
    err = tx.QueryRow(`INSERT INTO table (...) RETURNING c1`, ...).Scan(&res)
    // tricky bit: this would not change the value of err 
    // but the deferred.Cond would still be triggered by err being set before
    deferred.Format(err, "insert table")?

    _, err = tx.Exec(`INSERT INTO table2 (...) VALUES ($1)`, res)
    deferred.Format(err, "insert table2")?

    return tx.Commit()
}

Saya suka ini dari @jargv :

Karena try() sudah ajaib, dan menyadari nilai pengembalian kesalahan, dapatkah itu ditambah untuk juga mengembalikan pointer ke nilai itu saat dipanggil dalam bentuk nullary (argumen nol)? Itu akan menghilangkan kebutuhan untuk pengembalian bernama

Tetapi alih-alih membebani nama try berdasarkan jumlah argumen, saya pikir mungkin ada keajaiban lain yang ada di dalamnya, katakan reterr atau sesuatu.

Saya telah diberi pengarahan melalui beberapa paket yang sangat sering digunakan, mencari kode go yang "menderita" dari penanganan err tetapi harus dipikirkan dengan matang sebelum ditulis, mencoba mencari tahu "keajaiban" apa yang akan dilakukan oleh try() yang diusulkan.
Saat ini, kecuali saya salah memahami proposal, banyak di antaranya (misalnya penanganan kesalahan yang tidak terlalu mendasar) tidak akan mendapatkan banyak, atau harus tetap menggunakan gaya penanganan kesalahan "lama".
Contoh dari net/http/request.go:

func (r *Request) write(w io.Writer, usingProxy bool, extraHeaders Header, waitForContinue func() bool) (err error) {
`

trace := httptrace.ContextClientTrace(r.Context())
if trace != nil && trace.WroteRequest != nil {
    defer func() {
        trace.WroteRequest(httptrace.WroteRequestInfo{
            Err: err,
        })
    }()
}

// Find the target host. Prefer the Host: header, but if that
// is not given, use the host from the request URL.
//
// Clean the host, in case it arrives with unexpected stuff in it.
host := cleanHost(r.Host)
if host == "" {
    if r.URL == nil {
        return errMissingHost
    }
    host = cleanHost(r.URL.Host)
}

// According to RFC 6874, an HTTP client, proxy, or other
// intermediary must remove any IPv6 zone identifier attached
// to an outgoing URI.
host = removeZone(host)

ruri := r.URL.RequestURI()
if usingProxy && r.URL.Scheme != "" && r.URL.Opaque == "" {
    ruri = r.URL.Scheme + "://" + host + ruri
} else if r.Method == "CONNECT" && r.URL.Path == "" {
    // CONNECT requests normally give just the host and port, not a full URL.
    ruri = host
    if r.URL.Opaque != "" {
        ruri = r.URL.Opaque
    }
}
if stringContainsCTLByte(ruri) {
    return errors.New("net/http: can't write control character in Request.URL")
}
// TODO: validate r.Method too? At least it's less likely to
// come from an attacker (more likely to be a constant in
// code).

// Wrap the writer in a bufio Writer if it's not already buffered.
// Don't always call NewWriter, as that forces a bytes.Buffer
// and other small bufio Writers to have a minimum 4k buffer
// size.
var bw *bufio.Writer
if _, ok := w.(io.ByteWriter); !ok {
    bw = bufio.NewWriter(w)
    w = bw
}

_, err = fmt.Fprintf(w, "%s %s HTTP/1.1\r\n", valueOrDefault(r.Method, "GET"), ruri)
if err != nil {
    return err
}

// Header lines
_, err = fmt.Fprintf(w, "Host: %s\r\n", host)
if err != nil {
    return err
}
if trace != nil && trace.WroteHeaderField != nil {
    trace.WroteHeaderField("Host", []string{host})
}

// Use the defaultUserAgent unless the Header contains one, which
// may be blank to not send the header.
userAgent := defaultUserAgent
if r.Header.has("User-Agent") {
    userAgent = r.Header.Get("User-Agent")
}
if userAgent != "" {
    _, err = fmt.Fprintf(w, "User-Agent: %s\r\n", userAgent)
    if err != nil {
        return err
    }
    if trace != nil && trace.WroteHeaderField != nil {
        trace.WroteHeaderField("User-Agent", []string{userAgent})
    }
}

// Process Body,ContentLength,Close,Trailer
tw, err := newTransferWriter(r)
if err != nil {
    return err
}
err = tw.writeHeader(w, trace)
if err != nil {
    return err
}

err = r.Header.writeSubset(w, reqWriteExcludeHeader, trace)
if err != nil {
    return err
}

if extraHeaders != nil {
    err = extraHeaders.write(w, trace)
    if err != nil {
        return err
    }
}

_, err = io.WriteString(w, "\r\n")
if err != nil {
    return err
}

if trace != nil && trace.WroteHeaders != nil {
    trace.WroteHeaders()
}

// Flush and wait for 100-continue if expected.
if waitForContinue != nil {
    if bw, ok := w.(*bufio.Writer); ok {
        err = bw.Flush()
        if err != nil {
            return err
        }
    }
    if trace != nil && trace.Wait100Continue != nil {
        trace.Wait100Continue()
    }
    if !waitForContinue() {
        r.closeBody()
        return nil
    }
}

if bw, ok := w.(*bufio.Writer); ok && tw.FlushHeaders {
    if err := bw.Flush(); err != nil {
        return err
    }
}

// Write body and trailer
err = tw.writeBody(w)
if err != nil {
    if tw.bodyReadError == err {
        err = requestBodyReadError{err}
    }
    return err
}

if bw != nil {
    return bw.Flush()
}
return nil

}
`

atau seperti yang digunakan dalam pengujian menyeluruh seperti pprof/profile/profile_test.go:
`
func checkAggregation(prof *Profil, a *aggTest) kesalahan {
// Periksa apakah jumlah total sampel untuk baris dipertahankan.
jumlah := int64(0)

samples := make(map[string]bool)
for _, sample := range prof.Sample {
    tb := locationHash(sample)
    samples[tb] = true
    total += sample.Value[0]
}

if total != totalSamples {
    return fmt.Errorf("sample total %d, want %d", total, totalSamples)
}

// Check the number of unique sample locations
if a.rows != len(samples) {
    return fmt.Errorf("number of samples %d, want %d", len(samples), a.rows)
}

// Check that all mappings have the right detail flags.
for _, m := range prof.Mapping {
    if m.HasFunctions != a.function {
        return fmt.Errorf("unexpected mapping.HasFunctions %v, want %v", m.HasFunctions, a.function)
    }
    if m.HasFilenames != a.fileline {
        return fmt.Errorf("unexpected mapping.HasFilenames %v, want %v", m.HasFilenames, a.fileline)
    }
    if m.HasLineNumbers != a.fileline {
        return fmt.Errorf("unexpected mapping.HasLineNumbers %v, want %v", m.HasLineNumbers, a.fileline)
    }
    if m.HasInlineFrames != a.inlineFrame {
        return fmt.Errorf("unexpected mapping.HasInlineFrames %v, want %v", m.HasInlineFrames, a.inlineFrame)
    }
}

// Check that aggregation has removed finer resolution data.
for _, l := range prof.Location {
    if !a.inlineFrame && len(l.Line) > 1 {
        return fmt.Errorf("found %d lines on location %d, want 1", len(l.Line), l.ID)
    }

    for _, ln := range l.Line {
        if !a.fileline && (ln.Function.Filename != "" || ln.Line != 0) {
            return fmt.Errorf("found line %s:%d on location %d, want :0",
                ln.Function.Filename, ln.Line, l.ID)
        }
        if !a.function && (ln.Function.Name != "") {
            return fmt.Errorf(`found file %s location %d, want ""`,
                ln.Function.Name, l.ID)
        }
    }
}

return nil

}
`
Ini adalah dua contoh yang dapat saya pikirkan di mana orang akan mengatakan: "Saya ingin opsi penanganan kesalahan yang lebih baik"

Adakah yang bisa mendemonstrasikan bagaimana ini akan meningkat menggunakan try() ?

Saya sebagian besar mendukung proposal ini.

Perhatian utama saya, yang dibagikan dengan banyak komentator, adalah tentang parameter hasil bernama. Proposal saat ini tentu saja mendorong lebih banyak penggunaan parameter hasil bernama dan saya pikir itu akan menjadi kesalahan. Saya tidak percaya ini hanya masalah gaya seperti yang dinyatakan oleh proposal: hasil bernama adalah fitur halus dari bahasa yang, dalam banyak kasus, membuat kode lebih rawan bug atau kurang jelas. Setelah ~8 tahun membaca dan menulis kode Go, saya benar-benar hanya menggunakan parameter hasil bernama untuk dua tujuan:

  • Dokumentasi parameter hasil
  • Memanipulasi nilai hasil (biasanya error ) di dalam defer

Untuk menyerang masalah ini dari arah yang baru, inilah ide yang menurut saya tidak sejalan dengan apa pun yang telah dibahas dalam dokumen desain atau utas komentar masalah ini. Sebut saja "penangguhan kesalahan":

Izinkan penangguhan digunakan untuk memanggil fungsi dengan parameter kesalahan implisit.

Jadi jika Anda memiliki fungsi

func f(err error, t1 T1, t2 T2, ..., tn Tn) error

Kemudian, dalam fungsi g di mana parameter hasil terakhir memiliki tipe error (yaitu, fungsi apa pun di mana try saya digunakan), panggilan ke f dapat ditangguhkan sebagai berikut:

func g() (R0, R0, ..., error) {
    defer f(t0, t1, ..., tn) // err is implicit
}

Semantik dari penangguhan kesalahan adalah:

  1. Panggilan yang ditangguhkan ke f dipanggil dengan parameter hasil terakhir g sebagai parameter input pertama f
  2. f hanya dipanggil jika kesalahan itu tidak nihil
  3. Hasil f ditetapkan ke parameter hasil terakhir g

Jadi untuk menggunakan contoh dari dokumen desain penanganan kesalahan lama, menggunakan penangguhan kesalahan dan coba, kita bisa melakukannya

func printSum(a, b string) error {
    defer func(err error) error {
        return fmt.Errorf("printSum(%q + %q): %v", a, b, err)
    }()
    x := try(strconv.Atoi(a))
    y := try(strconv.Atoi(b))
    fmt.Println("result:", x+y)
    return nil
}

Begini cara HandleErrorf akan bekerja:

func printSum(a, b string) error {
    defer handleErrorf("printSum(%q + %q)", a, b)
    x := try(strconv.Atoi(a))
    y := try(strconv.Atoi(b))
    fmt.Println("result:", x+y)
    return nil
}

func handleErrorf(err error, format string, args ...interface{}) error {
    return fmt.Errorf(format+": %v", append(args, err)...)
}

Satu kasus sudut yang perlu diselesaikan adalah bagaimana menangani kasus-kasus di mana tidak jelas bentuk penangguhan mana yang kita gunakan. Saya pikir itu hanya terjadi dengan fungsi (sangat tidak biasa) dengan tanda tangan seperti ini:

func(error, ...error) error

Tampaknya masuk akal untuk mengatakan bahwa kasus ini ditangani dengan cara non-error-defer (dan ini mempertahankan kompatibilitas ke belakang).


Memikirkan ide ini selama beberapa hari terakhir, ini sedikit ajaib, tetapi penghindaran parameter hasil yang disebutkan adalah keuntungan besar yang menguntungkannya. Karena try mendorong lebih banyak penggunaan defer untuk manipulasi kesalahan, masuk akal bahwa defer dapat diperluas agar lebih sesuai dengan tujuan itu. Juga, ada simetri tertentu antara try dan penangguhan kesalahan.

Akhirnya, penangguhan kesalahan berguna hari ini bahkan tanpa mencoba, karena mereka menggantikan penggunaan parameter hasil bernama untuk memanipulasi pengembalian kesalahan. Misalnya, ini adalah versi yang diedit dari beberapa kode asli:

// GetMulti retrieves multiple files through the cache at once and returns its
// results as a slice parallel to the input.
func (c *FileCache) GetMulti(keys []string) (_ []*File, err error) {
    files := make([]*file, len(keys))

    defer func() {
        if err != nil {
            // Return any successfully retrieved files.
            for _, f := range files {
                if f != nil {
                    c.put(f)
                }
            }
        }
    }()

    // ...
}

Dengan penangguhan kesalahan, ini menjadi:

// GetMulti retrieves multiple files through the cache at once and returns its
// results as a slice parallel to the input.
func (c *FileCache) GetMulti(keys []string) ([]*File, error) {
    files := make([]*file, len(keys))

    defer func(err error) error {
        // Return any successfully retrieved files.
        for _, f := range files {
            if f != nil {
                c.put(f)
            }
        }
        return err
    }()

    // ...
}

@beoran Mengenai komentar Anda bahwa kami harus menunggu obat generik. Obat generik tidak akan membantu di sini - harap baca FAQ .

Mengenai saran Anda tentang 2-argumen @velovix try 's perilaku default: Seperti yang saya katakan sebelumnya , gagasan Anda tentang apa pilihan yang jelas masuk akal adalah mimpi buruk orang lain.

Bolehkah saya menyarankan agar kita melanjutkan diskusi ini setelah konsensus luas berkembang bahwa try dengan penangan kesalahan eksplisit adalah ide yang lebih baik daripada try minimal saat ini. Pada saat itu masuk akal untuk membahas poin-poin bagus dari desain semacam itu.

(Saya suka memiliki handler, dalam hal ini. Ini adalah salah satu proposal kami sebelumnya. Dan jika kita mengadopsi try apa adanya, kita masih bisa bergerak menuju try dengan handler di depan -cara yang kompatibel - setidaknya jika handler adalah opsional. Tapi mari kita ambil satu langkah pada satu waktu.)

@aarzilli Terima kasih atas saran Anda.

Selama kesalahan dekorasi adalah opsional, orang akan condong ke arah tidak melakukannya (bagaimanapun juga itu adalah pekerjaan ekstra). Lihat juga komentar saya di sini .

Jadi, saya tidak berpikir try _mencegah_ orang yang diusulkan dari kesalahan dekorasi (mereka sudah putus asa bahkan dengan if karena alasan di atas); itu try tidak _mendorong_ itu.

(Salah satu cara untuk mendorongnya adalah dengan mengikatnya ke try : Seseorang hanya dapat menggunakan try jika ia juga menghiasi kesalahannya, atau secara eksplisit menyisih.)

Tetapi kembali ke saran Anda: Saya pikir Anda memperkenalkan lebih banyak mesin di sini. Mengubah semantik defer hanya untuk membuatnya bekerja lebih baik untuk try bukanlah sesuatu yang ingin kami pertimbangkan kecuali perubahan defer bermanfaat dalam cara yang lebih umum. Juga, saran Anda mengikat defer bersama-sama dengan try dan dengan demikian membuat kedua mekanisme tersebut kurang ortogonal; sesuatu yang ingin kita hindari.

Tetapi yang lebih penting, saya ragu Anda ingin memaksa semua orang untuk menulis defer agar mereka dapat menggunakan try . Tapi tanpa melakukan itu, kita kembali ke titik awal: orang akan condong ke arah tidak mendekorasi kesalahan.

(Saya suka memiliki handler, dalam hal ini. Ini adalah salah satu proposal kami sebelumnya. Dan jika kita mengadopsi try apa adanya, kita masih dapat bergerak ke arah try dengan handler dengan cara yang kompatibel ke depan - setidaknya jika handlernya opsional. Tapi mari kita lakukan selangkah demi selangkah.)

Tentu, mungkin pendekatan multi-langkah adalah cara yang harus dilakukan. Jika kita menambahkan argumen handler opsional di masa mendatang, perkakas dapat dibuat untuk memperingatkan penulis tentang try yang tidak ditangani dalam semangat yang sama dengan alat errcheck . Apapun, saya menghargai umpan balik Anda!

@alanfo Terima kasih atas umpan balik positif Anda.

Mengenai poin yang Anda kemukakan:

1) Jika satu-satunya masalah dengan try adalah kenyataan bahwa seseorang harus memberi nama pengembalian kesalahan sehingga kami dapat menghias kesalahan melalui defer , saya pikir kami baik-baik saja. Jika penamaan hasil ternyata menjadi masalah nyata, kita bisa mengatasinya. Mekanisme sederhana yang dapat saya pikirkan adalah variabel yang dideklarasikan sebelumnya yang merupakan alias untuk hasil kesalahan (anggap itu sebagai menahan kesalahan yang memicu try terbaru). Mungkin ada ide yang lebih baik. Kami tidak mengusulkan ini karena sudah ada mekanisme dalam bahasa, yaitu untuk memberi nama hasilnya.
2) try dan pengujian: Ini dapat diatasi dan dibuat berfungsi. Lihat dokumen rinci.
3) Ini secara eksplisit dibahas dalam dokumen terperinci.
4) Diakui.

@benhoyt Terima kasih atas umpan balik positif Anda.

Jika argumen utama yang menentang proposal ini adalah fakta bahwa try adalah bawaan, kami berada di posisi yang tepat. Menggunakan built-in hanyalah solusi pragmatis untuk masalah kompatibilitas ke belakang (kebetulan tidak menyebabkan pekerjaan tambahan untuk parser, dan alat dll. - tapi itu hanya manfaat sampingan yang bagus, bukan alasan utama). Ada juga beberapa keuntungan karena harus menulis tanda kurung, hal ini dibahas secara rinci dalam dokumen desain (bagian tentang Properti dari desain yang diusulkan).

Semua yang dikatakan, jika menggunakan built-in adalah showstopper, kita harus mempertimbangkan kata kunci try . Itu tidak akan kompatibel ke belakang meskipun dengan kode yang ada karena kata kunci mungkin bertentangan dengan pengidentifikasi yang ada.

(Untuk melengkapi, ada juga opsi operator seperti ? , yang akan kompatibel ke belakang. Namun, menurut saya, itu bukan pilihan terbaik untuk bahasa seperti Go. Tapi sekali lagi, jika hanya itu yang diperlukan untuk membuat try enak, kita mungkin harus mempertimbangkannya.)

@ugorji Terima kasih atas umpan balik positif Anda.

try dapat diperluas untuk mengambil argumen tambahan. Preferensi kami adalah mengambil hanya fungsi dengan tanda tangan func (error) error . Jika Anda ingin panik, mudah untuk menyediakan fungsi pembantu satu baris:

func doPanic(err error) error { panic(err) }

Lebih baik menjaga desain try tetap sederhana.

@patrick-nyt Apa yang Anda sarankan :

file, err := os.Open("file.go")
try(err)

akan mungkin dengan proposal saat ini.

@dpinela , @ugorji Silakan baca juga dokumen desain tentang must vs try . Lebih baik menyimpan try sesederhana mungkin. must adalah "pola" umum dalam ekspresi inisialisasi, tetapi tidak ada kebutuhan mendesak untuk "memperbaikinya".

@jargv Terima kasih atas saran Anda. Ini adalah ide yang menarik (lihat juga komentar saya di sini tentang hal ini). Untuk meringkas:

  • try(x) beroperasi seperti yang diusulkan
  • try() mengembalikan *error yang menunjuk ke hasil kesalahan

Ini memang cara lain untuk mendapatkan hasil tanpa harus menamainya.

@cespare Saran oleh @jargv terlihat jauh lebih sederhana bagi saya daripada yang Anda usulkan . Ini memecahkan masalah yang sama dari akses ke kesalahan hasil. Bagaimana menurutmu?

Sesuai https://github.com/golang/go/issues/32437#issuecomment -499320588:

func doPanic(err error) error { panik(err) }

Saya mengantisipasi fungsi ini akan sangat umum. Bisakah ini ditentukan sebelumnya dalam "builtin" (atau di tempat lain dalam paket standar misalnya errors )?

Sayang sekali Anda tidak mengantisipasi obat generik yang cukup kuat untuk diterapkan
coba, saya sebenarnya berharap itu mungkin dilakukan.

Ya, proposal ini bisa menjadi langkah pertama, meskipun saya tidak melihat banyak gunanya
itu sendiri seperti berdiri sekarang.

Memang, masalah ini mungkin terlalu fokus pada alternatif terperinci,
tetapi ini menunjukkan bahwa banyak peserta tidak sepenuhnya senang dengan
dia. Apa yang tampaknya kurang adalah konsensus yang luas tentang proposal ini...

Op vr 7 Juni 2019 01:04 schreef pj [email protected] :

Asper #32437 (komentar)
https://github.com/golang/go/issues/32437#issuecomment-499320588 :

func doPanic(err error) error { panik(err) }

Saya mengantisipasi fungsi ini akan sangat umum. Mungkinkah ini sudah ditentukan sebelumnya?
dalam "bawaan"?


Anda menerima ini karena Anda disebutkan.
Balas email ini secara langsung, lihat di GitHub
https://github.com/golang/go/issues/32437?email_source=notifications&email_token=AAARM6OOOLLYO5ZCE6VVL2TPZGJWRA5CNFSM4HTGCZ72YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVX99HJKTDN5WW69
atau matikan utasnya
https://github.com/notifications/unsubscribe-auth/AAARM6K5AOR2DES4QDTNLSTPZGJWRANCNFSM4HTGCZ7Q
.

@pjebs , saya telah menulis fungsi yang setara puluhan kali. Saya biasanya menyebutnya "orDie" atau "check". Ini sangat sederhana, tidak perlu menjadikannya bagian dari perpustakaan standar. Plus orang yang berbeda mungkin ingin masuk atau apa pun sebelum penghentian.

@beoran Mungkin Anda bisa memperluas koneksi antara obat generik dan penanganan kesalahan. Ketika saya memikirkannya, mereka tampak seperti dua hal yang berbeda. Generics bukanlah catch all yang bisa mengatasi semua masalah dengan bahasa. Ini adalah kemampuan untuk menulis satu fungsi yang dapat beroperasi pada beberapa jenis.

Proposal penanganan kesalahan khusus ini mencoba mengurangi boilerplate dengan memperkenalkan fungsi yang telah dideklarasikan sebelumnya try yang mengubah kontrol aliran dalam beberapa keadaan. Obat generik tidak akan pernah mengubah aliran kontrol. Jadi saya benar-benar tidak melihat hubungannya.

Reaksi awal saya terhadap ini adalah karena saya membayangkan bahwa menangani beberapa panggilan rawan kesalahan dalam suatu fungsi akan membuat pegangan kesalahan defer membingungkan. Setelah membaca seluruh proposal, saya membalikkan reaksi saya menjadi ❤️ dan 👍 ketika saya mengetahui bahwa ini masih dapat dicapai dengan kompleksitas yang relatif rendah.

@carlmjohnson Ya itu sederhana tapi...

Saya telah menulis fungsi yang setara puluhan kali.

Keuntungan dari fungsi yang dideklarasikan adalah:

  1. Kita bisa satu baris itu
  2. Kita tidak perlu mendeklarasikan ulang fungsi err => panic di setiap paket yang kita gunakan, atau mempertahankan lokasi yang sama untuk itu. Karena mungkin umum untuk semua orang di komunitas Go, "paket standar" adalah _ _ lokasi umum untuk itu.

@griesemer Dengan varian penangan kesalahan dari proposal percobaan asli, apakah persyaratan fungsi menyeluruh untuk mengembalikan kesalahan sekarang tidak lagi diperlukan.

Ketika saya pertama kali menanyakannya err => panic, saya ditunjukkan bahwa proposal tersebut mempertimbangkannya tetapi menganggapnya terlalu berbahaya (untuk alasan yang baik). Tetapi jika kita menggunakan try() tanpa penangan kesalahan dalam skenario di mana fungsi menyeluruh tidak mengembalikan kesalahan, membuatnya menjadi kesalahan waktu kompilasi mengurangi kekhawatiran yang dibahas dalam proposal

@pjebs Persyaratan fungsi menyeluruh untuk mengembalikan kesalahan tidak diperlukan dalam desain asli _jika_ penangan kesalahan disediakan. Tapi itu hanya komplikasi lain dari try . Ini _jauh_ lebih baik untuk membuatnya tetap sederhana. Alih-alih, akan lebih jelas untuk memiliki fungsi must terpisah, yang selalu panik karena kesalahan (tetapi sebaliknya seperti try ). Maka jelas apa yang terjadi dalam kode dan kita tidak perlu melihat konteksnya.

Daya tarik utama memiliki must seperti itu adalah dapat digunakan dengan pengujian unit; terutama jika paket testing telah disesuaikan dengan tepat untuk pulih dari kepanikan yang disebabkan oleh must dan melaporkannya sebagai kegagalan pengujian dengan cara yang baik. Tetapi mengapa menambahkan mekanisme bahasa baru ketika kita dapat menyesuaikan paket pengujian untuk juga menerima fungsi pengujian dari bentuk TestXxx(t *testing.T) error ? Jika mereka mengembalikan kesalahan, yang tampaknya cukup alami (mungkin kita seharusnya melakukan ini sejak awal), maka try akan bekerja dengan baik. Tes lokal akan membutuhkan sedikit lebih banyak pekerjaan, tetapi mungkin bisa dilakukan.

Penggunaan lain yang relatif umum untuk must adalah dalam ekspresi inisialisasi global ( must(regexp.Compile... , dll.). Jika akan menjadi "baik untuk dimiliki" tetapi itu tidak selalu menaikkannya ke tingkat yang diperlukan untuk fitur bahasa baru.

@griesemer Mengingat bahwa must samar-samar terkait dengan try , dan mengingat bahwa momentum menuju try diimplementasikan, tidakkah menurut Anda baik untuk mempertimbangkan must pada saat yang sama - meskipun itu hanya "senang untuk dimiliki".

Kemungkinannya adalah jika tidak dibahas dalam babak ini, itu tidak akan diterapkan/dipertimbangkan secara serius, setidaknya selama 3+ tahun (atau mungkin pernah). Tumpang tindih dalam diskusi juga akan lebih baik daripada memulai dari awal dan diskusi daur ulang.

Banyak orang telah menyatakan bahwa must memuji try dengan sangat baik.

@pjebs Tampaknya tidak ada "momentum menuju try diimplementasikan" sekarang... - Dan kami juga baru memposting ini dua hari yang lalu. Juga belum ada yang diputuskan. Mari kita beri waktu ini.

Tidak luput dari kita bahwa must cocok dengan try , tetapi itu tidak sama dengan menjadikannya bagian dari bahasa. Kami baru mulai menjelajahi ruang ini dengan kelompok orang yang lebih luas. Kami benar-benar belum tahu apa yang mungkin muncul untuk mendukung atau menentangnya. Terima kasih.

Setelah menghabiskan berjam-jam membaca semua komentar dan dokumen desain terperinci, saya ingin menambahkan pandangan saya ke proposal ini.

Saya akan melakukan yang terbaik untuk menghormati permintaan @ianlancetaylor untuk tidak hanya menyatakan kembali poin sebelumnya tetapi sebagai gantinya menambahkan komentar baru ke diskusi. Namun saya rasa saya tidak dapat membuat komentar baru tanpa merujuk pada komentar sebelumnya.

Kekhawatiran

Sayangnya kelebihan penangguhan

Preferensi untuk membebani sifat yang jelas dan lugas dari defer sebagai hal yang mengkhawatirkan. Jika saya menulis defer closeFile(f) itu langsung dan jelas bagi saya apa yang terjadi dan mengapa; di akhir fungsi yang akan dipanggil. Dan sementara menggunakan defer untuk panic() dan recover() kurang jelas, saya jarang jika pernah menggunakannya dan hampir tidak pernah melihatnya ketika membaca kode orang lain.

Menipu untuk membebani defer juga menangani kesalahan tidak jelas dan membingungkan. Mengapa kata kunci defer ? Apakah defer tidak berarti _"Lakukan nanti"_ bukannya _"Mungkin nanti?"_

Juga ada kekhawatiran yang disebutkan oleh tim Go tentang kinerja defer . Mengingat hal itu, tampaknya sangat disayangkan bahwa defer sedang dipertimbangkan untuk aliran kode _"hot path"_.

Tidak ada statistik yang memverifikasi kasus penggunaan yang signifikan

Seperti yang disebutkan @prologic , apakah proposal try() ini didasarkan pada sebagian besar kode yang akan menggunakan kasus penggunaan ini, atau apakah ini didasarkan pada upaya untuk menenangkan mereka yang mengeluh tentang penanganan kesalahan Go?

Saya berharap saya tahu bagaimana memberi Anda statistik dari basis kode saya tanpa meninjau secara mendalam setiap file dan membuat catatan; Saya tidak tahu bagaimana @prologic bisa meskipun senang dia melakukannya.

Tetapi secara anekdot saya akan terkejut jika try() menangani 5% dari kasus penggunaan saya dan akan curiga bahwa itu akan menangani kurang dari 1%. Apakah Anda tahu pasti bahwa orang lain memiliki hasil yang sangat berbeda? Sudahkah Anda mengambil bagian dari pustaka standar dan mencoba melihat bagaimana penerapannya?

Karena tanpa statistik yang diketahui bahwa ini sesuai untuk sejumlah besar kode di alam liar, saya harus bertanya apakah perubahan rumit baru ini pada bahasa yang akan mengharuskan semua orang untuk mempelajari konsep-konsep baru benar-benar mengatasi sejumlah kasus penggunaan yang menarik?

Memudahkan pengembang untuk mengabaikan kesalahan

Ini adalah pengulangan total dari apa yang orang lain komentari, tetapi apa yang pada dasarnya memberikan try() analog dalam banyak hal untuk hanya merangkul yang berikut sebagai kode idomatik, dan ini adalah kode yang tidak akan pernah menemukan jalannya ke kode mana pun. -menghormati kapal pengembang:

f, _ := os.Open(filename)

Saya tahu saya bisa lebih baik dalam kode saya sendiri, tetapi saya juga tahu banyak dari kita bergantung pada kebesaran pengembang Go lain yang menerbitkan beberapa paket yang sangat berguna, tetapi dari apa yang saya lihat di _"Kode Orang Lain(tm)"_ praktik terbaik dalam penanganan kesalahan sering diabaikan.

Serius, apakah kami benar-benar ingin mempermudah pengembang untuk mengabaikan kesalahan dan mengizinkan mereka mengotori GitHub dengan paket yang tidak kuat?

Bisa (kebanyakan) sudah mengimplementasikan try() di userland

Kecuali saya salah memahami proposal - yang mungkin saya lakukan - ini adalah try() di Go Playground yang diimplementasikan di userland , meskipun hanya dengan satu (1) nilai pengembalian dan mengembalikan antarmuka alih-alih tipe yang diharapkan:

package main

import (
    "errors"
    "fmt"
    "strings"
)
func main() {
    defer func() {
        r := recover()
        if r != nil && strings.HasPrefix(r.(string),"TRY:") {
            fmt.Printf("Ouch! %s",strings.TrimPrefix(r.(string),"TRY: "))
        }
    }()
    n := try(badjuju()).(int)
    fmt.Printf("Just chillin %dx!",n)   
}
func badjuju() (int,error) {
    return 10, errors.New("this is a really bad error")
}
func try(args ...interface{}) interface{} {
    err,ok := args[1].(error)
    if ok && err != nil {
        panic(fmt.Sprintf("TRY: %s",err.Error()))
    }
    return args[0]
}

Jadi pengguna dapat menambahkan try2() , try3() dan seterusnya tergantung pada berapa banyak nilai pengembalian yang mereka butuhkan untuk dikembalikan.

Tetapi Go hanya memerlukan satu (1) fitur bahasa _ sederhana _ yang universal untuk memungkinkan pengguna yang ingin try() menggulirkan dukungan mereka sendiri, meskipun yang masih memerlukan pernyataan tipe eksplisit. Tambahkan kemampuan _(sepenuhnya kompatibel ke belakang)_ untuk Go func untuk mengembalikan sejumlah variadik nilai pengembalian, misalnya:

func try(args ...interface{}) ...interface{} {
    err,ok := args[1].(error)
    if ok && err != nil {
        panic(fmt.Sprintf("TRY: %s",err.Error()))
    }
    return args[0:len(args)-2]
}

Dan jika Anda membahas obat generik terlebih dahulu maka jenis pernyataan bahkan tidak akan diperlukan _(walaupun saya pikir kasus penggunaan untuk obat generik harus dikurangi dengan menambahkan builtin untuk mengatasi kasus penggunaan generik daripada menambahkan semantik membingungkan dan salad sintaks dari obat generik dari Java dkk.)_

Kurangnya kejelasan

Saat mempelajari kode proposal, saya menemukan bahwa perilakunya tidak jelas dan agak sulit untuk dipikirkan.

Ketika saya melihat try() membungkus ekspresi, apa yang akan terjadi jika kesalahan dikembalikan?

Apakah kesalahan akan diabaikan begitu saja? Atau akankah ia melompat ke defer pertama atau terbaru, dan jika demikian, ia akan secara otomatis menetapkan variabel bernama err di dalam penutupan itu, atau akankah ia meneruskannya sebagai parameter _(I tidak melihat parameter?)_. Dan jika bukan nama kesalahan otomatis, bagaimana saya menamainya? Dan apakah itu berarti saya tidak dapat mendeklarasikan variabel err saya sendiri di fungsi saya, untuk menghindari bentrokan?

Dan apakah itu akan memanggil semua defer ? Dalam urutan terbalik atau urutan biasa?

Atau apakah itu akan kembali dari penutupan dan func tempat kesalahan dikembalikan? _(Sesuatu yang tidak akan pernah saya pertimbangkan jika saya tidak membaca di sini kata-kata yang menyiratkan hal itu.)_

Setelah membaca proposal dan semua komentar sejauh ini saya masih jujur ​​​​tidak tahu jawaban atas pertanyaan di atas. Apakah itu jenis fitur yang ingin kami tambahkan ke bahasa yang pendukungnya diperjuangkan sebagai _"Captain Obvious?"_

Kurang kontrol

Menggunakan defer , tampaknya satu-satunya kontrol yang akan diberikan pengembang adalah bercabang ke _(yang terbaru?)_ defer . Tetapi dalam pengalaman saya dengan metode apa pun di luar func yang sepele biasanya lebih rumit dari itu.

Seringkali saya merasa berguna untuk berbagi aspek penanganan kesalahan dalam func — atau bahkan melintasi package — tetapi kemudian juga memiliki penanganan yang lebih spesifik yang dibagikan di satu atau lebih paket lain.

Misalnya, saya dapat memanggil lima (5) func panggilan yang mengembalikan error() dari dalam func lain; mari kita beri label A() , B() , C() , D() , dan E() . Saya mungkin perlu C() untuk memiliki penanganan kesalahannya sendiri, A() , B() , D() , dan E() untuk membagikan beberapa penanganan kesalahan, dan B() dan E() untuk memiliki penanganan khusus.

Tapi saya tidak percaya akan mungkin untuk melakukan itu dengan proposal ini. Setidaknya tidak mudah.

Namun, ironisnya, Go sudah memiliki fitur bahasa yang memungkinkan tingkat fleksibilitas tinggi yang tidak perlu dibatasi pada sejumlah kecil kasus penggunaan; func s dan penutupan. Jadi pertanyaan retoris saya adalah:

_ "Mengapa kita tidak bisa menambahkan sedikit peningkatan pada bahasa yang ada untuk mengatasi kasus penggunaan ini dan tidak perlu menambahkan fungsi bawaan baru atau menerima semantik yang membingungkan?" _

Ini adalah pertanyaan retoris karena saya berencana untuk mengajukan proposal sebagai alternatif, yang saya pikirkan selama studi proposal ini dan sambil mempertimbangkan semua kekurangannya.

Tapi saya ngelantur, itu akan datang nanti dan komentar ini tentang mengapa proposal saat ini perlu dipertimbangkan kembali.

Kurangnya dukungan yang dinyatakan untuk break

Ini mungkin terasa seperti keluar dari bidang kiri karena kebanyakan orang menggunakan pengembalian awal untuk penanganan kesalahan, tetapi saya telah menemukan bahwa lebih baik menggunakan break untuk penanganan kesalahan membungkus sebagian besar atau semua fungsi sebelum return .

Saya telah menggunakan pendekatan ini untuk sementara dan manfaatnya dalam mengurangi refactoring saja membuatnya lebih disukai daripada awal return , tetapi memiliki beberapa manfaat lain termasuk titik keluar tunggal dan kemampuan untuk mengakhiri bagian dari suatu fungsi lebih awal tetapi masih dapat menjalankan pembersihan _(yang mungkin mengapa saya sangat jarang menggunakan defer , yang menurut saya lebih sulit untuk dijelaskan dalam hal aliran program.)_

Untuk menggunakan break alih-alih pengembalian awal, gunakan loop for range "1" {...} untuk membuat blok agar break keluar dari _(Saya sebenarnya membuat paket bernama only yang hanya berisi konstanta disebut Once dengan nilai "1" ):_

func (me *Config) WriteFile() (err error) {
    for range only.Once {
        var j []byte
        j, err = json.MarshalIndent(me, "", "    ")
        if err != nil {
            err = fmt.Errorf("unable to marshal config; %s", 
                err.Error(),
            )
            break
        }
        err = me.MaybeMakeDir(me.GetDir(), os.ModePerm)
        if err != nil {
            err = fmt.Errorf("unable to make directory'%s'; %s", 
                me.GetDir(), 
                err.Error(),
            )
            break
        }
        err = ioutil.WriteFile(string(me.GetFilepath()), j, os.ModePerm)
        if err != nil {
            err = fmt.Errorf("unable to write to config file '%s'; %s", 
                me.GetFilepath(), 
                err.Error(),
            )
            break
        }
    }
    return err
}

Saya berencana untuk blog tentang pola panjang lebar dalam waktu dekat, dan membahas beberapa alasan mengapa saya menemukan itu bekerja lebih baik daripada pengembalian awal.

Tapi saya ngelantur. Alasan saya membawanya ke sini adalah saya harus Go untuk mengimplementasikan penanganan kesalahan yang mengasumsikan return s awal dan mengabaikan penggunaan break untuk penanganan kesalahan

Pendapat saya err == nil bermasalah

Sebagai penyimpangan lebih lanjut, saya ingin mengemukakan kekhawatiran yang saya rasakan tentang penanganan kesalahan idiomatik di Go. Meskipun saya sangat percaya pada filosofi Go untuk menangani kesalahan ketika terjadi vs. menggunakan penanganan pengecualian, saya merasa penggunaan nil untuk menunjukkan tidak ada kesalahan merupakan masalah karena saya sering menemukan bahwa saya ingin mengembalikan pesan sukses dari rutin — untuk digunakan dalam respons API — dan tidak hanya mengembalikan nilai bukan nol hanya ketika ada kesalahan.

Jadi untuk Go 2 saya sangat ingin melihat Go mempertimbangkan untuk menambahkan tipe bawaan baru status dan tiga fungsi bawaan iserror() , iswarning() , issuccess() . status dapat mengimplementasikan error — memungkinkan banyak kompatibilitas ke belakang dan nilai nil yang diteruskan ke issuccess() akan mengembalikan true — tetapi status akan memiliki status internal tambahan untuk tingkat kesalahan sehingga pengujian untuk tingkat kesalahan akan selalu dilakukan dengan salah satu fungsi bawaan dan idealnya tidak pernah dengan pemeriksaan nil . Itu akan memungkinkan sesuatu seperti pendekatan berikut sebagai gantinya:

func (me *Config) WriteFile() (sts status) {
    for range only.Once {
        var j []byte
        j, sts = json.MarshalIndent(me, "", "    ")
        if iserror(sts) {
            sts.AddMessage("unable to marshal config")
            break
        }
        sts = me.MaybeMakeDir(me.GetDir(), os.ModePerm)
        if iserror(sts) {
            sts.AddMessage("unable to make directory'%s'", me.GetDir())
            break
        }
        sts = ioutil.WriteFile(string(me.GetFilepath()), j, os.ModePerm)
        if iserror(sts) {
            sts.AddMessage("unable to write to config file '%s'", 
                me.GetFilepath(), 
            )
            break
        }
        sts = fmt.Status("config file written")
    }
    return sts
}

Saya sudah menggunakan pendekatan userland di tingkat pra-beta saat ini paket penggunaan internal yang mirip dengan di atas untuk penanganan kesalahan. Terus terang saya menghabiskan lebih sedikit waktu untuk memikirkan bagaimana menyusun kode saat menggunakan pendekatan ini daripada ketika saya mencoba mengikuti penanganan kesalahan Go idiomatis.

Jika menurut Anda ada peluang untuk mengembangkan kode Go idiomatis ke pendekatan ini, harap pertimbangkan saat menerapkan penanganan kesalahan, termasuk saat mempertimbangkan proposal try() ini.

_"Tidak untuk semua orang"_ pembenaran

Salah satu tanggapan utama dari tim Go adalah _"Sekali lagi, proposal ini tidak berusaha menyelesaikan semua situasi penanganan kesalahan."_
Dan itu mungkin masalah yang paling meresahkan, dari perspektif tata kelola.

Apakah perubahan baru yang rumit pada bahasa yang akan mengharuskan setiap orang untuk mempelajari konsep-konsep baru ini benar-benar mengatasi sejumlah kasus penggunaan yang menarik?

Dan bukankah itu pembenaran yang sama bahwa anggota tim inti telah menolak banyak permintaan fitur dari komunitas? Berikut ini adalah kutipan langsung dari komentar yang dibuat oleh anggota tim Go dalam tanggapan tipikal atas permintaan fitur yang diajukan sekitar 2 tahun yang lalu _(Saya tidak menyebutkan nama orang atau permintaan fitur tertentu karena diskusi ini seharusnya tidak dapat dilakukan orangnya tapi tentang bahasanya):_

_"Fitur bahasa baru memerlukan kasus penggunaan yang menarik. Semua fitur bahasa berguna, atau tidak ada yang akan mengusulkannya; pertanyaannya adalah: apakah fitur tersebut cukup berguna untuk membenarkan kerumitan bahasa dan mengharuskan semua orang mempelajari konsep baru? Apa kegunaan yang menarik itu? kasus di sini? Bagaimana orang akan menggunakan ini? Misalnya, apakah orang berharap dapat ... dan jika demikian, bagaimana mereka akan melakukannya? Apakah proposal ini lebih dari sekadar memungkinkan Anda...?"_
— Anggota tim inti Go

Terus terang ketika saya melihat tanggapan itu, saya merasakan salah satu dari dua perasaan:

  1. Kemarahan jika itu adalah fitur yang saya setujui, atau
  2. Kegembiraan jika itu adalah fitur yang saya tidak setuju.

Tetapi dalam kedua kasus, perasaan saya tidak relevan; Saya mengerti dan setuju bahwa sebagian dari alasan Go adalah bahasa yang dipilih oleh banyak dari kita untuk berkembang adalah karena penjagaan kemurnian bahasa yang cemburu.

Dan itulah mengapa proposal ini sangat mengganggu saya, karena tim inti Go tampaknya menggali proposal ini ke tingkat yang sama dengan seseorang yang secara dogmatis menginginkan fitur esoteris yang tidak mungkin ditoleransi oleh komunitas Go.

_(Dan saya sangat berharap tim tidak akan menembak utusan dan menganggap ini sebagai kritik membangun dari seseorang yang ingin melihat Go terus menjadi yang terbaik untuk kita semua karena saya harus dianggap "Persona non grata" oleh tim inti.)_

Jika memerlukan serangkaian kasus penggunaan dunia nyata yang menarik adalah standar untuk semua proposal fitur yang dibuat komunitas, bukankah seharusnya itu juga menjadi standar yang sama untuk _ semua _ proposal fitur?

Bersarang dari coba()

Ini juga dicakup oleh beberapa, tetapi saya ingin menggambar perbandingan antara try() dan permintaan lanjutan untuk operator ternary. Mengutip dari komentar anggota tim Go lainnya sekitar 18 bulan yang lalu:

_"ketika "memprogram dalam skala besar" (basis kode besar dengan tim besar dalam jangka waktu yang lama), kode dibaca JAUH lebih sering daripada yang tertulis, jadi kami mengoptimalkan keterbacaan, bukan penulisan."_

Salah satu alasan yang dinyatakan _primary_ untuk tidak menambahkan operator ternary adalah karena operator tersebut sulit dibaca dan/atau mudah salah dibaca saat bersarang. Namun hal yang sama dapat terjadi pada pernyataan try() bersarang seperti try(try(try(to()).parse().this)).easily()) .

Alasan tambahan untuk membantah operator ternary adalah bahwa mereka adalah _"ekspresi"_ dengan argumen bahwa ekspresi bersarang dapat menambah kompleksitas. Tetapi bukankah try() membuat ekspresi yang dapat disarang juga?

Sekarang seseorang di sini berkata _"Saya pikir contoh seperti [bersarang try() s] tidak realistis"_ dan pernyataan itu tidak ditentang.

Tetapi jika orang menerima sebagai dalil bahwa pengembang tidak akan bersarang try() lalu mengapa penghormatan yang sama tidak diberikan kepada operator ternary ketika orang mengatakan _"Saya pikir operator ternary yang sangat bersarang tidak realistis?"_

Intinya untuk poin ini, saya pikir jika argumen terhadap operator ternary benar-benar valid, maka mereka juga harus dianggap sebagai argumen yang valid terhadap proposal try() ini.

Singkatnya

Pada saat penulisan ini 58% suara turun menjadi 42% suara naik. Saya pikir ini saja sudah cukup untuk menunjukkan bahwa ini cukup memecah belah proposal sehingga sudah waktunya untuk kembali ke papan gambar tentang masalah ini.

fwiw

PS Untuk membuatnya lebih lidah-di-pipi, saya pikir kita harus mengikuti kebijaksanaan Yoda yang diparafrasekan:

_"Tidak ada try() . Hanya do() ."_

@ianlancetaylor

@beoran Mungkin Anda bisa memperluas koneksi antara obat generik dan penanganan kesalahan.

Tidak berbicara untuk @beoran tetapi dalam komentar saya dari beberapa menit yang lalu Anda akan melihat bahwa jika kami memiliki obat generik _(plus parameter pengembalian variadic)_ maka kami dapat membangun try() kami sendiri.

Namun — dan saya akan mengulangi apa yang saya katakan di atas tentang obat generik di sini di mana akan lebih mudah untuk melihat:

_" Saya pikir kasus penggunaan untuk obat generik harus dikurangi dengan menambahkan bawaan untuk mengatasi kasus penggunaan generik daripada menambahkan semantik membingungkan dan salad sintaks generik dari Java et. al.)"_

@ianlancetaylor

Ketika mencoba merumuskan jawaban untuk pertanyaan Anda, saya mencoba menerapkan fungsi try di Go apa adanya, dan saya senang, sebenarnya sudah mungkin untuk meniru sesuatu yang sangat mirip:

func try(v interface{}, err error) interface{} {
   if err != nil { 
     panic(err)
   }
   return v
}

Lihat di sini cara menggunakannya: https://play.golang.org/p/Kq9Q0hZHlXL

Kelemahan dari pendekatan ini adalah:

  1. Penyelamatan yang ditangguhkan diperlukan, tetapi dengan try seperti dalam proposal ini, penangan yang ditangguhkan juga diperlukan jika kita ingin melakukan penanganan kesalahan yang tepat. Jadi saya merasa ini bukan kerugian yang serius. Bahkan bisa lebih baik jika Go memiliki semacam super(arg1, ..., argn) bawaan yang menyebabkan penelepon, satu tingkat di atas tumpukan panggilan, untuk kembali dengan argumen yang diberikan arg1,...argn, semacam pengembalian super jika Anda mau.
  2. try yang saya terapkan ini hanya dapat bekerja dengan fungsi yang mengembalikan satu hasil dan kesalahan.
  3. Anda harus mengetikkan hasil antarmuka kosong yang dikembalikan.

Obat generik yang cukup kuat dapat menyelesaikan masalah 2 dan 3, hanya menyisakan 1, yang dapat diselesaikan dengan menambahkan super() . Dengan dua fitur tersebut, kita bisa mendapatkan sesuatu seperti:

func (T ... interface{})try(T, err error) super {
   if err != nil { 
      super(err)
   }
  super(T...)
}

Dan kemudian penyelamatan yang ditangguhkan tidak akan diperlukan lagi. Manfaat ini akan tersedia bahkan jika tidak ada obat generik yang ditambahkan ke Go.

Sebenarnya, ide super() builtin ini sangat kuat dan menarik sehingga saya mungkin memposting proposal untuk itu secara terpisah.

@beoran Senang melihat kami sampai pada kendala yang sama secara independen terkait penerapan try() di userland, kecuali untuk bagian super yang tidak saya sertakan karena saya ingin membicarakan sesuatu yang serupa dalam proposal alternatif. :-)

Saya menyukai proposalnya tetapi fakta bahwa Anda harus secara eksplisit menentukan bahwa defer try(...) dan go try(...) tidak diizinkan membuat saya berpikir ada sesuatu yang tidak beres.... Orthogonalitas adalah panduan desain yang baik. Pada bacaan lebih lanjut dan melihat hal-hal seperti
x = try(foo(...)) y = try(bar(...))
Saya ingin tahu apakah mungkin try perlu menjadi konteks ! Mempertimbangkan:
try ( x = foo(...) y = bar(...) )
Di sini foo() dan bar() mengembalikan dua nilai, yang kedua adalah error . Coba semantik hanya penting untuk panggilan dalam blok try di mana nilai kesalahan yang dikembalikan dihilangkan (tidak ada penerima) sebagai lawan diabaikan (penerima adalah _ ). Anda bahkan dapat menangani beberapa kesalahan di antara panggilan foo dan bar .

Ringkasan:
a) masalah pelarangan try untuk go dan defer menghilang berdasarkan sintaks.
b) penanganan kesalahan beberapa fungsi dapat difaktorkan.
c) sifat ajaibnya lebih baik diekspresikan sebagai sintaksis khusus daripada sebagai pemanggilan fungsi.

Jika try adalah sebuah konteks maka kami baru saja membuat blok try/catch yang secara khusus kami coba hindari (dan untuk alasan yang bagus)

Tidak ada tangkapan. Kode yang sama persis akan dihasilkan seperti saat proposal saat ini memiliki
x = try(foo(...)) y = try(bar(...))
Ini hanya sintaks yang berbeda, bukan semantik.
````

Saya kira saya telah membuat beberapa asumsi tentang hal itu yang seharusnya tidak saya lakukan, meskipun masih ada beberapa kekurangannya.

Bagaimana jika foo atau bar tidak mengembalikan kesalahan, dapatkah mereka ditempatkan dalam konteks try juga? Jika tidak, sepertinya akan agak buruk untuk beralih antara fungsi error dan non-error, dan jika bisa, maka kita kembali ke masalah blok try dalam bahasa yang lebih lama.

Hal kedua adalah bahwa biasanya sintaks keyword ( ... ) berarti Anda mengawali kata kunci pada setiap baris. Jadi untuk import, var, const, dll: setiap baris dimulai dengan kata kunci. Mengecualikan aturan itu sepertinya bukan keputusan yang baik

Alih-alih menggunakan fungsi, apakah akan lebih idiomatis menggunakan pengenal khusus?

Kami sudah memiliki pengidentifikasi kosong _ yang mengabaikan nilai.
Kita dapat memiliki sesuatu seperti # yang hanya dapat digunakan dalam fungsi yang memiliki nilai kesalahan tipe yang dikembalikan terakhir.

func foo() (error) {
    f, # := os.Open()
    defer f.Close()
    _, # = f.WriteString("foo")
    return nil
}

ketika kesalahan ditetapkan ke # fungsi segera kembali dengan kesalahan yang diterima. Adapun variabel lain nilainya adalah:

  • jika mereka tidak bernama nilai nol
  • nilai yang diberikan ke variabel bernama sebaliknya

@deanveloper , try blok semantik hanya penting untuk fungsi yang mengembalikan nilai kesalahan dan di mana nilai kesalahan tidak ditetapkan. Jadi contoh terakhir dari proposal ini juga dapat ditulis sebagai
try(x = foo(...)) try(y = bar(...))
menempatkan kedua pernyataan dalam blok yang sama mirip dengan apa yang kita lakukan untuk pernyataan import , const dan var berulang.

Sekarang jika sudah, misalnya
try( x = foo(...)) go zee(...) defer fum() y = bar(...) )
Ini setara dengan menulis
try(x = foo(...)) go zee(...) defer fum() try(y = bar(...))
Memfaktorkan semua itu dalam satu blok percobaan membuatnya kurang sibuk.

Mempertimbangkan
try(x = foo())
Jika foo() tidak mengembalikan nilai kesalahan, ini setara dengan
x = foo()

Mempertimbangkan
try(f, _ := os.open(filename))
Karena nilai kesalahan yang dikembalikan diabaikan, ini setara dengan hanya
f, _ := os.open(filename)

Mempertimbangkan
try(f, err := os.open(filename))
Karena nilai kesalahan yang dikembalikan tidak diabaikan, ini setara dengan
f, err := os.open(filename) if err != nil { return ..., err }
Seperti yang saat ini ditentukan dalam proposal.

Dan itu juga mendeklarasikan percobaan bersarang dengan baik!

Berikut ini tautan ke proposal alternatif yang saya sebutkan di atas:

Itu panggilan untuk menambahkan dua (2) fitur bahasa tujuan kecil tapi umum untuk mengatasi kasus penggunaan yang sama seperti try()

  1. Kemampuan untuk memanggil func /closure dalam pernyataan tugas.
  2. Kemampuan untuk break , continue atau return lebih dari satu level.

Dengan dua fitur ini, mereka tidak akan _"ajaib"_ dan saya yakin penggunaannya akan menghasilkan kode Go yang lebih mudah dipahami dan lebih sesuai dengan kode Go idiomatis yang kita semua kenal.

Saya sudah membaca proposalnya dan sangat suka ke mana coba.

Mengingat seberapa lazimnya percobaan, saya bertanya-tanya apakah menjadikannya perilaku yang lebih default akan membuatnya lebih mudah untuk ditangani.

Pertimbangkan peta. Ini berlaku:

v := m[key]

seperti ini:

v, ok := m[key]

Bagaimana jika kita menangani kesalahan persis seperti yang disarankan oleh try, tetapi hapus file bawaan. Jadi jika kita mulai dengan:

v, err := fn()

Alih-alih menulis:

v := try(fn())

Sebagai gantinya, kita bisa menulis:

v := fn()

Ketika nilai err tidak ditangkap, itu akan ditangani persis seperti percobaan. Perlu sedikit membiasakan diri, tetapi rasanya sangat mirip dengan v, ok := m[key] dan v, ok := x.(string) . Pada dasarnya, setiap kesalahan yang tidak tertangani menyebabkan fungsi kembali dan nilai err ditetapkan.

Untuk kembali ke kesimpulan dokumen desain dan persyaratan implementasi:

• Sintaks bahasa dipertahankan dan tidak ada kata kunci baru yang diperkenalkan
• Ini terus menjadi gula sintaksis seperti try dan mudah-mudahan mudah untuk dijelaskan.
• Tidak memerlukan sintaks baru
• Ini harus benar-benar kompatibel ke belakang.

Saya membayangkan ini akan memiliki persyaratan implementasi yang hampir sama dengan try karena perbedaan utamanya adalah daripada builtin yang memicu gula sintaksis, sekarang tidak adanya bidang err.

Jadi menggunakan contoh CopyFile dari proposal bersama dengan defer fmt.HandleErrorf(&err, "copy %s %s", src, dst) , kita mendapatkan:

func CopyFile(src, dst string) (err error) {
        defer fmt.HandleErrorf(&err, "copy %s %s", src, dst)

        r := os.Open(src)
        defer r.Close()

        w := os.Create(dst)
        defer func() {
                err := w.Close()
                if err != nil {
                        os.Remove(dst) // only if a “try” fails
                }
        }()

        io.Copy(w, r)
        w.Close()
        return nil
}

@savaki Saya suka ini dan berpikir tentang apa yang diperlukan untuk membuat Go membalik penanganan kesalahan dengan selalu menangani kesalahan secara default dan membiarkan programmer menentukan kapan tidak melakukannya (dengan menangkap kesalahan ke dalam variabel) tetapi sama sekali tidak ada pengenal akan membuat kode sulit untuk diikuti karena orang tidak akan dapat melihat semua poin kembali. Mungkin konvensi untuk memberi nama fungsi yang dapat mengembalikan kesalahan secara berbeda dapat berfungsi (seperti kapitalisasi pengidentifikasi publik). Mungkin jika suatu fungsi mengembalikan kesalahan, itu harus selalu diakhiri dengan, katakanlah ? . Kemudian Go selalu dapat menangani kesalahan secara implisit dan secara otomatis mengembalikannya ke fungsi panggilan seperti try. Ini membuatnya sangat mirip dengan beberapa proposal yang menyarankan untuk menggunakan pengenal ? daripada mencoba tetapi perbedaan penting adalah bahwa di sini ? akan menjadi bagian dari nama fungsi dan bukan pengenal tambahan. Sebenarnya fungsi yang mengembalikan error sebagai nilai pengembalian terakhir bahkan tidak akan dikompilasi jika tidak diakhiri ? . Tentu saja ? bersifat arbitrer dan dapat diganti dengan apa pun yang membuat maksud lebih eksplisit. operation?() akan setara dengan membungkus try(someFunc()) tetapi ? akan menjadi bagian dari nama fungsi dan satu-satunya tujuan adalah untuk menunjukkan bahwa fungsi dapat mengembalikan kesalahan seperti kapitalisasi huruf pertama variabel.

Ini secara dangkal akhirnya menjadi sangat mirip dengan proposal lain yang meminta untuk mengganti try dengan ? tetapi perbedaan kritis adalah membuat penanganan kesalahan implisit (otomatis) dan malah membuat kesalahan pengabaian (atau pembungkusan) eksplisit yang jenis praktik terbaik pula. Masalah yang paling jelas dengan ini tentu saja adalah tidak kompatibel ke belakang dan saya yakin masih banyak lagi.

Yang mengatakan, saya akan sangat tertarik untuk melihat bagaimana Go dapat membuat kesalahan menangani kasus default/implisit dengan mengotomatiskannya dan membiarkan programmer menulis sedikit kode tambahan untuk mengabaikan/mengganti penanganannya. Tantangannya menurut saya adalah bagaimana membuat semua titik balik menjadi jelas dalam kasus ini karena tanpanya kesalahan akan menjadi lebih seperti pengecualian dalam arti bahwa mereka bisa datang dari mana saja karena aliran program tidak akan membuatnya jelas. Bisa dikatakan membuat kesalahan implisit dengan indikator visual sama dengan mengimplementasikan try dan membuat errcheck kegagalan kompilator.

bisakah kita melakukan sesuatu seperti pengecualian c++ dengan dekorator untuk fungsi lama?

func some_old_test() (int, error){
    return 0, errors.New("err1")
}
func some_new_test() (int){
        if true {
             return 1
        }
    throw errors.New("err2")
}
func throw_res(int, e error) int {
    if e != nil {
        throw e
    }
    return int
}
func main() {
    fmt.Println("Hello, playground")
    try{
        i := throw_res(some_old_test())
        fmt.Println("i=", i + some_new_test())
    } catch(err io.Error) {
        return err
    } catch(err error) {
        fmt.Println("unknown err", err)
    }
}

@owais Saya berpikir semantiknya akan persis sama dengan try jadi setidaknya Anda perlu mendeklarasikan tipe err. Jadi jika kita mulai dengan:

func foo() error {
  _, err := fn() 
  if err != nil {
    return err
  }

  return nil
} 

Jika saya memahami proposal percobaan, cukup lakukan ini:

func foo() error {
  _  := fn() 
  return nil
} 

tidak akan dikompilasi. Satu keuntungan yang bagus adalah memberikan kompilasi kesempatan untuk memberi tahu pengguna apa yang hilang. Sesuatu yang menyatakan bahwa menggunakan penanganan kesalahan implisit memerlukan jenis pengembalian kesalahan untuk dinamai, err.

Ini kemudian, akan bekerja:

func foo() (err error) {
  _  := fn() 
  return nil
} 

mengapa tidak menangani kasus kesalahan yang tidak ditetapkan ke variabel saja.

  • hapus kebutuhan untuk pengembalian bernama, kompiler dapat melakukan ini semua sendiri.
  • memungkinkan untuk menambahkan konteks.
  • menangani kasus penggunaan umum.
  • kompatibel ke belakang
  • tidak berinteraksi secara aneh dengan penundaan, loop, atau sakelar.

pengembalian implisit untuk kasus if err != nil, compiler dapat menghasilkan nama variabel lokal untuk pengembalian jika perlu tidak dapat diakses oleh programmer.
secara pribadi saya tidak suka kasus khusus ini dari sudut pandang keterbacaan kode

f := os.Open("foo.txt")

lebih suka pengembalian eksplisit, mengikuti kode lebih banyak dibaca daripada mantra tertulis

f := os.Open("foo.txt") else return

menariknya kami dapat menerima kedua formulir tersebut, dan telah gofmt secara otomatis menambahkan pengembalian else.

menambahkan konteks, juga penamaan lokal variabel. return menjadi eksplisit karena kita ingin menambahkan konteks.

f := os.Open("foo.txt") else err {
  return errors.Wrap(err, "some context")
}

menambahkan konteks dengan beberapa nilai pengembalian

f := os.Open("foo.txt") else err {
  return i, j, errors.Wrap(err, "some context")
}

fungsi bersarang mengharuskan fungsi luar menangani semua hasil dalam urutan yang sama
dikurangi kesalahan terakhir.

bits := ioutil.ReadAll(os.Open("foo")) else err {
  // either error ends up here.
  return i, j, errors.Wrap(err, "some context")
}

kompiler menolak kompilasi karena nilai pengembalian kesalahan yang hilang dalam fungsi

func foo(s string) int {
   i := strconv.Atoi(s) // cannot implicitly return error due to missing error return value for foo.
   return i * 2
}

dengan senang hati dikompilasi karena kesalahan diabaikan secara eksplisit.

func foo(s string) int {
   i, _ := strconv.Atoi(s)
   return i * 2
} 

kompiler senang. itu mengabaikan kesalahan seperti yang terjadi saat ini karena tidak ada penetapan atau akhiran yang terjadi.

func foo() error {
  return errors.New("whoops")
}

func bar() {
  foo()
}

dalam satu lingkaran Anda dapat menggunakan lanjutkan.

for _, s := range []string{"1","2","3","4","5","6"} {
  i := strconv.Atoi(s) else continue
}

edit: ganti ; dengan else

@savaki Saya pikir saya mengerti komentar asli Anda dan saya suka ide Go menangani kesalahan secara default, tetapi saya pikir itu tidak layak tanpa menambahkan beberapa perubahan sintaks tambahan dan setelah kami melakukannya, itu menjadi sangat mirip dengan proposal saat ini.

Kelemahan terbesar dari apa yang Anda usulkan adalah ia tidak memaparkan semua titik dari mana suatu fungsi dapat kembali tidak seperti if err != nil {return err} saat ini atau fungsi coba yang diperkenalkan dalam proposal ini. Meskipun itu akan berfungsi dengan cara yang persis sama di bawah tenda, secara visual kodenya akan terlihat sangat berbeda. Saat membaca kode, tidak akan ada cara untuk mengetahui panggilan fungsi mana yang mungkin menghasilkan kesalahan. Itu akan menjadi pengalaman yang lebih buruk daripada pengecualian IMO.

Mungkin penanganan kesalahan dapat dibuat implisit jika kompiler memaksakan beberapa konvensi semantik pada fungsi yang dapat mengembalikan kesalahan. Seperti mereka harus dimulai atau diakhiri dengan frasa atau karakter tertentu. Itu akan membuat semua poin pengembalian sangat jelas dan saya pikir itu akan lebih baik daripada penanganan kesalahan manual tetapi tidak yakin seberapa jauh lebih baik mengingat sudah ada pemeriksaan serat yang berteriak memuat ketika mereka melihat kesalahan diabaikan. Akan sangat menarik untuk melihat apakah kompiler dapat memaksa fungsi diberi nama dengan cara tertentu tergantung pada apakah mereka dapat mengembalikan kemungkinan kesalahan.

Kelemahan utama dari pendekatan ini adalah bahwa parameter hasil kesalahan perlu diberi nama, mungkin mengarah ke API yang kurang cantik (tetapi lihat FAQ tentang hal ini). Kami percaya bahwa kami akan terbiasa setelah gaya ini terbentuk dengan sendirinya.

Tidak yakin apakah hal seperti ini telah disarankan sebelumnya, tidak dapat menemukannya di sini atau di proposal. Sudahkah Anda mempertimbangkan fungsi bawaan lain yang mengembalikan pointer ke nilai pengembalian kesalahan fungsi saat ini?
misalnya:

func example() error {
        var err *error = funcerror() // always return a non-nil pointer
        fmt.Print(*err) // always nil if the return parameters are not named and not in a defer

        defer func() {
                err := funcerror()
                fmt.Print(*err) // "x"
        }

        return errors.New("x")
}
func exampleNamed() (err error) {
        funcErr := funcerror()
        fmt.Print(*funcErr) // "nil"

        err = errors.New("x")
        fmt.Print(*funcErr) // "x", named return parameter is reflected even before return is called

        *funcErr = errors.New("y")
        fmt.Print(err) // "y", unfortunate side effect?

        defer func() {
                funcErr := funcerror()
                fmt.Print(*funcErr) // "z"
                fmt.Print(err) // "z"
        }

        return errors.New("z")
}

penggunaan dengan coba:

func CopyFile(src, dst string) (error) {
        defer func() {
                err := funcerror()
                if *err != nil {
                        *err = fmt.Errorf("copy %s %s: %v", src, dst, err)
                }
        }()
        // one liner alternative
        // defer fmt.HandleErrorf(funcerror(), "copy %s %s", src, dst)

        r := try(os.Open(src))
        defer r.Close()

        w := try(os.Create(dst))
        defer func() {
                w.Close()
                err := funcerror()
                if *err != nil {
                        os.Remove(dst) // only if a “try” fails
                }
        }()

        try(io.Copy(w, r))
        try(w.Close())
        return nil
}

Atau funcerror (namanya sedang dalam proses :D ) dapat mengembalikan nil jika tidak dipanggil di dalam penangguhan.

Alternatif lain adalah funcerror mengembalikan antarmuka "Errorer" untuk membuatnya hanya-baca:

type interface Errorer() {
        Error() error
}

@savaki Saya benar-benar menyukai proposal Anda untuk menghilangkan try() dan membiarkannya lebih seperti menguji peta atau pernyataan tipe. Itu terasa jauh lebih _"Go-like."_

Namun, masih ada satu masalah mencolok yang saya lihat, dan proposal Anda menganggap bahwa semua kesalahan menggunakan pendekatan ini akan memicu return dan meninggalkan fungsi. Apa yang tidak dipikirkannya adalah mengeluarkan break dari for saat ini atau continue untuk for saat ini.

return awal adalah palu godam ketika berkali-kali pisau bedah adalah pilihan yang lebih baik.

Jadi saya menegaskan break dan continue harus diizinkan menjadi strategi penanganan kesalahan yang valid dan saat ini proposal Anda hanya menganggap return sedangkan try() menganggap itu atau memanggil kesalahan handler itu sendiri hanya dapat return , bukan break atau continue .

Sepertinya savaki dan saya memiliki ide yang sama, saya baru saja menambahkan semantik blok untuk menangani kesalahan jika diinginkan. Misalnya menambahkan comtext, untuk loop di mana Anda ingin korsleting dll

@mikeschinkel lihat ekstensi saya, dia dan saya memiliki ide yang sama, saya baru saja memperpanjangnya dengan pernyataan blok opsional

@james-lawrence

@mikesckinkel lihat ekstensi saya, dia dan saya memiliki ide yang sama, saya baru saja memperpanjangnya dengan pernyataan blok opsional

Mengambil contoh Anda:

f := os.Open("foo.txt"); err {
  return errors.Wrap(err, "some context")
}

Yang dibandingkan dengan apa yang kita lakukan hari ini:

f,err := os.Open("foo.txt"); 
if err != nil {
  return errors.Wrap(err, "some context")
}

Pasti lebih disukai daripada saya. Kecuali itu memiliki beberapa masalah:

  1. err tampaknya _"secara ajaib"_ dideklarasikan. Sihir harus diminimalkan, bukan? Jadi mari kita nyatakan:
f, err := os.Open("foo.txt"); err {
  return errors.Wrap(err, "some context")
}
  1. Tapi itu masih tidak berhasil karena Go tidak menafsirkan nilai nil sebagai false atau nilai pointer sebagai true , jadi itu harus:
f, err := os.Open("foo.txt"); err != nil {
  return errors.Wrap(err, "some context")
}

Dan apa yang berhasil, itu mulai terasa seperti banyak pekerjaan dan banyak sintaks dalam satu baris, jadi dan saya mungkin terus melakukan cara lama untuk kejelasan.

Tetapi bagaimana jika Go menambahkan dua (2) bawaan; iserror() dan error() ? Kemudian kita bisa melakukan ini, yang tidak terasa buruk bagi saya:

f := os.Open("foo.txt"); iserror() {
  return errors.Wrap(error(), "some context")
}

Atau lebih baik _(sesuatu seperti):_

f := os.Open("foo.txt"); iserror() {
  return error().Extend("some context")
}

Apa yang Anda, dan orang lain pikirkan?

Sebagai tambahan, periksa ejaan nama pengguna saya. Saya tidak akan diberitahu tentang penyebutan Anda jika saya tidak memperhatikan ...

@mikeschinkel maaf tentang nama yang saya gunakan di ponsel saya dan github tidak menyarankan secara otomatis.

err tampaknya "secara ajaib" dinyatakan. Sihir harus diminimalkan, bukan? Jadi mari kita nyatakan:

meh, seluruh ide memasukkan pengembalian secara otomatis adalah ajaib. ini bukanlah hal yang paling ajaib yang terjadi di seluruh proposal ini. Ditambah lagi, saya berpendapat bahwa err dinyatakan; tepat di akhir di dalam konteks blok cakupan yang mencegahnya mencemari ruang lingkup induk sambil tetap mempertahankan semua hal baik yang kita dapatkan secara normal dengan menggunakan pernyataan if.

Saya biasanya cukup senang dengan penanganan kesalahan go dengan penambahan yang akan datang ke paket kesalahan. Saya tidak melihat apa pun dalam proposal ini sebagai hal yang sangat membantu. Saya hanya mencoba menawarkan kecocokan paling alami untuk golang jika kita benar-benar ingin melakukannya.

_"seluruh gagasan untuk memasukkan pengembalian secara otomatis adalah ajaib."_

Anda tidak akan mendapatkan argumen dari saya di sana.

_"ini bukanlah hal yang paling ajaib yang terjadi di seluruh proposal ini."_

Kurasa aku mencoba berargumen bahwa _"semua sihir itu bermasalah."_

_"Ditambah lagi, saya berpendapat bahwa err dinyatakan; tepat di akhir di dalam konteks blok cakupan..."_

Jadi jika saya ingin menyebutnya err2 ini akan berhasil juga?

f := os.Open("foo.txt"); err2 {
  return errors.Wrap(err, "some context")
}

Jadi saya berasumsi Anda juga mengusulkan penanganan kasus khusus dari err / err2 setelah titik koma, yaitu bahwa itu akan dianggap sebagai nil atau bukan nil bukannya bool seperti saat memeriksa peta?

if _,ok := m[a]; !ok {
   print("there is no 'a' in 'm'")
}

Saya biasanya cukup senang dengan penanganan kesalahan go dengan penambahan yang akan datang ke paket kesalahan.

Saya juga senang dengan penanganan kesalahan, bila digabungkan dengan break dan continue _(tetapi tidak return .)_

Karena itu, saya melihat proposal try() ini lebih berbahaya daripada membantu, dan lebih suka tidak melihat apa pun selain implementasi ini sebagaimana diusulkan. #jmtcw.

@beoran @mikeschinkel Sebelumnya saya menyarankan bahwa kita tidak bisa mengimplementasikan versi try ini menggunakan obat generik, karena mengubah aliran kontrol. Jika saya membaca dengan benar, Anda berdua menyarankan agar kami menggunakan obat generik untuk mengimplementasikan try dengan memanggilnya panic . Tetapi versi try ini secara eksplisit tidak panic . Jadi kami tidak dapat menggunakan obat generik untuk mengimplementasikan versi try ini.

Ya, kami dapat menggunakan obat generik (versi obat generik secara signifikan lebih kuat daripada yang ada di draf desain di https://go.googlesource.com/proposal/+/master/design/go2draft-contracts.md) untuk menulis fungsi yang panik pada kesalahan. Tapi panik pada kesalahan bukanlah jenis penanganan kesalahan yang ditulis oleh programmer Go hari ini, dan sepertinya itu bukan ide yang baik bagi saya.

@mikeschinkel penanganan khusus adalah blok dijalankan hanya ketika ada kesalahan.
```
f := os.Open('foo'); err { return err } // err akan selalu nol di sini.

@ianlancetaylor

_"Ya, kita bisa menggunakan obat generik ... Tapi panik pada kesalahan bukanlah jenis penanganan kesalahan yang ditulis oleh programmer Go hari ini, dan sepertinya itu bukan ide yang bagus untuk saya."_

Saya sebenarnya sangat setuju dengan Anda tentang hal ini sehingga tampaknya Anda mungkin salah mengartikan maksud dari komentar saya. Saya sama sekali tidak menyarankan bahwa tim Go akan menerapkan penanganan kesalahan yang menggunakan panic() — tentu saja tidak.

Sebaliknya saya mencoba untuk benar-benar mengikuti petunjuk Anda dari banyak komentar Anda sebelumnya tentang masalah lain dan menyarankan agar kami menghindari membuat perubahan apa pun pada Go yang tidak mutlak diperlukan karena hal itu mungkin dilakukan di userland . Jadi _if_ obat generik ditangani _then_ orang yang ingin try() sebenarnya dapat mengimplementasikannya sendiri, meskipun dengan memanfaatkan panic() . Dan itu akan menjadi satu fitur lebih sedikit yang perlu ditambahkan dan didokumentasikan oleh tim untuk Go.

Apa yang tidak saya lakukan — dan mungkin itu tidak jelas — adalah menganjurkan agar orang benar-benar menggunakan panic() untuk mengimplementasikan try() , hanya saja mereka dapat melakukannya jika mereka benar-benar menginginkannya, dan mereka memiliki fitur obat generik.

Apakah itu memperjelas?

Bagi saya memanggil panic , bagaimanapun itu dilakukan, sangat berbeda dari proposal ini untuk try . Jadi sementara saya pikir saya mengerti apa yang Anda katakan, saya tidak setuju bahwa mereka setara. Bahkan jika kami memiliki obat generik yang cukup kuat untuk mengimplementasikan versi try yang membuat panik, saya pikir masih ada keinginan yang masuk akal untuk versi try yang disajikan dalam proposal ini.

@ianlancetaylor Diakui. Sekali lagi, saya mencari alasan bahwa try() tidak perlu ditambahkan daripada mencari cara untuk menambahkannya. Seperti yang saya katakan di atas, saya lebih suka tidak memiliki sesuatu yang baru untuk penanganan kesalahan daripada memiliki try() seperti yang diusulkan di sini.

Saya pribadi lebih menyukai proposal check sebelumnya, berdasarkan aspek visual semata; check memiliki kekuatan yang sama dengan try() ini tetapi bar(check foo()) lebih mudah dibaca oleh saya daripada bar(try(foo())) (saya hanya perlu satu detik untuk menghitung parens!).

Lebih penting lagi, keluhan utama saya tentang handle / check adalah bahwa ia tidak mengizinkan pembungkusan cek individu dengan cara yang berbeda -- dan sekarang proposal try() ini memiliki kelemahan yang sama, sambil menerapkan fitur penangguhan dan pengembalian bernama yang membingungkan bagi pemula yang jarang digunakan. Dan dengan handle setidaknya kami memiliki opsi untuk menggunakan cakupan untuk mendefinisikan blok pegangan, dengan defer bahkan itu tidak mungkin.

Sejauh yang saya ketahui, proposal ini kalah dari proposal handle / check sebelumnya dalam segala hal.

Inilah masalah lain dengan menggunakan penangguhan untuk penanganan kesalahan.

try adalah keluar yang dikendalikan/dimaksudkan dari suatu fungsi. menunda berjalan selalu, termasuk keluar tidak terkendali/tidak diinginkan dari fungsi. Ketidakcocokan itu dapat menyebabkan kebingungan. Berikut adalah skenario imajiner:

func someHTTPHandlerGuts() (err error) {
  defer func() {
    recordMetric("db call failed")
    return fmt.HandleErrorf("db unavailable: %v", err)
  }()
  data := try(makeDBCall)
  // some code that panics due to a bug
  return nil
}

Ingat bahwa net/http pulih dari kepanikan, dan bayangkan men-debug masalah produksi di sekitar kepanikan. Anda akan melihat instrumentasi Anda dan melihat lonjakan kegagalan panggilan db, dari panggilan recordMetric . Ini mungkin menutupi masalah sebenarnya, yaitu kepanikan di baris berikutnya.

Saya tidak yakin seberapa serius masalah ini dalam praktiknya, tetapi (sayangnya) mungkin alasan lain untuk berpikir bahwa penangguhan bukanlah mekanisme yang ideal untuk penanganan kesalahan.

Berikut adalah modifikasi yang dapat membantu mengatasi beberapa masalah yang muncul: Perlakukan try seperti goto alih-alih seperti return . Dengarkan aku. :)

try sebagai gantinya akan menjadi gula sintaksis untuk:

t1, … tn, te := f()  // t1, … tn, te are local (invisible) temporaries
if te != nil {
        err = te   // assign te to the error result parameter
        goto error // goto "error" label
}
x1, … xn = t1, … tn  // assignment only if there was no error

Manfaat:

  • defer tidak diperlukan untuk menghias kesalahan. (Namun, pengembalian yang disebutkan masih diperlukan.)
  • Keberadaan label error: adalah petunjuk visual bahwa ada try di suatu tempat dalam fungsi.

Ini juga menyediakan mekanisme untuk menambahkan penangan yang menghindari masalah penangan sebagai fungsi: Gunakan label sebagai penangan. try(fn(), wrap) akan goto wrap bukannya goto error . Kompiler dapat mengonfirmasi bahwa wrap: ada dalam fungsi. Perhatikan bahwa memiliki penangan juga membantu proses debug: Anda dapat menambahkan/mengubah penangan untuk menyediakan jalur debug.

Kode sampel:

func CopyFile(src, dst string) (err error) {
    r := try(os.Open(src))
    defer r.Close()

    w := try(os.Create(dst))
    defer func() {
        w.Close()
        if err != nil {
            os.Remove(dst) // only if a “try” fails
        }
    }()

    try(io.Copy(w, r), copyfail)
    try(w.Close())
    return nil

error:
    return fmt.Errorf("copy %s %s: %v", src, dst, err)

copyfail:
    recordMetric("copy failure") // count incidents of this failure
    return fmt.Errorf("copy %s %s: %v", src, dst, err)
}

Komentar lain:

  • Kami mungkin ingin mengharuskan label apa pun yang digunakan sebagai target try didahului oleh pernyataan penghentian. Dalam praktiknya, ini akan memaksa mereka ke akhir fungsi dan dapat mencegah beberapa kode spageti. Di sisi lain, mungkin mencegah beberapa penggunaan yang masuk akal dan bermanfaat.
  • try dapat digunakan untuk membuat loop. Saya pikir ini berada di bawah panji "jika sakit, jangan lakukan", tapi saya tidak yakin.
  • Ini akan membutuhkan perbaikan https://github.com/golang/go/issues/26058 .

Kredit: Saya yakin varian dari ide ini pertama kali disarankan oleh @griesemer secara langsung di GopherCon tahun lalu.

@josharian Memikirkan interaksi dengan panic penting di sini, dan saya senang Anda mengemukakannya, tetapi contoh Anda tampak aneh bagi saya. Dalam kode berikut, tidak masuk akal bagi saya bahwa penangguhan selalu mencatat metrik "db call failed" . Ini akan menjadi metrik yang salah jika someHTTPHandlerGuts berhasil dan mengembalikan nil . defer berjalan di semua kasus keluar, bukan hanya kasus kesalahan atau panik, jadi kodenya tampak salah meskipun tidak ada kepanikan.

func someHTTPHandlerGuts() (err error) {
  defer func() {
    recordMetric("db call failed")
    return fmt.HandleErrorf("db unavailable: %v", err)
  }()
  data := try(makeDBCall)
  // some code that panics due to a bug
  return nil
}

@josharian Ya, ini kurang lebih persis versi yang kita bahas tahun lalu (kecuali bahwa kita menggunakan check bukannya try ). Saya pikir akan sangat penting bahwa seseorang tidak dapat melompat "kembali" ke seluruh badan fungsi, begitu kita berada di label error . Itu akan memastikan bahwa goto agak "terstruktur" (tidak mungkin kode spageti). Satu kekhawatiran yang diangkat adalah bahwa label error handler ( error: ) akan selalu berakhir di akhir fungsi (jika tidak, seseorang harus melompatinya entah bagaimana). Secara pribadi, saya suka kode penanganan kesalahan keluar dari jalan (di akhir), tetapi yang lain merasa itu harus terlihat tepat di awal.

@mikeshenkel Saya melihat kembali dari satu lingkaran sebagai nilai tambah daripada negatif. Dugaan saya adalah itu akan mendorong pengembang untuk menggunakan fungsi terpisah untuk menangani konten loop atau secara eksplisit menggunakan err seperti yang kita lakukan saat ini. Kedua hal ini tampak seperti hasil yang baik bagi saya.

Dari POV saya, saya tidak merasa sintaks try ini harus menangani setiap use case sama seperti saya tidak merasa perlu menggunakan

V, oke:= m[kunci]

Bentuk dari membaca dari peta

Anda dapat menghindari label goto yang memaksa penangan ke akhir fungsi dengan menghidupkan kembali proposal handle / check dalam bentuk yang disederhanakan. Bagaimana jika kita menggunakan sintaks handle err { ... } tetapi tidak membiarkan penangan berantai, sebaliknya hanya yang terakhir yang digunakan. Ini sangat menyederhanakan proposal itu, dan sangat mirip dengan ide goto, kecuali itu menempatkan penanganan lebih dekat ke titik penggunaan.

func CopyFile(src, dst string) (err error) {
    handle err {
        return fmt.Errorf("copy %s %s: %v", src, dst, err)
    }
    r := check os.Open(src)
    defer r.Close()

    w := check os.Create(dst)
    defer func() {
        w.Close()
        if err != nil {
            os.Remove(dst) // only if a “check” fails
        }
    }()

    {
        // handlers are scoped, after this scope the original handle is used again.
        // as an alternative, we could have repeated the first handle after the io.Copy,
        // or come up with a syntax to name the handlers, though that's often not useful.
        handle err {
            recordMetric("copy failure") // count incidents of this failure
            return fmt.Errorf("copy %s %s: %v", src, dst, err)
        }
        check io.Copy(w, r)
    }
    check w.Close()
    return nil
}

Sebagai bonus, ini memiliki jalur masa depan untuk membiarkan rantai penangan, karena semua penggunaan yang ada akan memiliki pengembalian.

@josharian @griesemer jika Anda memperkenalkan penangan bernama (yang banyak tanggapan untuk diperiksa/ditangani, lihat tema berulang ), ada opsi sintaks yang lebih disukai daripada try(f(), err) :

try.err f()
try?err f()
try#err f()

?err    f() // because 'try' is redundant
?return f() // no handler
?panic  f() // no handler

(?err f()).method()

f?err() // lead with function name, instead of error handling
f?err().method()

file, ?err := os.Open(...) // many check/handle responses also requested this style

Salah satu hal yang paling saya sukai dari Go adalah sintaksnya relatif bebas tanda baca, dan dapat dibaca dengan keras tanpa masalah besar. Saya akan sangat membenci Go berakhir sebagai $#@!perl .

Bagi saya membuat "coba" fungsi bawaan dan mengaktifkan rantai memiliki 2 masalah:

  • Ini tidak konsisten dengan aliran kontrol lainnya yang sedang berjalan (misalnya, kata kunci for/if/return/etc).
  • Itu membuat kode kurang mudah dibaca.

Saya lebih suka membuat pernyataan tanpa tanda kurung. Contoh dalam proposal akan memerlukan banyak baris tetapi akan menjadi lebih mudah dibaca (yaitu, contoh "coba" individual akan lebih sulit untuk dilewatkan). Ya, itu akan merusak parser eksternal tetapi saya lebih suka menjaga konsistensi.

Operator ternary adalah tempat lain yang pergi tidak memiliki sesuatu dan membutuhkan lebih banyak penekanan tombol tetapi pada saat yang sama meningkatkan keterbacaan/pemeliharaan. Menambahkan "coba" dalam bentuk yang lebih terbatas ini akan lebih menyeimbangkan ekspresif vs keterbacaan, IMO.

FWIW, panic mempengaruhi aliran kontrol dan memiliki parens, tetapi go dan defer juga memengaruhi aliran dan tidak. Saya cenderung berpikir bahwa try lebih mirip dengan defer karena ini adalah operasi aliran yang tidak biasa dan membuatnya lebih sulit untuk dilakukan try (try os.Open(file)).Read(buf) baik karena kami ingin mencegah one-liners bagaimanapun, tapi apa pun. Baik baik saja.

Saran yang akan dibenci semua orang untuk nama implisit untuk variabel pengembalian kesalahan terakhir: $err . Ini lebih baik daripada try() IMO. :-)

@griesemer

_"Secara pribadi, saya suka kode penanganan kesalahan (di akhir)"_

+1 untuk itu!

Saya menemukan bahwa penanganan kesalahan yang diterapkan _sebelum_ kesalahan terjadi jauh lebih sulit untuk dijelaskan daripada penanganan kesalahan yang diterapkan _setelah_ kesalahan terjadi. Harus mental melompat mundur dan memaksa untuk mengikuti alur logika terasa seperti saya kembali pada tahun 1980 menulis Dasar dengan GOTOs.

Izinkan saya mengusulkan cara potensial lain untuk menangani kesalahan menggunakan CopyFile() sebagai contoh lagi:

func CopyFile(src, dst string) (err error) {

    r := os.Open(src)
    defer r.Close()

    w := os.Create(dst)
    defer w.Close()

    io.Copy(w, r)
    w.Close()

    for err := error {
        switch err.Source() {
        case w.Close:
            os.Remove(dst) // only if a “try” fails
            fallthrough
        case os.Open, os.Create, io.Copy:
            err = fmt.Errorf("copy %s %s: %v", src, dst, err)
        default:
            err = fmt.Errorf("an unexpected error occurred")
        }
    }

    return err
}

Perubahan bahasa yang diperlukan adalah:

  1. Izinkan konstruksi for error{} , mirip dengan for range{} tetapi hanya dimasukkan jika ada kesalahan dan hanya dijalankan sekali.

  2. Izinkan penghilangan pengambilan nilai kembalian yang mengimplementasikan <object>.Error() string tetapi hanya jika for error{} konstruk ada dalam func yang sama.

  3. Menyebabkan aliran kontrol program melompat ke baris pertama dari konstruksi for error{} ketika func mengembalikan _"error"_ dalam nilai pengembalian terakhirnya.

  4. Saat mengembalikan _"error"_ Go akan menambahkan assign referensi ke fungsi yang mengembalikan kesalahan yang seharusnya dapat diambil dengan <error>.Source()

Apa itu _"kesalahan"_?

Saat ini _"error"_ didefinisikan sebagai objek apa pun yang mengimplementasikan Error() string dan tentu saja bukan nil .

Namun, seringkali ada kebutuhan untuk memperluas kesalahan _bahkan pada keberhasilan_ untuk memungkinkan pengembalian nilai yang diperlukan untuk hasil sukses dari RESTful API. Jadi saya akan meminta agar tim Go jangan secara otomatis menganggap err!=nil berarti _"error"_ tetapi sebaliknya memeriksa apakah objek kesalahan mengimplementasikan IsError() dan jika IsError() mengembalikan true sebelum mengasumsikan bahwa nilai non- nil adalah _"error."_

_(Saya tidak harus berbicara tentang kode di perpustakaan standar tetapi terutama jika Anda memilih aliran kontrol Anda untuk bercabang pada _"error"_. Jika Anda hanya melihat err!=nil kita akan sangat terbatas dalam apa yang kita dapat dilakukan dalam hal mengembalikan nilai dalam fungsi kami.)_

BTW, mengizinkan semua orang untuk menguji _"error"_ dengan cara yang sama mungkin paling mudah dilakukan dengan menambahkan fungsi iserror() bawaan baru :

type ErrorIser interface {
    IsError() bool
}
func iserror(err error) bool {
    if err == nil { 
        return false
    }
    if _,ok := err.(ErrorIser); !ok {
        return true
    }
    return err.IsError()
}

Manfaat sampingan memungkinkan non-menangkap _"kesalahan"_

Perhatikan bahwa mengizinkan non-capturing _"error"_ terakhir dari panggilan func akan memungkinkan pemfaktoran ulang nanti untuk mengembalikan kesalahan dari func yang awalnya tidak perlu mengembalikan kesalahan. Dan itu akan memungkinkan refactoring ini tanpa merusak kode yang ada yang menggunakan bentuk pemulihan kesalahan ini dan panggilan mengatakan func s.

Bagi saya, keputusan _"Haruskah saya mengembalikan kesalahan atau melupakan penanganan kesalahan untuk memanggil kesederhanaan?"_ adalah salah satu kebingungan terbesar saya saat menulis kode Go. Mengizinkan tidak menangkap _"kesalahan"_ di atas akan menghilangkan kebingungan itu.

Saya sebenarnya mencoba menerapkan ide ini sebagai penerjemah Go sekitar setengah tahun yang lalu. Saya tidak memiliki pendapat yang kuat apakah fitur ini harus ditambahkan sebagai bawaan Go, tetapi izinkan saya berbagi pengalaman (walaupun saya tidak yakin itu berguna).

https://github.com/rhysd/trygo

Saya memanggil bahasa yang diperluas TryGo dan menerapkan penerjemah TryGo to Go.

Dengan penerjemah, kode

func CreateFileInSubdir(subdir, filename string, content []byte) error {
    cwd := try(os.Getwd())

    try(os.Mkdir(filepath.Join(cwd, subdir)))

    p := filepath.Join(cwd, subdir, filename)
    f := try(os.Create(p))
    defer f.Close()

    try(f.Write(content))

    fmt.Println("Created:", p)
    return nil
}

dapat diterjemahkan menjadi

func CreateFileInSubdir(subdir, filename string, content []byte) error {
    cwd, _err0 := os.Getwd()
    if _err0 != nil {
        return _err0
    }

    if _err1 := os.Mkdir(filepath.Join(cwd, subdir)); _err1 != nil {
        return _err1
    }

    p := filepath.Join(cwd, subdir, filename)
    f, _err2 := os.Create(p)
    if _err2 != nil {
        return _err2
    }
    defer f.Close()

    if _, _err3 := f.Write(content); _err3 != nil {
        return _err3
    }

    fmt.Println("Created:", p)
    return nil
}

Untuk batasan bahasa, saya tidak dapat mengimplementasikan panggilan try() generik. Hal ini dibatasi untuk

  • RHS pernyataan definisi
  • RHS pernyataan tugas
  • Pernyataan panggilan

tapi saya bisa mencoba ini dengan proyek kecil saya. Pengalaman saya adalah

  • ini pada dasarnya berfungsi dengan baik dan menghemat beberapa baris
  • nilai pengembalian bernama sebenarnya tidak dapat digunakan untuk err karena nilai pengembalian fungsinya ditentukan oleh penugasan dan fungsi khusus try() . sangat membingungkan
  • fungsi try() ini tidak memiliki fitur 'wrapping error' seperti yang dibahas di atas.

_"Keduanya tampak seperti hasil yang baik bagi saya."_

Kita harus setuju untuk tidak setuju di sini.

_"sintaks percobaan ini (tidak harus) menangani setiap kasus penggunaan"_

Meme itu mungkin yang paling meresahkan. Setidaknya mengingat seberapa tahan tim/komunitas Go terhadap perubahan apa pun di masa lalu yang tidak berlaku secara luas.

Jika kita mengizinkan pembenaran itu di sini, mengapa kita tidak dapat meninjau kembali proposal sebelumnya yang telah ditolak karena tidak dapat diterapkan secara luas?

Dan apakah kita sekarang terbuka untuk memperdebatkan perubahan di Go yang hanya berguna untuk kasus tepi yang dipilih?

Menurut perkiraan saya, menetapkan preseden ini tidak akan menghasilkan hasil yang baik dalam jangka panjang...

_"@mikeshenkel"_

PS Saya tidak melihat pesan Anda pada awalnya karena salah eja. _(ini tidak menyinggung saya, saya hanya tidak mendapatkan pemberitahuan ketika nama pengguna saya salah eja...)_

Saya menghargai komitmen untuk kompatibilitas ke belakang yang memotivasi Anda untuk menjadikan try sebagai bawaan, bukan kata kunci, tetapi setelah bergulat dengan _keanehan_ total memiliki fungsi yang sering digunakan yang dapat mengubah aliran kontrol ( panic dan recover sangat jarang), saya bertanya-tanya: adakah yang pernah melakukan analisis skala besar tentang frekuensi try sebagai pengenal dalam basis kode sumber terbuka? Saya penasaran dan skeptis, jadi saya melakukan pencarian awal di berikut ini:

Dari 11.108.770 baris signifikan Go yang tinggal di repositori ini, hanya ada 63 instance try yang digunakan sebagai pengenal. Tentu saja, saya menyadari bahwa basis kode ini (walaupun besar, digunakan secara luas, dan penting dalam hak mereka sendiri) hanya mewakili sebagian kecil dari kode Go di luar sana, dan selain itu, kami tidak memiliki cara untuk menganalisis secara langsung basis kode pribadi, tetapi tentu saja hasil yang menarik.

Selain itu, karena try , seperti kata kunci lainnya, adalah huruf kecil, Anda tidak akan pernah menemukannya di API publik sebuah paket. Penambahan kata kunci hanya akan memengaruhi internal paket.

Ini semua adalah kata pengantar untuk beberapa ide yang ingin saya masukkan ke dalam campuran yang akan mendapat manfaat dari try sebagai kata kunci.

Saya akan mengusulkan konstruksi berikut.

1) Tidak ada penangan

// The existing proposal, but as a keyword rather than builtin.  When an error is 
// "caught", the function returns all zero values plus the error.  Nothing 
// particularly new here.
func doSomething() (int, error) {
    try SomeFunc()
    a, b := try AnotherFunc()

    // ...

    return 123, nil
}

2) Penangan

Perhatikan bahwa penangan kesalahan adalah blok kode sederhana, dimaksudkan untuk digariskan, bukan fungsi. Lebih lanjut tentang ini di bawah ini.

func doSomething() (int, error) {
    // Inline error handler
    a, b := try SomeFunc() else err {
        return 0, errors.Wrap(err, "error in doSomething:")
    }

    // Named error handlers
    handler logAndContinue err {
        log.Errorf("non-critical error: %v", err)
    }
    handler annotateAndReturn err {
        return 0, errors.Wrap(err, "error in doSomething:")
    }

    c, d := try SomeFunc() else logAndContinue
    e, f := try OtherFunc() else annotateAndReturn

    // ...

    return 123, nil
}

Pembatasan yang diusulkan:

  • Anda hanya dapat try panggilan fungsi. Tidak try err .
  • Jika Anda tidak menentukan handler, Anda hanya dapat try dari dalam fungsi yang mengembalikan kesalahan sebagai nilai pengembalian paling kanan. Tidak ada perubahan dalam perilaku try berdasarkan konteksnya. Itu tidak pernah panik (seperti yang dibahas lebih awal di utas).
  • Tidak ada "rantai pawang" dalam bentuk apa pun. Penangan hanyalah blok kode yang tidak dapat disejajarkan.

Manfaat:

  • Sintaks try / else dapat disederhanakan menjadi "compound if" yang ada:
    go a, b := try SomeFunc() else err { return 0, errors.Wrap(err, "error in doSomething:") }
    menjadi
    go if a, b, err := SomeFunc(); err != nil { return 0, errors.Wrap(err, "error in doSomething:") }
    Bagi saya, gabungan if selalu tampak lebih membingungkan daripada membantu karena alasan yang sangat sederhana: kondisional umumnya terjadi _setelah_ operasi, dan ada hubungannya dengan pemrosesan hasilnya. Jika operasi terjepit di dalam pernyataan bersyarat, itu hanya kurang jelas bahwa itu terjadi. Mata terganggu. Lebih jauh lagi, ruang lingkup variabel yang ditentukan tidak segera terlihat seperti ketika mereka berada paling kiri pada sebuah baris.
  • Penangan kesalahan sengaja tidak didefinisikan sebagai fungsi (atau dengan apa pun yang menyerupai semantik seperti fungsi). Ini melakukan beberapa hal untuk kami:

    • Kompiler dapat dengan mudah memasukkan handler bernama di mana pun ia dirujuk. Ini lebih seperti templat makro/kodegen sederhana daripada panggilan fungsi. Runtime bahkan tidak perlu tahu bahwa penangan ada.

    • Kami tidak terbatas pada apa yang bisa kami lakukan di dalam handler. Kami mengatasi kritik check / handle bahwa "kerangka kerja penanganan kesalahan ini hanya baik untuk dana talangan". Kami juga mengatasi kritik "rantai penangan". Kode arbitrer apa pun dapat ditempatkan di dalam salah satu penangan ini, dan tidak ada aliran kontrol lain yang tersirat.

    • Kita tidak perlu membajak return di dalam handler untuk mengartikan super return . Membajak kata kunci sangat membingungkan. return hanya berarti return , dan sebenarnya tidak perlu super return .

    • defer tidak perlu cahaya bulan sebagai mekanisme penanganan kesalahan. Kita dapat terus memikirkannya terutama sebagai cara untuk membersihkan sumber daya, dll.

  • Mengenai menambahkan konteks ke kesalahan:

    • Menambahkan konteks dengan penangan sangat sederhana dan terlihat sangat mirip dengan blok if err != nil yang ada

    • Meskipun konstruk "coba tanpa penangan" tidak secara langsung mendorong penambahan konteks, sangat mudah untuk memfaktorkan ulang ke dalam bentuk penangan. Penggunaannya yang dimaksudkan terutama selama pengembangan, dan akan sangat mudah untuk menulis cek go vet untuk menyoroti kesalahan yang tidak tertangani.

Mohon maaf jika ide-ide ini sangat mirip dengan proposal lain — saya telah mencoba mengikuti semuanya, tetapi mungkin banyak yang terlewatkan.

@brynbellomy Terima kasih untuk analisis kata kunci - itu informasi yang sangat membantu. Tampaknya try sebagai kata kunci mungkin baik-baik saja. (Anda mengatakan bahwa API tidak terpengaruh - itu benar, tetapi try mungkin masih muncul sebagai nama parameter atau sejenisnya - jadi dokumentasi mungkin harus diubah. Tapi saya setuju bahwa itu tidak akan memengaruhi klien dari paket-paket itu.)

Mengenai proposal Anda: Itu akan baik-baik saja bahkan tanpa penangan bernama, bukan? (Itu akan menyederhanakan proposal tanpa kehilangan daya. Seseorang cukup memanggil fungsi lokal dari handler inline.)

Mengenai proposal Anda: Itu akan baik-baik saja bahkan tanpa penangan bernama, bukan? (Itu akan menyederhanakan proposal tanpa kehilangan daya. Seseorang cukup memanggil fungsi lokal dari handler inline.)

@griesemer Memang — Saya merasa cukup suam-suam kuku tentang memasukkan itu. Tentu saja lebih Go-ish tanpa.

Di sisi lain, tampaknya orang menginginkan kemampuan untuk melakukan penanganan kesalahan satu baris, termasuk satu baris yang return . Kasus tipikal adalah log, lalu return . Jika kita keluar ke fungsi lokal di klausa else , kita mungkin kehilangan itu:

a, b := try SomeFunc() else err {
    someLocalFunc(err)
    return 0, err
}

(Saya masih lebih suka ini untuk menggabungkan jika)

Namun, Anda masih bisa mendapatkan pengembalian satu baris yang menambahkan konteks kesalahan dengan menerapkan tweak gofmt sederhana yang dibahas sebelumnya di utas:

a, b := try SomeFunc() else err { return 0, errors.Wrap(err, "bad stuff!") }

Apakah kata kunci baru diperlukan dalam proposal di atas? Kenapa tidak:

SomeFunc() else return
a, b := SomeOtherFunc() else err { return 0, errors.Wrap(err, "bad stuff!") }

@griesemer jika penangan kembali, saya sarankan Anda membuat masalah baru untuk diskusi try/handle atau try/_label_. Proposal ini secara khusus menghilangkan penangan, dan ada banyak cara untuk mendefinisikan dan memanggilnya.

Siapa pun yang menyarankan penangan harus terlebih dahulu membaca cek/menangani umpan balik wiki. Kemungkinannya bagus bahwa apa pun yang Anda impikan sudah dijelaskan di sana :-)
https://github.com/golang/go/wiki/Go2ErrorHandlingFeedback

@smonkewitz tidak, kata kunci baru tidak diperlukan dalam versi itu karena terikat dengan pernyataan penugasan, yang sejauh ini telah disebutkan beberapa kali dalam berbagai sintaks gula.

https://github.com/golang/go/issues/32437#issuecomment -499808741
https://github.com/golang/go/issues/32437#issuecomment -499852124
https://github.com/golang/go/issues/32437#issuecomment -50095505

@ianlancetaylor apakah penanganan kesalahan ini sudah dipertimbangkan oleh tim go? Implementasinya tidak semudah yang diusulkan, tetapi terasa lebih idiomatis. ~pernyataan yang tidak perlu, maaf.~

Saya ingin mengulangi sesuatu yang @deanveloper dan beberapa orang lain katakan, tetapi dengan penekanan saya sendiri. Di https://github.com/golang/go/issues/32437#issuecomment -498939499 @deanveloper berkata:

try adalah pengembalian bersyarat. Aliran kontrol DAN pengembalian keduanya ditahan di atas tumpuan di Go. Semua aliran kontrol dalam suatu fungsi diindentasi, dan semua pengembalian dimulai dengan return . Menggabungkan kedua konsep ini bersama-sama menjadi panggilan fungsi yang mudah terlewatkan terasa agak aneh.

Selanjutnya, dalam proposal ini try adalah fungsi yang mengembalikan nilai, sehingga dapat digunakan sebagai bagian dari ekspresi yang lebih besar.

Beberapa berpendapat bahwa panic telah menetapkan preseden untuk fungsi bawaan yang mengubah aliran kontrol, tetapi saya pikir panic dasarnya berbeda karena dua alasan:

  1. Panik tidak bersyarat; itu selalu membatalkan fungsi panggilan.
  2. Panic tidak mengembalikan nilai apa pun dan dengan demikian hanya dapat muncul sebagai pernyataan mandiri, yang meningkatkan visibilitasnya.

Coba di sisi lain:

  1. Bersyarat; itu mungkin atau mungkin tidak kembali dari fungsi panggilan.
  2. Mengembalikan nilai dan dapat muncul dalam ekspresi majemuk, mungkin beberapa kali, pada satu baris, berpotensi melewati margin kanan jendela editor saya.

Untuk alasan ini saya pikir try terasa lebih dari "sedikit", saya pikir itu pada dasarnya merusak keterbacaan kode.

Hari ini, ketika kami menemukan beberapa kode Go untuk pertama kalinya, kami dapat dengan cepat menelusurinya untuk menemukan kemungkinan titik keluar dan mengontrol titik aliran. Saya percaya itu adalah properti kode Go yang sangat berharga. Menggunakan try menjadi terlalu mudah untuk menulis kode yang tidak memiliki properti tersebut.

Saya akui bahwa kemungkinan pengembang Go yang menghargai keterbacaan kode akan berkumpul pada idiom penggunaan untuk try yang menghindari jebakan keterbacaan ini. Saya harap itu akan terjadi karena keterbacaan kode tampaknya menjadi nilai inti bagi banyak pengembang Go. Tetapi tidak jelas bagi saya bahwa try menambahkan nilai yang cukup pada idiom kode yang ada untuk membawa beban menambahkan konsep baru ke bahasa untuk dipelajari semua orang dan itu dapat dengan mudah merusak keterbacaan.

````
jika != "rusak" {
jangan perbaiki
}

@ChrisHines Untuk poin Anda (yang digaungkan di tempat lain di utas ini), mari tambahkan batasan lain:

  • setiap pernyataan try (bahkan yang tidak memiliki handler) harus muncul pada barisnya sendiri.

Anda masih akan mendapat manfaat dari pengurangan kebisingan visual yang besar. Kemudian, Anda telah menjamin pengembalian yang dianotasi oleh return dan pengembalian bersyarat yang dianotasi oleh try , dan kata kunci tersebut selalu berdiri di awal baris (atau paling buruk, langsung setelah penetapan variabel).

Jadi, tidak satu pun dari jenis omong kosong ini:

try EmitEvent(try (try DecodeMsg(m)).SaveToDB())

melainkan ini:

dm := try DecodeMsg(m)
um := try dm.SaveToDB()
try EmitEvent(um)

yang masih terasa lebih jelas dari ini:

dm, err := DecodeMsg(m)
if err != nil {
    return nil, err
}

um, err := dm.SaveToDB()
if err != nil {
    return nil, err
}

err = EmitEvent(um)
if err != nil {
    return nil, err
}

Satu hal yang saya suka tentang desain ini adalah bahwa tidak mungkin untuk mengabaikan kesalahan secara diam-diam tanpa tetap menjelaskan bahwa kesalahan itu mungkin terjadi . Sedangkan sekarang, Anda kadang-kadang melihat x, _ := SomeFunc() (apa nilai pengembalian yang diabaikan? kesalahan? sesuatu yang lain?), sekarang Anda harus membubuhi keterangan dengan jelas:

x := try SomeFunc() else err {}

Sejak posting saya sebelumnya untuk mendukung proposal tersebut, saya telah melihat dua ide yang diposting oleh @jagv (tanpa parameter try mengembalikan *error ) dan oleh @josharian (penangan kesalahan berlabel) yang saya percaya pada bentuk yang sedikit dimodifikasi akan sangat meningkatkan proposal.

Menyatukan ide-ide ini dengan ide lain yang saya miliki sendiri, kami akan memiliki empat versi try :

  1. mencoba()
  2. coba (params)
  3. coba (params, label)
  4. coba (params, panik)

1 hanya akan mengembalikan pointer ke parameter pengembalian kesalahan (ERP) atau nihil jika tidak ada (hanya #4). Ini akan memberikan alternatif untuk ERP bernama tanpa perlu menambahkan built-in lebih lanjut.

2 akan bekerja persis seperti yang dibayangkan saat ini. Kesalahan non-nol akan segera dikembalikan tetapi dapat didekorasi dengan pernyataan defer .

3 akan berfungsi seperti yang disarankan oleh @josharian yaitu pada kesalahan non-nol kode akan bercabang ke label. Namun, tidak akan ada label penangan kesalahan default karena kasus itu sekarang akan berubah menjadi #2.

Tampaknya bagi saya bahwa ini biasanya akan menjadi cara yang lebih baik untuk mendekorasi kesalahan (atau menanganinya secara lokal dan kemudian mengembalikan nihil) daripada defer karena lebih sederhana dan lebih cepat. Siapa saja yang tidak suka masih bisa menggunakan #2.

Ini akan menjadi praktik terbaik untuk menempatkan label/kode penanganan kesalahan di dekat akhir fungsi dan tidak melompat kembali ke seluruh badan fungsi. Namun, saya tidak berpikir kompiler harus menegakkan baik karena mungkin ada kesempatan aneh di mana mereka berguna dan penegakan mungkin sulit dalam hal apa pun.

Jadi label normal dan perilaku goto akan menerapkan subjek (seperti yang dikatakan @josharian ) ke #26058 diperbaiki terlebih dahulu tetapi saya pikir itu harus tetap diperbaiki.

Nama label tidak boleh panic karena akan bertentangan dengan #4.

4 akan panic segera daripada kembali atau bercabang. Akibatnya, jika ini adalah satu-satunya versi try yang digunakan dalam fungsi tertentu, ERP tidak diperlukan.

Saya telah menambahkan ini sehingga paket pengujian dapat berfungsi seperti sekarang tanpa perlu lebih lanjut built-in atau perubahan lainnya. Namun, ini mungkin berguna dalam skenario _fatal_ lainnya juga.

Ini perlu menjadi versi terpisah dari try sebagai alternatif percabangan ke penangan kesalahan dan kemudian panik dari itu masih memerlukan ERP.

Salah satu jenis reaksi terkuat terhadap proposal awal adalah kekhawatiran seputar hilangnya visibilitas aliran normal tempat fungsi kembali.

Misalnya, @deanveloper mengungkapkan kekhawatiran itu dengan sangat baik di https://github.com/golang/go/issues/32437#issuecomment -498932961, yang menurut saya merupakan komentar dengan suara terbanyak di sini.

@dominikh menulis di https://github.com/golang/go/issues/32437#issuecomment -499067357:

Dalam kode gofmt'ed, return selalu cocok dengan /^\t*return / – ini adalah pola yang sangat sepele untuk dilihat, tanpa bantuan apa pun. coba, di sisi lain, dapat terjadi di mana saja dalam kode, bersarang secara sewenang-wenang jauh di dalam panggilan fungsi. Tidak ada jumlah pelatihan yang akan membuat kita dapat segera melihat semua aliran kontrol dalam suatu fungsi tanpa bantuan alat.

Untuk membantu dengan itu, @brynbellomy menyarankan kemarin:

setiap pernyataan try (bahkan yang tanpa handler) harus muncul pada barisnya sendiri.

Lebih jauh lagi, try dapat diminta untuk menjadi awal baris, bahkan untuk sebuah tugas.

Jadi bisa jadi:

try dm := DecodeMsg(m)
try um := dm.SaveToDB()
try EmitEvent(um)

daripada yang berikut (dari contoh @brynbellomy ):

dm, err := DecodeMsg(m)
if err != nil {
    return nil, err
}

um, err := dm.SaveToDB()
if err != nil {
    return nil, err
}

err = EmitEvent(um)
if err != nil {
    return nil, err
}

Tampaknya itu akan mempertahankan visibilitas yang cukup, bahkan tanpa bantuan editor atau IDE, sambil tetap mengurangi boilerplate.

Itu bisa bekerja dengan pendekatan berbasis penangguhan yang saat ini diusulkan yang bergantung pada parameter hasil bernama, atau bisa bekerja dengan menentukan fungsi penangan normal. (Menentukan fungsi handler tanpa memerlukan nilai pengembalian bernama tampaknya lebih baik bagi saya daripada membutuhkan nilai pengembalian bernama, tetapi itu adalah poin terpisah).

Proposal mencakup contoh ini:

info := try(try(os.Open(file)).Stat())    // proposed try built-in

Itu bisa saja:

try f := os.Open(file)
try info := f.Stat()

Itu masih pengurangan boilerplate dibandingkan dengan apa yang mungkin ditulis seseorang hari ini, bahkan jika tidak sesingkat sintaks yang diusulkan. Mungkin itu akan cukup singkat?

@elagergren-spideroak memberikan contoh ini:

try(try(try(to()).parse().this)).easily())

Saya pikir itu memiliki parens yang tidak cocok, yang mungkin merupakan poin yang disengaja atau sedikit humor yang halus, jadi saya tidak yakin apakah contoh itu bermaksud memiliki 2 try atau 3 try . Bagaimanapun, mungkin akan lebih baik untuk meminta penyebaran itu di 2-3 baris yang dimulai dengan try .

@thepudds , inilah yang saya maksud di komentar saya sebelumnya. Kecuali yang diberikan

try f := os.Open(file)
try info := f.Stat()

Hal yang jelas untuk dilakukan adalah menganggap try sebagai blok coba di mana lebih dari satu kalimat dapat dimasukkan ke dalam tanda kurung . Jadi yang di atas bisa menjadi

try (
    f := os.Open(file)
    into := f.Stat()
)

Jika kompiler tahu bagaimana menangani ini, hal yang sama juga berfungsi untuk bersarang. Jadi sekarang di atas bisa menjadi

try info := os.Open(file).Stat()

Dari tanda tangan fungsi, kompiler mengetahui bahwa Open dapat mengembalikan nilai kesalahan dan seperti dalam blok coba, perlu menghasilkan penanganan kesalahan dan kemudian memanggil Stat() pada nilai utama yang dikembalikan dan seterusnya.

Hal berikutnya adalah mengizinkan pernyataan di mana tidak ada nilai kesalahan yang dihasilkan atau ditangani secara lokal. Jadi sekarang Anda bisa mengatakan

try (
    f := os.Open(file)
    debug("f: %v\n", f) // debug returns nothing
    into := f.Stat()
)

Ini memungkinkan kode yang berkembang tanpa harus mengatur ulang blok percobaan. Tetapi untuk beberapa alasan aneh orang tampaknya berpikir bahwa penanganan kesalahan harus dijabarkan secara eksplisit! Mereka ingin

try(try(try(to()).parse()).this)).easily())

Sementara saya baik-baik saja dengan

try to().parse().this().easily()

Meskipun dalam kedua kasus kode pengecekan kesalahan yang sama dapat dihasilkan. Pandangan saya adalah Anda selalu dapat menulis kode khusus untuk penanganan kesalahan jika perlu. try (atau apa pun yang Anda suka menyebutnya) cukup mendeklarasikan penanganan kesalahan default (yaitu untuk menyepaknya ke pemanggil).

Manfaat lain adalah jika kompiler menghasilkan penanganan kesalahan default, ia dapat menambahkan beberapa informasi pengenal lainnya sehingga Anda tahu mana dari empat fungsi di atas yang gagal.

Saya agak khawatir tentang keterbacaan program di mana try muncul di dalam ekspresi lain. Jadi saya menjalankan grep "return .*err$" di perpustakaan standar dan mulai membaca blok secara acak. Ada 7214 hasil, saya hanya membaca beberapa ratus.

Hal pertama yang perlu diperhatikan adalah di mana try berlaku, itu membuat hampir semua blok ini sedikit lebih mudah dibaca.

Hal kedua adalah bahwa sangat sedikit dari ini, kurang dari 1 dari 10, akan menempatkan try di dalam ekspresi lain. Kasus tipikal adalah pernyataan dalam bentuk x := try(...) atau ^try(...)$ .

Berikut adalah beberapa contoh di mana try akan muncul di dalam ekspresi lain:

teks/templat

func gt(arg1, arg2 reflect.Value) (bool, error) {
        // > is the inverse of <=.
        lessOrEqual, err := le(arg1, arg2)
        if err != nil {
                return false, err
        }
        return !lessOrEqual, nil
}

menjadi:

func gt(arg1, arg2 reflect.Value) (bool, error) {
        // > is the inverse of <=.
        return !try(le(arg1, arg2)), nil
}

teks/templat

func index(item reflect.Value, indices ...reflect.Value) (reflect.Value, error) {
...
        switch v.Kind() {
        case reflect.Map:
                index, err := prepareArg(index, v.Type().Key())
                if err != nil {
                        return reflect.Value{}, err
                }
                if x := v.MapIndex(index); x.IsValid() {
                        v = x
                } else {
                        v = reflect.Zero(v.Type().Elem())
                }
        ...
        }
}

menjadi

func index(item reflect.Value, indices ...reflect.Value) (reflect.Value, error) {
        ...
        switch v.Kind() {
        case reflect.Map:
                if x := v.MapIndex(try(prepareArg(index, v.Type().Key()))); x.IsValid() {
                        v = x
                } else {
                        v = reflect.Zero(v.Type().Elem())
                }
        ...
        }
}

(ini adalah contoh yang paling dipertanyakan yang saya lihat)

regexp/sintaks:

regexp/syntax/parse.go

func Parse(s string, flags Flags) (*Regexp, error) {
        ...
        if c, t, err = nextRune(t); err != nil {
                return nil, err
        }
        p.literal(c)
        ...
}

menjadi

func Parse(s string, flags Flags) (*Regexp, error) {
        ...
        c, t = try(nextRune(t))
        p.literal(c)
        ...
}

Ini bukan contoh percobaan di dalam ekspresi lain tetapi saya ingin menyebutnya karena meningkatkan keterbacaan. Jauh lebih mudah untuk melihat di sini bahwa nilai c dan t hidup di luar cakupan pernyataan if.

bersih/http

net/http/request.go:readRequest

        mimeHeader, err := tp.ReadMIMEHeader()
        if err != nil {
                return nil, err
        }
        req.Header = Header(mimeHeader)

menjadi:

        req.Header = Header(try(tp.ReadMIMEHeader())

database/sql

        if driverCtx, ok := driveri.(driver.DriverContext); ok {
                connector, err := driverCtx.OpenConnector(dataSourceName)
                if err != nil {
                        return nil, err
                }
                return OpenDB(connector), nil
        }

menjadi

        if driverCtx, ok := driveri.(driver.DriverContext); ok {
                return OpenDB(try(driverCtx.OpenConnector(dataSourceName))), nil
        }

database/sql

        si, err := ctxDriverPrepare(ctx, dc.ci, query)
        if err != nil {
                return nil, err
        }
        ds := &driverStmt{Locker: dc, si: si}

menjadi

        ds := &driverStmt{
                Locker: dc,
                si: try(ctxDriverPrepare(ctx, dc.ci, query)),
        }

bersih/http

        if http2isconnectioncloserequest(req) && dialonmiss {
                // it gets its own connection.
                http2tracegetconn(req, addr)
                const singleuse = true
                cc, err := p.t.dialclientconn(addr, singleuse)
                if err != nil {
                        return nil, err
                }
                return cc, nil
        }

menjadi

        if http2isconnectioncloserequest(req) && dialonmiss {
                // it gets its own connection.
                http2tracegetconn(req, addr)
                const singleuse = true
                return try(p.t.dialclientconn(addr, singleuse))
        }

bersih/http

func (f *http2Framer) endWrite() error {
        ...
        n, err := f.w.Write(f.wbuf)
        if err == nil && n != len(f.wbuf) {
                err = io.ErrShortWrite
        }
        return err
}

menjadi

func (f *http2Framer) endWrite() error {
        ...
        if try(f.w.Write(f.wbuf) != len(f.wbuf) {
                return io.ErrShortWrite
        }
        return nil
}

(Yang ini saya sangat suka.)

bersih/http

        if f, err := fr.ReadFrame(); err != nil {
                return nil, err
        } else {
                hc = f.(*http2ContinuationFrame) // guaranteed by checkFrameOrder
        }

menjadi

        hc = try(fr.ReadFrame()).(*http2ContinuationFrame)// guaranteed by checkFrameOrder

}

(Juga bagus.)

bersih :

        if ctrlFn != nil {
                c, err := newRawConn(fd)
                if err != nil {
                        return err
                }
                if err := ctrlFn(fd.ctrlNetwork(), laddr.String(), c); err != nil {
                        return err
                }
        }

menjadi

        if ctrlFn != nil {
                try(ctrlFn(fd.ctrlNetwork(), laddr.String(), try(newRawConn(fd))))
        }

mungkin ini terlalu banyak, dan sebaliknya seharusnya:

        if ctrlFn != nil {
                c := try(newRawConn(fd))
                try(ctrlFn(fd.ctrlNetwork(), laddr.String(), c))
        }

Secara keseluruhan, saya cukup menikmati efek try pada kode perpustakaan standar yang saya baca.

Satu poin terakhir: Melihat try diterapkan untuk membaca kode di luar beberapa contoh dalam proposal itu mencerahkan. Saya pikir perlu mempertimbangkan untuk menulis alat untuk secara otomatis mengonversi kode untuk menggunakan try (di mana itu tidak mengubah semantik program). Akan menarik untuk membaca contoh perbedaan yang dihasilkan terhadap paket-paket populer di github untuk melihat apakah apa yang saya temukan di perpustakaan standar bertahan. Keluaran program semacam itu dapat memberikan wawasan tambahan tentang pengaruh proposal.

@crawshaw terima kasih telah melakukan ini, senang melihatnya beraksi. Tetapi melihatnya dalam tindakan membuat saya menganggap lebih serius argumen terhadap penanganan kesalahan inline yang sampai sekarang saya abaikan.

Karena ini sangat dekat dengan saran menarik @thepudds untuk membuat pernyataan try , saya menulis ulang semua contoh menggunakan sintaks itu dan menemukannya jauh lebih jelas daripada ekspresi- try atau status quo, tanpa memerlukan terlalu banyak baris tambahan:

func gt(arg1, arg2 reflect.Value) (bool, error) {
        // > is the inverse of <=.
        try lessOrEqual := le(arg1, arg2)
        return !lessOrEqual, nil
}
func index(item reflect.Value, indices ...reflect.Value) (reflect.Value, error) {
        ...
        switch v.Kind() {
        case reflect.Map:
                try index := prepareArg(index, v.Type().Key())
                if x := v.MapIndex(index); x.IsValid() {
                        v = x
                } else {
                        v = reflect.Zero(v.Type().Elem())
                }
        ...
        }
}
func Parse(s string, flags Flags) (*Regexp, error) {
        ...
        try c, t = nextRune(t)
        p.literal(c)
        ...
}
        try mimeHeader := tp.ReadMIMEHeader()
        req.Header = Header(mimeHeader)
        if driverCtx, ok := driveri.(driver.DriverContext); ok {
                try connector := driverCtx.OpenConnector(dataSourceName)
                return OpenDB(connector), nil
        }

Yang ini bisa dibilang lebih baik dengan ekspresi- try jika ada beberapa bidang yang harus try -ed, tapi saya masih lebih suka keseimbangan trade off ini

        try si := ctxDriverPrepare(ctx, dc.ci, query)
        ds := &driverStmt{Locker: dc, si: si}

Ini pada dasarnya adalah kasus terburuk untuk ini dan terlihat baik-baik saja:

        if http2isconnectioncloserequest(req) && dialonmiss {
                // it gets its own connection.
                http2tracegetconn(req, addr)
                const singleuse = true
                try cc := p.t.dialclientconn(addr, singleuse)
                return cc, nil
        }

Saya berdebat dengan diri saya sendiri apakah if try akan atau harus legal, tetapi saya tidak dapat memberikan penjelasan yang masuk akal mengapa itu tidak boleh dan itu bekerja dengan cukup baik di sini:

func (f *http2Framer) endWrite() error {
        ...
        if try n := f.w.Write(f.wbuf); n != len(f.wbuf) {
                return io.ErrShortWrite
        }
        return nil
}
        try f := fr.ReadFrame()
        hc = f.(*http2ContinuationFrame) // guaranteed by checkFrameOrder
        if ctrlFn != nil {
                try c := newRawConn(fd)
                try ctrlFn(fd.ctrlNetwork(), laddr.String(), c)
        }

Memindai melalui contoh @crawshaw hanya membuat saya merasa lebih yakin bahwa aliran kontrol akan sering dibuat cukup samar untuk lebih berhati-hati tentang desain. Menghubungkan bahkan sejumlah kecil kompleksitas menjadi sulit dibaca dan mudah rusak. Saya senang melihat opsi dipertimbangkan, tetapi aliran kontrol yang rumit dalam bahasa yang dijaga seperti itu tampaknya sangat tidak sesuai karakter.

func (f *http2Framer) endWrite() error {
        ...
        if try(f.w.Write(f.wbuf) != len(f.wbuf) {
                return io.ErrShortWrite
        }
        return nil
}

Juga, try tidak "mencoba" apa pun. Ini adalah "relai pelindung". Jika semantik dasar proposal mati, saya tidak heran kode yang dihasilkan juga bermasalah.

func (f *http2Framer) endWrite() error {
        ...
        relay n := f.w.Write(f.wbuf)
        return checkShortWrite(n, len(f.wbuf))
}

Jika Anda membuat pernyataan try, Anda bisa menggunakan flag untuk menunjukkan nilai yang dikembalikan, dan tindakan apa:

try c, @      := newRawConn(fd) // return
try c, <strong i="6">@panic</strong> := newRawConn(fd) // panic
try c, <strong i="7">@hname</strong> := newRawConn(fd) // invoke named handler
try c, <strong i="8">@_</strong>     := newRawConn(fd) // ignore, or invoke "ignored" handler if defined

Anda masih memerlukan sintaks sub-ekspresi (Russ telah menyatakan itu persyaratan), setidaknya untuk panik dan mengabaikan tindakan.

Pertama, saya memuji @crawshaw karena meluangkan waktu untuk melihat sekitar 200 contoh nyata dan meluangkan waktu untuk tulisannya yang bijaksana di atas.

Kedua, @jimmyfrasche , mengenai tanggapan Anda di sini tentang contoh http2Framer :


Saya berdebat dengan diri sendiri apakah if try akan atau harus legal, tetapi saya tidak dapat memberikan penjelasan yang masuk akal mengapa itu tidak boleh dan itu bekerja dengan cukup baik di sini:

```
func (f *http2Framer) endWrite() kesalahan {
...
jika mencoba n := fwWrite(f.wbuf); n != len(f.wbuf) {
kembalikan io.ErrShortWrite
}
kembali nihil
}

At least under what I was suggesting above in https://github.com/golang/go/issues/32437#issuecomment-500213884, under that proposal variation I would suggest `if try` is not allowed.

That `http2Framer` example could instead be:

func (f *http2Framer) endWrite() kesalahan {
...
coba n := fwWrite(f.wbuf)
jika n != len(f.wbuf) {
kembalikan io.ErrShortWrite
}
kembali nihil
}
`` That is one line longer, but hopefully still "light on the page". Personally, I think that (arguably) reads more cleanly, but more importantly it is easier to see the coba`.

@deanveloper menulis di atas di https://github.com/golang/go/issues/32437#issuecomment -498932961:

Kembali dari suatu acara tampaknya merupakan hal yang "suci" untuk dilakukan

Contoh spesifik http2Framer itu akhirnya tidak sesingkat mungkin. Namun, ini menahan pengembalian dari fungsi yang lebih "suci" jika try harus menjadi hal pertama dalam sebuah baris.

@crawshaw menyebutkan:

Hal kedua adalah bahwa sangat sedikit dari ini, kurang dari 1 dari 10, akan mencoba di dalam ekspresi lain. Kasus tipikal adalah pernyataan dalam bentuk x := try(...) atau ^try(...)$.

Mungkin tidak apa-apa untuk hanya membantu sebagian dari 1 dari 10 contoh tersebut dengan bentuk try yang lebih terbatas, terutama jika kasus tipikal dari contoh tersebut berakhir dengan jumlah baris yang sama meskipun try adalah diperlukan untuk menjadi hal pertama dalam satu baris?

@jimmyfrasche

@crawshaw terima kasih telah melakukan ini, senang melihatnya beraksi. Tetapi melihatnya dalam tindakan membuat saya menganggap lebih serius argumen terhadap penanganan kesalahan inline yang sampai sekarang saya abaikan.

Karena ini sangat dekat dengan saran menarik @thepudds untuk membuat pernyataan try , saya menulis ulang semua contoh menggunakan sintaks itu dan menemukannya jauh lebih jelas daripada ekspresi- try atau status quo, tanpa memerlukan terlalu banyak baris tambahan:

func gt(arg1, arg2 reflect.Value) (bool, error) {
        // > is the inverse of <=.
        try lessOrEqual := le(arg1, arg2)
        return !lessOrEqual, nil
}

Contoh pertama Anda menggambarkan dengan baik mengapa saya sangat menyukai ekspresi- try . Dalam versi Anda, saya harus memasukkan hasil panggilan ke le dalam variabel, tetapi variabel itu tidak memiliki arti semantik yang belum disiratkan oleh istilah le . Jadi tidak ada nama yang bisa saya berikan yang tidak berarti (seperti x ) atau berlebihan (seperti lessOrEqual ). Dengan ekspresi- try , tidak diperlukan variabel perantara, jadi masalah ini bahkan tidak muncul.

Saya lebih suka tidak perlu mengeluarkan upaya mental untuk menemukan nama untuk hal-hal yang lebih baik dibiarkan anonim.

Saya senang untuk memberikan dukungan saya di belakang beberapa posting terakhir di mana try (kata kunci) telah dipindahkan ke awal baris. Itu benar-benar harus berbagi ruang visual yang sama dengan return .

Re: Saran @jimmyfrasche untuk mengizinkan try dalam pernyataan gabungan if , itulah hal yang saya pikir banyak orang di sini coba hindari, karena beberapa alasan:

  • itu menggabungkan dua mekanisme aliran kontrol yang sangat berbeda menjadi satu baris
  • ekspresi try sebenarnya dievaluasi terlebih dahulu, dan dapat menyebabkan fungsi kembali, namun muncul setelah if
  • mereka kembali dengan kesalahan yang sama sekali berbeda, salah satunya tidak benar-benar kita lihat dalam kode, dan satu yang kita lakukan
  • itu membuatnya kurang jelas bahwa try sebenarnya tidak tertangani, karena blok tersebut sangat mirip dengan blok penangan (walaupun menangani masalah yang sama sekali berbeda)

Seseorang dapat mendekati situasi ini dari sudut yang sedikit berbeda yang mendukung mendorong orang untuk menangani try s. Bagaimana dengan mengizinkan sintaks try / else berisi kondisional berikutnya (yang merupakan pola umum dengan banyak fungsi I/O yang mengembalikan err dan n , salah satunya mungkin menunjukkan masalah):

func (f *http2Framer) endWrite() error {
        // ...
        try n := f.w.Write(f.wbuf) else err {
                return errors.Wrap(err, "error writing:")
        } else if n != len(f.wbuf) {
                return io.ErrShortWrite
        }
        return nil
}

Jika Anda tidak menangani kesalahan yang dikembalikan oleh .Write , Anda masih memiliki anotasi yang jelas bahwa .Write mungkin salah (seperti yang ditunjukkan oleh @thepudds):

func (f *http2Framer) endWrite() error {
        // ...
        try n := f.w.Write(f.wbuf)
        if n != len(f.wbuf) {
                return io.ErrShortWrite
        }
        return nil
}

Saya menggandakan tanggapan @daved . Menurut pendapat saya, setiap contoh yang disorot oleh @crawshaw menjadi kurang jelas dan lebih rawan kesalahan sebagai akibat dari try .

Saya senang untuk memberikan dukungan saya di belakang beberapa posting terakhir di mana try (kata kunci) telah dipindahkan ke awal baris. Itu benar-benar harus berbagi ruang visual yang sama dengan return .

Mengingat dua opsi untuk poin ini dan dengan asumsi bahwa satu dipilih dan dengan demikian menjadi preseden untuk fitur potensial di masa depan:

SEBUAH.)

try f := os.Open(filepath) else err {
    return errors.Wrap(err, "can't open")
}

B.)

f := try os.Open(filepath) else err {
    return errors.Wrap(err, "can't open")
}

Manakah dari keduanya yang memberikan lebih banyak fleksibilitas untuk penggunaan kata kunci baru di masa mendatang? _(Saya tidak tahu jawabannya karena saya belum menguasai seni gelap menulis kompiler.)_ Apakah satu pendekatan akan lebih membatasi daripada yang lain?

@davecheney @daved @crawshaw
Saya cenderung setuju dengan Daves yang satu ini: dalam contoh @crawshaw , ada banyak pernyataan try yang tertanam jauh di dalam baris yang memiliki banyak hal lain yang terjadi. Sangat sulit untuk menemukan titik keluar. Lebih lanjut, paren try tampaknya mengacaukan banyak hal dalam beberapa contoh.

Melihat banyak kode stdlib yang diubah seperti ini sangat berguna, jadi saya telah mengambil contoh yang sama tetapi menulis ulang sesuai proposal alternatif, yang lebih ketat:

  • try sebagai kata kunci
  • hanya satu try per baris
  • try harus di awal baris

Semoga ini bisa membantu kita membandingkan. Secara pribadi, saya menemukan bahwa contoh-contoh ini terlihat jauh lebih ringkas daripada aslinya, tetapi tanpa mengaburkan aliran kontrol. try tetap sangat terlihat di mana pun ia digunakan.

teks/templat

func gt(arg1, arg2 reflect.Value) (bool, error) {
        // > is the inverse of <=.
        lessOrEqual, err := le(arg1, arg2)
        if err != nil {
                return false, err
        }
        return !lessOrEqual, nil
}

menjadi:

func gt(arg1, arg2 reflect.Value) (bool, error) {
        // > is the inverse of <=.
        try lessOrEqual := le(arg1, arg2)
        return !lessOrEqual, nil
}

teks/templat

func index(item reflect.Value, indices ...reflect.Value) (reflect.Value, error) {
...
        switch v.Kind() {
        case reflect.Map:
                index, err := prepareArg(index, v.Type().Key())
                if err != nil {
                        return reflect.Value{}, err
                }
                if x := v.MapIndex(index); x.IsValid() {
                        v = x
                } else {
                        v = reflect.Zero(v.Type().Elem())
                }
        ...
        }
}

menjadi

func index(item reflect.Value, indices ...reflect.Value) (reflect.Value, error) {
        ...
        switch v.Kind() {
        case reflect.Map:
                try index := prepareArg(index, v.Type().Key())
                if x := v.MapIndex(index); x.IsValid() {
                        v = x
                } else {
                        v = reflect.Zero(v.Type().Elem())
                }
        ...
        }
}

regexp/sintaks:

regexp/syntax/parse.go

func Parse(s string, flags Flags) (*Regexp, error) {
        ...
        if c, t, err = nextRune(t); err != nil {
                return nil, err
        }
        p.literal(c)
        ...
}

menjadi

func Parse(s string, flags Flags) (*Regexp, error) {
        ...
        try c, t = nextRune(t)
        p.literal(c)
        ...
}

bersih/http

net/http/request.go:readRequest

        mimeHeader, err := tp.ReadMIMEHeader()
        if err != nil {
                return nil, err
        }
        req.Header = Header(mimeHeader)

menjadi:

        try mimeHeader := tp.ReadMIMEHeader()
        req.Header = Header(mimeHeader)

database/sql

        if driverCtx, ok := driveri.(driver.DriverContext); ok {
                connector, err := driverCtx.OpenConnector(dataSourceName)
                if err != nil {
                        return nil, err
                }
                return OpenDB(connector), nil
        }

menjadi

        if driverCtx, ok := driveri.(driver.DriverContext); ok {
                try connector := driverCtx.OpenConnector(dataSourceName)
                return OpenDB(connector), nil
        }

database/sql

        si, err := ctxDriverPrepare(ctx, dc.ci, query)
        if err != nil {
                return nil, err
        }
        ds := &driverStmt{Locker: dc, si: si}

menjadi

        try si := ctxDriverPrepare(ctx, dc.ci, query)
        ds := &driverStmt{Locker: dc, si: si}

bersih/http

        if http2isconnectioncloserequest(req) && dialonmiss {
                // it gets its own connection.
                http2tracegetconn(req, addr)
                const singleuse = true
                cc, err := p.t.dialclientconn(addr, singleuse)
                if err != nil {
                        return nil, err
                }
                return cc, nil
        }

menjadi

        if http2isconnectioncloserequest(req) && dialonmiss {
                // it gets its own connection.
                http2tracegetconn(req, addr)
                const singleuse = true
                try cc := p.t.dialclientconn(addr, singleuse)
                return cc, nil
        }

bersih/http
Yang ini sebenarnya tidak menyelamatkan kita dari baris apa pun, tetapi saya merasa jauh lebih jelas karena if err == nil adalah konstruksi yang relatif tidak umum.

func (f *http2Framer) endWrite() error {
        ...
        n, err := f.w.Write(f.wbuf)
        if err == nil && n != len(f.wbuf) {
                err = io.ErrShortWrite
        }
        return err
}

menjadi

func (f *http2Framer) endWrite() error {
        ...
        try n := f.w.Write(f.wbuf)
        if n != len(f.wbuf) {
                return io.ErrShortWrite
        }
        return nil
}

bersih/http

        if f, err := fr.ReadFrame(); err != nil {
                return nil, err
        } else {
                hc = f.(*http2ContinuationFrame) // guaranteed by checkFrameOrder
        }

menjadi

        try f := fr.ReadFrame()
        hc = f.(*http2ContinuationFrame) // guaranteed by checkFrameOrder
}

bersih:

        if ctrlFn != nil {
                c, err := newRawConn(fd)
                if err != nil {
                        return err
                }
                if err := ctrlFn(fd.ctrlNetwork(), laddr.String(), c); err != nil {
                        return err
                }
        }

menjadi

        if ctrlFn != nil {
                try c := newRawConn(fd)
                try ctrlFn(fd.ctrlNetwork(), laddr.String(), c)
        }

@james-lawrence Sebagai balasan untuk https://github.com/golang/go/issues/32437#issuecomment -500116099 : Saya tidak ingat ide seperti , err opsional sedang dipertimbangkan secara serius, tidak. Secara pribadi saya pikir itu ide yang buruk, karena itu berarti bahwa jika suatu fungsi berubah untuk menambahkan parameter error tambahan, kode yang ada akan terus dikompilasi, tetapi akan bertindak sangat berbeda.

Menggunakan penangguhan untuk menangani kesalahan sangat masuk akal, tetapi itu mengarah pada kebutuhan untuk memberi nama kesalahan dan jenis baru dari if err != nil boilerplate.

Penangan eksternal perlu melakukan ini:

func handler(err *error) {
  if *err != nil {
    *err = handle(*err)
  }
} 

yang digunakan seperti

defer handler(&err)

Penangan eksternal hanya perlu ditulis sekali tetapi perlu ada dua versi dari banyak fungsi penanganan kesalahan: yang dimaksudkan untuk ditangguhkan dan yang digunakan secara biasa.

Penangan internal perlu melakukan ini:

defer func() {
  if err != nil {
    err = handle(err)
  }
}()

Dalam kedua kasus, kesalahan fungsi luar harus diberi nama agar dapat diakses.

Seperti yang saya sebutkan sebelumnya di utas, ini dapat diabstraksikan menjadi satu fungsi:

func catch(err *error, handle func(error) error) {
  if *err != nil && handle != nil {
    *err = handle(*err)
  }
}

Itu bertentangan dengan kekhawatiran @griesemer atas ambiguitas fungsi handler nil dan memiliki boilerplate defer dan func(err error) error sendiri, selain harus memberi nama err di fungsi luar.

Jika try berakhir sebagai kata kunci, maka masuk akal untuk memiliki kata kunci catch , yang juga akan dijelaskan di bawah ini.

Secara sintaksis, itu akan seperti handle :

catch err {
  return handleThe(err)
}

Secara semantik, itu akan menjadi gula untuk kode handler internal di atas:

defer func() {
  if err != nil {
    err = handleThe(err)
  }
}()

Karena agak ajaib, itu bisa mengambil kesalahan fungsi luar, bahkan jika itu tidak disebutkan namanya. ( err setelah catch lebih mirip nama parameter untuk blok catch ).

catch akan memiliki batasan yang sama dengan try bahwa itu harus dalam fungsi yang memiliki pengembalian kesalahan akhir, karena keduanya adalah gula yang bergantung pada itu.

Itu sama sekali tidak sekuat proposal handle asli, tetapi itu akan meniadakan persyaratan untuk menyebutkan kesalahan untuk menanganinya dan itu akan menghapus boilerplate baru yang dibahas di atas untuk penangan internal sambil membuatnya cukup mudah untuk tidak memerlukan versi fungsi yang terpisah untuk penangan eksternal.

Penanganan kesalahan yang rumit mungkin mengharuskan tidak menggunakan catch sama seperti mungkin mengharuskan tidak menggunakan try .

Karena keduanya adalah gula, tidak perlu menggunakan catch dengan try . Handler catch dijalankan setiap kali fungsi mengembalikan kesalahan non- nil , yang memungkinkan, misalnya, memasukkan beberapa pencatatan cepat:

catch err {
  log.Print(err)
  return err
}

atau hanya membungkus semua kesalahan yang dikembalikan:

catch err {
  return fmt.Errorf("foo: %w", err)
}

@ianlancetaylor

_" Saya pikir itu ide yang buruk, karena itu berarti bahwa jika suatu fungsi berubah untuk menambahkan parameter error tambahan, kode yang ada akan terus dikompilasi, tetapi akan bertindak sangat berbeda."_

Itu mungkin cara yang benar untuk melihatnya, jika Anda dapat mengontrol kode hulu dan hilir sehingga jika Anda perlu mengubah tanda tangan fungsi untuk juga mengembalikan kesalahan maka Anda dapat melakukannya.

Tetapi saya akan meminta Anda untuk mempertimbangkan apa yang terjadi ketika seseorang tidak mengontrol hulu atau hilir paket mereka sendiri? Dan juga untuk mempertimbangkan kasus penggunaan di mana kesalahan mungkin ditambahkan, dan apa yang terjadi jika kesalahan perlu ditambahkan tetapi Anda tidak dapat memaksa kode hilir untuk berubah?

Bisakah Anda memikirkan contoh di mana seseorang akan mengubah tanda tangan untuk menambahkan nilai pengembalian? Bagi saya mereka biasanya termasuk dalam kategori _"Saya tidak menyadari akan terjadi kesalahan"_ atau _"Saya merasa malas dan tidak ingin berusaha karena kesalahan itu mungkin tidak akan terjadi." _

Dalam kedua kasus itu, saya mungkin menambahkan pengembalian kesalahan karena menjadi jelas bahwa kesalahan perlu ditangani. Ketika itu terjadi, jika saya tidak dapat mengubah tanda tangan karena saya tidak ingin merusak kompatibilitas untuk pengembang lain yang menggunakan paket saya, apa yang harus dilakukan? Dugaan saya adalah bahwa sebagian besar waktu kesalahan akan terjadi dan kode yang memanggil fungsi yang tidak mengembalikan kesalahan akan bertindak sangat berbeda, _anyway._

Sebenarnya, saya jarang melakukan yang terakhir tetapi terlalu sering melakukan yang pertama. Tapi saya perhatikan paket pihak ke-3 sering mengabaikan kesalahan pengambilan di tempat yang seharusnya, dan saya tahu ini karena ketika saya menampilkan kode mereka di bendera GoLand dalam warna oranye terang setiap contoh. Saya ingin sekali dapat mengirimkan permintaan tarik untuk menambahkan penanganan kesalahan ke paket yang sering saya gunakan, tetapi jika saya melakukannya sebagian besar tidak akan menerimanya karena saya akan melanggar tanda tangan kode mereka.

Dengan tidak menawarkan cara yang kompatibel ke belakang untuk menambahkan kesalahan yang akan dikembalikan oleh fungsi, pengembang yang mendistribusikan kode dan peduli untuk tidak merusak sesuatu untuk pengguna mereka tidak akan dapat mengembangkan paket mereka untuk menyertakan penanganan kesalahan sebagaimana mestinya.


Mungkin alih-alih mempertimbangkan masalahnya karena kode itu akan bertindak berbeda alih-alih melihat masalah sebagai tantangan rekayasa mengenai bagaimana meminimalkan kerugian dari metode yang tidak secara aktif menangkap kesalahan? Itu akan memiliki nilai jangka panjang dan lebih luas.

Misalnya, pertimbangkan untuk menambahkan penangan kesalahan paket yang harus ditetapkan sebelum dapat mengabaikan kesalahan?


Sejujurnya, idiom Go untuk mengembalikan kesalahan di samping nilai pengembalian reguler adalah salah satu inovasi yang lebih baik. Tetapi seperti yang sering terjadi ketika Anda memperbaiki sesuatu, Anda sering mengekspos kelemahan lain dan saya berpendapat bahwa penanganan kesalahan Go tidak cukup berinovasi.

Kami Gophers telah tenggelam dalam mengembalikan kesalahan daripada melemparkan pengecualian sehingga pertanyaan saya adalah _"Mengapa kami tidak mengembalikan kesalahan dari setiap fungsi?"_ Kami tidak selalu melakukannya karena menulis kode tanpa penanganan kesalahan adalah lebih nyaman daripada coding dengan itu. Jadi kami menghilangkan penanganan kesalahan ketika kami pikir kami bisa menghindarinya. Namun seringkali kita salah menebak.

Jadi sungguh, jika mungkin untuk mengetahui cara membuat kode elegan dan mudah dibaca, saya berpendapat bahwa nilai pengembalian dan kesalahan benar-benar harus ditangani secara terpisah, dan bahwa fungsi _setiap_ harus memiliki kemampuan untuk mengembalikan kesalahan terlepas dari tanda tangan fungsi sebelumnya. Dan membuat kode yang ada untuk menangani kode yang sekarang menghasilkan kesalahan dengan anggun akan menjadi upaya yang bermanfaat.

Saya belum mengusulkan apa pun karena saya belum dapat membayangkan sintaks yang bisa diterapkan, tetapi jika kita ingin jujur ​​pada diri kita sendiri, bukankah semua yang ada di utas ini dan terkait dengan penanganan kesalahan Go secara umum adalah tentang fakta bahwa penanganan kesalahan dan logika program adalah teman yang aneh sehingga idealnya kesalahan akan lebih baik ditangani di luar band dalam beberapa cara?

try sebagai kata kunci tentu saja membantu keterbacaan (vs. panggilan fungsi) dan tampaknya tidak terlalu rumit. @brynbellomy @crawshaw terima kasih telah meluangkan waktu untuk menulis contoh.

Saya kira pemikiran umum saya adalah bahwa try melakukan terlalu banyak. Ini memecahkan: memanggil fungsi, menetapkan variabel, memeriksa kesalahan, dan mengembalikan kesalahan jika ada. Saya mengusulkan kita sebagai gantinya memotong ruang lingkup dan menyelesaikan hanya untuk pengembalian bersyarat: "kembalikan jika argumen terakhir tidak nihil".

Ini mungkin bukan ide baru... Tapi setelah membaca sepintas proposal di wiki umpan balik kesalahan , saya tidak menemukannya (bukan berarti tidak ada)

Proposal mini tentang pengembalian bersyarat

kutipan:

err, thing := newThing(name)
refuse nil, err

Saya menambahkannya ke wiki juga di bawah "ide alternatif"

Tidak melakukan apa-apa juga sepertinya merupakan pilihan yang sangat masuk akal.

@alexhornbake yang memberi saya ide yang sedikit berbeda yang akan lebih berguna

assert(nil, err)
assert(len(a), len(b))
assert(true, condition)
assert(expected, given)

cara ini tidak hanya berlaku untuk pemeriksaan kesalahan, tetapi untuk banyak jenis kesalahan logika.

Yang diberikan akan dibungkus dengan kesalahan dan dikembalikan.

@alexhornbake

Sama seperti try sebenarnya tidak mencoba, refuse sebenarnya tidak "menolak". Maksud umum di sini adalah bahwa kita sedang menyetel "relai pelindung" ( relay singkat, akurat, dan aliteratif ke return ) yang "berjalan" ketika salah satu nilai berkabel memenuhi suatu kondisi (yaitu kesalahan non-nol). Ini semacam pemutus sirkuit dan, saya percaya, dapat menambah nilai jika desainnya terbatas pada kasing yang tidak menarik untuk sekadar mengurangi beberapa pelat ketel yang menggantung paling rendah. Apa pun yang rumit dari jarak jauh harus bergantung pada kode Go biasa.

Saya juga memuji cranshaw untuk karyanya melihat melalui perpustakaan standar, tapi saya sampai pada kesimpulan yang sangat berbeda... Saya pikir itu membuat hampir semua potongan kode lebih sulit untuk dibaca dan lebih rentan terhadap kesalahpahaman.

        req.Header = Header(try(tp.ReadMIMEHeader())

Saya akan sangat sering melewatkan bahwa ini bisa error. Pembacaan cepat membuat saya "ok, atur header ke Header of ReadMimeHeader of the thing".

        if driverCtx, ok := driveri.(driver.DriverContext); ok {
                return OpenDB(try(driverCtx.OpenConnector(dataSourceName))), nil
        }

Yang ini, mata saya hanya terbelalak mencoba mengurai baris OpenDB itu. Ada begitu banyak kepadatan di sana... Ini menunjukkan masalah utama yang dimiliki semua panggilan fungsi bersarang, di mana Anda harus membaca dari dalam ke luar, dan Anda harus menguraikannya di kepala Anda untuk mencari tahu di mana bagian terdalamnya. .

Perhatikan juga bahwa ini dapat kembali dari dua tempat berbeda di baris yang sama .. .Anda akan melakukan debugging, dan itu akan mengatakan ada kesalahan yang dikembalikan dari baris ini, dan hal pertama yang akan dilakukan semua orang adalah mencoba cari tahu mengapa OpenDB gagal dengan kesalahan aneh ini, padahal sebenarnya OpenConnector gagal (atau sebaliknya).

        ds := &driverStmt{
                Locker: dc,
                si: try(ctxDriverPrepare(ctx, dc.ci, query)),
        }   

Ini adalah tempat di mana kode bisa gagal di mana sebelumnya tidak mungkin. Tanpa try , konstruksi literal struct tidak dapat gagal . Mata saya akan melihat sekilas seperti "ok, membangun driverStmt ... pindah.." dan itu akan sangat mudah untuk dilewatkan, ini dapat menyebabkan fungsi Anda keluar dari kesalahan. Satu-satunya cara yang mungkin terjadi sebelumnya adalah jika ctxDriverPrepare panik... dan kita semua tahu bahwa 1.) pada dasarnya tidak akan pernah terjadi dan 2.) jika ya, berarti ada sesuatu yang salah secara drastis.

Mencoba kata kunci dan pernyataan memperbaiki banyak masalah saya dengannya. Saya tahu itu tidak kompatibel, tetapi saya tidak berpikir menggunakan versi yang lebih buruk itu adalah solusi untuk masalah kompatibilitas mundur.

@daved Saya tidak yakin saya mengikuti. Apakah Anda tidak menyukai namanya, atau tidak menyukai idenya?

Bagaimanapun, saya memposting ini di sini sebagai alternatif ... Jika ada minat yang sah, saya dapat membuka masalah baru untuk diskusi, tidak ingin mencemari utas ini (mungkin terlambat?) Jempol ke atas/bawah pada ide asli akan memberi kami rasa ... Tentu saja terbuka untuk nama alternatif. Bagian penting adalah "pengembalian bersyarat tanpa mencoba menangani tugas".

Meskipun saya menyukai proposal tangkapan oleh @jimmyfrasche , saya ingin mengusulkan alternatif:
go handler fmt.HandleErrorf("copy %s %s", src, dst)
akan setara dengan:
go defer func(){ if(err != nil){ fmt.HandleErrorf(&err,"copy %s %s", src, dst) } }()
di mana err adalah nilai pengembalian bernama terakhir, dengan kesalahan tipe. Namun, penangan juga dapat digunakan ketika nilai yang dikembalikan tidak disebutkan. Kasus yang lebih umum akan diizinkan juga:
go handler func(err *error){ *err = fmt.Errorf("foo: %w", *err) }() `
Masalah utama yang saya miliki dengan menggunakan nilai pengembalian bernama (yang tidak diselesaikan oleh tangkapan) adalah bahwa err berlebihan. Saat menunda panggilan ke penangan seperti fmt.HandleErrorf , tidak ada argumen pertama yang masuk akal kecuali penunjuk ke nilai pengembalian kesalahan, mengapa memberi pengguna opsi untuk membuat kesalahan?

Dibandingkan dengan catch, perbedaan utamanya adalah handler membuat sedikit lebih mudah untuk memanggil handler yang telah ditentukan dengan mengorbankan membuat lebih verbose untuk mendefinisikannya di tempat. Saya tidak yakin ini ideal, tetapi saya pikir ini lebih sesuai dengan proposal awal.

@yiyus catch , seperti yang saya definisikan, itu tidak memerlukan err untuk dinamai pada fungsi yang berisi catch .

Dalam catch err { , err adalah nama kesalahan dalam blok catch . Ini seperti nama parameter fungsi.

Dengan itu, tidak perlu sesuatu seperti fmt.HandleErrorf karena Anda cukup menggunakan fmt.Errorf biasa :

func f() error {
  catch err {
    return fmt.Errorf("foo: %w", err)
  }
  return errors.New("bar")
}

yang mengembalikan kesalahan yang dicetak sebagai foo: bar .

Saya tidak suka pendekatan ini, karena:

  • Panggilan fungsi try() menginterupsi eksekusi kode dalam fungsi induk.
  • tidak ada kata kunci return , tetapi kodenya benar-benar kembali.

Banyak cara untuk melakukan penangan sedang diusulkan, tetapi saya pikir mereka sering melewatkan dua persyaratan utama:

  1. Itu harus berbeda secara signifikan dan lebih baik dari if x, err := thingie(); err != nil { handle(err) } . Saya pikir saran di sepanjang baris try x := thingie else err { handle(err) } tidak memenuhi standar itu. Mengapa tidak mengatakan if saja?

  2. Itu harus ortogonal dengan fungsi defer yang ada. Artinya, itu harus cukup berbeda sehingga jelas bahwa mekanisme penanganan yang diusulkan diperlukan dengan sendirinya tanpa membuat kasus sudut yang aneh saat menangani dan menunda berinteraksi.

Harap ingat desiderata ini saat kita membahas mekanisme alternatif untuk try /handle.

@carlmjohnson Saya suka ide catch @jimmyfrasche mengenai poin 2 Anda - itu hanya gula sintaks untuk defer yang menghemat 2 baris dan memungkinkan Anda menghindari keharusan memberi nama nilai pengembalian kesalahan (yang dalam gilirannya juga akan mengharuskan Anda untuk menyebutkan semua yang lain jika Anda belum melakukannya). Itu tidak menimbulkan masalah ortogonalitas dengan defer , karena itu adalah defer .

menggemakan apa yang @ubombi katakan:

try() panggilan fungsi menginterupsi eksekusi kode dalam fungsi induk.; tidak ada kata kunci kembali, tetapi kode benar-benar kembali.

Di Ruby, procs dan lambda adalah contoh dari apa yang dilakukan try ...Proc adalah blok kode yang pernyataan pengembaliannya dikembalikan bukan dari blok itu sendiri, tetapi dari pemanggil.

Inilah tepatnya yang dilakukan try ...ini hanya proc Ruby yang telah ditentukan sebelumnya.

Saya pikir jika kita akan pergi ke rute itu, mungkin kita benar-benar dapat membiarkan pengguna mendefinisikan fungsi try mereka sendiri dengan memperkenalkan proc functions

Saya masih lebih suka if err != nil , karena lebih mudah dibaca tetapi saya pikir try akan lebih bermanfaat jika pengguna mendefinisikan proc mereka sendiri:

proc try(err *error, msg string) {
  if *err != nil {
    *err = fmt.Errorf("%v: %w", msg, *err)
    return
  }
}

Dan kemudian Anda dapat menyebutnya:

func someFunc() (string, error) {
  err := doSomething()
  try(&err, "someFunc failed")
}

Manfaatnya di sini, adalah Anda dapat mendefinisikan penanganan kesalahan dalam istilah Anda sendiri. Dan Anda juga dapat membuat proc terbuka, pribadi, atau internal.

Ini juga lebih baik daripada klausa handle {} dalam proposal Go2 asli karena Anda dapat mendefinisikan ini hanya sekali untuk seluruh basis kode dan tidak di setiap fungsi.

Satu pertimbangan untuk keterbacaan, adalah bahwa func() dan proc() mungkin dipanggil secara berbeda seperti func() dan proc!() sehingga programmer tahu bahwa panggilan proc mungkin benar-benar kembali dari fungsi panggilan.

@marwan-at-work, bukankah try(err, "someFunc failed") menjadi try(&err, "someFunc failed") dalam contoh Anda?

@dpinela terima kasih atas koreksinya, perbarui kodenya :)

Praktik umum yang kami coba timpa di sini adalah apa yang disarankan oleh tumpukan standar di banyak bahasa dalam pengecualian, (dan karenanya kata "coba" dipilih ...).
Tetapi jika kita hanya dapat mengizinkan fungsi(...try() atau lainnya) yang akan melompat mundur dua tingkat dalam jejak, maka

try := handler(err error) {     //which corelates with - try := func(err error) 
   if err !=nil{
       //do what ever you want to do when there's an error... log/print etc
       return2   //2 levels
   }
} 

dan kemudian kode seperti
f := coba(os.Buka(nama file))
dapat melakukan persis seperti yang disarankan proposal, tetapi sebagai fungsinya (atau sebenarnya "fungsi penangan"), pengembang akan memiliki lebih banyak kendali atas apa yang dilakukan fungsi, bagaimana memformat kesalahan dalam kasus yang berbeda, menggunakan penangan serupa di sekitar kode untuk menangani (katakanlah) os.Open, alih-alih wrting fmt.Errorf("error membuka file %s ....") setiap saat.
Ini juga akan memaksa penanganan kesalahan seolah-olah "coba" tidak akan ditentukan - ini adalah kesalahan waktu kompilasi.

@guybrand Memiliki pengembalian dua tingkat return2 (atau "pengembalian non-lokal" seperti yang disebut konsep umum di Smalltalk) akan menjadi mekanisme tujuan umum yang bagus (juga disarankan oleh @mikeschinkel di #32473) . Tetapi tampaknya try masih diperlukan dalam saran Anda, jadi saya tidak melihat alasan untuk return2 - try hanya dapat melakukan return . Akan lebih menarik jika seseorang juga dapat menulis try secara lokal, tetapi itu tidak mungkin untuk tanda tangan yang sewenang-wenang.

@griesemer

_"jadi saya tidak melihat alasan untuk return2 - try hanya dapat melakukan return ."_

Salah satu alasannya — seperti yang saya tunjukkan di #32473 _(terima kasih atas referensinya)_ — adalah untuk mengizinkan beberapa level break dan continue , selain return .

Sekali lagi terima kasih kepada semua orang untuk semua komentar baru; itu adalah investasi waktu yang signifikan untuk mengikuti diskusi dan menulis umpan balik yang luas. Dan bahkan lebih baik, terlepas dari argumen yang kadang-kadang penuh gairah, sejauh ini ini telah menjadi utas yang agak sipil. Terima kasih!

Berikut ringkasan singkat lainnya, kali ini sedikit lebih ringkas; permintaan maaf kepada mereka yang tidak saya sebutkan, lupakan, atau salah mengartikan. Pada titik ini saya pikir beberapa tema yang lebih besar sedang muncul:

1) Secara umum, menggunakan built-in untuk fungsi try dirasakan sebagai pilihan yang buruk: Mengingat hal itu mempengaruhi aliran kontrol, itu harus _setidaknya_ kata kunci ( @carloslenz "lebih suka membuat pernyataan tanpa kurung"); try sebagai ekspresi tampaknya bukan ide yang baik, itu merusak keterbacaan ( @ChrisHines , @jimmyfrasche), mereka "dikembalikan tanpa return ". @brynbellomy melakukan analisis aktual dari try yang digunakan sebagai pengidentifikasi; tampaknya hanya ada sedikit persentase, jadi mungkin saja menggunakan rute kata kunci tanpa memengaruhi terlalu banyak kode.

2) @crawshaw meluangkan waktu untuk menganalisis beberapa ratus kasus penggunaan dari perpustakaan std dan sampai pada kesimpulan bahwa try seperti yang diusulkan hampir selalu meningkatkan keterbacaan. @jimmyfrasche sampai pada kesimpulan yang berlawanan .

3) Tema lain adalah bahwa menggunakan defer untuk dekorasi kesalahan tidak ideal. @josharian menunjukkan defer 's selalu dijalankan setelah fungsi kembali, tetapi jika mereka ada di sini untuk dekorasi kesalahan, kami hanya peduli dengan tubuh mereka jika ada kesalahan, yang bisa menjadi sumber kebingungan.

4) Banyak menulis saran untuk perbaikan proposal. @zeebo , @patrick-nyt mendukung gofmt memformat pernyataan if sederhana dalam satu baris (dan senang dengan status quo). @jargv menyarankan bahwa try() (tanpa argumen) dapat mengembalikan pointer ke kesalahan "tertunda" saat ini, yang akan menghilangkan kebutuhan untuk memberi nama hasil kesalahan agar seseorang memiliki akses ke dalam defer ; @masterada menyarankan menggunakan errorfunc() sebagai gantinya. @velovix menghidupkan kembali gagasan 2-argumen try di mana argumen ke-2 akan menjadi penangan kesalahan.

@klaidliadon , @networkimprov mendukung "operator penugasan" khusus seperti di f, # := os.Open() alih-alih try . @networkimprov mengajukan proposal alternatif yang lebih komprehensif yang menyelidiki pendekatan semacam itu (lihat masalah #32500). @mikeschinkel juga mengajukan proposal alternatif yang menyarankan untuk memperkenalkan dua fitur bahasa tujuan umum baru yang dapat digunakan untuk penanganan kesalahan juga, daripada try khusus kesalahan (lihat masalah #32473). @josharian menghidupkan kembali kemungkinan yang kita diskusikan di GopherCon tahun lalu di mana try tidak kembali pada kesalahan melainkan melompat (dengan goto ) ke label bernama error (sebagai alternatif , try mungkin menggunakan nama label target).

5) Pada subjek try sebagai kata kunci, dua baris pemikiran telah muncul. @brynbellomy menyarankan versi yang mungkin dapat menentukan penangan:

a, b := try f()
a, b := try f() else err { /* handle error */ }

@thepudds melangkah lebih jauh dan menyarankan try di awal baris, memberikan try visibilitas yang sama dengan return :

try a, b := f()

Keduanya dapat bekerja dengan defer .

@griesemer

Terima kasih atas referensi ke @mikeschinkel #32473, ini memang memiliki banyak kesamaan.

tentang

Tetapi tampaknya percobaan masih diperlukan dalam saran Anda
Meskipun saran saya dapat diimplementasikan dengan penangan "apa saja" dan bukan "build in/keyword/expression" yang dicadangkan, saya tidak berpikir "coba ()" adalah ide yang buruk (dan karena itu tidak memilihnya), saya mencoba untuk "memperluasnya" - jadi itu akan menunjukkan lebih banyak sisi positif, banyak yang berharap "sekali pergi 2.0 diperkenalkan"

Saya pikir itu mungkin juga menjadi sumber "getaran campuran" yang Anda laporkan pada ringkasan terakhir Anda - ini bukan "coba () tidak meningkatkan penanganan kesalahan" - tentu saja, "menunggu Go 3.0 untuk menyelesaikan beberapa kesalahan besar lainnya menangani rasa sakit" orang-orang yang disebutkan di atas, sepertinya terlalu lama :)

Saya melakukan survei tentang "rasa sakit penanganan kesalahan" (dan terdengar beberapa rasa sakit hanyalah "Saya tidak menggunakan praktik yang baik", sedangkan beberapa saya bahkan tidak membayangkan orang (kebanyakan berasal dari bahasa lain) ingin melakukannya - dari keren ke WTF).

Semoga saya dapat segera membagikan beberapa hasil yang menarik.

terakhir -Terima kasih atas kerja keras dan kesabarannya!

Melihat hanya pada panjang sintaks yang diusulkan saat ini versus apa yang tersedia sekarang, kasus di mana kesalahan hanya perlu dikembalikan tanpa menangani atau menghiasnya adalah kasus di mana sebagian besar kenyamanan diperoleh. Contoh dengan sintaks favorit saya sejauh ini:

try a, b := f() else err { return fmt.Errorf("Decorated: %s", err); }
if a,b, err :=f;  err != nil { return fmt.Errorf("Decorated: %s", err); }
try a, b := f()
if a,b, err :=f;  err != nil { return err; }

Jadi, berbeda dari apa yang saya pikirkan sebelumnya, mungkin cukup mengubah go fmt, setidaknya untuk kasus kesalahan yang dihias/ditangani. Sementara untuk kasus kesalahan yang baru saja diteruskan, sesuatu seperti try mungkin masih diinginkan sebagai gula sintaksis untuk kasus penggunaan yang sangat umum ini.

Mengenai try else , saya pikir kesalahan bersyarat berfungsi seperti fmt.HandleErrorf (edit: Saya berasumsi itu mengembalikan nil ketika inputnya nihil) di komentar awal berfungsi dengan baik jadi menambahkan else tidak perlu.

a, b, err := doSomething()
try fmt.HandleErrorf(&err, "Decorated "...)

Seperti banyak orang lain di sini, saya lebih suka try menjadi pernyataan daripada ekspresi, terutama karena ekspresi yang mengubah aliran kontrol benar-benar asing bagi Go. Juga karena ini bukan ekspresi, itu harus di awal baris.

Saya juga setuju dengan @daved bahwa nama itu tidak pantas. Lagi pula, apa yang ingin kita capai di sini adalah tugas yang dijaga, jadi mengapa tidak menggunakan guard seperti di Swift dan menjadikan klausa else opsional? Sesuatu seperti

GuardStmt = "guard" ( Assignment | ShortVarDecl | Expression ) [  "else" Identifier Block ] .

di mana Identifier adalah nama variabel kesalahan yang terikat dalam Block berikut. Tanpa klausa else , cukup kembali dari fungsi saat ini (dan gunakan penangan penangguhan untuk menghias kesalahan jika perlu).

Awalnya saya tidak menyukai klausa else karena itu hanya gula sintaksis di sekitar tugas biasa diikuti oleh if err != nil , tetapi setelah melihat beberapa contoh, itu masuk akal: menggunakan guard membuat maksud lebih jelas.

EDIT: beberapa menyarankan untuk menggunakan hal-hal seperti catch entah bagaimana menentukan penangan kesalahan yang berbeda. Saya menemukan else sama-sama layak secara semantik dan itu sudah dalam bahasa.

Sementara saya menyukai pernyataan try-else, bagaimana dengan sintaks ini?

a, b, (err) := func() else { return err }

Ekspresi try - else adalah operator ternary.

a, b := try f() else err { ... }
fmt.Println(try g() else err { ... })`

Pernyataan try - else adalah pernyataan if .

try a, b := f() else err { ... }
// (modulo scope of err) same as
a, b, err := f()
if err != nil { ... }

try bawaan dengan handler opsional dapat dicapai dengan fungsi helper (di bawah) atau tidak menggunakan try (tidak digambarkan, kita semua tahu seperti apa tampilannya).

a, b := try(f(), decorate)
// same as
a, b := try(g())
// where g is
func g() (whatever, error) {
  x, err := f()
  if err != nil {
    try(decorate(err))
  }
  return x, nil
}

Ketiganya mengurangi boilerplate dan membantu mengatasi ruang lingkup kesalahan.

Ini memberikan penghematan paling banyak untuk try tetapi itu memiliki masalah yang disebutkan dalam dokumen desain.

Untuk pernyataan try - else , ini memberikan keuntungan dibandingkan menggunakan if daripada try . Tetapi keuntungannya sangat kecil sehingga saya sulit melihatnya membenarkan dirinya sendiri, meskipun saya menyukainya.

Ketiganya berasumsi bahwa adalah umum untuk memerlukan penanganan kesalahan khusus untuk kesalahan individu.

Menangani semua kesalahan secara merata dapat dilakukan di defer . Jika penanganan kesalahan yang sama dilakukan di setiap blok else yang agak berulang:

func printSum(a, b string) error {
  try x := strconv.Atoi(a) else err {
    return fmt.Errorf("printSum: %w", err)
  }
  try y := strconv.Atoi(b) else err {
    return fmt.Errorf("printSum: %w", err)
  }
  fmt.Println("result:", x + y)
  return nil
}

Saya tentu tahu bahwa ada kalanya kesalahan tertentu memerlukan penanganan khusus. Itulah contoh-contoh yang membekas dalam ingatan saya. Tetapi, jika itu hanya terjadi, katakanlah, 1 dari 100 kali, bukankah lebih baik menjaga try sederhana dan tidak menggunakan try dalam situasi tersebut? Di sisi lain, jika lebih seperti 1 dari 10 kali, menambahkan else /handler tampaknya lebih masuk akal.

Akan menarik untuk melihat distribusi aktual tentang seberapa sering try tanpa else /handler vs try dengan else /handler akan berguna, meskipun itu bukan data yang mudah untuk dikumpulkan.

Saya ingin memperluas komentar terbaru @jimmyfrasche .

Tujuan dari proposal ini adalah untuk mengurangi boilerplate

    a, b, err := f()
    if err != nil {
        return nil, err
    }

Kode ini mudah dibaca. Perluasan bahasa hanya layak dilakukan jika kita dapat mencapai pengurangan boilerplate yang cukup besar. Ketika saya melihat sesuatu seperti

    try a, b := f() else err { return nil, err }

Mau tak mau saya merasa bahwa kita tidak menabung sebanyak itu. Kami menyimpan tiga baris, yang bagus, tetapi menurut hitungan saya, kami mengurangi 56 menjadi 46 karakter. Itu tidak banyak. Dibandingkan dengan

    a, b := try(f())

yang memotong 56 menjadi 18 karakter, pengurangan yang jauh lebih signifikan. Dan sementara pernyataan try membuat potensi perubahan aliran kontrol lebih jelas, secara keseluruhan saya tidak menemukan pernyataan itu lebih mudah dibaca. Meskipun di sisi positifnya, pernyataan try memudahkan untuk membuat anotasi kesalahan.

Bagaimanapun, maksud saya adalah: jika kita akan mengubah sesuatu, itu akan mengurangi boilerplate secara signifikan atau harus secara signifikan lebih mudah dibaca. Yang terakhir ini cukup sulit, jadi perubahan apa pun harus benar-benar berhasil pada yang pertama. Jika kita hanya mendapatkan sedikit pengurangan boilerplate, maka menurut saya itu tidak layak dilakukan.

Seperti yang lain, saya ingin berterima kasih kepada @crawshaw untuk contohnya.

Saat membaca contoh-contoh itu, saya mendorong orang untuk mencoba mengadopsi pola pikir di mana Anda tidak khawatir tentang aliran kontrol karena fungsi try . Saya percaya, mungkin salah, bahwa aliran kontrol itu akan segera menjadi kebiasaan bagi orang-orang yang tahu bahasa itu. Dalam kasus normal, saya percaya bahwa orang akan berhenti mengkhawatirkan apa yang terjadi dalam kasus kesalahan. Coba baca contoh-contoh itu sambil melapisi try sama seperti Anda sudah melapisi if err != nil { return err } .

Setelah membaca semuanya di sini, dan setelah refleksi lebih lanjut, saya tidak yakin saya melihat try bahkan sebagai pernyataan sesuatu yang layak ditambahkan.

  1. alasan untuk itu tampaknya mengurangi kesalahan penanganan kode pelat boiler. IMHO itu "mengungkapkan" kode tetapi tidak benar-benar menghilangkan kerumitannya; itu hanya mengaburkannya . Ini sepertinya bukan alasan yang cukup kuat. "pergi" sintaks ditangkap dengan indah memulai utas bersamaan. Saya tidak mendapatkan perasaan "aha!" semacam itu di sini. Rasanya tidak benar. Rasio biaya/manfaat tidak cukup besar.

  2. namanya tidak mencerminkan fungsinya. Dalam bentuknya yang paling sederhana, yang dilakukannya adalah ini: "jika suatu fungsi mengembalikan kesalahan, kembali dari pemanggil dengan kesalahan" tetapi itu terlalu panjang :-) Setidaknya diperlukan nama yang berbeda.

  3. dengan kesalahan pengembalian implisit try, rasanya seperti Go dengan enggan mendukung penanganan pengecualian. Yaitu jika A memanggil dalam try guard dan B memanggil C dalam try guard, dan C memanggil D dalam try guard, jika D mengembalikan kesalahan pada dasarnya Anda telah menyebabkan goto non-lokal. Rasanya terlalu "ajaib".

  4. namun saya percaya cara yang lebih baik mungkin dilakukan. Memilih coba sekarang akan menutup opsi itu.

@ianlancetaylor
Jika saya memahami proposal "coba yang lain" dengan benar, tampaknya blok else adalah opsional, dan dicadangkan untuk penanganan yang disediakan pengguna. Dalam contoh Anda try a, b := f() else err { return nil, err } else sebenarnya berlebihan, dan seluruh ekspresi dapat ditulis hanya sebagai try a, b := f()

Saya setuju dengan @ianlancetaylor ,
Keterbacaan dan boilerplate adalah dua perhatian utama dan mungkin dorongan untuk
penanganan kesalahan go 2.0 (meskipun saya dapat menambahkan beberapa masalah penting lainnya)

Juga, bahwa arus

a, b, err := f()
if err != nil {
    return nil, err
}

Sangat mudah dibaca.
Dan karena saya percaya

if a, b, err := f(); err != nil {
    return nil, err
}

Hampir dapat dibaca, namun memiliki "masalah" cakupannya, mungkin a

ifErr a, b, err := f() {
    return nil, err
}

Itu hanya akan ; err != nil bagian, dan tidak akan membuat ruang lingkup, atau

demikian pula

coba a, b, err := f() {
kembali nihil, err
}

Menyimpan dua baris tambahan, tetapi masih dapat dibaca.

Pada Selasa, 11 Jun 2019, 20:19 Dmitriy Matrenichev, [email protected]
menulis:

@ianlancetaylor https://github.com/ianlancetaylor
Jika saya memahami proposal "coba yang lain" dengan benar, sepertinya itu blok lain
adalah opsional, dan disediakan untuk penanganan yang disediakan pengguna. Dalam contoh Anda
coba a, b := f() else err { return nil, err } klausa else sebenarnya
berlebihan, dan seluruh ekspresi dapat ditulis secara sederhana sebagai try a, b :=
F()


Anda menerima ini karena Anda disebutkan.
Balas email ini secara langsung, lihat di GitHub
https://github.com/golang/go/issues/32437?email_source=notifications&email_token=ABNEY4XPURMASWKZKOBPBVDPZ7NALA5CNFSM4HTGCZ72YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXH4ZLOORPVWWZ2JKTDN5WWW2
atau matikan utasnya
https://github.com/notifications/unsubscribe-auth/ABNEY4SAFK4M5NLABF3NZO3PZ7NALANCNFSM4HTGCZ7Q
.

@ianlancetaylor

Bagaimanapun, maksud saya adalah: jika kita akan mengubah sesuatu, itu akan mengurangi boilerplate secara signifikan atau harus secara signifikan lebih mudah dibaca. Yang terakhir ini cukup sulit, jadi perubahan apa pun harus benar-benar berhasil pada yang pertama. Jika kita hanya mendapatkan sedikit pengurangan boilerplate, maka menurut saya itu tidak layak dilakukan.

Setuju, dan mengingat bahwa else hanya akan menjadi gula sintaksis (dengan sintaks yang aneh!), kemungkinan besar jarang digunakan, saya tidak terlalu peduli tentang itu. Saya masih lebih suka try sebagai pernyataan.

@ianlancetaylor Menggemakan @DmitriyMV , blok else akan menjadi opsional. Izinkan saya memberikan contoh yang menggambarkan keduanya (dan tampaknya tidak terlalu jauh dari sasaran dalam hal proporsi relatif dari blok try yang ditangani vs. yang tidak ditangani dalam kode nyata):

func createMergeCommit(r *repo.Repo, index *git.Index, remote *git.Remote, remoteBranch *git.Branch) error {
    headRef, err := r.Head()
    if err != nil {
        return err
    }

    parentObjOne, err := headRef.Peel(git.ObjectCommit)
    if err != nil {
        return err
    }

    parentObjTwo, err := remoteBranch.Reference.Peel(git.ObjectCommit)
    if err != nil {
        return err
    }

    parentCommitOne, err := parentObjOne.AsCommit()
    if err != nil {
        return err
    }

    parentCommitTwo, err := parentObjTwo.AsCommit()
    if err != nil {
        return err
    }

    treeOid, err := index.WriteTree()
    if err != nil {
        return err
    }

    tree, err := r.LookupTree(treeOid)
    if err != nil {
        return err
    }

    remoteBranchName, err := remoteBranch.Name()
    if err != nil {
        return err
    }

    userName, userEmail, err := r.UserIdentityFromConfig()
    if err != nil {
        userName = ""
        userEmail = ""
    }

    var (
        now       = time.Now()
        author    = &git.Signature{Name: userName, Email: userEmail, When: now}
        committer = &git.Signature{Name: userName, Email: userEmail, When: now}
        message   = fmt.Sprintf(`Merge branch '%v' of %v`, remoteBranchName, remote.Url())
        parents   = []*git.Commit{
            parentCommitOne,
            parentCommitTwo,
        }
    )

    _, err = r.CreateCommit(headRef.Name(), author, committer, message, tree, parents...)
    if err != nil {
        return err
    }
    return nil
}
func createMergeCommit(r *repo.Repo, index *git.Index, remote *git.Remote, remoteBranch *git.Branch) error {
    try headRef := r.Head()
    try parentObjOne := headRef.Peel(git.ObjectCommit)
    try parentObjTwo := remoteBranch.Reference.Peel(git.ObjectCommit)
    try parentCommitOne := parentObjOne.AsCommit()
    try parentCommitTwo := parentObjTwo.AsCommit()
    try treeOid := index.WriteTree()
    try tree := r.LookupTree(treeOid)
    try remoteBranchName := remoteBranch.Name()
    try userName, userEmail := r.UserIdentityFromConfig() else err {
        userName = ""
        userEmail = ""
    }

    var (
        now       = time.Now()
        author    = &git.Signature{Name: userName, Email: userEmail, When: now}
        committer = &git.Signature{Name: userName, Email: userEmail, When: now}
        message   = fmt.Sprintf(`Merge branch '%v' of %v`, remoteBranchName, remote.Url())
        parents   = []*git.Commit{
            parentCommitOne,
            parentCommitTwo,
        }
    )

    try r.CreateCommit(headRef.Name(), author, committer, message, tree, parents...)
    return nil
}

Meskipun pola try / else tidak menyimpan banyak karakter di atas gabungan if , pola tersebut:

  • satukan sintaks penanganan kesalahan dengan try yang tidak ditangani
  • memperjelas sekilas bahwa blok bersyarat sedang menangani kondisi kesalahan
  • beri kami kesempatan untuk mengurangi keanehan pelingkupan yang diderita oleh compound if s

try yang tidak ditangani kemungkinan akan menjadi yang paling umum.

@ianlancetaylor

Coba baca contoh-contoh itu sambil berkaca-kaca coba seperti Anda sudah berkaca-kaca if err != nil { return err }.

Saya tidak berpikir itu mungkin/sama. Tidak ada percobaan yang ada di garis yang ramai, atau apa yang sebenarnya dibungkus, atau ada beberapa contoh dalam satu baris... Ini tidak sama dengan dengan mudah/cepat menandai titik kembali dan tidak mengkhawatirkan secara spesifik di dalamnya.

@ianlancetaylor

Ketika saya melihat tanda berhenti, saya mengenalinya dengan bentuk dan warna lebih daripada membaca kata yang tercetak di atasnya dan merenungkan implikasinya yang lebih dalam.

Mata saya mungkin berkaca-kaca di atas if err != nil { return err } tetapi pada saat yang sama masih mencatat—dengan jelas dan seketika.

Apa yang saya suka tentang varian try -statement adalah bahwa ia mengurangi boilerplate tetapi dengan cara yang mudah untuk diglasir tetapi sulit untuk dilewatkan.

Ini mungkin berarti garis tambahan di sana-sini tetapi itu masih lebih sedikit daripada status quo.

@brynbellomy

  1. Bagaimana Anda menawarkan untuk menangani fungsi yang mengembalikan banyak nilai, seperti:
    func createMergeCommit(r *repo.Repo, index *git.Index, remote *git.Remote, remoteBranch *git.Branch) (hash , error) {
  2. Bagaimana Anda melacak baris yang benar yang mengembalikan kesalahan?
  3. membuang masalah ruang lingkup (yang dapat diselesaikan dengan cara lain), saya tidak yakin
func createMergeCommit(r *repo.Repo, index *git.Index, remote *git.Remote, remoteBranch *git.Branch) error {

    if headRef, err := r.Head(); err != nil {
        return err
    } else if parentObjOne, err := headRef.Peel(git.ObjectCommit); err != nil {
        return err
    } else parentObjTwo, err := remoteBranch.Reference.Peel(git.ObjectCommit); err != nil {
        return err
    } ...

tidak begitu berbeda dari segi keterbacaan, namun (atau fmt.Errorf("error with getting head : %s", err.Error() ) memungkinkan Anda untuk memodifikasi dan memberikan data tambahan dengan mudah.

Yang masih cerewet adalah

  1. harus memeriksa ulang; err != nihil
  2. mengembalikan kesalahan sebagaimana adanya jika kami tidak ingin memberikan info tambahan - yang dalam beberapa kasus bukanlah praktik yang baik, karena Anda bergantung pada fungsi yang Anda panggil untuk mencerminkan kesalahan "baik" yang akan mengisyaratkan "apa yang salah ", dalam kasus file.Open , close , Remove , Db fungsi dll banyak panggilan fungsi dapat mengembalikan kesalahan yang sama (kita dapat berdebat jika itu berarti pengembang yang menulis kesalahan melakukan pekerjaan dengan baik atau tidak... TIDAK terjadi) - dan kemudian - Anda memiliki kesalahan, mungkin mencatatnya dari fungsi yang disebut
    " createMergeCommit", tetapi tidak dapat melacaknya ke baris persis di mana itu terjadi.

Maaf jika seseorang sudah memposting sesuatu seperti ini (ada banyak ide bagus :P ) Bagaimana dengan sintaks alternatif ini:

fail := func(err error) error {
  log.Print("unexpected error", err)
  return err
}

a, b, err := f1()          // normal
c, d := f2() -> throw      // calls throw(err)
e, f := f3() -> panic      // calls panic(err)
g, h := f4() -> t.Error    // calls t.Error(err)
i, j := f5() -> fail       // calls fail(err)

yaitu, Anda memiliki -> handler di sebelah kanan pemanggilan fungsi yang dipanggil jika err != nil yang dikembalikan. Handler adalah fungsi apa pun yang menerima kesalahan sebagai argumen tunggal dan secara opsional mengembalikan kesalahan (yaitu, func(error) atau func(error) error ). Jika pawang mengembalikan kesalahan nihil, fungsi berlanjut jika tidak, kesalahan dikembalikan.

jadi a := b() -> handler setara dengan:

a, err := b()
if err != nil {
  if herr := handler(err); herr != nil {
    return herr
  }
}

Sekarang, sebagai jalan pintas Anda dapat mendukung try bawaan (atau kata kunci atau operator ?= atau apa pun) yang merupakan kependekan dari a := b() -> throw sehingga Anda dapat menulis sesuatu seperti:

func() error {
  a, b := try(f1())
  c, d := try(f2())
  e, f := try(f3())
  ...
  return nil
}() -> panic // or throw/fail/whatever

Secara pribadi saya menemukan operator ?= lebih mudah dibaca daripada mencoba kata kunci/bawaan:

func() error {
  a, b ?= f1()
  c, d ?= f2()
  e, f ?= f3()
  ...
  return nil
}() -> panic

catatan : di sini saya menggunakan throw sebagai pengganti untuk builtin yang akan mengembalikan kesalahan ke pemanggil.

Saya belum mengomentari proposal penanganan kesalahan sejauh ini karena saya umumnya mendukung, dan saya suka cara mereka menuju. Baik fungsi try yang didefinisikan dalam proposal dan pernyataan try yang diusulkan oleh @thepudds sepertinya merupakan tambahan yang masuk akal untuk bahasa tersebut. Saya yakin bahwa apa pun yang dihasilkan oleh tim Go akan menjadi hal yang baik.

Saya ingin mengemukakan apa yang saya lihat sebagai masalah kecil dengan cara coba didefinisikan dalam proposal dan bagaimana hal itu dapat memengaruhi ekstensi di masa mendatang.

Coba didefinisikan sebagai fungsi yang mengambil sejumlah variabel argumen.

func try(t1 T1, t2 T2, … tn Tn, te error) (T1, T2, … Tn)

Melewati hasil pemanggilan fungsi ke try seperti pada try(f()) bekerja secara implisit karena cara beberapa nilai kembalian bekerja di Go.

Dengan membaca proposal saya, cuplikan berikut valid dan setara secara semantik.

a, b = try(f())
//
u, v, err := f()
a, b = try(u, v, err)

Proposal juga meningkatkan kemungkinan untuk memperpanjang try dengan argumen tambahan.

Jika kami menentukan di jalan bahwa memiliki beberapa bentuk fungsi pengendali kesalahan yang disediakan secara eksplisit, atau parameter tambahan lainnya dalam hal ini, adalah ide yang bagus, sangat mungkin untuk meneruskan argumen tambahan itu ke panggilan coba.

Misalkan kita ingin menambahkan argumen handler. Itu bisa di awal atau di akhir daftar argumen.

var h handler
a, b = try(h, f())
// or
a, b = try(f(), h)

Menempatkannya di awal tidak berhasil, karena (mengingat semantik di atas) try tidak akan dapat membedakan antara argumen handler eksplisit dan fungsi yang mengembalikan handler.

func f() (handler, error) { ... }
func g() (error) { ... }
try(f())
try(h, g())

Menempatkannya di akhir mungkin akan berhasil, tetapi kemudian try akan menjadi unik dalam bahasa sebagai satu-satunya fungsi dengan parameter varargs di awal daftar argumen.

Tak satu pun dari masalah ini adalah penghenti, tetapi mereka membuat try merasa tidak konsisten dengan bahasa lainnya, jadi saya tidak yakin try akan mudah diperluas di masa mendatang karena status proposal.

@gaib

Memiliki penangan itu kuat, mungkin:
Saya Anda sudah menyatakan h,

kamu bisa

var h handler
a, b, h = f()

atau

a, b, h.err = f()

jika fungsinya seperti:

h:= handler(err error){
 log(...)
 return ....
} 

Lalu ada saran untuk

a, b, h(err) = f()

Semua dapat memanggil pawang
Dan Anda juga dapat "memilih" penangan yang mengembalikan atau hanya menangkap kesalahan (lanjutan/istirahat/kembali) seperti yang disarankan beberapa orang.

Dan dengan demikian masalah varargs hilang.

Salah satu alternatif dari saran else @brynbellomy tentang:

a, b := try f() else err { /* handle error */ }

bisa untuk mendukung fungsi dekorasi segera setelah yang lain:

decorate := func(err error) error { return fmt.Errorf("foo failed: %v", err) }

try a, b := f() else decorate
try c, d := g() else decorate

Dan mungkin juga beberapa utilitas berfungsi seperti:

decorate := fmt.DecorateErrorf("foo failed")

Fungsi dekorasi dapat memiliki tanda tangan func(error) error , dan dipanggil oleh try jika ada kesalahan, tepat sebelum try kembali dari fungsi terkait yang sedang dicoba.

Itu akan serupa dalam semangat dengan salah satu "iterasi desain" sebelumnya dari dokumen proposal:

f := try(os.Open(filename), handler)              // handler will be called in error case

Jika seseorang menginginkan sesuatu yang lebih kompleks atau blok pernyataan, mereka dapat menggunakan if (seperti yang mereka dapat sekarang).

Yang mengatakan, ada sesuatu yang bagus tentang perataan visual try yang ditunjukkan dalam contoh @brynbellomy di https://github.com/golang/go/issues/32437#issuecomment -500949780.

Semua ini masih dapat bekerja dengan defer jika pendekatan itu dipilih untuk dekorasi kesalahan seragam (atau bahkan secara teori mungkin ada bentuk alternatif untuk mendaftarkan fungsi dekorasi, tetapi itu adalah poin terpisah).

Bagaimanapun, saya tidak yakin apa yang terbaik di sini, tetapi ingin membuat opsi lain eksplisit.

Berikut contoh @brynbellomy yang ditulis ulang dengan fungsi try , menggunakan blok var untuk mempertahankan keselarasan yang bagus yang ditunjukkan oleh @thepudds di https://github.com/golang/go/issues /32437#issuecomment -500998690.

package main

import (
    "fmt"
    "time"
)

func createMergeCommit(r *repo.Repo, index *git.Index, remote *git.Remote, remoteBranch *git.Branch) error {
    var (
        headRef          = try(r.Head())
        parentObjOne     = try(headRef.Peel(git.ObjectCommit))
        parentObjTwo     = try(remoteBranch.Reference.Peel(git.ObjectCommit))
        parentCommitOne  = try(parentObjOne.AsCommit())
        parentCommitTwo  = try(parentObjTwo.AsCommit())
        treeOid          = try(index.WriteTree())
        tree             = try(r.LookupTree(treeOid))
        remoteBranchName = try(remoteBranch.Name())
    )

    userName, userEmail, err := r.UserIdentityFromConfig()
    if err != nil {
        userName = ""
        userEmail = ""
    }

    var (
        now       = time.Now()
        author    = &git.Signature{Name: userName, Email: userEmail, When: now}
        committer = &git.Signature{Name: userName, Email: userEmail, When: now}
        message   = fmt.Sprintf(`Merge branch '%v' of %v`, remoteBranchName, remote.Url())
        parents   = []*git.Commit{
            parentCommitOne,
            parentCommitTwo,
        }
    )

    _, err = r.CreateCommit(headRef.Name(), author, committer, message, tree, parents...)
    return err
}

Ini sesingkat versi pernyataan try -, dan saya berpendapat sama dapat dibaca. Karena try adalah sebuah ekspresi, beberapa dari variabel perantara tersebut dapat dihilangkan, dengan mengorbankan beberapa keterbacaan, tetapi itu tampaknya lebih seperti masalah gaya daripada yang lainnya.

Itu menimbulkan pertanyaan tentang bagaimana try bekerja di blok var . Saya menganggap setiap baris dari var dihitung sebagai pernyataan terpisah, daripada seluruh blok menjadi pernyataan tunggal, sejauh urutan apa yang ditugaskan kapan.

Akan lebih baik jika proposal "coba" secara eksplisit menyebutkan konsekuensi untuk alat seperti cmd/cover yang memperkirakan statistik cakupan pengujian menggunakan penghitungan pernyataan naif. Saya khawatir bahwa aliran kontrol kesalahan yang tidak terlihat dapat mengakibatkan penghitungan yang kurang.

@thepudds

coba a, b := f() kalau tidak hias

Mungkin luka bakarnya terlalu dalam di sel-sel otakku, tapi ini terlalu memukulku sebagai

try a, b := f() ;catch(decorate)

dan lereng licin ke a

a, b := f()
catch(decorate)

Saya pikir Anda dapat melihat ke mana arahnya, dan bagi saya membandingkan

    try headRef := r.Head()
    try parentObjOne := headRef.Peel(git.ObjectCommit)
    try parentObjTwo := remoteBranch.Reference.Peel(git.ObjectCommit)
    try parentCommitOne := parentObjOne.AsCommit()
    try parentCommitTwo := parentObjTwo.AsCommit()
    try treeOid := index.WriteTree()
    try tree := r.LookupTree(treeOid)
    try remoteBranchName := remoteBranch.Name()

dengan

    try ( 
    headRef := r.Head()
    parentObjOne := headRef.Peel(git.ObjectCommit)
    parentObjTwo := remoteBranch.Reference.Peel(git.ObjectCommit)
    parentCommitOne := parentObjOne.AsCommit()
    parentCommitTwo := parentObjTwo.AsCommit()
    treeOid := index.WriteTree()
    tree := r.LookupTree(treeOid)
    remoteBranchName := remoteBranch.Name()
    )

(atau bahkan tangkapan di akhir)
Yang kedua lebih mudah dibaca, tetapi menekankan fakta bahwa fungsi di bawah mengembalikan 2 vars, dan kami secara ajaib membuang satu, mengumpulkannya menjadi "magic return err" .

    try(err) ( 
    headRef := r.Head()
    parentObjOne := headRef.Peel(git.ObjectCommit)
    parentObjTwo := remoteBranch.Reference.Peel(git.ObjectCommit)
    parentCommitOne := parentObjOne.AsCommit()
    parentCommitTwo := parentObjTwo.AsCommit()
    treeOid := index.WriteTree()
    tree := r.LookupTree(treeOid)
    remoteBranchName := remoteBranch.Name()
    ); err!=nil {
      //handle the err
    }

setidaknya secara eksplisit mengatur variabel untuk dikembalikan, dan biarkan saya menanganinya di dalam fungsi, kapan pun saya mau.

Hanya menyela komentar tertentu karena saya tidak melihat orang lain secara eksplisit menyebutkannya, khususnya tentang mengubah gofmt untuk mendukung pemformatan baris tunggal berikut, atau varian apa pun:

if f() { return nil, err }

Kumohon tidak. Jika kita ingin satu baris if maka silahkan buat satu baris if , misalnya:

if f() then return nil, err

Tapi tolong, tolong, tolong, jangan merangkul sintaksis salad menghapus jeda baris yang membuatnya lebih mudah untuk membaca kode yang menggunakan kurung kurawal.

Saya ingin menekankan beberapa hal yang mungkin telah dilupakan dalam panasnya diskusi:

1) Inti dari proposal ini adalah untuk membuat penanganan kesalahan umum memudar ke latar belakang - penanganan kesalahan tidak boleh mendominasi kode. Tapi harus tetap eksplisit. Salah satu saran alternatif yang membuat penanganan kesalahan semakin menonjol tidak ada gunanya. Seperti yang sudah dikatakan @ianlancetaylor , jika saran alternatif ini tidak mengurangi jumlah boilerplate secara signifikan, kita bisa tetap menggunakan pernyataan if . (Dan permintaan untuk mengurangi boilerplate datang dari Anda, komunitas Go.)

2) Salah satu keluhan tentang proposal saat ini adalah kebutuhan untuk menyebutkan hasil kesalahan untuk mendapatkan akses ke sana. Setiap proposal alternatif akan memiliki masalah yang sama kecuali jika alternatif tersebut memperkenalkan sintaks tambahan, yaitu, lebih banyak boilerplate (seperti ... else err { ... } dan sejenisnya) untuk secara eksplisit menamai variabel tersebut. Tetapi yang menarik: Jika kita tidak peduli dengan mendekorasi kesalahan dan tidak memberi nama parameter hasil, tetapi masih memerlukan return eksplisit karena ada semacam penangan eksplisit, pernyataan return itu harus menghitung semua nilai hasil (biasanya nol) karena pengembalian telanjang tidak diizinkan dalam kasus ini. Terutama jika suatu fungsi melakukan banyak pengembalian kesalahan tanpa mendekorasi kesalahan, pengembalian eksplisit tersebut ( return nil, err , dll.) ditambahkan ke boilerplate. Proposal saat ini, dan alternatif apa pun yang tidak memerlukan return eksplisit menghapusnya. Di sisi lain, jika seseorang memang ingin menghias kesalahan, proposal saat ini _memerlukan_ bahwa satu nama hasil kesalahan (dan dengan itu semua hasil lainnya) untuk mendapatkan akses ke nilai kesalahan. Ini memiliki efek samping yang bagus bahwa dalam penangan eksplisit seseorang dapat menggunakan pengembalian telanjang dan tidak harus mengulangi semua nilai hasil lainnya. (Saya tahu ada beberapa perasaan kuat tentang pengembalian telanjang, tetapi kenyataannya adalah bahwa ketika semua yang kita pedulikan adalah hasil kesalahan, itu adalah gangguan nyata untuk menghitung semua nilai hasil lainnya (biasanya nol) - itu tidak menambahkan apa pun ke pemahaman kode). Dengan kata lain, harus menamai hasil kesalahan agar dapat didekorasi memungkinkan pengurangan lebih lanjut dari pelat ketel.

@magical Terima kasih telah menunjukkan ini . Saya melihat hal yang sama segera setelah memposting proposal (tetapi tidak membawanya agar tidak menimbulkan kebingungan lebih lanjut). Anda benar bahwa try tidak dapat diperpanjang. Untungnya perbaikannya cukup mudah. (Kebetulan proposal internal kami sebelumnya tidak memiliki masalah ini - itu diperkenalkan ketika saya menulis ulang versi final kami untuk publikasi dan mencoba menyederhanakan try untuk mencocokkan aturan melewati parameter yang ada lebih dekat. Sepertinya bagus - tetapi ternyata, cacat, dan sebagian besar tidak berguna - manfaat untuk dapat menulis try(a, b, c, handle) .)

Versi sebelumnya dari try mendefinisikannya secara kasar sebagai berikut: try(expr, handler) mengambil satu (atau mungkin dua) ekspresi sebagai argumen, di mana ekspresi pertama mungkin multi-nilai (hanya dapat terjadi jika ekspresinya adalah panggilan fungsi). Nilai terakhir dari ekspresi tersebut (mungkin multi-nilai) harus bertipe error , dan nilai tersebut diuji terhadap nil. (dll. - sisanya dapat Anda bayangkan).

Bagaimanapun, intinya adalah bahwa try secara sintaksis hanya menerima satu, atau mungkin dua ekspresi. (Tapi agak sulit untuk menggambarkan semantik dari try .) Konsekuensinya adalah kode seperti:

a, b := try(u, v, err)

tidak akan diizinkan lagi. Tapi ada sedikit alasan untuk membuat ini bekerja di tempat pertama: Dalam kebanyakan kasus (kecuali a dan b diberi nama hasil) kode ini - jika penting untuk beberapa alasan - dapat ditulis ulang dengan mudah menjadi

a, b := u, v  // we don't care if the assignment happens in case of an error
try(err)

(atau gunakan pernyataan if sesuai kebutuhan). Tapi sekali lagi, ini sepertinya tidak penting.

pernyataan pengembalian itu harus menghitung semua nilai hasil (biasanya nol) karena pengembalian telanjang tidak diizinkan dalam kasus ini

Pengembalian telanjang tidak diizinkan, tetapi cobalah. Satu hal yang saya suka tentang try (baik sebagai fungsi atau pernyataan) adalah bahwa saya tidak perlu memikirkan cara mengatur nilai non-error saat mengembalikan kesalahan lagi, saya hanya akan menggunakan try.

@griesemer Terima kasih atas penjelasannya. Itu juga kesimpulan yang saya dapatkan.

Komentar singkat tentang try sebagai pernyataan: seperti yang saya pikir dapat dilihat pada contoh di https://github.com/golang/go/issues/32437#issuecomment -501035322, try mengubur lede. Kode menjadi serangkaian pernyataan try , yang mengaburkan apa yang sebenarnya dilakukan kode.

Kode yang ada dapat menggunakan kembali variabel kesalahan yang baru dideklarasikan setelah blok if err != nil . Menyembunyikan variabel akan merusaknya, dan menambahkan variabel pengembalian bernama ke tanda tangan fungsi tidak akan selalu memperbaikinya.

Mungkin yang terbaik adalah membiarkan deklarasi/penugasan kesalahan apa adanya, dan menemukan stmt penanganan kesalahan satu baris.

err := f() // followed by one of

on err, return err            // any type can be tested for non-zero
on err, return fmt.Errorf(...)
on err, fmt.Println(err)      // doesn't stop the function
on err, hname err             // handler invocation without parens
on err, ignore err            // optional ignore handler logs error if defined

if err, return err            // alternatively, use if

handle hname(err error, clr caller) { // type caller has results of runtime.Caller()
   if err == io.Bad { return err }
   fmt.Println(clr.name, err)
}

Subekspresi try bisa panik, artinya kesalahan tidak pernah diharapkan. Varian dari itu dapat mengabaikan kesalahan apa pun.

f(try g()) // panic on error
f(try_ g()) // ignore any error

Inti dari proposal ini adalah untuk membuat penanganan kesalahan umum memudar ke latar belakang - penanganan kesalahan tidak boleh mendominasi kode. Tapi harus tetap eksplisit.

Saya suka ide komentar yang mencantumkan try sebagai pernyataan. Ini eksplisit, masih mudah untuk dipoles (karena panjangnya tetap), tetapi tidak _so_ mudah untuk dipoles (karena selalu di tempat yang sama) sehingga dapat disembunyikan di barisan yang ramai. Itu juga dapat digabungkan dengan defer fmt.HandleErrorf(...) seperti yang disebutkan sebelumnya, namun memiliki perangkap menyalahgunakan parameter bernama untuk membungkus kesalahan (yang masih tampak seperti peretasan yang cerdas bagi saya. peretasan pintar itu buruk.)

Salah satu alasan saya tidak menyukai try sebagai ekspresi adalah karena terlalu mudah untuk disamarkan, atau tidak cukup mudah untuk disamarkan. Ambil dua contoh berikut:

Coba sebagai ekspresi

// Too hidden, it's in a crowded function with many symbols that complicate the function.

f, err := os.OpenFile(try(FileName()), os.O_APPEND|os.O_WRONLY, 0600)

// Not easy enough, the word "try" already increases horizontal complexity, and it
// being an expression only encourages more horizontal complexity.
// If this code weren't collapsed to multiple lines, it would be extremely
// hard to read and unclear as to what's executing when.

fullContents := try(io.CopyN(
    os.Stdout,
    try(net.Dial("tcp", "localhost:"+try(buf.ReadString("\n"))),
    try(strconv.Atoi(try(buf.ReadString("\n")))),
))

Coba sebagai pernyataan

// easy to see while still not being too verbose

try name := FileName()
os.OpenFile(name, os.O_APPEND|os.O_WRONLY, 0600)

// does not allow for clever one-liners, code remains much more readable.
// also, the code reads in sequence from top-to-bottom.

try port := r.ReadString("\n")
try lengthStr := r.ReadString("\n")
try length := strconv.Atoi(lengthStr)

try con := net.Dial("tcp", "localhost:"+port)
try io.CopyN(os.Stdout, con, length)

Bawa pulang

Kode ini pasti dibuat-buat, saya akui. Tapi yang saya maksud adalah, secara umum, try sebagai ekspresi tidak berfungsi dengan baik di:

  1. Di tengah ekspresi ramai yang tidak perlu banyak pengecekan kesalahan
  2. Pernyataan multiline yang relatif sederhana yang membutuhkan banyak pemeriksaan kesalahan

Namun saya setuju dengan @ianlancetaylor bahwa memulai setiap baris dengan try tampaknya menghalangi bagian penting dari setiap pernyataan (variabel yang didefinisikan atau fungsi yang dieksekusi). Namun saya pikir karena berada di lokasi yang sama dan lebarnya tetap, lebih mudah untuk menutupinya, sambil tetap memperhatikannya. Namun, mata setiap orang berbeda.

Saya juga berpikir mendorong one-liners yang pintar dalam kode hanyalah ide yang buruk secara umum. Saya terkejut bahwa saya bisa membuat one-liner yang begitu kuat seperti pada contoh pertama saya, ini adalah snippet yang layak mendapatkan seluruh fungsinya karena melakukan begitu banyak - tetapi cocok pada satu baris jika saya tidak menciutkannya ke beberapa demi keterbacaan. Semua dalam satu baris:

fullContents := try(io.CopyN(os.Stdout, try(net.Dial("tcp", "localhost:"+try(r.ReadString("\n"))), try(strconv.Atoi(try(r.ReadString("\n"))))))

Itu membaca port dari *bufio.Reader , memulai koneksi TCP, dan menyalin sejumlah byte yang ditentukan oleh *bufio.Reader yang sama ke stdout . Semua dengan penanganan kesalahan. Untuk bahasa dengan konvensi pengkodean yang ketat, saya rasa ini tidak boleh diizinkan. Saya kira gofmt dapat membantu dengan ini.

Untuk bahasa dengan konvensi pengkodean yang ketat, saya rasa ini tidak boleh diizinkan.

Dimungkinkan untuk menulis kode yang menjijikkan di Go. Bahkan dimungkinkan untuk memformatnya dengan sangat buruk; hanya ada norma dan alat yang kuat untuk menentangnya. Go bahkan memiliki goto .

Selama tinjauan kode, saya terkadang meminta orang untuk memecah ekspresi rumit menjadi beberapa pernyataan, dengan nama perantara yang berguna. Saya akan melakukan hal serupa untuk try s yang sangat bersarang, untuk alasan yang sama.

Artinya: Mari kita tidak berusaha terlalu keras untuk melarang kode yang buruk, dengan biaya mendistorsi bahasa. Kami memiliki mekanisme lain untuk menjaga kode tetap bersih yang lebih cocok untuk sesuatu yang pada dasarnya melibatkan penilaian manusia berdasarkan kasus per kasus.

Dimungkinkan untuk menulis kode yang menjijikkan di Go. Bahkan dimungkinkan untuk memformatnya dengan sangat buruk; hanya ada norma dan alat yang kuat untuk menentangnya. Pergi bahkan harus.

Selama tinjauan kode, saya terkadang meminta orang untuk memecah ekspresi rumit menjadi beberapa pernyataan, dengan nama perantara yang berguna. Saya akan melakukan hal serupa untuk percobaan yang sangat bersarang, untuk alasan yang sama.

Artinya: Mari kita tidak berusaha terlalu keras untuk melarang kode yang buruk, dengan biaya mendistorsi bahasa. Kami memiliki mekanisme lain untuk menjaga kode tetap bersih yang lebih cocok untuk sesuatu yang pada dasarnya melibatkan penilaian manusia berdasarkan kasus per kasus.

Ini adalah poin yang bagus. Kita tidak boleh melarang ide yang baik hanya karena dapat digunakan untuk membuat kode yang buruk. Namun, saya pikir jika kami memiliki alternatif yang mempromosikan kode yang lebih baik, itu mungkin ide yang bagus. Saya benar-benar belum melihat banyak pembicaraan _melawan_ ide mentah di balik try sebagai pernyataan (tanpa semua else { ... } sampah) hingga komentar @ianlancetaylor , namun saya mungkin melewatkannya.

Juga, tidak semua orang memiliki peninjau kode, beberapa orang (terutama di masa depan) harus mempertahankan kode Go yang belum ditinjau. Pergi sebagai bahasa biasanya melakukan pekerjaan yang sangat baik untuk memastikan bahwa hampir semua kode tertulis terpelihara dengan baik (setidaknya setelah go fmt ), yang bukan merupakan prestasi untuk diabaikan.

Karena itu, saya sangat kritis terhadap ide ini padahal sebenarnya tidak mengerikan.

Coba sebagai pernyataan tidak mengurangi boilerplate secara signifikan, dan lebih dari mencoba sebagai ekspresi, jika kita mengizinkannya bekerja pada blok ekspresi seperti yang diusulkan sebelumnya, bahkan tanpa mengizinkan blok lain atau penangan kesalahan. Dengan menggunakan ini, contoh deandeveloper menjadi:

try (
    name := FileName()
    file := os.OpenFile(name, os.O_APPEND|os.O_WRONLY, 0600)
    port := r.ReadString("\n")
    lengthStr := r.ReadString("\n")
    length := strconv.Atoi(lengthStr)
    con := net.Dial("tcp", "localhost:"+port)
    io.CopyN(os.Stdout, con, length)
)

Jika tujuannya adalah untuk mengurangi boilerplate if err!= nil {return err} , maka saya pikir pernyataan try yang memungkinkan untuk mengambil blok kode memiliki potensi paling besar untuk melakukan itu, tanpa menjadi tidak jelas.

@beoran Pada saat itu, mengapa harus mencoba sama sekali? Cukup izinkan penugasan di mana nilai kesalahan terakhir tidak ada dan buat itu berperilaku seperti jika itu adalah pernyataan coba (atau panggilan fungsi). Bukannya saya mengusulkannya, tetapi itu akan mengurangi boilerplate lebih banyak lagi.

Saya pikir boilerplate akan dikurangi secara efisien oleh blok var ini, tetapi saya khawatir ini dapat menyebabkan sejumlah besar kode menjadi menjorok ke level tambahan, yang akan sangat disayangkan.

@deanveloper

fullContents := try(io.CopyN(os.Stdout, try(net.Dial("tcp", "localhost:"+try(r.ReadString("\n"))), try(strconv.Atoi(try(r.ReadString("\n"))))))

Saya harus mengakui tidak dapat dibaca untuk saya, saya mungkin merasa saya harus:

fullContents := try(io.CopyN(os.Stdout, 
                               try(net.Dial("tcp", "localhost:"+try(r.ReadString("\n"))),
                                     try(strconv.Atoi(try(r.ReadString("\n"))))))

atau serupa, untuk keterbacaan, dan kemudian kami kembali dengan "coba" di awal setiap baris, dengan lekukan.

Yah, saya pikir kita masih perlu mencoba untuk kompatibilitas mundur, dan juga secara eksplisit tentang pengembalian yang dapat terjadi di blok. Tetapi perhatikan bahwa saya hanya mengikuti logika mengurangi pelat ketel dan kemudian melihat ke mana arahnya. Selalu ada ketegangan antara pengurangan boilerplate dan kejelasan. Saya pikir masalah utama yang dihadapi dalam masalah ini adalah bahwa kita semua tampaknya tidak setuju di mana keseimbangan seharusnya.

Adapun indentasi, itulah gunanya go fmt, jadi secara pribadi saya tidak merasa itu masalah besar.

Saya ingin bergabung dengan keributan untuk menyebutkan dua kemungkinan lain, yang masing-masing independen, jadi saya akan menyimpannya di pos terpisah.

Saya pikir saran bahwa try() (tanpa argumen) dapat didefinisikan untuk mengembalikan pointer ke variabel pengembalian kesalahan adalah hal yang menarik, tetapi saya tidak tertarik pada permainan kata-kata seperti itu - ini berdampak pada fungsi yang berlebihan , sesuatu yang dihindari Go.

Namun saya menyukai gagasan umum tentang pengidentifikasi yang telah ditentukan yang mengacu pada nilai kesalahan lokal.

Jadi, bagaimana dengan mendefinisikan pengidentifikasi err itu sendiri sebagai alias untuk variabel pengembalian kesalahan? Jadi ini akan valid:

func foo() error {
    defer handleError(&err, etc)
    try(something())
}

Secara fungsional akan identik dengan:

func foo() (err error) {
    defer handleError(&err, etc)
    try(something())
}

Pengidentifikasi err akan didefinisikan pada lingkup semesta, meskipun bertindak sebagai alias fungsi-lokal, jadi definisi tingkat paket atau definisi fungsi-lokal dari err akan menimpanya. Ini mungkin tampak berbahaya tetapi saya memindai garis 22m Go di korpus Go dan itu sangat jarang. Hanya ada 4 contoh berbeda err digunakan sebagai global (semua sebagai variabel, bukan tipe atau konstanta) - ini adalah sesuatu yang vet dapat memperingatkan tentang.

Ada kemungkinan bahwa ada dua variabel pengembalian kesalahan fungsi dalam cakupan; dalam hal ini, saya pikir yang terbaik adalah kompiler akan mengeluh bahwa ada ambiguitas dan mengharuskan pengguna untuk secara eksplisit menyebutkan variabel pengembalian yang benar. Jadi ini tidak valid:

func foo() error {
    f := func() error {
        defer handleError(&err, etc)
        try(something())
        return nil
    }
    return f()
}

tetapi Anda selalu bisa menulis ini sebagai gantinya:

func foo() error {
    f := func() (err error) {
        defer handleError(&err, etc)
        try(something())
        return nil
    }
    return f()
}

Pada subjek try sebagai pengidentifikasi yang telah ditentukan daripada operator,
Saya mendapati diri saya cenderung memilih yang terakhir setelah berulang kali salah memasukkan tanda kurung saat menulis:

try(try(os.Create(filename)).Write(data))

Di bawah "Mengapa kami tidak dapat menggunakan ? like Rust", FAQ mengatakan:

Sejauh ini kita telah menghindari singkatan atau simbol samar dalam bahasa, termasuk operator yang tidak biasa seperti ?, yang memiliki arti yang ambigu atau tidak jelas.

Saya tidak sepenuhnya yakin itu benar. Operator .() tidak biasa sampai Anda mengetahui Go, seperti halnya operator saluran. Jika kami menambahkan operator ? , saya yakin itu akan segera menjadi cukup umum sehingga tidak akan menjadi penghalang yang signifikan.

Operator Rust ? ditambahkan setelah tanda kurung tutup dari pemanggilan fungsi, dan itu berarti mudah untuk dilewatkan jika daftar argumennya panjang.

Bagaimana dengan menambahkan ?() sebagai operator panggilan:

Jadi alih-alih:

x := try(foo(a, b))

Anda akan melakukan:

x := foo?(a, b)

Semantik ?() akan sangat mirip dengan try built-in yang diusulkan. Itu akan bertindak seperti panggilan fungsi kecuali bahwa fungsi atau metode yang dipanggil harus mengembalikan kesalahan sebagai argumen terakhirnya. Seperti try , jika kesalahannya bukan nihil, pernyataan ?() akan mengembalikannya.

Sepertinya diskusi sudah cukup terfokus sehingga kita sekarang berputar-putar di sekitar serangkaian pengorbanan yang terdefinisi dengan baik dan didiskusikan. Ini membesarkan hati, setidaknya bagi saya, karena kompromi sangat dijiwai oleh bahasa ini.

@ianlancetaylor Saya benar-benar akan mengakui bahwa kita akan berakhir dengan lusinan baris yang diawali dengan try . Namun, saya tidak melihat bagaimana itu lebih buruk daripada lusinan baris yang di-postfix oleh ekspresi bersyarat dua hingga empat baris yang secara eksplisit menyatakan ekspresi return yang sama. Sebenarnya, try (dengan klausa else ) membuatnya sedikit lebih mudah dikenali ketika penangan kesalahan melakukan sesuatu yang khusus/non-default. Juga, secara tangensial, re: conditional if ekspresi, saya pikir mereka mengubur lede lebih dari yang diusulkan try -as-a-statement: panggilan fungsi hidup di baris yang sama dengan conditional , conditional itu sendiri berakhir di bagian paling akhir dari baris yang sudah penuh sesak, dan penugasan variabel dicakupkan ke blok (yang memerlukan sintaks yang berbeda jika Anda memerlukan variabel tersebut setelah blok).

@josharian Saya sudah memiliki pemikiran ini baru-baru ini. Go berusaha keras untuk pragmatisme, bukan kesempurnaan, dan perkembangannya sering kali tampaknya didorong oleh data daripada didorong oleh prinsip. Anda dapat menulis Go yang buruk, tetapi biasanya lebih sulit daripada menulis Go yang layak (yang cukup baik untuk kebanyakan orang). Juga perlu diperhatikan — kami memiliki banyak alat untuk memerangi kode buruk: tidak hanya gofmt dan go vet , tetapi rekan-rekan kami, dan budaya yang telah (sangat hati-hati) dibuat oleh komunitas ini untuk memandu dirinya sendiri. Saya akan benci untuk menghindari perbaikan yang membantu kasus umum hanya karena seseorang di suatu tempat mungkin menginjak kaki sendiri.

@beoran Ini elegan, dan ketika Anda memikirkannya, sebenarnya secara semantik berbeda dari blok try bahasa lain, karena hanya memiliki satu kemungkinan hasil: kembali dari fungsi dengan kesalahan yang tidak tertangani. Namun: 1) ini mungkin membingungkan bagi pembuat kode Go baru yang telah bekerja dengan bahasa lain tersebut (sejujurnya bukan perhatian terbesar saya; saya percaya pada kecerdasan pemrogram), dan 2) ini akan menyebabkan sejumlah besar kode diindentasi di banyak basis kode. Sejauh menyangkut kode saya, saya bahkan cenderung menghindari blok type / const / var yang ada karena alasan ini. Juga, satu-satunya kata kunci yang saat ini mengizinkan blok seperti ini adalah definisi, bukan pernyataan kontrol.

@yiyus Saya tidak setuju dengan menghapus kata kunci, karena ketegasan adalah (menurut saya) salah satu kebajikan Go. Tetapi saya setuju bahwa membuat indentasi kode dalam jumlah besar untuk memanfaatkan ekspresi try adalah ide yang buruk. Jadi mungkin tidak ada blok try sama sekali?

@rogpeppe Saya pikir operator halus semacam itu hanya masuk akal untuk panggilan yang seharusnya tidak pernah mengembalikan kesalahan, dan jadi panik jika mereka melakukannya. Atau panggilan di mana Anda selalu mengabaikan kesalahan. Tapi keduanya sepertinya jarang. Jika Anda terbuka untuk operator baru, lihat #32500.

Saya menyarankan bahwa f(try g()) harus panik di https://github.com/golang/go/issues/32437#issuecomment -501074836, bersama dengan stmt penanganan 1 baris:
on err, return ...

Saya pikir else opsional di try ... else { ... } akan mendorong kode terlalu banyak ke kanan, mungkin mengaburkannya. Saya berharap blok kesalahan harus mengambil setidaknya 25 karakter sebagian besar waktu. Juga, sampai sekarang blok tidak disimpan pada baris yang sama oleh go fmt dan saya berharap perilaku ini akan disimpan untuk try else . Jadi kita harus mendiskusikan dan membandingkan sampel di mana blok else berada pada baris yang terpisah. Tetapi meskipun demikian saya tidak yakin tentang keterbacaan else { di akhir baris.

@yiyus https://github.com/golang/go/issues/32437#issuecomment -501139662

@beoran Pada saat itu, mengapa harus mencoba sama sekali? Cukup izinkan penugasan di mana nilai kesalahan terakhir tidak ada dan buat itu berperilaku seperti jika itu adalah pernyataan coba (atau panggilan fungsi). Bukannya saya mengusulkannya, tetapi itu akan mengurangi boilerplate lebih banyak lagi.

Itu tidak dapat dilakukan karena Go1 sudah mengizinkan pemanggilan func foo() error hanya sebagai foo() . Menambahkan , error ke nilai kembalian pemanggil akan mengubah perilaku kode yang ada di dalam fungsi itu. Lihat https://github.com/golang/go/issues/32437#issuecomment -500289410

@rogpeppe Dalam komentar Anda tentang mendapatkan tanda kurung yang benar dengan bersarang try 's: Apakah Anda memiliki pendapat tentang prioritas try ? Lihat juga dokumen desain rinci tentang hal ini .

@griesemer Saya memang tidak tertarik pada try sebagai operator awalan unary karena alasan yang ditunjukkan di sana. Terpikir oleh saya bahwa pendekatan alternatif adalah mengizinkan try sebagai metode semu pada Tuple pengembalian fungsi:

 f := os.Open(path).try()

Itu memecahkan masalah prioritas, saya pikir, tapi itu tidak terlalu mirip Go.

@rogpeppe

Sangat menarik! . Anda mungkin benar-benar menyukai sesuatu di sini.

Dan bagaimana kalau kita memperluas ide seperti itu?

for _,fp := range filepaths {
    f := os.Open(path).try(func(err error)bool{
        fmt.Printf( "Cannot open file %s\n", fp );
        continue;
    });
}

BTW, saya mungkin lebih suka nama yang berbeda vs try() seperti mungkin guard() tetapi saya tidak boleh mengganti nama sebelum arsitektur dibahas oleh orang lain.

vs:

for _,fp := range filepaths {
    if f,err := os.Open(path);err!=nil{
        fmt.Printf( "Cannot open file %s\n", fp )
    }
}

?

Saya suka try a,b := foo() daripada if err!=nil {return err} karena menggantikan boilerplate untuk kasus yang sangat sederhana. Tetapi untuk semua hal lain yang menambahkan konteks, apakah kita benar-benar membutuhkan sesuatu selain if err!=nil {...} (akan sangat sulit untuk menemukan yang lebih baik)?

Jika baris tambahan biasanya diperlukan untuk dekorasi/bungkus, mari kita "mengalokasikan" satu baris untuk itu.

f, err := os.Open(path)  // normal Go \o/
on err, return fmt.Errorf("Cannot open %s, due to %v", path, err)

@networkimprov Saya pikir saya juga bisa menyukainya. Mendorong istilah yang lebih aliteratif dan deskriptif yang sudah saya kemukakan ...

f, err := os.Open(path)
relay err { nil, fmt.Errorf("Cannot open %s, due to %v", path, err) }

// marginally shorter, doesn't trigger vertical formatting unless excessively wide
// enclosed expression restricted to a list of values that match the return args

atau

f, err := os.Open(path)
relay(err) nil, fmt.Errorf("Cannot open %s, due to %v", path, err)

// somewhere between statement and func, prob more pleasing to type w/out completion
// trailing expression restricted to a list of values that match the return args
// maybe excessive width triggers linting noise - with a reformatter available
// providing a reformatter would make swapping old (narrow enough) code easy

@daved senang Anda menyukainya! on err, ... akan mengizinkan penangan stmt tunggal:

err := f() // followed by one of

on err, return err            // any type can be tested for non-zero
on err, return fmt.Errorf(...)
on err, fmt.Println(err)      // doesn't stop the function
on err, continue              // retry in a loop
on err, hname err             // named handler invocation without parens
on err, ignore err            // logs error if handle ignore() defined

handle hname(err error, clr caller) { // type caller has results of runtime.Caller()
   if err == io.Bad { return err } // non-local return
   fmt.Println(clr, err)
}

EDIT: on meminjam dari Javascript. Saya tidak ingin membebani if .
Koma tidak penting, tapi saya tidak suka titik koma di sana. Mungkin usus besar?

Saya tidak cukup mengikuti relay ; itu berarti return-on-error?

Relai pelindung tersandung ketika beberapa kondisi terpenuhi. Dalam hal ini, ketika nilai kesalahan tidak nol, relai mengubah aliran kontrol untuk kembali menggunakan nilai berikutnya.

*Saya tidak ingin membebani , untuk kasus ini, dan saya bukan penggemar istilah on , tapi saya suka premis dan tampilan keseluruhan dari struktur kode.

Untuk poin @josharian sebelumnya, saya merasa sebagian besar diskusi tentang tanda kurung yang cocok sebagian besar bersifat hipotetis dan menggunakan contoh yang dibuat-buat. Saya tidak tahu tentang Anda, tetapi saya tidak menemukan diri saya mengalami kesulitan menulis panggilan fungsi dalam pemrograman sehari-hari saya. Jika saya sampai pada titik di mana ekspresi menjadi sulit untuk dibaca atau dipahami, saya membaginya menjadi beberapa ekspresi menggunakan variabel perantara. Saya tidak mengerti mengapa try() dengan sintaks panggilan fungsi akan berbeda dalam hal ini dalam praktiknya.

@eandre Biasanya, fungsi tidak memiliki definisi dinamis seperti itu. Banyak bentuk proposal ini mengurangi keamanan di sekitar komunikasi aliran kontrol, dan itu merepotkan.

@networkimprov @daved Saya tidak menyukai dua ide ini, tetapi mereka tidak merasa cukup sebagai peningkatan dibandingkan hanya mengizinkan satu baris if err != nil { ... } pernyataan untuk menjamin perubahan bahasa. Juga, apakah itu melakukan sesuatu untuk mengurangi boilerplate berulang dalam kasus di mana Anda hanya mengembalikan kesalahan? Atau apakah gagasan bahwa Anda selalu harus menulis return ?

@brynbellomy Dalam contoh saya, tidak ada return . relay adalah relay pelindung yang didefinisikan sebagai "jika kesalahan ini tidak nihil, berikut ini akan dikembalikan".

Menggunakan contoh kedua saya dari sebelumnya:

f, err := os.Open(path)
relay(err) nil, fmt.Errorf("Cannot open %s, due to %v", path, err)

Bisa juga seperti:

f, err := os.Open(path)
relay(err)

Dengan kesalahan yang membuat relai dikembalikan bersama dengan nilai nol untuk nilai pengembalian lainnya (atau nilai apa pun yang ditetapkan untuk nilai yang dikembalikan bernama). Bentuk lain yang mungkin berguna:

wrap := func(err error, msg string) error {
    if err != nil {
        fmt.Errorf("%s: %s", msg, err)
    }
    return nil
}

// ...

f, err := os.Open(path)
relay(err, wrap(err, fmt.Sprintf("Cannot open %s", path)))

Dimana rele kedua arg tidak dipanggil kecuali rele tersebut di trip oleh rele pertama arg. Arg kesalahan relai kedua opsional akan menjadi nilai yang dikembalikan.

Haruskah _go fmt_ mengizinkan satu baris if tetapi tidak case, for, else, var () ? Saya ingin semuanya, tolong ;-)

Tim Go telah menolak banyak permintaan untuk pemeriksaan kesalahan satu baris.

Pernyataan on err, return err bisa berulang, tetapi eksplisit, singkat, dan jelas.

@magical Umpan balik Anda telah dibahas dalam versi terbaru dari proposal terperinci .

Hal kecil, tetapi jika try adalah kata kunci, itu bisa dikenali sebagai pernyataan penghentian jadi alih-alih

func f() error {
  try(g())
  return nil
}

kamu hanya bisa melakukan

func f() error {
  try g()
}

( try -pernyataan mendapatkan itu secara gratis, try -operator akan membutuhkan penanganan khusus, saya menyadari di atas bukan contoh yang bagus: tetapi minimal)

@jimmyfrasche try dapat dikenali sebagai pernyataan penghentian meskipun itu bukan kata kunci - kami sudah melakukannya dengan panic , tidak ada penanganan khusus yang diperlukan selain apa yang sudah kami lakukan. Tetapi selain itu, try bukanlah pernyataan yang mengakhiri, dan mencoba membuatnya menjadi pernyataan yang dibuat-buat tampaknya aneh.

Semua poin yang valid. Saya kira itu hanya dapat dianggap sebagai pernyataan penghentian jika itu adalah baris terakhir dari suatu fungsi yang hanya mengembalikan kesalahan, seperti CopyFile dalam proposal terperinci, atau sedang digunakan sebagai try(err) dalam if di mana diketahui bahwa err != nil . Sepertinya tidak layak.

Karena utas ini semakin panjang dan sulit untuk diikuti (dan mulai berulang hingga tingkat tertentu), saya pikir kita semua akan setuju bahwa kita perlu berkompromi pada "beberapa keuntungan yang ditawarkan proposal.

Karena kami terus menyukai atau tidak menyukai permutasi kode yang diusulkan di atas, kami tidak membantu diri kami sendiri untuk memahami "apakah ini kompromi yang lebih masuk akal daripada yang lain/apa yang sudah ditawarkan"?

Saya pikir kita memerlukan beberapa kriteria objektif untuk menilai variasi "coba" dan proposal alternatif kita.

  • Apakah itu mengurangi boilerplate?
  • Keterbacaan
  • Kompleksitas ditambahkan ke bahasa
  • Standarisasi kesalahan
  • go-ish
    ...
    ...
  • upaya implementasi dan risiko
    ...

Kami tentu saja juga dapat menetapkan beberapa aturan dasar untuk larangan (tidak ada kompatibilitas mundur yang akan menjadi satu), dan meninggalkan area abu-abu untuk "apakah itu terlihat menarik / firasat dll (kriteria "keras" di atas juga bisa diperdebatkan.. .).

Jika kami menguji proposal apa pun terhadap daftar ini, dan menilai setiap poin (boilerplate 5 poin , keterbacaan 4 poin dll), maka saya pikir kami dapat menyelaraskan pada:
Pilihan kami mungkin A,B dan C, apalagi, seseorang yang ingin menambahkan proposal baru, dapat menguji (sampai tingkat tertentu) jika proposalnya memenuhi kriteria.

Jika ini masuk akal, acungkan jempol ini , kita dapat mencoba membahas proposal aslinya
https://github.com/golang/proposal/blob/master/design/32437-try-builtin.md

Dan mungkin beberapa proposal lain sejalan dengan komentar atau link, mungkin kita akan belajar sesuatu, atau bahkan datang dengan campuran yang akan menilai lebih tinggi.

Kriteria += penggunaan kembali kode penanganan kesalahan, lintas paket & dalam fungsi

Terima kasih semua orang untuk umpan balik lanjutan pada proposal ini.

Diskusi telah menyimpang sedikit dari masalah inti. Hal ini juga telah menjadi didominasi oleh selusin kontributor (Anda tahu siapa Anda) hashing apa jumlah proposal alternatif.

Jadi izinkan saya untuk mengingatkan bahwa masalah ini adalah tentang _spesifik_ proposal . Ini _bukan_ ajakan ide sintaksis baru untuk penanganan kesalahan (yang merupakan hal yang baik untuk dilakukan, tetapi ini bukan masalah _ini_).

Mari kita diskusikan lebih fokus lagi dan kembali ke jalurnya.

Umpan balik paling produktif jika membantu mengidentifikasi _fakta_ teknis yang kami lewatkan, seperti "proposal ini tidak berfungsi dengan baik dalam kasus ini" atau "ini akan memiliki implikasi yang tidak kami sadari".

Misalnya, @magical menunjukkan bahwa proposal yang ditulis tidak dapat diperluas seperti yang diklaim (teks aslinya tidak memungkinkan untuk menambahkan argumen ke-2 di masa mendatang). Untungnya ini adalah masalah kecil yang mudah diatasi dengan sedikit penyesuaian pada proposal. Masukannya secara langsung membantu membuat proposal menjadi lebih baik.

@crawshaw meluangkan waktu untuk menganalisis beberapa ratus kasus penggunaan dari perpustakaan std dan menunjukkan bahwa try jarang berakhir di dalam ekspresi lain, sehingga secara langsung menyangkal kekhawatiran bahwa try mungkin terkubur dan tidak terlihat. Itu adalah umpan balik berbasis fakta yang sangat berguna, dalam hal ini memvalidasi desain.

Sebaliknya, penilaian _estetika_ pribadi tidak terlalu membantu. Kami dapat mendaftarkan umpan balik itu, tetapi kami tidak dapat menindaklanjutinya (selain membuat proposal lain).

Mengenai membuat proposal alternatif: Proposal saat ini adalah buah dari banyak pekerjaan, dimulai dengan draf desain tahun lalu. Kami telah mengulangi desain itu beberapa kali dan meminta umpan balik dari banyak orang sebelum kami merasa cukup nyaman untuk mempostingnya dan merekomendasikan untuk memajukannya ke fase eksperimen yang sebenarnya, tetapi kami belum melakukan eksperimen tersebut. Masuk akal untuk kembali ke papan gambar jika eksperimen gagal, atau jika umpan balik memberi tahu kita sebelumnya bahwa itu jelas akan gagal. Jika kita mendesain ulang dengan cepat, berdasarkan kesan pertama, kita hanya membuang-buang waktu semua orang, dan lebih buruk lagi, tidak belajar apa pun dalam prosesnya.

Semua yang dikatakan, kekhawatiran paling signifikan yang disuarakan oleh banyak orang dengan proposal ini adalah bahwa proposal ini tidak secara eksplisit mendorong dekorasi kesalahan selain apa yang sudah dapat kita lakukan dalam bahasa. Terima kasih, kami telah mendaftarkan umpan balik itu. Kami telah menerima umpan balik yang sama secara internal, sebelum memposting proposal ini. Tetapi tidak ada alternatif yang kami pertimbangkan lebih baik daripada yang kami miliki sekarang (dan kami telah melihat banyak secara mendalam). Sebagai gantinya kami telah memutuskan untuk mengusulkan ide minimal yang membahas satu bagian dari penanganan kesalahan dengan baik, dan yang dapat diperluas jika perlu, tepat untuk mengatasi masalah ini (proposal berbicara panjang lebar tentang ini).

Terima kasih.

(Saya perhatikan bahwa beberapa orang yang mengadvokasi proposal alternatif telah memulai masalah mereka sendiri. Itu adalah hal yang baik untuk dilakukan dan membantu menjaga masalah masing-masing tetap fokus. Terima kasih.)

@griesemer
Saya sangat setuju kita harus fokus dan itulah yang membuat saya menulis:

Jika ini masuk akal, acungkan jempol ini , kita dapat mencoba membahas proposal aslinya
https://github.com/golang/proposal/blob/master/design/32437-try-builtin.md

Dua pertanyaan:

  1. Apakah Anda setuju jika kami menandai sisi positifnya (pengurangan boilerplate, keterbacaan, dll) vs kerugian (tidak ada dekorasi kesalahan eksplisit/kemampuan penelusuran yang lebih rendah dari sumber baris kesalahan) kami sebenarnya dapat menyatakan: proposal ini sangat bertujuan untuk menyelesaikan a,b , agak bantuan c, tidak bertujuan untuk menyelesaikan d,e
    Dan dengan itu hilangkan semua kekacauan "tetapi tidak" , "bagaimana bisa" dan dapatkan lebih banyak masalah teknis seperti yang ditunjukkan @magical
    Dan juga tidak mendorong komentar "tetapi solusi XXX memecahkan d,e lebih baik.
  2. banyak posting sebaris adalah "saran untuk perubahan kecil dalam proposal" - Saya tahu ini garis yang bagus, tapi saya pikir masuk akal untuk menyimpannya.

LMKWYT.

Apakah menggunakan try() dengan argumen nol (atau bawaan yang berbeda) masih dipertimbangkan atau ini telah dikesampingkan.

Setelah perubahan pada proposal, saya masih khawatir bagaimana hal itu membuat penggunaan nilai pengembalian bernama lebih "umum". Namun saya tidak memiliki data untuk mendukungnya :upside_down_ face:.
Jika try() dengan argumen nol (atau bawaan yang berbeda) ditambahkan ke proposal, dapatkah contoh dalam proposal diperbarui untuk menggunakan try() (atau bawaan yang berbeda) untuk menghindari pengembalian bernama?

@guybrand Upvoting dan down-voting adalah hal yang baik untuk mengekspresikan _sentiment_ - tapi itu saja. Tidak ada informasi lebih lanjut di sana. Kami tidak akan membuat keputusan berdasarkan penghitungan suara, yaitu sentimen saja. Tentu saja, jika semua orang - katakanlah 90%+ - membenci proposal, itu mungkin pertanda buruk dan kita harus berpikir dua kali sebelum melanjutkan. Namun hal itu tampaknya tidak berlaku di sini. Sejumlah besar orang tampaknya senang dengan mencoba berbagai hal, dan telah beralih ke hal-hal lain (dan tidak perlu repot-repot mengomentari utas ini).

Seperti yang saya coba ungkapkan di atas , sentimen pada tahap proposal ini tidak didasarkan pada pengalaman nyata apa pun dengan fitur tersebut; itu adalah perasaan. Perasaan cenderung berubah dari waktu ke waktu, terutama ketika seseorang memiliki kesempatan untuk benar-benar mengalami subjek perasaan itu... :-)

@Goodwine Tidak ada yang mengesampingkan try() untuk mendapatkan nilai kesalahan; meskipun _if_ sesuatu seperti ini diperlukan, mungkin lebih baik untuk memiliki variabel err yang telah dideklarasikan sebelumnya seperti yang disarankan @rogpeppe (saya pikir).

Sekali lagi, proposal ini tidak mengesampingkan semua ini. Ayo pergi ke sana jika kita tahu itu perlu.

@griesemer
Saya pikir Anda benar-benar salah paham.
Saya tidak mencari suara naik/turun memilih ini atau proposal apa pun, saya hanya mencari cara untuk mendapatkan pemahaman yang baik tentang "Apakah menurut kami masuk akal untuk mengambil keputusan berdasarkan kriteria keras daripada 'Saya suka x ' atau 'kamu tidak terlihat cantik'"

Dari apa yang Anda tulis - itulah yang Anda pikirkan ... jadi tolong upvote komentar saya seperti mengatakan:

"Saya pikir kita harus membuat daftar tentang apa yang ingin ditingkatkan oleh proposal ini, dan berdasarkan itu kita bisa
A. putuskan apakah itu cukup berarti
B. memutuskan apakah proposal benar-benar menyelesaikan apa yang ingin dipecahkannya
C. (seperti yang Anda tambahkan) lakukan upaya ekstra untuk melihat apakah itu layak...

@guybrand mereka jelas yakin itu layak untuk membuat prototipe di 1.14(?) pra-rilis dan mengumpulkan umpan balik dari pengguna langsung. IOW keputusan telah dibuat.

Juga, mengajukan #32611 untuk diskusi tentang on err, <statement>

@guybrand Mohon maaf. Ya, saya setuju kita perlu melihat berbagai properti proposal, seperti pengurangan boilerplate, apakah itu menyelesaikan masalah yang dihadapi, dll. Tapi proposal lebih dari jumlah bagian-bagiannya - pada akhirnya kita perlu melihat gambaran keseluruhan. Ini adalah rekayasa, dan rekayasa itu berantakan: Ada banyak faktor yang berperan dalam sebuah desain, dan bahkan jika secara objektif (berdasarkan kriteria yang sulit) sebagian dari sebuah desain tidak memuaskan, itu mungkin masih merupakan desain yang "benar" secara keseluruhan. Jadi saya agak ragu-ragu untuk mendukung keputusan berdasarkan semacam penilaian _independen_ dari aspek individual proposal.

(Semoga ini membahas lebih baik apa yang Anda maksud.)

Tetapi mengenai kriteria yang relevan, saya yakin proposal ini memperjelas apa yang coba ditangani. Artinya, daftar yang Anda maksud sudah ada:

..., tujuan kami adalah membuat penanganan kesalahan lebih ringan dengan mengurangi jumlah kode sumber yang didedikasikan hanya untuk pemeriksaan kesalahan. Kami juga ingin membuatnya lebih nyaman untuk menulis kode penanganan kesalahan, untuk meningkatkan kemungkinan pemrogram akan meluangkan waktu untuk melakukannya. Pada saat yang sama kami ingin menjaga kode penanganan kesalahan terlihat secara eksplisit dalam teks program.

Kebetulan untuk dekorasi kesalahan kami menyarankan untuk menggunakan defer dan bernama parameter hasil (atau pernyataan if Anda yang lama) karena itu tidak memerlukan perubahan bahasa - yang merupakan hal yang fantastis karena perubahan bahasa memiliki biaya tersembunyi yang sangat besar. Kami mendapatkan banyak komentator merasa bahwa bagian dari desain ini "benar-benar menyebalkan". Namun, pada titik ini, dalam gambaran keseluruhan, dengan semua yang kami tahu, kami pikir itu mungkin cukup baik. Di sisi lain, kita memerlukan perubahan bahasa - dukungan bahasa, lebih tepatnya - untuk menghilangkan boilerplate, dan try adalah perubahan minimal yang bisa kita lakukan. Dan yang jelas, semuanya masih eksplisit dalam kode.

Saya akan mengatakan bahwa alasan mengapa ada begitu banyak reaksi dan begitu banyak proposal mini adalah karena ini adalah masalah di mana hampir semua orang setuju bahwa bahasa Go perlu melakukan sesuatu untuk mengurangi boilerplate penanganan kesalahan, tetapi kami tidak benar-benar setuju bagaimana melakukannya.

Proposal ini, pada dasarnya, bermuara pada "makro" bawaan untuk kasus boilerplate yang sangat umum namun spesifik, seperti fungsi append() bawaan. Jadi, meskipun ini berguna untuk kasus penggunaan id err!=nil { return err } tertentu, hanya itu yang dilakukannya. Karena itu tidak terlalu membantu dalam kasus lain, atau benar-benar berlaku secara umum, saya akan mengatakan itu mengecewakan. Saya merasa bahwa sebagian besar programmer Go mengharapkan lebih banyak, dan diskusi di utas ini terus berlanjut.

Ini kontra intuitif sebagai fungsi. Karena tidak mungkin di Go memiliki fungsi dengan urutan argumen ini func(... interface{}, error) .
Diketik terlebih dahulu kemudian nomor variabel dari pola apa pun ada di mana-mana di modul Go.

Semakin saya berpikir, saya menyukai proposal saat ini, apa adanya.

Jika kita membutuhkan penanganan kesalahan, kita selalu memiliki pernyataan if.

Halo semuanya. Terima kasih atas diskusi yang tenang, penuh hormat, dan konstruktif selama ini. Saya menghabiskan beberapa waktu membuat catatan dan akhirnya cukup frustrasi sehingga saya membuat program untuk membantu saya mempertahankan pandangan berbeda dari utas komentar ini yang seharusnya lebih mudah dinavigasi dan lengkap daripada yang ditunjukkan GitHub. (Ini juga memuat lebih cepat!) Lihat https://swtch.com/try.html. Saya akan terus memperbaruinya tetapi dalam batch, bukan menit demi menit. (Ini adalah diskusi yang membutuhkan pemikiran yang matang dan tidak terbantu oleh "waktu internet".)

Saya memiliki beberapa pemikiran untuk ditambahkan, tetapi itu mungkin harus menunggu hingga Senin. Terima kasih lagi.

@mishak87 Kami membahas ini dalam proposal terperinci . Perhatikan bahwa kami memiliki built-in lain ( try , make , unsafe.Offsetof , dll.) yang "tidak teratur" - itulah gunanya built-in.

@rsc , sangat berguna! Jika Anda masih merevisinya, mungkin menghubungkan referensi masalah #id? Dan font-style sans-serif?

Ini mungkin telah dibahas sebelumnya jadi saya minta maaf karena menambahkan lebih banyak suara tetapi hanya ingin menjelaskan tentang try builtin vs the try ... ide lain.

Saya pikir mencoba fungsi bawaan bisa sedikit membuat frustrasi selama pengembangan. Terkadang kami mungkin ingin menambahkan simbol debug atau menambahkan lebih banyak konteks spesifik kesalahan sebelum kembali. Seseorang harus menulis ulang baris seperti

user := try(getUser(userID))

ke

user, err := getUser(userID)
if err != nil {  
    // inspect error here
    return err
}

Menambahkan pernyataan penangguhan dapat membantu tetapi itu masih bukan pengalaman terbaik ketika suatu fungsi memunculkan banyak kesalahan karena akan memicu untuk setiap panggilan try().

Menulis ulang beberapa panggilan try() bersarang dalam fungsi yang sama akan lebih menyebalkan.

Di sisi lain, menambahkan konteks atau kode inspeksi ke

user := try getUser(userID)

akan sesederhana menambahkan pernyataan catch di akhir diikuti oleh kode

user := try getUser(userID) catch {
   // inspect error here
}

Menghapus atau menonaktifkan sementara handler akan sesederhana memutus garis sebelum menangkap dan mengomentarinya.

Beralih antara try() dan if err != nil terasa lebih mengganggu IMO.

Ini juga berlaku untuk menambah atau menghapus konteks kesalahan. Seseorang dapat menulis try func() sambil membuat prototipe sesuatu dengan sangat cepat dan kemudian menambahkan konteks ke kesalahan tertentu sesuai kebutuhan saat program matang sebagai lawan dari try() sebagai bawaan di mana seseorang harus menulis ulang baris untuk menambahkan konteks atau menambahkan kode inspeksi tambahan selama debugging.

Saya yakin try() akan berguna tetapi ketika saya membayangkan menggunakannya dalam pekerjaan saya sehari-hari, saya tidak bisa tidak membayangkan bagaimana try ... catch akan jauh lebih membantu dan tidak terlalu mengganggu ketika saya' d perlu menambah/menghapus kode tambahan khusus untuk beberapa kesalahan.


Juga, saya merasa bahwa menambahkan try() dan kemudian merekomendasikan untuk menggunakan if err != nil untuk menambahkan konteks sangat mirip dengan memiliki make() vs new() vs := vs var . Fitur-fitur ini berguna dalam skenario yang berbeda tetapi bukankah lebih baik jika kita memiliki lebih sedikit cara atau bahkan satu cara untuk menginisialisasi variabel? Tentu saja tidak ada yang memaksa siapa pun untuk menggunakan try dan orang-orang dapat terus menggunakan if err != nil tapi saya merasa ini akan membagi penanganan kesalahan di Go seperti beberapa cara untuk menetapkan variabel baru. Saya pikir metode apa pun yang ditambahkan ke bahasa juga harus menyediakan cara untuk dengan mudah menambah/menghapus penangan kesalahan alih-alih memaksa orang untuk menulis ulang seluruh baris untuk menambah/menghapus penangan. Itu tidak terasa seperti hasil yang baik bagi saya.

Maaf sekali lagi atas kebisingannya tetapi ingin menunjukkannya jika seseorang ingin menulis proposal terperinci terpisah untuk ide try ... else .

//cc @brynbellomy

Terima kasih, @owais , untuk mengangkat ini lagi - ini adalah poin yang adil (dan masalah debug memang telah disebutkan sebelumnya ). try tidak membiarkan pintu terbuka untuk ekstensi, seperti argumen ke-2, yang bisa menjadi fungsi handler. Tetapi memang benar bahwa fungsi try tidak membuat debugging lebih mudah - seseorang mungkin harus menulis ulang kode lebih dari try - catch atau try - else .

@owais

Menambahkan pernyataan penangguhan dapat membantu tetapi itu masih bukan pengalaman terbaik ketika suatu fungsi memunculkan banyak kesalahan karena akan memicu untuk setiap panggilan try().

Anda selalu dapat menyertakan sakelar tipe dalam fungsi yang ditangguhkan yang akan menangani (atau tidak) berbagai jenis kesalahan dengan cara yang sesuai sebelum kembali.

Mengingat diskusi sejauh ini — khususnya tanggapan dari tim Go — saya mendapat kesan kuat bahwa tim berencana untuk bergerak maju dengan proposal yang ada di atas meja. Jika ya, maka komentar dan permintaan:

  1. IMO proposal apa adanya akan menghasilkan pengurangan kualitas kode yang tidak signifikan dalam repo yang tersedia untuk umum. Harapan saya adalah banyak pengembang akan mengambil jalan yang paling sedikit resistensinya, secara efektif menggunakan teknik penanganan pengecualian dan memilih untuk menggunakan try() daripada menangani kesalahan pada saat kesalahan itu terjadi. Tetapi mengingat sentimen yang berlaku di utas ini, saya menyadari bahwa setiap kemegahan sekarang hanya akan kalah dalam pertempuran, jadi saya hanya mengajukan keberatan saya untuk anak cucu.

  2. Dengan asumsi bahwa tim bergerak maju dengan proposal seperti yang ditulis saat ini, dapatkah Anda menambahkan sakelar kompiler yang akan menonaktifkan try() bagi mereka yang tidak menginginkan kode apa pun yang mengabaikan kesalahan dengan cara ini dan untuk melarang pemrogram yang mereka pekerjakan dari menggunakannya? _(melalui CI, tentu saja.)_ Terima kasih sebelumnya atas pertimbangan ini.

dapatkah Anda menambahkan sakelar kompiler yang akan menonaktifkan try()

Ini harus pada alat linting, bukan pada kompiler IMO, tapi saya setuju

Ini harus pada alat linting, bukan pada kompiler IMO, tapi saya setuju

Saya secara eksplisit meminta opsi kompiler dan bukan alat linting karena melarang kompilasi seperti opsi. Jika tidak, akan terlalu mudah untuk _"melupakan"_ serat selama pengembangan lokal.

@mikeschinkel Bukankah akan semudah itu untuk lupa mengaktifkan opsi kompiler dalam situasi itu?

Bendera kompiler tidak boleh mengubah spesifikasi bahasa. Ini jauh lebih cocok untuk vet/lint

Bukankah akan semudah itu untuk lupa mengaktifkan opsi kompiler dalam situasi itu?

Tidak saat menggunakan alat seperti GoLand di mana tidak ada cara untuk memaksa lint dijalankan sebelum kompilasi.

Bendera kompiler tidak boleh mengubah spesifikasi bahasa.

-nolocalimports mengubah spesifikasi, dan -s memperingatkan.

Bendera kompiler tidak boleh mengubah spesifikasi bahasa.

-nolocalimports mengubah spesifikasi, dan -s memperingatkan.

Tidak, itu tidak mengubah spesifikasi. Tata bahasa tidak hanya tetap sama, tetapi spesifikasinya secara khusus menyatakan:

Interpretasi ImportPath bergantung pada implementasi tetapi biasanya merupakan substring dari nama file lengkap dari paket yang dikompilasi dan mungkin relatif terhadap repositori paket yang diinstal.

Tidak saat menggunakan alat seperti GoLand di mana tidak ada cara untuk memaksa lint dijalankan sebelum kompilasi.

https://github.com/vmware/dispatch/wiki/Configure-GoLand-with-golint

@deanveloper

https://github.com/vmware/dispatch/wiki/Configure-GoLand-with-golint

Tentu saja itu ada, tetapi Anda membandingkan apel dengan organ. Apa yang Anda tunjukkan adalah file watcher yang berjalan pada file yang berubah dan karena GoLand menyimpan file secara otomatis, itu berarti ia berjalan terus-menerus yang menghasilkan lebih banyak noise daripada sinyal.

Serat selalu tidak dan tidak dapat (AFAIK) dikonfigurasi sebagai prasyarat untuk menjalankan kompiler:

image

Tidak, itu tidak mengubah spesifikasi. Tata bahasa tidak hanya tetap sama, tetapi spesifikasinya secara khusus menyatakan:

Anda bermain dengan semantik di sini alih-alih berfokus pada hasilnya. Jadi saya akan melakukan hal yang sama.

Saya meminta agar opsi kompiler ditambahkan yang akan melarang kompilasi kode dengan try() . Itu bukan permintaan untuk mengubah spesifikasi bahasa, itu hanya permintaan agar kompiler berhenti dalam kasus khusus ini.

Dan jika itu membantu, spesifikasi bahasa dapat diperbarui untuk mengatakan sesuatu seperti:

Interpretasi try() bergantung pada implementasi tetapi biasanya yang memicu pengembalian ketika parameter terakhir adalah kesalahan namun dapat diimplementasikan untuk tidak diizinkan.

Waktu untuk meminta sakelar kompiler atau pemeriksaan dokter hewan adalah setelah prototipe try() mendarat di tip 1.14(?). Pada saat itu Anda akan mengajukan masalah baru untuk itu (dan ya saya pikir itu ide yang bagus). Kami telah diminta untuk membatasi komentar di sini untuk masukan faktual tentang dokumen desain saat ini.

Hai, jadi hanya untuk menambahkan ke seluruh masalah dengan menambahkan pernyataan debug dan semacamnya selama pengembangan.
Saya pikir ide parameter kedua baik-baik saja untuk fungsi try() , tetapi ide lain untuk membuangnya adalah dengan menambahkan klausa emit menjadi bagian kedua untuk try() .

Misalnya, saya percaya ketika mengembangkan dan semacamnya mungkin ada kasus ketika saya ingin memanggil fmt untuk instan ini untuk mencetak kesalahan. Jadi saya bisa pergi dari ini:

func writeStuff(filename string) (io.ReadCloser, error) {
    f := try(os.Open(filename))
    try(fmt.Fprintf(f, "stuff\n"))

    return f, nil
}

Dapat ditulis ulang menjadi sesuatu seperti ini untuk pernyataan debug atau penanganan umum atau kesalahan sebelum kembali.

func writeStuff(filename string) (io.ReadCloser, error) {
    emit err {
        fmt.Printf("something happened [%v]\n", err.Error())
        return nil, err
    }

    f := try(os.Open(filename))
    try(fmt.Fprintf(f, "stuff\n"))

    return f, nil
}

Jadi di sini saya akhirnya mengajukan proposal untuk kata kunci baru emit yang bisa berupa pernyataan atau satu kalimat untuk segera kembali seperti fungsi try() awal:

emit return nil, err

Apa yang dipancarkan pada dasarnya hanyalah sebuah klausa di mana Anda dapat meletakkan logika apa pun yang Anda inginkan di dalamnya jika try() dipicu oleh kesalahan yang tidak sama dengan nil. Kemampuan lain dengan kata kunci emit adalah Anda dapat mengakses kesalahan di sana jika Anda menambahkan tepat setelah kata kunci nama variabel seperti yang saya lakukan pada contoh pertama menggunakannya.

Proposal ini memang membuat sedikit verbositas pada fungsi try() , tapi saya pikir setidaknya sedikit lebih jelas tentang apa yang terjadi dengan kesalahan. Dengan cara ini Anda dapat menghias kesalahan juga tanpa harus macet semua menjadi satu baris dan Anda dapat melihat bagaimana kesalahan ditangani segera saat Anda membaca fungsinya.

Ini adalah tanggapan untuk @mikeschinkel , saya menempatkan tanggapan saya di blok detail sehingga saya tidak terlalu mengacaukan diskusi. Either way, @networkimprov benar bahwa diskusi ini harus diajukan sampai setelah proposal ini diimplementasikan (jika ya).

detail tentang bendera untuk menonaktifkan try
@mikeschinkel

Serat selalu tidak dan tidak dapat (AFAIK) dikonfigurasi sebagai prasyarat untuk menjalankan kompiler:

Instal ulang GoLand hanya untuk menguji ini. Ini tampaknya berfungsi dengan baik, satu-satunya perbedaan adalah jika lint menemukan sesuatu yang tidak disukainya, itu tidak akan gagal dalam kompilasi. Itu dapat dengan mudah diperbaiki dengan skrip khusus, yang menjalankan golint dan gagal dengan kode keluar bukan nol jika ada keluaran.
image

(Sunting: Saya memperbaiki kesalahan yang coba diberitahukan kepada saya di bagian bawah. Itu berjalan dengan baik bahkan ketika kesalahan itu ada, tetapi mengubah "Jalankan Jenis" ke direktori menghapus kesalahan dan itu berfungsi dengan baik)

Juga alasan lain mengapa itu TIDAK boleh menjadi flag compiler - semua kode Go dikompilasi dari sumber. Itu termasuk perpustakaan. Itu berarti bahwa jika Anda ingin mengaktifkan try melalui kompilator, Anda akan mematikan try untuk setiap perpustakaan yang Anda gunakan juga. Itu hanya ide yang buruk untuk menjadikannya sebagai flag compiler.

Anda bermain dengan semantik di sini alih-alih berfokus pada hasilnya.

Tidak. Bendera kompiler tidak boleh mengubah spesifikasi bahasa. Spesifikasinya ditata dengan sangat baik dan agar sesuatu menjadi "Pergi", itu harus mengikuti spesifikasinya. Bendera kompiler yang Anda sebutkan memang mengubah perilaku bahasa, tetapi apa pun yang terjadi, mereka memastikan bahasa masih mengikuti spesifikasi. Ini adalah aspek penting dari Go. Selama Anda mengikuti spesifikasi Go, kode Anda harus dikompilasi pada kompiler Go mana pun.

Saya meminta agar opsi kompiler ditambahkan yang akan melarang kompilasi kode dengan try(). Itu bukan permintaan untuk mengubah spesifikasi bahasa, itu hanya permintaan agar kompiler berhenti dalam kasus khusus ini.

Ini adalah permintaan untuk mengubah spesifikasi. Proposal ini sendiri merupakan permintaan untuk mengubah spesifikasi. Fungsi bawaan sangat khusus disertakan dalam spesifikasi. . Meminta memiliki flag kompiler yang menghapus try builtin karenanya akan menjadi flag kompiler yang akan mengubah spesifikasi bahasa yang sedang dikompilasi.

Karena itu, saya pikir ImportPath harus distandarisasi dalam spesifikasi. Saya dapat membuat proposal untuk ini.

Dan jika itu membantu, spesifikasi bahasa dapat diperbarui untuk mengatakan sesuatu seperti [...]

Meskipun ini benar, Anda tidak ingin implementasi try bergantung pada implementasi. Itu dibuat menjadi bagian penting dari penanganan kesalahan bahasa, yang merupakan sesuatu yang harus sama di setiap kompiler Go.

@deanveloper

_"Bagaimanapun, @networkimprov benar bahwa diskusi ini harus diajukan sampai setelah proposal ini diimplementasikan (jika ya)."_

Lalu mengapa Anda memutuskan untuk mengabaikan saran itu dan memposting di utas ini alih-alih menunggu nanti? Anda memperdebatkan poin Anda di sini sementara pada saat yang sama menegaskan bahwa saya tidak boleh menantang poin Anda. Lakukanlah apa yang kamu khotbahkan...

Diberi pilihan Anda, saya akan memilih untuk merespons juga, juga dalam blok detail

di sini:

_"Itu dapat dengan mudah diperbaiki dengan skrip khusus, yang menjalankan golint dan gagal dengan kode keluar bukan nol jika ada keluaran."_

Ya, dengan coding yang cukup masalah _any_ bisa diperbaiki. Tetapi kita berdua tahu dari pengalaman bahwa semakin kompleks suatu solusi, semakin sedikit orang yang ingin menggunakannya yang akan benar-benar menggunakannya.

Jadi saya secara eksplisit meminta solusi sederhana di sini, bukan solusi roll-your-own.

_"Anda akan mematikan try untuk setiap perpustakaan yang Anda gunakan juga."_

Dan itulah _secara eksplisit_ alasan mengapa saya memintanya. Karena saya ingin memastikan bahwa semua kode yang menggunakan _"fitur"_ yang merepotkan ini tidak akan masuk ke executable yang kami distribusikan.

_"Ini adalah permintaan untuk mengubah spesifikasi. Proposal ini sendiri adalah permintaan untuk mengubah spesifikasi._"

Ini BENAR- BENAR bukan perubahan spesifikasi. Ini adalah permintaan switch untuk mengubah _behavior_ dari perintah build , bukan perubahan dalam spesifikasi bahasa.

Jika seseorang meminta perintah go untuk memiliki sakelar untuk menampilkan keluaran terminalnya dalam bahasa Mandarin, itu bukan perubahan pada spesifikasi bahasa.

Demikian pula jika go build melihat sakelar ini maka itu hanya akan mengeluarkan pesan kesalahan dan berhenti ketika menemukan try() . Tidak diperlukan perubahan spesifikasi bahasa.

_"Itu dibuat menjadi bagian penting dari penanganan kesalahan bahasa, yang merupakan sesuatu yang harus sama di setiap kompiler Go."_

Ini akan menjadi bagian bermasalah dari penanganan kesalahan bahasa dan menjadikannya opsional akan memungkinkan mereka yang ingin menghindari masalahnya dapat melakukannya.

Tanpa sakelar kemungkinan besar orang hanya akan melihat sebagai fitur baru dan menerimanya dan tidak pernah bertanya pada diri sendiri apakah itu harus digunakan.

_Dengan sakelar_ — dan artikel yang menjelaskan fitur baru yang menyebutkan sakelar — banyak orang akan memahami bahwa itu memiliki potensi bermasalah dan dengan demikian akan memungkinkan tim Go untuk mempelajari apakah itu penyertaan yang baik atau tidak dengan melihat seberapa banyak kode publik menghindari menggunakannya vs. bagaimana kode publik menggunakannya. Itu bisa menginformasikan desain Go 3.

_"Tidak, saya tidak. Bendera kompiler tidak boleh mengubah spesifikasi bahasa."_

Mengatakan Anda tidak bermain semantik tidak berarti Anda tidak bermain semantik.

Bagus. Kemudian saya meminta perintah tingkat atas baru yang disebut _(sesuatu seperti)_ build-guard digunakan untuk melarang fitur bermasalah selama kompilasi, dimulai dengan melarang try() .

Tentu saja hasil terbaik adalah jika fitur try() diajukan dengan rencana untuk mempertimbangkan kembali pemecahan masalah dengan cara yang berbeda di masa depan, cara yang sebagian besar setuju. Tapi saya khawatir kapal sudah berlayar dengan try() jadi saya berharap untuk meminimalkan kerugiannya.


Jadi sekarang jika Anda benar-benar setuju dengan @networkimprov maka tahan balasan Anda sampai nanti, seperti yang mereka sarankan.

Maaf mengganggu, tapi saya punya fakta untuk dilaporkan :-)

Saya yakin tim Go sudah melakukan benchmark penangguhan, tapi saya belum melihat angka apapun...

$ go test -bench=.
goos: linux
goarch: amd64
BenchmarkAlways2-2      20000000                72.3 ns/op
BenchmarkAlways4-2      20000000                68.1 ns/op
BenchmarkAlways6-2      20000000                68.0 ns/op

BenchmarkNever2-2       100000000               16.5 ns/op
BenchmarkNever4-2       100000000               13.1 ns/op
BenchmarkNever6-2       100000000               13.5 ns/op

Sumber

package deferbench

import (
   "fmt"
   "errors"
   "testing"
)

func Always(iM, iN int) (err error) {
   defer func() {
      if err != nil {
         err = fmt.Errorf("d: %v", err)
      }
   }()
   if iN % iM == 0 {
      return errors.New("e")
   }
   return nil
}

func Never(iM, iN int) (err error) {
   if iN % iM == 0 {
      return fmt.Errorf("r: %v", errors.New("e"))
   }
   return nil
}

func BenchmarkAlways2(iB *testing.B) { for a := 0; a < iB.N; a++ { Always(1e2, a) }}
func BenchmarkAlways4(iB *testing.B) { for a := 0; a < iB.N; a++ { Always(1e4, a) }}
func BenchmarkAlways6(iB *testing.B) { for a := 0; a < iB.N; a++ { Always(1e6, a) }}

func BenchmarkNever2(iB *testing.B) { for a := 0; a < iB.N; a++ { Never(1e2, a) }}
func BenchmarkNever4(iB *testing.B) { for a := 0; a < iB.N; a++ { Never(1e4, a) }}
func BenchmarkNever6(iB *testing.B) { for a := 0; a < iB.N; a++ { Never(1e6, a) }}

@networkimprov

Dari https://github.com/golang/proposal/blob/master/design/32437-try-builtin.md#efficiency -of-defer (penekanan saya dicetak tebal)

Secara independen, tim runtime dan compiler Go telah mendiskusikan opsi implementasi alternatif dan kami yakin bahwa kami dapat menggunakan penangguhan tipikal untuk penanganan kesalahan seefisien kode "manual" yang ada. Kami berharap untuk membuat implementasi penangguhan yang lebih cepat ini tersedia di Go 1.14 (lihat juga * CL 171758 * yang merupakan langkah pertama ke arah ini).

yaitu penangguhan sekarang 30% peningkatan kinerja untuk go1.13 untuk penggunaan umum, dan harus lebih cepat dan seefisien mode non-penundaan di go 1.14

Mungkin seseorang dapat memposting nomor untuk 1,13 dan 1,14 CL?

Pengoptimalan tidak selalu bertahan dari kontak dengan musuh... eh, ekosistem.

Penangguhan 1,13 akan menjadi sekitar 30% lebih cepat:

name     old time/op  new time/op  delta
Defer-4  52.2ns ± 5%  36.2ns ± 3%  -30.70%  (p=0.000 n=10+10)

Inilah yang saya dapatkan pada tes @networkimprov di atas (1.12.5 ke tip):

name       old time/op  new time/op  delta
Always2-4  59.8ns ± 1%  47.5ns ± 1%  -20.57%  (p=0.008 n=5+5)
Always4-4  57.9ns ± 2%  43.5ns ± 1%  -24.96%  (p=0.008 n=5+5)
Always6-4  57.6ns ± 2%  44.1ns ± 1%  -23.43%  (p=0.008 n=5+5)
Never2-4   13.7ns ± 8%   3.8ns ± 4%  -72.27%  (p=0.008 n=5+5)
Never4-4   10.5ns ± 6%   1.3ns ± 2%  -87.76%  (p=0.008 n=5+5)
Never6-4   10.8ns ± 6%   1.2ns ± 1%  -88.46%  (p=0.008 n=5+5)

(Saya tidak yakin mengapa Tidak pernah ada yang jauh lebih cepat. Mungkin perubahan sebaris?)

Optimalisasi untuk penangguhan untuk 1,14 belum diterapkan, jadi kami tidak tahu seperti apa kinerjanya nanti. Tapi kami pikir kami harus mendekati kinerja pemanggilan fungsi biasa.

Lalu mengapa Anda memutuskan untuk mengabaikan saran itu dan memposting di utas ini alih-alih menunggu nanti?

Blok detail diedit nanti, setelah saya membaca komentar @networkimprov . Aku minta maaf karena membuatnya terlihat seperti aku mengerti apa yang dia katakan dan mengabaikannya. Saya mengakhiri diskusi setelah pernyataan ini, saya ingin menjelaskan diri saya sendiri karena Anda telah bertanya kepada saya mengapa saya memposting komentar.


Mengenai pengoptimalan yang ditunda, saya senang dengan mereka. Mereka membantu proposal ini sedikit, membuat defer HandleErrorf(...) sedikit lebih ringan. Saya masih tidak suka gagasan menyalahgunakan parameter bernama agar trik ini berfungsi. Berapa yang diharapkan untuk dipercepat untuk 1,14? Haruskah mereka berlari dengan kecepatan yang sama?

@griesemer Salah satu area yang mungkin perlu dikembangkan lebih jauh adalah bagaimana transisi bekerja di dunia dengan try , mungkin termasuk:

  • Biaya transisi antara gaya dekorasi kesalahan.
  • Kelas kesalahan yang mungkin terjadi saat transisi antar gaya.
  • Kelas kesalahan apa yang akan (a) ditangkap segera oleh kesalahan kompiler, vs. (b) ditangkap oleh vet atau staticcheck atau serupa, vs. (c) dapat menyebabkan bug yang mungkin tidak diperhatikan atau perlu ditangkap melalui pengujian.
  • Sejauh mana perkakas dapat mengurangi biaya dan kemungkinan kesalahan saat transisi antar gaya, dan khususnya, apakah gopls (atau utilitas lain) dapat atau harus memiliki peran dalam mengotomatisasi transisi gaya dekorasi umum.

Tahapan dekorasi kesalahan

Ini tidak lengkap, tetapi serangkaian tahapan yang representatif dapat berupa:

0. Tidak ada dekorasi kesalahan (misalnya, menggunakan try tanpa dekorasi apa pun).
1. Dekorasi kesalahan seragam (misalnya, menggunakan try + defer untuk dekorasi seragam).
2. Titik keluar N-1 memiliki dekorasi kesalahan yang seragam , tetapi 1 titik keluar memiliki dekorasi yang berbeda (misalnya, mungkin dekorasi kesalahan terperinci yang permanen hanya di satu lokasi, atau mungkin log debug sementara, dll.).
3. Semua titik keluar masing-masing memiliki dekorasi kesalahan yang unik , atau sesuatu yang mendekati unik.

Setiap fungsi yang diberikan tidak akan memiliki kemajuan yang ketat melalui tahapan tersebut, jadi mungkin "tahapan" adalah kata yang salah, tetapi beberapa fungsi akan bertransisi dari satu gaya dekorasi ke gaya dekorasi lainnya, dan mungkin berguna untuk lebih eksplisit tentang transisi tersebut. seperti kapan atau jika itu terjadi.

Tahap 0 dan tahap 1 tampaknya menjadi titik manis untuk proposal saat ini, dan juga merupakan kasus penggunaan yang cukup umum. Transisi tahap 0->1 tampaknya mudah. Jika Anda menggunakan try tanpa dekorasi di tahap 0, Anda dapat menambahkan sesuatu seperti defer fmt.HandleErrorf(&err, "foo failed with %s", arg1) . Anda mungkin pada saat itu juga perlu memperkenalkan parameter pengembalian bernama di bawah proposal seperti yang tertulis di awal. Namun, jika proposal mengadopsi salah satu saran di sepanjang baris variabel bawaan yang telah ditentukan sebelumnya yaitu alias untuk parameter hasil kesalahan akhir, maka biaya dan risiko kesalahan di sini mungkin kecil?

Di sisi lain, transisi tahap 1->2 tampak canggung (atau "mengganggu" seperti yang dikatakan beberapa orang lain) jika tahap 1 adalah dekorasi kesalahan seragam dengan defer . Untuk menambahkan sedikit dekorasi tertentu pada satu titik keluar, pertama-tama Anda harus menghapus defer (untuk menghindari dekorasi ganda), maka tampaknya seseorang perlu mengunjungi semua titik kembali untuk menghilangkan try digunakan ke dalam pernyataan if , dengan N-1 kesalahan didekorasi dengan cara yang sama dan 1 dihias secara berbeda.

Transisi tahap 1->3 juga tampak canggung jika dilakukan secara manual.

Kesalahan saat beralih di antara gaya dekorasi

Beberapa kesalahan yang mungkin terjadi sebagai bagian dari proses desugaring manual termasuk secara tidak sengaja membayangi variabel, atau mengubah bagaimana parameter pengembalian bernama terpengaruh, dll. Misalnya, jika Anda melihat contoh pertama dan terbesar di bagian "Contoh" dari coba proposal, fungsi CopyFile memiliki 4 kegunaan try , termasuk di bagian ini:

        w := try(os.Create(dst))
        defer func() {
                w.Close()
                if err != nil {
                        os.Remove(dst) // only if a “try” fails
                }
        }()

Jika seseorang melakukan desugaring manual "jelas" dari w := try(os.Create(dst)) , satu baris itu dapat diperluas ke:

        w, err := os.Create(dst)
        if err != nil {
            // do something here
            return err
        }

Itu terlihat bagus pada pandangan pertama, tetapi tergantung pada blok apa perubahan itu berada, itu juga dapat secara tidak sengaja membayangi parameter pengembalian bernama err dan mematahkan penanganan kesalahan di defer berikutnya.

Mengotomatiskan transisi antara gaya dekorasi

Untuk membantu dengan biaya waktu dan risiko kesalahan, mungkin gopls (atau utilitas lain) dapat memiliki beberapa jenis perintah untuk mendesugar try tertentu, atau perintah desugar semua penggunaan try dalam fungsi tertentu yang dapat 100% bebas dari kesalahan. Satu pendekatan mungkin gopls perintah apa pun yang hanya fokus pada penghapusan dan penggantian try , tetapi mungkin perintah yang berbeda dapat menghilangkan semua penggunaan try sambil juga mengubah setidaknya kasus umum seperti defer fmt.HandleErrorf(&err, "copy %s %s", src, dst) di bagian atas fungsi ke dalam kode yang setara di setiap lokasi try sebelumnya (yang akan membantu saat transisi dari tahap 1->2 atau tahap 1->3). Itu bukan ide yang matang, tetapi mungkin lebih berharga untuk dipikirkan tentang apa yang mungkin atau diinginkan atau memperbarui proposal dengan pemikiran saat ini.

Hasil idiomatis?

Komentar terkait yang tidak segera terlihat adalah seberapa sering transformasi bebas kesalahan program dari try akan berakhir seperti kode Go idiomatik normal. Mengadaptasi salah satu contoh dari proposal, jika misalnya Anda ingin desugar:

x1, x2, x3 = try(f())

Dalam beberapa kasus, transformasi terprogram yang mempertahankan perilaku dapat berakhir dengan sesuatu seperti:

t1, t2, t3, te := f()  // visible temporaries
if te != nil {
        return x1, x2, x3, te
}
x1, x2, x3 = t1, t2, t3

Bentuk persisnya mungkin jarang, dan tampaknya hasil dari editor atau IDE yang melakukan desugaring terprogram sering kali terlihat lebih idiomatis, tetapi akan menarik untuk mendengar betapa benarnya hal itu, termasuk dalam menghadapi parameter pengembalian bernama yang mungkin menjadi lebih umum, dan dengan mempertimbangkan bayangan, := vs = , kegunaan lain dari err dalam fungsi yang sama, dll.

Proposal berbicara tentang kemungkinan perbedaan perilaku antara if dan try karena parameter hasil bernama, tetapi di bagian tertentu itu tampaknya berbicara terutama tentang transisi dari if ke try (di bagian yang menyimpulkan _"Meskipun ini adalah perbedaan tipis, kami yakin kasus seperti ini jarang terjadi. Jika perilaku saat ini diharapkan, pertahankan pernyataan if."_). Sebaliknya, mungkin ada kemungkinan kesalahan berbeda yang perlu dijelaskan saat transisi dari try kembali ke if sambil mempertahankan perilaku yang identik.


Bagaimanapun, maaf atas komentar yang panjang, tetapi tampaknya ketakutan akan biaya transisi yang tinggi antara gaya mendasari beberapa kekhawatiran yang diungkapkan dalam beberapa komentar lain yang diposting di sini, dan karenanya saran untuk lebih eksplisit tentang biaya transisi tersebut dan mitigasi potensial.

@thepudds Saya suka Anda menyoroti biaya dan potensi bug yang terkait dengan bagaimana fitur bahasa dapat memengaruhi refactoring secara positif atau negatif. Ini bukan topik yang sering saya lihat, tetapi topik yang dapat memiliki efek hilir yang besar.

transisi tahap 1->2 tampak canggung jika tahap 1 adalah dekorasi kesalahan yang seragam dengan penundaan. Untuk menambahkan sedikit dekorasi tertentu pada satu titik keluar, pertama-tama Anda perlu menghapus penangguhan (untuk menghindari dekorasi ganda), maka tampaknya seseorang perlu mengunjungi semua titik kembali untuk menghapus penggunaan percobaan ke dalam pernyataan if, dengan N -1 kesalahan didekorasi dengan cara yang sama dan 1 kesalahan didekorasi secara berbeda.

Di sinilah menggunakan break alih-alih return bersinar dengan 1,12. Gunakan di blok for range once { ... } di mana once = "1" untuk membatasi urutan kode yang mungkin ingin Anda keluarkan dan kemudian jika Anda perlu mendekorasi hanya satu kesalahan, Anda melakukannya pada titik break . Dan jika Anda perlu mendekorasi semua kesalahan, Anda melakukannya tepat sebelum satu-satunya return di akhir metode.

Alasan mengapa pola ini bagus adalah karena tahan terhadap perubahan persyaratan; Anda jarang harus memecahkan kode kerja untuk menerapkan persyaratan baru. Dan ini adalah pendekatan IMO yang lebih bersih dan lebih jelas daripada melompat kembali ke awal metode sebelum kemudian melompat keluar darinya.

fwiw

Hasil @randall77 untuk benchmark saya menunjukkan 40+ns per panggilan overhead untuk 1,12 & tip. Itu menyiratkan bahwa penundaan dapat menghambat pengoptimalan, memberikan peningkatan untuk menunda diperdebatkan dalam beberapa kasus.

@networkimprov Defer saat ini menghambat pengoptimalan, dan itulah bagian yang ingin kami perbaiki. Misalnya, akan lebih baik untuk menyejajarkan tubuh fungsi penangguhan seperti halnya kita membuat panggilan biasa.

Saya gagal untuk melihat bagaimana setiap perbaikan yang kami buat akan diperdebatkan. Dari mana pernyataan itu berasal?

Dari mana pernyataan itu berasal?

40+ns per panggilan overhead untuk fungsi dengan penundaan untuk membungkus kesalahan tidak berubah.

Perubahan pada 1.13 adalah salah satu bagian dari pengoptimalan penangguhan. Ada perbaikan lain yang direncanakan. Ini tercakup dalam dokumen desain, dan di bagian dokumen desain yang dikutip di beberapa poin di atas.

Re swtch.com/try.html dan https://github.com/golang/go/issues/32437#issuecomment -502192315:

@rsc , sangat berguna! Jika Anda masih merevisinya, mungkin menghubungkan referensi masalah #id? Dan font-style sans-serif?

Halaman itu tentang konten. Jangan fokus pada detail rendering. Saya menggunakan output blackfriday pada penurunan harga input yang tidak diubah (jadi tidak ada tautan #id khusus GitHub), dan saya senang dengan font serif.

Nonaktifkan kembali/pemeriksaan coba :

Maaf, tetapi tidak akan ada opsi kompiler untuk menonaktifkan fitur Go tertentu, juga tidak akan ada pemeriksaan dokter hewan yang mengatakan untuk tidak menggunakan fitur tersebut. Jika fitur itu cukup buruk untuk dinonaktifkan atau diperiksa, kami tidak akan memasukkannya. Sebaliknya, jika fitur itu ada, tidak apa-apa untuk digunakan. Ada satu bahasa Go, bukan bahasa yang berbeda untuk setiap pengembang berdasarkan flag compiler pilihan mereka.

@mikeschinkel , dua kali sekarang pada masalah ini Anda telah menjelaskan penggunaan try sebagai kesalahan _ignoring_.
Pada 7 Juni Anda menulis, di bawah judul "Mempermudah pengembang untuk mengabaikan kesalahan":

Ini adalah pengulangan total dari apa yang orang lain komentari, tetapi apa yang pada dasarnya memberikan try() analog dalam banyak hal untuk hanya merangkul yang berikut sebagai kode idomatik, dan ini adalah kode yang tidak akan pernah menemukan jalannya ke kode mana pun. -menghormati kapal pengembang:

f, _ := os.Open(filename)

Saya tahu saya bisa lebih baik dalam kode saya sendiri, tetapi saya juga tahu banyak dari kita bergantung pada kebesaran pengembang Go lain yang menerbitkan beberapa paket yang sangat berguna, tetapi dari apa yang saya lihat di _"Kode Orang Lain(tm)"_ praktik terbaik dalam penanganan kesalahan sering diabaikan.

Serius, apakah kami benar-benar ingin mempermudah pengembang untuk mengabaikan kesalahan dan mengizinkan mereka mengotori GitHub dengan paket yang tidak kuat?

Dan kemudian pada 14 Juni lagi Anda menyebut menggunakan coba sebagai "kode yang mengabaikan kesalahan dengan cara ini".

Jika bukan karena cuplikan kode f, _ := os.Open(filename) , saya pikir Anda hanya melebih-lebihkan dengan mengkarakterisasi "memeriksa kesalahan dan mengembalikannya" sebagai "mengabaikan" kesalahan. Tetapi cuplikan kode, bersama dengan banyak pertanyaan yang sudah dijawab dalam dokumen proposal atau dalam spesifikasi bahasa membuat saya bertanya-tanya apakah kita berbicara tentang semantik yang sama. Jadi untuk memperjelas dan menjawab pertanyaan Anda:

Saat mempelajari kode proposal, saya menemukan bahwa perilakunya tidak jelas dan agak sulit untuk dipikirkan.

Ketika saya melihat try() membungkus ekspresi, apa yang akan terjadi jika kesalahan dikembalikan?

Ketika Anda melihat try(f()) , jika f() mengembalikan kesalahan, try akan menghentikan eksekusi kode dan mengembalikan kesalahan itu dari fungsi yang tubuhnya try muncul.

Apakah kesalahan akan diabaikan begitu saja?

Tidak. Kesalahan tidak pernah diabaikan. Itu dikembalikan, sama seperti menggunakan pernyataan pengembalian. Suka:

{ err := f(); if err != nil { return err } }

Atau akan melompat ke defer pertama atau terbaru,

Semantiknya sama dengan menggunakan pernyataan return.

Fungsi yang ditangguhkan berjalan dalam " dalam urutan terbalik dari penangguhannya ."

dan jika demikian, apakah itu akan secara otomatis menetapkan variabel bernama err di dalam penutupan itu, atau akankah ia meneruskannya sebagai parameter _(Saya tidak melihat parameter?)_.

Semantiknya sama dengan menggunakan pernyataan return.

Jika Anda perlu merujuk ke parameter hasil di badan fungsi yang ditangguhkan, Anda bisa memberinya nama. Lihat contoh result di https://golang.org/ref/spec#Defer_statements.

Dan jika bukan nama kesalahan otomatis, bagaimana saya menamainya? Dan apakah itu berarti saya tidak dapat mendeklarasikan variabel err saya sendiri di fungsi saya, untuk menghindari bentrokan?

Semantiknya sama dengan menggunakan pernyataan return.

Pernyataan kembali selalu ditetapkan ke hasil fungsi yang sebenarnya, bahkan jika hasilnya tidak disebutkan namanya, dan bahkan jika hasilnya diberi nama tetapi dibayangi.

Dan apakah itu akan memanggil semua defer ? Dalam urutan terbalik atau urutan biasa?

Semantiknya sama dengan menggunakan pernyataan return.

Fungsi yang ditangguhkan berjalan dalam " dalam urutan terbalik dari penangguhannya ." (Urutan terbalik adalah urutan biasa.)

Atau apakah itu akan kembali dari penutupan dan func tempat kesalahan dikembalikan? _(Sesuatu yang tidak akan pernah saya pertimbangkan jika saya tidak membaca di sini kata-kata yang menyiratkan hal itu.)_

Saya tidak tahu apa artinya ini tetapi mungkin jawabannya adalah tidak. Saya akan mendorong untuk fokus pada teks proposal dan spesifikasi dan bukan pada komentar lain di sini tentang apa yang mungkin atau mungkin tidak dimaksudkan oleh teks itu.

Setelah membaca proposal dan semua komentar sejauh ini saya masih jujur ​​​​tidak tahu jawaban atas pertanyaan di atas. Apakah itu jenis fitur yang ingin kami tambahkan ke bahasa yang pendukungnya diperjuangkan sebagai _"Captain Obvious?"_

Secara umum kami bertujuan untuk bahasa yang sederhana dan mudah dipahami. Saya minta maaf Anda memiliki begitu banyak pertanyaan. Tapi proposal ini benar-benar menggunakan kembali sebanyak mungkin bahasa yang ada (khususnya, penundaan), jadi harus ada sedikit detail tambahan untuk dipelajari. Setelah Anda tahu itu

x, y := try(f())

cara

tmp1, tmp2, tmpE := f()
if tmpE != nil {
   return ..., tmpE
}
x, y := tmp1, tmp2

hampir segala sesuatu yang lain harus mengikuti dari implikasi definisi itu.

Ini bukan "mengabaikan" kesalahan. Mengabaikan kesalahan adalah saat Anda menulis:

c, _ := net.Dial("tcp", "127.0.0.1:1234")
io.Copy(os.Stdout, c)

dan kode panik karena net.Dial gagal dan kesalahan diabaikan, c adalah nil, dan panggilan io.Copy ke kesalahan c.Read. Sebaliknya, kode ini memeriksa dan mengembalikan kesalahan:

 c := try(net.Dial("tcp", "127.0.0.1:1234"))
 io.Copy(os.Stdout, c)

Untuk menjawab pertanyaan Anda tentang apakah kami ingin mendorong yang terakhir daripada yang pertama: ya.

@damienfamed75 Pernyataan emit yang Anda usulkan pada dasarnya terlihat sama dengan pernyataan handle dari rancangan desain . Alasan utama untuk mengabaikan deklarasi handle adalah tumpang tindihnya dengan defer . Tidak jelas bagi saya mengapa seseorang tidak bisa hanya menggunakan defer untuk mendapatkan efek yang sama yang dicapai emit .

@dominikh bertanya :

Akankah acme mulai menyoroti coba?

Begitu banyak tentang proposal percobaan yang belum diputuskan, di udara, tidak diketahui.

Tapi pertanyaan ini bisa saya jawab dengan pasti: tidak.

@rsc

Terima kasih atas tanggapan Anda.

_"sekarang dua kali pada masalah ini Anda telah menggambarkan penggunaan coba sebagai mengabaikan kesalahan."_

Ya, saya berkomentar menggunakan perspektif saya dan secara teknis tidak benar.

Yang saya maksud adalah _"Mengizinkan kesalahan diteruskan tanpa didekorasi."_ Bagi saya itu adalah _"mengabaikan"_ — seperti bagaimana orang yang menggunakan pengecualian menangani kesalahan _"mengabaikan"_ — tetapi saya pasti dapat melihat bagaimana orang lain akan melakukannya melihat kata-kata saya sebagai tidak benar secara teknis.

_"Bila Anda melihat try(f()) , jika f() mengembalikan kesalahan, percobaan akan menghentikan eksekusi kode dan mengembalikan kesalahan itu dari fungsi yang tubuhnya tempat percobaan itu muncul."_

Itu adalah jawaban atas pertanyaan dari komentar saya beberapa waktu lalu, tetapi sekarang saya sudah menemukan jawabannya.

Dan akhirnya melakukan dua hal yang membuatku sedih. Alasan:

  1. Ini akan membuat jalur yang paling tidak tahan untuk menghindari kesalahan dekorasi — mendorong banyak pengembang untuk melakukan hal itu — dan banyak yang akan menerbitkan kode itu untuk digunakan orang lain sehingga menghasilkan kode yang tersedia untuk umum dengan kualitas yang lebih rendah dengan penanganan kesalahan/pelaporan kesalahan yang kurang kuat .

  2. Bagi mereka seperti saya yang menggunakan break dan continue untuk penanganan kesalahan alih-alih return — pola yang lebih tahan terhadap perubahan persyaratan — kami bahkan tidak akan dapat menggunakan try() , bahkan ketika tidak ada alasan untuk membuat anotasi kesalahan.

_"Atau akankah itu kembali dari penutupan dan fungsi tempat kesalahan dikembalikan? (Sesuatu yang tidak akan pernah saya pertimbangkan jika saya tidak membaca di sini kata-kata yang menyiratkan hal itu.)"_

_"Saya tidak tahu apa artinya ini, tetapi mungkin jawabannya adalah tidak. Saya akan mendorong untuk berfokus pada teks proposal dan spesifikasinya dan bukan pada komentar lain di sini tentang apa arti teks itu atau mungkin tidak."_

Sekali lagi, pertanyaan itu sudah lebih dari seminggu yang lalu jadi saya lebih mengerti sekarang.

Untuk memperjelas, untuk anak cucu, defer memiliki penutupan, bukan? Jika Anda kembali dari penutupan itu — kecuali saya salah paham — itu tidak hanya akan kembali dari penutupan tetapi juga kembali dari func tempat kesalahan terjadi, bukan? _(Tidak perlu menjawab jika ya.)_

func example() {
    defer func(err) {
       return err // returns from both defer and example()
    }
    try(SomethingThatReturnsAnError)    
} 

BTW, pemahaman saya adalah alasan try() adalah karena pengembang mengeluh tentang boilerplate. Saya juga merasa sedih karena menurut saya persyaratan untuk menerima kesalahan yang dikembalikan yang menghasilkan boilerplate ini adalah yang membantu membuat aplikasi Go lebih kuat daripada di banyak bahasa lain.

Saya pribadi lebih suka melihat Anda mempersulit untuk tidak mendekorasi kesalahan daripada membuatnya lebih mudah untuk mengabaikan mendekorasinya. Tapi saya mengakui bahwa saya tampaknya minoritas dalam hal ini.


BTW, beberapa orang telah mengusulkan sintaks seperti salah satu dari berikut _(Saya telah menambahkan .Extend() hipotetis untuk menjaga contoh saya tetap ringkas):_

f := try os.Open(filename) else err {
    err.Extend("Cannot open file %s",filename)
    //break, continue or return err   
}

Atau

try f := os.Open(filename) else err {
    err.Extend("Cannot open file %s",filename)
    //break, continue or return err    
}

Dan kemudian yang lain mengklaim bahwa itu tidak benar-benar menyimpan karakter apa pun atas ini:

f,err := os.Open(filename)
if err != nil {
    err.Extend("Cannot open file %s",filename)
    //break, continue or return err    
}

Tetapi satu hal yang hilang dari kritik adalah ia bergerak dari 5 baris ke 4 baris, pengurangan ruang vertikal dan itu tampaknya signifikan, terutama ketika Anda membutuhkan banyak konstruksi seperti itu dalam func .

Bahkan lebih baik akan seperti ini yang akan menghilangkan 40% dari ruang vertikal _(walaupun diberikan komentar tentang kata kunci saya ragu ini akan dipertimbangkan):_

try f := os.Open(filename) 
    else err().Extend("Cannot open file %s",filename)
    end //break, continue or return err    

#fwiw


Ngomong -ngomong, seperti yang saya katakan sebelumnya, saya kira kapal telah berlayar jadi saya hanya akan belajar menerimanya.

Sasaran

Beberapa komentar di sini mempertanyakan apa yang kami coba lakukan dengan proposal tersebut. Sebagai pengingat, Pernyataan Masalah Penanganan Kesalahan yang kami terbitkan Agustus lalu mengatakan di bagian "Tujuan" :

“Untuk Go 2, kami ingin membuat pemeriksaan kesalahan lebih ringan, mengurangi jumlah teks program Go yang didedikasikan untuk pemeriksaan kesalahan. Kami juga ingin membuatnya lebih nyaman untuk menulis penanganan kesalahan, meningkatkan kemungkinan bahwa programmer akan meluangkan waktu untuk melakukannya.

Pemeriksaan kesalahan dan penanganan kesalahan harus tetap eksplisit, artinya terlihat dalam teks program. Kami tidak ingin mengulangi jebakan penanganan pengecualian.

Kode yang ada harus tetap berfungsi dan tetap valid seperti saat ini. Setiap perubahan harus beroperasi dengan kode yang ada.”

Untuk lebih lanjut tentang “perangkap penanganan pengecualian,” lihat diskusi di bagian “Masalah” yang lebih panjang. Secara khusus, pemeriksaan kesalahan harus dilampirkan dengan jelas pada apa yang sedang diperiksa.

@mikeschinkel ,

Untuk memperjelas, untuk anak cucu, defer memiliki penutupan, bukan? Jika Anda kembali dari penutupan itu — kecuali saya salah paham — itu tidak hanya akan kembali dari penutupan tetapi juga kembali dari func tempat kesalahan terjadi, bukan? _(Tidak perlu menjawab jika ya.)_

Tidak. Ini bukan tentang penanganan kesalahan tetapi tentang fungsi yang ditangguhkan. Mereka tidak selalu menutup. Misalnya, pola umum adalah:

func (d *Data) Op() int {
    d.mu.Lock()
    defer d.mu.Unlock()

     ... code to implement Op ...
}

Setiap pengembalian dari d.Op menjalankan panggilan buka kunci yang ditangguhkan setelah pernyataan pengembalian tetapi sebelum kode ditransfer ke pemanggil d.Op. Tidak ada yang dilakukan di dalam d.mu.Unlock mempengaruhi nilai kembalian d.Op. Pernyataan pengembalian di d.mu.Unlock kembali dari Buka Kunci. Itu tidak dengan sendirinya kembali dari d.Op. Tentu saja, begitu d.mu.Unlock kembali, begitu juga d.Op, tetapi tidak secara langsung karena d.mu.Unlock. Ini adalah poin yang halus tapi penting.

Mendapatkan contoh Anda:

func example() {
    defer func(err) {
       return err // returns from both defer and example()
    }
    try(SomethingThatReturnsAnError)    
} 

Setidaknya seperti yang tertulis, ini adalah program yang tidak valid. Saya tidak mencoba bertele-tele di sini - detailnya penting. Berikut adalah program yang valid:

func example() (err error) {
    defer func() {
        if err != nil {
            println("FAILED:", err.Error())
        }
    }()

    try(funcReturningError())
    return nil
}

Hasil apa pun dari panggilan fungsi yang ditangguhkan akan dibuang saat panggilan dijalankan, jadi dalam kasus di mana yang ditangguhkan adalah panggilan ke penutupan, sama sekali tidak masuk akal untuk menulis penutupan untuk mengembalikan nilai. Jadi jika Anda menulis return err di dalam badan penutup, kompiler akan memberi tahu Anda "terlalu banyak argumen untuk dikembalikan" .

Jadi, tidak, menulis return err tidak kembali dari fungsi yang ditangguhkan dan fungsi luar dalam arti sebenarnya, dan dalam penggunaan konvensional bahkan tidak mungkin untuk menulis kode yang tampaknya melakukan itu.

Banyak dari kontra-proposal yang diposting ke masalah ini menyarankan konstruksi penanganan kesalahan lain yang lebih mampu menduplikasi konstruksi bahasa yang ada, seperti pernyataan if. (Atau mereka bertentangan dengan tujuan "membuat pemeriksaan kesalahan lebih ringan, mengurangi jumlah teks program Go menjadi pemeriksaan kesalahan." Atau keduanya.)

Secara umum, Go sudah memiliki konstruksi penanganan kesalahan yang sangat mampu: seluruh bahasa, terutama pernyataan if. @DavexPro benar untuk merujuk kembali ke entri blog Go Errors are values ​​. Kita tidak perlu merancang seluruh sub-bahasa yang terpisah terkait dengan kesalahan, kita juga tidak perlu. Saya pikir wawasan utama selama setengah tahun terakhir ini adalah untuk menghapus "pegangan" dari proposal "pemeriksaan/pegangan" demi penggunaan kembali bahasa apa yang sudah kita miliki, termasuk kembali ke pernyataan if jika sesuai. Pengamatan tentang melakukan sesedikit mungkin menghilangkan dari pertimbangan sebagian besar gagasan seputar parameterisasi lebih lanjut sebuah konstruksi baru.

Dengan terima kasih kepada @brynbellomy atas banyak komentar baiknya, saya akan menggunakan try-else-nya sebagai contoh ilustratif. Ya, kita mungkin menulis:

func doSomething() (int, error) {
    // Inline error handler
    a, b := try SomeFunc() else err {
        return 0, errors.Wrap(err, "error in doSomething:")
    }

    // Named error handlers
    handler logAndContinue err {
        log.Errorf("non-critical error: %v", err)
    }
    handler annotateAndReturn err {
        return 0, errors.Wrap(err, "error in doSomething:")
    }

    c, d := try SomeFunc() else logAndContinue
    e, f := try OtherFunc() else annotateAndReturn

    // ...

    return 123, nil
}

tetapi semua hal dianggap ini mungkin bukan peningkatan yang signifikan dibandingkan menggunakan konstruksi bahasa yang ada:

func doSomething() (int, error) {
    a, b, err := SomeFunc()
    if err != nil {
        return 0, errors.Wrap(err, "error in doSomething:")
    }

    // Named error handlers
    logAndContinue := func(err error) {
        log.Errorf("non-critical error: %v", err)
    }
    annotate:= func(err error) (int, error) {
        return 0, errors.Wrap(err, "error in doSomething:")
    }

    c, d, err := SomeFunc()
    if err != nil {
        logAndContinue(err)
    }
    e, f, err := SomeFunc()
    if err != nil {
        return annotate(err)
    }

    // ...

    return 123, nil
}

Artinya, terus mengandalkan bahasa yang ada untuk menulis logika penanganan kesalahan tampaknya lebih baik daripada membuat pernyataan baru, apakah itu try-else, try-goto, try-arrow, atau apa pun.

Inilah sebabnya mengapa try terbatas pada semantik sederhana if err != nil { return ..., err } dan tidak lebih: persingkat satu pola umum tetapi jangan mencoba menemukan kembali semua aliran kontrol yang mungkin. Ketika pernyataan if atau fungsi pembantu sesuai, kami sepenuhnya mengharapkan orang untuk terus menggunakannya.

@rsc Terima kasih telah mengklarifikasi.

Benar, saya tidak mendapatkan detailnya dengan benar. Saya kira saya tidak cukup sering menggunakan defer untuk mengingat sintaksnya.

_(FWIW saya menemukan menggunakan defer untuk sesuatu yang lebih kompleks daripada menutup pegangan file kurang jelas karena melompat mundur di func sebelum kembali. Jadi selalu letakkan kode itu di akhir func setelah for range once{...} kode penanganan kesalahan saya break keluar dari.)_

Saran untuk gofmt setiap mencoba memanggil ke beberapa baris secara langsung bertentangan dengan tujuan "membuat pemeriksaan kesalahan lebih ringan, mengurangi jumlah teks program Go menjadi pemeriksaan kesalahan."

Saran untuk gofmt pengujian kesalahan jika pernyataan dalam satu baris juga secara langsung bertentangan dengan tujuan ini. Pemeriksaan kesalahan tidak menjadi jauh lebih ringan atau berkurang jumlahnya dengan menghapus karakter baris baru interior. Jika ada, mereka menjadi lebih sulit untuk skim.

Manfaat utama dari try adalah memiliki singkatan yang jelas untuk satu kasus yang paling umum, membuat kasus yang tidak biasa lebih menonjol sehingga layak dibaca dengan cermat.

Mencadangkan dari gofmt ke alat umum, saran untuk fokus pada alat untuk menulis pemeriksaan kesalahan alih-alih perubahan bahasa juga bermasalah. Seperti yang dikatakan Abelson dan Sussman, "Program harus ditulis agar orang dapat membacanya, dan hanya secara kebetulan untuk dieksekusi oleh mesin." Jika perkakas mesin _diperlukan_ untuk mengatasi bahasa tersebut, maka bahasa tersebut tidak melakukan tugasnya. Keterbacaan tidak boleh terbatas pada orang yang menggunakan alat tertentu.

Beberapa orang menjalankan logika ke arah yang berlawanan: orang dapat menulis ekspresi kompleks, jadi mereka pasti akan melakukannya, jadi Anda memerlukan IDE atau dukungan alat lain untuk menemukan ekspresi try, jadi try adalah ide yang buruk. Namun, ada beberapa lompatan yang tidak didukung di sini. Yang utama adalah klaim bahwa karena _mungkin_ untuk menulis kode yang kompleks dan tidak dapat dibaca, kode seperti itu akan ada di mana-mana. Seperti yang dicatat oleh @josharian , sudah " mungkin untuk menulis kode yang menjijikkan di Go ." Itu tidak biasa karena pengembang memiliki norma tentang mencoba menemukan cara yang paling mudah dibaca untuk menulis bagian kode tertentu. Jadi sudah pasti _bukan_ kasus bahwa dukungan IDE akan diperlukan untuk membaca program yang melibatkan try. Dan dalam beberapa kasus di mana orang-orang menulis percobaan penyalahgunaan kode yang benar-benar mengerikan, dukungan IDE sepertinya tidak akan banyak berguna. Keberatan ini—orang dapat menulis kode yang sangat buruk menggunakan fitur baru—dimunculkan di hampir setiap diskusi tentang setiap fitur bahasa baru di setiap bahasa. Hal ini tidak sangat membantu. Keberatan yang lebih membantu adalah dalam bentuk "orang akan menulis kode yang awalnya tampak bagus tetapi ternyata kurang bagus karena alasan yang tidak terduga ini," seperti dalam diskusi tentang debug cetakan .

Sekali lagi: Keterbacaan tidak boleh terbatas pada orang yang menggunakan alat tertentu.
(Saya masih mencetak dan membaca program di atas kertas, meskipun orang sering memberi saya tatapan aneh karena melakukan itu.)

Terima kasih @rsc karena telah memberikan pemikiran Anda tentang mengizinkan pernyataan if untuk dijadikan satu baris.

Saran untuk gofmt pengujian kesalahan jika pernyataan dalam satu baris juga secara langsung bertentangan dengan tujuan ini. Pemeriksaan kesalahan tidak menjadi jauh lebih ringan atau berkurang jumlahnya dengan menghapus karakter baris baru interior. Jika ada, mereka menjadi lebih sulit untuk skim.

Saya memperkirakan pernyataan ini secara berbeda.

Saya menemukan pengurangan jumlah baris dari 3 menjadi 1 menjadi jauh lebih ringan. Bukankah gofmt memerlukan pernyataan if untuk memuat, misalnya, 9 (atau bahkan 5) baris baru alih-alih 3 secara substansial lebih berat? Ini adalah faktor (jumlah) pengurangan/perluasan yang sama. Saya berpendapat bahwa struct literal memiliki trade-off yang tepat ini, dan dengan tambahan try , akan memungkinkan aliran kontrol sama seperti pernyataan if .

Kedua, saya menemukan argumen bahwa mereka menjadi lebih sulit untuk dibaca sekilas untuk diterapkan dengan baik ke try , jika tidak lebih. Setidaknya pernyataan if harus berada di barisnya sendiri. Tapi mungkin saya salah paham apa yang dimaksud dengan "skim" dalam konteks ini. Saya menggunakannya untuk mengartikan "kebanyakan lewati tetapi waspadalah."

Semua yang dikatakan, saran gofmt didasarkan pada mengambil langkah yang bahkan lebih konservatif daripada try dan tidak berdampak pada try kecuali itu akan cukup. Kedengarannya tidak, jadi jika saya ingin membahasnya lebih lanjut, saya akan membuka edisi/proposal baru. :+1:

Saya menemukan pengurangan jumlah baris dari 3 menjadi 1 menjadi jauh lebih ringan.

Saya pikir semua orang setuju bahwa mungkin saja kode terlalu padat. Misalnya jika seluruh paket Anda adalah satu baris, saya pikir kita semua setuju itu masalah. Kita semua mungkin tidak setuju pada garis yang tepat. Bagi saya, kami telah menetapkan

n, err := src.Read(buf)
if err == io.EOF {
    return nil
} else if err != nil {
    return err
}

sebagai cara untuk memformat kode itu, dan saya pikir akan sangat mengejutkan untuk mencoba beralih ke contoh Anda

n, err := src.Read(buf)
if err == io.EOF { return nil }
else if err != nil { return err }

alih-alih. Jika kita mulai seperti itu, saya yakin itu akan baik-baik saja. Tapi kami tidak melakukannya, dan itu bukan tempat kami berada sekarang.

Secara pribadi, saya menemukan bobot yang lebih ringan pada halaman dalam arti lebih mudah untuk membaca sepintas. Anda dapat melihat if-else secara sekilas tanpa membaca huruf yang sebenarnya. Sebaliknya, versi yang lebih padat sulit untuk dilihat secara sekilas dari urutan tiga pernyataan, artinya Anda harus melihat lebih hati-hati sebelum maknanya menjadi jelas.

Pada akhirnya, tidak apa-apa jika kita menggambar garis kepadatan-vs-keterbacaan di tempat yang berbeda sejauh jumlah baris baru. Proposal percobaan difokuskan tidak hanya untuk menghapus baris baru tetapi juga menghapus konstruksi seluruhnya, dan itu menghasilkan kehadiran halaman yang lebih ringan yang terpisah dari pertanyaan gofmt.

Beberapa orang menjalankan logika ke arah yang berlawanan: orang dapat menulis ekspresi kompleks, jadi mereka pasti akan melakukannya, jadi Anda memerlukan IDE atau dukungan alat lain untuk menemukan ekspresi try, jadi try adalah ide yang buruk. Namun, ada beberapa lompatan yang tidak didukung di sini. Yang utama adalah klaim bahwa karena _mungkin_ untuk menulis kode yang kompleks dan tidak dapat dibaca, kode seperti itu akan ada di mana-mana. Seperti yang dicatat oleh @josharian , sudah " mungkin untuk menulis kode yang menjijikkan di Go ." Itu tidak biasa karena pengembang memiliki norma tentang mencoba menemukan cara yang paling mudah dibaca untuk menulis bagian kode tertentu. Jadi sudah pasti _bukan_ kasus bahwa dukungan IDE akan diperlukan untuk membaca program yang melibatkan try. Dan dalam beberapa kasus di mana orang-orang menulis percobaan penyalahgunaan kode yang benar-benar mengerikan, dukungan IDE sepertinya tidak akan banyak berguna. Keberatan ini—orang dapat menulis kode yang sangat buruk menggunakan fitur baru—dimunculkan di hampir setiap diskusi tentang setiap fitur bahasa baru di setiap bahasa. Hal ini tidak sangat membantu.

Bukankah ini alasan utama Go tidak memiliki operator ternary ?

Bukankah ini alasan utama Go tidak memiliki operator ternary?

Tidak. Kita dapat dan harus membedakan antara "fitur ini dapat digunakan untuk menulis kode yang sangat mudah dibaca, tetapi juga dapat disalahgunakan untuk menulis kode yang tidak dapat dibaca" dan "penggunaan dominan fitur ini adalah untuk menulis kode yang tidak dapat dibaca".

Pengalaman dengan C menunjukkan bahwa ? : jatuh tepat ke dalam kategori kedua. (Dengan kemungkinan pengecualian min dan max, saya tidak yakin pernah melihat kode menggunakan ? : yang tidak diperbaiki dengan menulis ulang untuk menggunakan pernyataan if. Tetapi paragraf ini keluar dari topik.)

Sintaksis

Diskusi ini telah mengidentifikasi enam sintaks yang berbeda untuk menulis semantik yang sama dari proposal:

(Maaf jika saya memiliki cerita asal yang salah!)

Semua ini memiliki pro dan kontra, dan hal yang menyenangkan adalah karena mereka semua memiliki semantik yang sama, tidak terlalu penting untuk memilih di antara berbagai sintaks untuk bereksperimen lebih lanjut.

Saya menemukan contoh ini oleh @brynbellomy yang menggugah pikiran:

headRef := try(r.Head())
parentObjOne := try(headRef.Peel(git.ObjectCommit))
parentObjTwo := try(remoteBranch.Reference.Peel(git.ObjectCommit))
parentCommitOne := try(parentObjOne.AsCommit())
parentCommitTwo := try(parentObjTwo.AsCommit())
treeOid := try(index.WriteTree())
tree := try(r.LookupTree(treeOid))

// vs

try headRef := r.Head()
try parentObjOne := headRef.Peel(git.ObjectCommit)
try parentObjTwo := remoteBranch.Reference.Peel(git.ObjectCommit)
try parentCommitOne := parentObjOne.AsCommit()
try parentCommitTwo := parentObjTwo.AsCommit()
try treeOid := index.WriteTree()
try tree := r.LookupTree(treeOid)

// vs

try (
    headRef := r.Head()
    parentObjOne := headRef.Peel(git.ObjectCommit)
    parentObjTwo := remoteBranch.Reference.Peel(git.ObjectCommit)
    parentCommitOne := parentObjOne.AsCommit()
    parentCommitTwo := parentObjTwo.AsCommit()
    treeOid := index.WriteTree()
    tree := r.LookupTree(treeOid)
)

Tidak ada banyak perbedaan antara contoh-contoh spesifik ini, tentu saja. Dan jika coba ada di semua lini, mengapa tidak mengaturnya atau memfaktorkannya? Bukankah itu lebih bersih? Saya juga bertanya-tanya tentang ini.

Tapi seperti yang diamati oleh @ianlancetaylor , “coba mengubur lede. Kode menjadi serangkaian pernyataan percobaan, yang mengaburkan apa yang sebenarnya dilakukan kode.”

Saya pikir itu poin penting: berbaris dengan cara itu, atau memfaktorkannya seperti di blok, menyiratkan paralelisme yang salah. Ini menyiratkan bahwa yang penting dari pernyataan-pernyataan ini adalah bahwa mereka semua mencoba. Itu biasanya bukan hal terpenting tentang kode dan bukan apa yang harus kita fokuskan saat membacanya.

Misalkan demi argumen bahwa AsCommit tidak pernah gagal dan akibatnya tidak mengembalikan kesalahan. Sekarang kita punya:

headRef := try(r.Head())
parentObjOne := try(headRef.Peel(git.ObjectCommit))
parentObjTwo := try(remoteBranch.Reference.Peel(git.ObjectCommit))
parentCommitOne := parentObjOne.AsCommit()
parentCommitTwo := parentObjTwo.AsCommit()
treeOid := try(index.WriteTree())
tree := try(r.LookupTree(treeOid))

// vs

try headRef := r.Head()
try parentObjOne := headRef.Peel(git.ObjectCommit)
try parentObjTwo := remoteBranch.Reference.Peel(git.ObjectCommit)
parentCommitOne := parentObjOne.AsCommit()
parentCommitTwo := parentObjTwo.AsCommit()
try treeOid := index.WriteTree()
try tree := r.LookupTree(treeOid)

// vs

try (
    headRef := r.Head()
    parentObjOne := headRef.Peel(git.ObjectCommit)
    parentObjTwo := remoteBranch.Reference.Peel(git.ObjectCommit)
)
parentCommitOne := parentObjOne.AsCommit()
parentCommitTwo := parentObjTwo.AsCommit()
try (
    treeOid := index.WriteTree()
    tree := r.LookupTree(treeOid)
)

Apa yang Anda lihat pada pandangan pertama adalah bahwa dua garis tengah jelas berbeda dari yang lain. Mengapa? Ternyata karena penanganan kesalahan. Apakah itu detail terpenting tentang kode ini, hal yang harus Anda perhatikan pada pandangan pertama? Jawaban saya adalah tidak. Saya pikir Anda harus memperhatikan logika inti dari apa yang dilakukan program terlebih dahulu, dan penanganan kesalahan nanti. Dalam contoh ini, pernyataan try dan blok try menghalangi pandangan logika inti tersebut. Bagi saya, ini menunjukkan bahwa mereka bukan sintaks yang tepat untuk semantik ini.

Itu meninggalkan empat sintaks pertama, yang bahkan lebih mirip satu sama lain:

headRef := try(r.Head())
parentObjOne := try(headRef.Peel(git.ObjectCommit))
parentObjTwo := try(remoteBranch.Reference.Peel(git.ObjectCommit))
parentCommitOne := parentObjOne.AsCommit()
parentCommitTwo := parentObjTwo.AsCommit()
treeOid := try(index.WriteTree())
tree := try(r.LookupTree(treeOid))

// vs

headRef := try r.Head()
parentObjOne := try headRef.Peel(git.ObjectCommit)
parentObjTwo := try remoteBranch.Reference.Peel(git.ObjectCommit)
parentCommitOne := parentObjOne.AsCommit()
parentCommitTwo := parentObjTwo.AsCommit()
treeOid := try index.WriteTree()
tree := try r.LookupTree(treeOid)

// vs

headRef := r.Head()?
parentObjOne := headRef.Peel(git.ObjectCommit)?
parentObjTwo := remoteBranch.Reference.Peel(git.ObjectCommit)?
parentCommitOne := parentObjOne.AsCommit()
parentCommitTwo := parentObjTwo.AsCommit()
treeOid := index.WriteTree()?
tree := r.LookupTree(treeOid)?

// vs

headRef := r.Head?()
parentObjOne := headRef.Peel?(git.ObjectCommit)
parentObjTwo := remoteBranch.Reference.Peel?(git.ObjectCommit)
parentCommitOne := parentObjOne.AsCommit()
parentCommitTwo := parentObjTwo.AsCommit()
treeOid := index.WriteTree?()
tree := r.LookupTree?(treeOid)

Sulit untuk terlalu sibuk memilih salah satu dari yang lain. Mereka semua memiliki poin baik dan buruk mereka. Keuntungan paling penting dari bentuk bawaan adalah:

(1) operan yang tepat sangat jelas, terutama dibandingkan dengan operator awalan try x.y().z() .
(2) alat yang tidak perlu tahu tentang coba dapat memperlakukannya sebagai panggilan fungsi biasa, jadi misalnya goimports akan berfungsi dengan baik tanpa penyesuaian apa pun, dan
(3) ada ruang untuk perluasan dan penyesuaian di masa mendatang jika diperlukan.

Sangat mungkin bahwa setelah melihat kode nyata menggunakan konstruksi ini, kami akan mengembangkan pemahaman yang lebih baik apakah keuntungan dari salah satu dari tiga sintaks lainnya lebih besar daripada keuntungan sintaks panggilan fungsi ini. Hanya eksperimen dan pengalaman yang dapat memberi tahu kita hal ini.

Terima kasih untuk semua klarifikasi. Semakin saya berpikir semakin saya menyukai proposal dan melihat bagaimana itu sesuai dengan tujuan.

Mengapa tidak menggunakan fungsi seperti recover() daripada err yang kita tidak tahu dari mana asalnya? Itu akan lebih konsisten dan mungkin lebih mudah untuk diterapkan.

func f() error {
 defer func() {
   if err:=error();err!=nil {
     ...
   }
 }()
}

edit: Saya tidak pernah menggunakan pengembalian bernama, maka akan aneh bagi saya untuk menambahkan pengembalian bernama hanya untuk ini

@flibustenet , lihat juga https://swtch.com/try.html#named untuk beberapa saran serupa.
(Menjawab semuanya: kita bisa melakukan itu, tetapi itu tidak sepenuhnya diperlukan mengingat hasil yang disebutkan, jadi sebaiknya kita mencoba menggunakan konsep yang ada sebelum memutuskan kita perlu menyediakan cara kedua.)

Konsekuensi yang tidak diinginkan dari try() adalah proyek meninggalkan _go fmt_ untuk mendapatkan pemeriksaan kesalahan satu baris. Itu hampir semua manfaat dari try() tanpa biaya apapun. Saya telah melakukannya selama beberapa tahun; itu bekerja dengan baik.

Tetapi saya lebih suka dapat mendefinisikan penangan kesalahan pilihan terakhir untuk package , dan menghilangkan semua pemeriksaan kesalahan yang membutuhkannya. Apa yang saya definisikan bukanlah try() .

@networkimprov , Anda tampaknya berasal dari posisi yang berbeda dari pengguna Go yang kami targetkan, dan pesan Anda akan berkontribusi lebih banyak pada percakapan jika berisi detail atau tautan tambahan sehingga kami dapat lebih memahami sudut pandang Anda.

Tidak jelas "biaya" apa yang Anda yakini miliki. Dan sementara Anda mengatakan bahwa mengabaikan gofmt tidak ada biayanya untuk mencoba (apa pun itu), Anda tampaknya mengabaikan bahwa pemformatan gofmt adalah yang digunakan oleh semua program yang membantu menulis ulang kode sumber Go, seperti goimports, mis., gorename , dan seterusnya. Anda mengabaikan go fmt dengan mengorbankan helper tersebut, atau setidaknya melakukan pengeditan insidental substansial pada kode Anda saat Anda memanggilnya. Meski begitu, jika itu berhasil bagi Anda untuk melakukannya, itu bagus: tentu saja terus lakukan itu.

Juga tidak jelas apa yang dimaksud dengan "define a last-resort error handler for the package" atau mengapa sebaiknya menerapkan kebijakan penanganan error ke seluruh paket alih-alih ke satu fungsi pada satu waktu. Jika hal utama yang ingin Anda lakukan dalam penangan kesalahan adalah menambahkan konteks, konteks yang sama tidak akan sesuai di seluruh paket.

@rsc , Seperti yang mungkin telah Anda lihat, ketika saya menyarankan sintaks blok try, saya kemudian kembali ke sisi "tidak" untuk fitur ini -- sebagian karena saya merasa tidak nyaman menyembunyikan satu atau lebih pengembalian kesalahan bersyarat dalam pernyataan atau aplikasi fungsi. Tapi izinkan saya mengklarifikasi satu hal. Dalam proposal blokir coba, saya secara eksplisit mengizinkan pernyataan yang tidak memerlukan try . Jadi contoh blok percobaan terakhir Anda adalah:

try (
    headRef := r.Head()
    parentObjOne := headRef.Peel(git.ObjectCommit)
    parentObjTwo := remoteBranch.Reference.Peel(git.ObjectCommit)
        parentCommitOne := parentObjOne.AsCommit()
        parentCommitTwo := parentObjTwo.AsCommit()
    treeOid := index.WriteTree()
    tree := r.LookupTree(treeOid)
)

Ini hanya mengatakan bahwa setiap kesalahan yang dikembalikan dalam blok coba dikembalikan ke pemanggil. Jika kontrol berhasil melewati blok percobaan, tidak ada kesalahan di blok tersebut.

Kamu berkata

Saya pikir Anda harus memperhatikan logika inti dari apa yang dilakukan program terlebih dahulu, dan penanganan kesalahan nanti.

Ini persis alasan saya memikirkan blok coba! Yang diperhitungkan bukan hanya kata kuncinya tetapi penanganan kesalahannya. Saya tidak ingin memikirkan N tempat berbeda yang dapat menghasilkan kesalahan (kecuali ketika saya secara eksplisit mencoba menangani kesalahan tertentu).

Beberapa poin lagi yang mungkin layak disebutkan:

  1. Penelepon tidak tahu persis dari mana kesalahan itu berasal dari dalam orang yang dipanggil. Ini juga berlaku untuk proposal sederhana yang Anda pertimbangkan secara umum. Saya berspekulasi bahwa kompiler dapat dibuat untuk menambahkan anotasinya sendiri pada titik pengembalian kesalahan. Tapi saya belum terlalu memikirkannya.
  2. Tidak jelas bagi saya apakah ekspresi seperti try(try(foo(try(bar)).fum()) diperbolehkan. Penggunaan seperti itu mungkin tidak disukai tetapi semantiknya perlu ditentukan. Dalam kasus blok coba kompiler harus bekerja lebih keras untuk mendeteksi penggunaan tersebut dan memeras semua penanganan kesalahan ke tingkat blok coba.
  3. Saya lebih cenderung menyukai return-on-error daripada try . Ini lebih mudah untuk ditelan pada level blok!
  4. Di sisi lain, kata kunci yang panjang membuat hal-hal menjadi kurang mudah dibaca.

FWIW, saya masih tidak berpikir ini layak dilakukan.

@rsc

[...]
Yang utama adalah klaim bahwa karena dimungkinkan untuk menulis kode yang kompleks dan tidak dapat dibaca, kode seperti itu akan ada di mana-mana. Seperti yang dicatat oleh @josharian , sudah "mungkin untuk menulis kode yang menjijikkan di Go."
[...]

headRef := try(r.Head())
parentObjOne := try(headRef.Peel(git.ObjectCommit))
parentObjTwo := try(remoteBranch.Reference.Peel(git.ObjectCommit))
parentCommitOne := try(parentObjOne.AsCommit())
parentCommitTwo := try(parentObjTwo.AsCommit())

Saya mengerti posisi Anda pada "kode buruk" adalah bahwa kita dapat menulis kode yang buruk hari ini seperti blok berikut.

parentCommitOne := try(try(try(r.Head()).Peel(git.ObjectCommit)).AsCommit())
parentCommitTwo := try(try(remoteBranch.Reference.Peel(git.ObjectCommit)).AsCommit())

Apa pendapat Anda tentang pelarangan panggilan try bersarang sehingga kami tidak dapat secara tidak sengaja menulis kode yang buruk?

Jika Anda melarang try bersarang pada versi pertama, Anda akan dapat menghapus batasan ini nanti jika diperlukan, tidak mungkin sebaliknya.

Saya sudah membahas poin ini tetapi tampaknya relevan - kompleksitas kode harus diskalakan secara vertikal, bukan horizontal.

try sebagai ekspresi mendorong kompleksitas kode untuk diskalakan secara horizontal dengan mendorong panggilan bersarang. try sebagai pernyataan mendorong kompleksitas kode untuk diskalakan secara vertikal.

@rsc , untuk pertanyaan Anda,

Penangan tingkat paket saya sebagai upaya terakhir -- ketika kesalahan tidak diharapkan:

func quit(err error) {
   fmt.Fprintf(os.Stderr, "quit after %s\n", err)
   debug.PrintStack()      // because panic(err) produces a pile of noise
   os.Exit(3)
}

Konteks: Saya sering menggunakan os.File (di mana saya menemukan dua bug: #26650 & #32088)

Dekorator tingkat paket yang menambahkan konteks dasar akan membutuhkan argumen caller -- struct yang dihasilkan yang menyediakan hasil runtime.Caller().

Saya berharap _go fmt_ penulis ulang akan menggunakan pemformatan yang ada, atau membiarkan Anda menentukan pemformatan per transformasi. Saya puas dengan alat lain.

Biaya (yaitu kekurangan) dari try() didokumentasikan dengan baik di atas.

Sejujurnya saya kecewa bahwa tim Go pertama-tama menawari kami check/handle (secara amal, ide baru), dan kemudian try() ternaryesque. Saya tidak mengerti mengapa Anda tidak mengeluarkan penanganan kesalahan ulang RFP , dan kemudian mengumpulkan komentar komunitas pada beberapa proposal yang dihasilkan (lihat #29860). Ada banyak kebijaksanaan di sini yang bisa Anda manfaatkan!

@rsc

Sintaksis

Diskusi ini telah mengidentifikasi enam sintaks yang berbeda untuk menulis semantik yang sama dari proposal:

try {error} {optional wrap func} {optional return args in brackets}

f, err := os.Open(file)
try err wrap { a, b }

... dan, IMO, meningkatkan keterbacaan (melalui aliterasi) serta akurasi semantik:

f, err := os.Open(file)
relay err

atau

f, err := os.Open(file)
relay err wrap

atau

f, err := os.Open(file)
relay err wrap { a, b }

atau

f, err := os.Open(file)
relay err { a, b }

Saya tahu menganjurkan estafet versus mencoba mudah diabaikan sebagai di luar topik, tetapi saya bisa membayangkan mencoba menjelaskan bagaimana mencoba tidak mencoba apa pun dan tidak membuang apa pun. Tidak jelas DAN memiliki bagasi. relay menjadi istilah baru akan memungkinkan penjelasan yang berpikiran jernih, dan deskripsi memiliki dasar di sirkuit (yang sebenarnya adalah semua ini).

Sunting untuk memperjelas:
Try dapat berarti - 1. mengalami sesuatu dan kemudian menilainya secara subjektif 2. memverifikasi sesuatu secara objektif 3. mencoba melakukan sesuatu 4. menjalankan beberapa aliran kontrol yang dapat diinterupsi dan meluncurkan notifikasi yang dapat dicegat jika demikian

Dalam proposal ini, coba lakukan tidak satupun dari itu. Kami benar-benar menjalankan fungsi. Hal ini kemudian rewiring aliran kontrol berdasarkan nilai kesalahan. Seperti itu penjelasan definisi sebenarnya dari kata rele pelindung. Kami secara langsung memasang kembali sirkuit (yaitu hubungan arus pendek lingkup fungsi saat ini) sesuai dengan nilai kesalahan yang diuji.

Dalam proposal blokir coba, saya secara eksplisit mengizinkan pernyataan yang tidak perlu dicoba

Keuntungan utama dalam penanganan kesalahan Go yang saya lihat dari sistem try-catch bahasa seperti Java dan Python adalah selalu jelas panggilan fungsi mana yang dapat menghasilkan kesalahan dan mana yang tidak. Keindahan try seperti yang didokumentasikan dalam proposal asli adalah ia dapat mengurangi boilerplate penanganan kesalahan sederhana sambil tetap mempertahankan fitur penting ini.

Meminjam dari contoh @Goodwine , meskipun jelek, dari perspektif penanganan kesalahan bahkan ini:

parentCommitOne := try(try(try(r.Head()).Peel(git.ObjectCommit)).AsCommit())
parentCommitTwo := try(try(remoteBranch.Reference.Peel(git.ObjectCommit)).AsCommit())

... lebih baik daripada yang sering Anda lihat dalam bahasa try-catch

parentCommitOne := r.Head().Peel(git.ObjectCommit).AsCommit()
parentCommitTwo := remoteBranch.Reference.Peel(git.ObjectCommit).AsCommit()

... karena Anda masih dapat mengetahui bagian kode mana yang dapat mengalihkan aliran kontrol karena kesalahan dan mana yang tidak.

Saya tahu bahwa @bakul tidak mendukung proposal sintaksis blok ini, tapi saya pikir ini memunculkan poin menarik tentang penanganan kesalahan Go dibandingkan dengan yang lain. Saya pikir penting bahwa setiap proposal penanganan kesalahan yang diadopsi Go tidak boleh mengaburkan bagian kode mana yang bisa dan tidak bisa error.

Saya telah menulis sebuah alat kecil: tryhard (yang tidak berusaha terlalu keras saat ini) beroperasi berdasarkan file-by-file dan menggunakan pencocokan pola AST sederhana untuk mengenali calon potensial untuk try dan untuk melaporkan (dan menulis ulang) mereka. Alat ini primitif (tidak ada pemeriksaan jenis) dan ada peluang yang layak untuk kesalahan positif, tergantung pada gaya pengkodean yang umum. Baca dokumentasi untuk detailnya.

Menerapkannya ke $GOROOT/src di laporan tip > 5000 (!) peluang untuk try . Mungkin ada banyak kesalahan positif, tetapi memeriksa sampel yang layak dengan tangan menunjukkan bahwa sebagian besar peluang itu nyata.

Menggunakan fitur rewrite menunjukkan bagaimana kode akan terlihat seperti menggunakan try . Sekali lagi, pandangan sepintas pada output menunjukkan peningkatan yang signifikan dalam pikiran saya.

( Perhatian: Fitur penulisan ulang akan menghancurkan file! Gunakan dengan risiko Anda sendiri. )

Mudah-mudahan ini akan memberikan beberapa wawasan konkret tentang seperti apa kode yang mungkin terlihat menggunakan try dan memungkinkan kita melewati spekulasi yang menganggur dan tidak produktif.

Terima kasih & selamat menikmati.

Saya mengerti posisi Anda pada "kode buruk" adalah bahwa kita dapat menulis kode yang buruk hari ini seperti blok berikut.

parentCommitOne := try(try(try(r.Head()).Peel(git.ObjectCommit)).AsCommit())
parentCommitTwo := try(try(remoteBranch.Reference.Peel(git.ObjectCommit)).AsCommit())

Posisi saya adalah pengembang Go melakukan pekerjaan yang layak dengan menulis kode yang jelas dan hampir pasti kompiler bukanlah satu-satunya yang menghalangi Anda atau rekan kerja Anda menulis kode yang terlihat seperti itu.

Apa pendapat Anda tentang pelarangan panggilan try bersarang sehingga kami tidak dapat secara tidak sengaja menulis kode yang buruk?

Sebagian besar kesederhanaan Go berasal dari pemilihan fitur ortogonal yang menyusun secara independen. Menambahkan batasan merusak ortogonalitas, komposisi, independensi, dan dengan demikian merusak kesederhanaan.

Hari ini, adalah aturan bahwa jika Anda memiliki:

x := expression
y := f(x)

tanpa penggunaan x di mana pun, maka ini adalah transformasi program yang valid untuk menyederhanakannya menjadi

y := f(expression)

Jika kita mengadopsi pembatasan pada ekspresi try, maka itu akan merusak alat apa pun yang menganggap ini selalu merupakan transformasi yang valid. Atau jika Anda memiliki pembuat kode yang bekerja dengan ekspresi dan mungkin memproses ekspresi percobaan, itu harus berusaha keras untuk memperkenalkan temporer untuk memenuhi batasan. Dan seterusnya dan seterusnya.

Singkatnya, pembatasan menambah kompleksitas yang signifikan. Mereka membutuhkan pembenaran yang signifikan, bukan "mari kita lihat apakah ada orang yang menabrak tembok ini dan meminta kita untuk merobohkannya".

Saya menulis penjelasan yang lebih panjang dua tahun lalu di https://github.com/golang/go/issues/18130#issuecomment -264195616 (dalam konteks tipe alias) yang berlaku sama baiknya di sini.

@bakul ,

Tapi izinkan saya mengklarifikasi satu hal. Dalam proposal blokir coba, saya secara eksplisit mengizinkan pernyataan yang _tidak perlu_ try .

Melakukan hal ini akan gagal mencapai tujuan kedua : "Pemeriksaan kesalahan dan penanganan kesalahan harus tetap eksplisit, artinya terlihat dalam teks program. Kami tidak ingin mengulangi kesalahan penanganan pengecualian."

Perangkap utama dari penanganan eksepsi tradisional adalah tidak mengetahui di mana cek berada. Mempertimbangkan:

try {
    s = canThrowErrors()
    t = cannotThrowErrors()
    u = canThrowErrors() // a second call
} catch {
    // how many ways can you get here?
}

Jika fungsi-fungsi tersebut tidak diberi nama yang sangat membantu, akan sangat sulit untuk membedakan fungsi mana yang mungkin gagal dan mana yang dijamin akan berhasil, yang berarti Anda tidak dapat dengan mudah menjelaskan tentang fragmen kode mana yang dapat diinterupsi oleh pengecualian dan mana yang tidak.

Bandingkan ini dengan pendekatan Swift , di mana mereka mengadopsi beberapa sintaks penanganan pengecualian tradisional tetapi sebenarnya melakukan penanganan kesalahan, dengan penanda eksplisit pada setiap fungsi yang diperiksa dan tidak ada cara untuk bersantai di luar bingkai tumpukan saat ini:

do {
    let s = try canThrowErrors()
    let t = cannotThrowErrors()
    let u = try canThrowErrors() // a second call
} catch {
    handle error from try above
}

Baik itu Rust atau Swift atau proposal ini, kuncinya, peningkatan kritis atas penanganan pengecualian secara eksplisit menandai dalam teks - bahkan dengan penanda yang sangat ringan - setiap tempat di mana cek berada.

Untuk lebih lanjut tentang masalah pemeriksaan implisit, lihat bagian Masalah ikhtisar masalah dari Agustus lalu, khususnya tautan ke dua artikel Raymond Chen.

Sunting: lihat juga komentar @velovix tiga, yang masuk saat saya mengerjakan yang ini.

@daved , saya senang analogi "relai pelindung" bekerja untuk Anda. Ini tidak bekerja untuk saya. Program bukanlah sirkuit.

Setiap kata dapat disalahpahami:
"break" tidak merusak program Anda.
"lanjutkan" tidak melanjutkan eksekusi pada pernyataan berikutnya seperti biasa.
"goto" ... yah goto tidak mungkin salah paham sebenarnya. :-)

https://www.google.com/search?q=define+try mengatakan "berusaha atau berusaha untuk melakukan sesuatu" dan "tunduk pada percobaan". Keduanya berlaku untuk "f := try(os.Open(file))". Ia mencoba untuk melakukan os.Open (atau, itu mengarahkan hasil kesalahan ke percobaan), dan jika upaya (atau hasil kesalahan) gagal, ia kembali dari fungsi.

Kami menggunakan cek Agustus lalu. Itu kata yang bagus juga. Kami beralih untuk mencoba, terlepas dari beban historis C++/Java/Python, karena arti try saat ini dalam proposal ini cocok dengan arti dalam try Swift (tanpa do-catch di sekitarnya) dan dalam percobaan asli Rust! . Tidak akan buruk jika nanti kita memutuskan bahwa cek adalah kata yang tepat, tetapi untuk saat ini kita harus fokus pada hal-hal selain nama.

Berikut adalah negatif palsu tryhard yang menarik, dari github.com/josharian/pct . Saya menyebutkannya di sini karena:

  • itu menunjukkan cara di mana deteksi otomatis try rumit
  • itu menggambarkan bahwa biaya visual if err != nil berdampak pada bagaimana orang (setidaknya saya) menyusun kode mereka, dan try dapat membantu dengan itu

Sebelum:

var err error
switch {
case *flagCumulative:
    _, err = fmt.Fprintf(w, "% 6.2f%% % 6.2f%%% 6d %s\n", p, f*float64(runtot), line.n, line.s)
case *flagQuiet:
    _, err = fmt.Fprintln(w, line.s)
default:
    _, err = fmt.Fprintf(w, "% 6.2f%%% 6d %s\n", p, line.n, line.s)
}
if err != nil {
    return err
}

Setelah (penulisan ulang manual):

switch {
case *flagCumulative:
    try(fmt.Fprintf(w, "% 6.2f%% % 6.2f%%% 6d %s\n", p, f*float64(runtot), line.n, line.s))
case *flagQuiet:
    try(fmt.Fprintln(w, line.s))
default:
    try(fmt.Fprintf(w, "% 6.2f%%% 6d %s\n", p, line.n, line.s))
}

Ubah https://golang.org/cl/182717 menyebutkan masalah ini: src: apply tryhard -r $GOROOT/src

Untuk ide visual try di perpustakaan std, buka CL 182717 .

Terima kasih @josharian , untuk ini . Ya, bahkan tidak mungkin untuk alat yang bagus untuk mendeteksi semua kemungkinan penggunaan kandidat untuk try . Tapi untungnya itu bukan tujuan utama (dari proposal ini). Memiliki alat memang berguna, tetapi saya melihat manfaat utama dari try dalam kode yang belum ditulis (karena akan ada lebih banyak dari itu daripada kode yang sudah kita miliki).

"break" tidak merusak program Anda.
"lanjutkan" tidak melanjutkan eksekusi pada pernyataan berikutnya seperti biasa.
"goto" ... yah goto tidak mungkin salah paham sebenarnya. :-)

break memang memutus loop. continue melanjutkan perulangan, dan goto pergi ke tujuan yang ditunjukkan. Pada akhirnya, saya mendengar Anda, tetapi harap pertimbangkan apa yang terjadi ketika suatu fungsi sebagian besar selesai dan mengembalikan kesalahan, tetapi tidak mengembalikan. Itu bukan percobaan/percobaan. Saya pikir check jauh lebih unggul dalam hal itu (untuk "menghentikan kemajuan" melalui "pemeriksaan" tentu saja tepat).

Lebih relevan, saya ingin tahu tentang bentuk coba/periksa yang saya tawarkan sebagai lawan dari sintaks lainnya.
try {error} {optional wrap func} {optional return args in brackets}

f, err := os.Open(file)
try err wrap { a, b }

Pustaka standar akhirnya tidak mewakili kode Go "nyata" karena tidak menghabiskan banyak waktu untuk mengoordinasikan atau menghubungkan paket lain. Kami telah memperhatikan ini di masa lalu sebagai alasan mengapa hanya ada sedikit penggunaan saluran di perpustakaan standar dibandingkan dengan paket yang lebih jauh di rantai makanan ketergantungan. Saya menduga penanganan kesalahan dan propagasi akhirnya mirip dengan saluran dalam hal ini: Anda akan menemukan lebih banyak semakin tinggi Anda pergi.

Untuk alasan ini, akan menarik bagi seseorang untuk menjalankan tryhard pada beberapa basis kode aplikasi yang lebih besar dan melihat hal-hal menyenangkan apa yang dapat ditemukan dalam konteks itu. (Perpustakaan standar juga menarik, tetapi lebih merupakan mikrokosmos daripada sampel dunia yang akurat.)

Saya ingin tahu tentang bentuk coba/periksa yang saya tawarkan sebagai lawan dari sintaks lainnya.

Saya pikir bentuk itu akhirnya menciptakan kembali struktur kontrol yang ada .

@networkimprov , kembali https://github.com/golang/go/issues/32437#issuecomment -502879351

Sejujurnya saya kecewa bahwa tim Go menawari kami pemeriksaan/pegangan pertama (dengan murah hati, ide baru), dan kemudian try() ternaryesque. Saya tidak mengerti mengapa Anda tidak mengeluarkan penanganan kesalahan ulang RFP, dan kemudian mengumpulkan komentar komunitas pada beberapa proposal yang dihasilkan (lihat #29860). Ada banyak kebijaksanaan di sini yang bisa Anda manfaatkan!

Seperti yang kita diskusikan di #29860, sejujurnya saya tidak melihat banyak perbedaan antara apa yang Anda sarankan agar kita lakukan sejauh meminta umpan balik komunitas dan apa yang sebenarnya kita lakukan. Halaman draf desain secara eksplisit mengatakan bahwa mereka adalah "titik awal untuk diskusi, dengan tujuan akhirnya menghasilkan desain yang cukup baik untuk diubah menjadi proposal aktual." Dan orang-orang memang menulis banyak hal mulai dari umpan balik singkat hingga proposal alternatif lengkap. Dan sebagian besar sangat membantu dan saya menghargai bantuan Anda khususnya dalam mengatur dan meringkas. Anda tampaknya terpaku pada menyebutnya nama yang berbeda atau memperkenalkan lapisan birokrasi tambahan, yang seperti yang kita diskusikan tentang masalah itu, kita tidak benar-benar melihat kebutuhan untuk itu.

Tapi tolong jangan mengklaim bahwa kami entah bagaimana tidak meminta saran komunitas atau mengabaikannya. Itu tidak benar.

Saya juga tidak bisa melihat bagaimana coba dengan cara apa pun "ternaryesque", apa pun artinya.

Setuju, saya pikir itu adalah tujuan saya; Saya tidak berpikir mekanisme yang lebih kompleks bermanfaat. Jika saya berada di posisi Anda, yang paling saya tawarkan adalah sedikit gula sintaksis untuk membungkam sebagian besar keluhan dan tidak lebih.

@rsc , mohon maaf karena menyimpang dari topik!
Saya mengangkat penangan tingkat paket di https://github.com/golang/go/issues/32437#issuecomment -502840914
dan menanggapi permintaan klarifikasi Anda di https://github.com/golang/go/issues/32437#issuecomment -502879351

Saya melihat penangan tingkat paket sebagai fitur yang hampir semua orang bisa dapatkan.

silakan gunakan sintaks try {} catch{}, jangan membangun lebih banyak roda

silakan gunakan sintaks try {} catch{}, jangan membangun lebih banyak roda

Saya pikir itu tepat untuk membuat roda yang lebih baik ketika roda yang digunakan orang lain berbentuk kotak

@jimwei

Penanganan kesalahan berbasis pengecualian mungkin merupakan roda yang sudah ada sebelumnya tetapi juga memiliki beberapa masalah yang diketahui. Pernyataan masalah dalam desain draf asli melakukan pekerjaan yang baik untuk menguraikan masalah ini.

Untuk menambahkan komentar saya sendiri yang kurang dipikirkan dengan baik, saya pikir menarik bahwa banyak bahasa baru yang sangat sukses (yaitu Swift, Rust, dan Go) belum mengadopsi pengecualian. Ini memberi tahu saya bahwa komunitas perangkat lunak yang lebih luas sedang memikirkan kembali pengecualian setelah bertahun-tahun kami harus bekerja dengan mereka.

Menanggapi https://github.com/golang/go/issues/32437#issuecomment -502837008 ( komentar @rsc tentang try sebagai pernyataan)

Anda meningkatkan poin yang bagus. Saya minta maaf karena entah bagaimana saya melewatkan komentar itu sebelum membuat yang ini: https://github.com/golang/go/issues/32437#issuecomment -502871889

Contoh Anda dengan try sebagai ekspresi terlihat jauh lebih baik daripada contoh dengan try sebagai pernyataan. Fakta bahwa pernyataan tersebut mengarah dengan try sebenarnya membuat lebih sulit untuk dibaca. Namun, saya masih khawatir bahwa orang-orang akan mencoba panggilan bersama untuk membuat kode yang buruk, karena try sebagai ekspresi benar-benar _mendorong_ perilaku ini di mata saya.

Saya rasa saya akan lebih menghargai proposal ini jika golint melarang panggilan try bersarang. Saya pikir melarang semua panggilan try di dalam ekspresi lain agak terlalu ketat, memiliki try sebagai ekspresi memang memiliki kelebihan.

Meminjam contoh Anda, bahkan hanya bersarang 2 panggilan coba bersama terlihat cukup mengerikan, dan saya dapat melihat pemrogram Go melakukannya, terutama jika mereka bekerja tanpa peninjau kode.

parentCommitOne := try(remoteBranch.Reference.Peel(git.ObjectCommit)).AsCommit()
parentCommitTwo := try(try(r.Head()).Peel(git.ObjectCommit)).AsCommit()
tree := try(r.LookupTree(try(index.WriteTree())))

Contoh aslinya sebenarnya terlihat cukup bagus, tetapi yang ini menunjukkan bahwa bersarangnya ekspresi try (bahkan hanya 2-deep) benar-benar merusak keterbacaan kode secara drastis. Menolak panggilan try bersarang juga akan membantu masalah "kemampuan debug", karena jauh lebih mudah untuk memperluas try menjadi if jika berada di luar ekspresi.

Sekali lagi, saya hampir ingin mengatakan bahwa try di dalam sub-ekspresi harus ditandai oleh golint , tapi saya pikir itu mungkin agak terlalu ketat. Itu juga akan menandai kode seperti ini, yang menurut saya baik-baik saja:

x := 5 + try(strconv.Atoi(input))

Dengan cara ini, kami mendapatkan kedua manfaat dari memiliki try sebagai ekspresi, tetapi kami tidak mempromosikan penambahan terlalu banyak kerumitan pada sumbu horizontal.

Mungkin solusi lain adalah golint seharusnya hanya mengizinkan maksimum 1 try per pernyataan, tetapi sudah terlambat, saya mulai lelah, dan saya perlu memikirkannya lebih rasional. Either way, saya telah cukup negatif terhadap proposal ini di beberapa titik, tapi saya pikir saya benar-benar dapat berubah untuk benar-benar menyukainya selama ada beberapa standar golint yang terkait dengannya.

@rsc

Kita dapat dan harus membedakan antara _"fitur ini dapat digunakan untuk menulis kode yang sangat mudah dibaca, tetapi juga dapat disalahgunakan untuk menulis kode yang tidak dapat dibaca"_ dan "penggunaan dominan fitur ini adalah untuk menulis kode yang tidak dapat dibaca".
Pengalaman dengan C menunjukkan bahwa ? : jatuh tepat ke dalam kategori kedua. (Dengan kemungkinan pengecualian min dan maks,

Apa yang pertama kali mengejutkan saya tentang try() — vs try sebagai pernyataan — adalah betapa miripnya nestability dengan operator ternary dan betapa berlawanannya argumen untuk try() dan melawan ternary adalah _(parafrase):_

  • ternary: _"Jika kita mengizinkannya, orang akan membuat sarangnya dan hasilnya akan banyak kode buruk"_ mengabaikan bahwa beberapa orang menulis kode yang lebih baik dengan mereka, vs.
  • try(): _"Anda dapat membuat sarangnya, tetapi kami ragu banyak yang akan melakukannya karena kebanyakan orang ingin menulis kode yang bagus"_,

Dengan hormat, bahwa rasional untuk perbedaan antara keduanya terasa sangat subjektif. Saya akan meminta beberapa introspeksi dan setidaknya mempertimbangkan apakah Anda mungkin merasionalisasi perbedaan untuk fitur yang Anda sukai vs. terhadap fitur yang tidak Anda sukai? #please_dont_shoot_the_messenger

_"Saya tidak yakin pernah melihat kode menggunakan ? : yang tidak diperbaiki dengan menulis ulang untuk menggunakan pernyataan if. Tapi paragraf ini keluar dari topik.)"_

Dalam bahasa lain saya sering meningkatkan pernyataan dengan menulis ulang dari if ke operator ternary, misalnya dari kode yang saya tulis hari ini di PHP:

return isset( $_COOKIE[ CookieNames::CART_ID ] )
    ? intval( $_COOKIE[ CookieNames::CART_ID ] )
    : null;

Dibandingkan dengan:

if ( isset( $_COOKIE[ CookieNames::CART_ID ] ) ) {
    return intval( $_COOKIE[ CookieNames::CART_ID ] );
} else { 
    return null;
}

Sejauh yang saya ketahui, yang pertama jauh lebih baik daripada yang terakhir.

fwiw

Saya pikir kritik terhadap proposal ini sebagian besar disebabkan oleh ekspektasi tinggi yang diajukan oleh proposal sebelumnya, yang akan jauh lebih komprehensif. Namun, saya pikir harapan yang tinggi seperti itu dibenarkan karena alasan konsistensi. Saya pikir apa yang ingin dilihat banyak orang, adalah konstruksi tunggal yang komprehensif untuk penanganan kesalahan yang berguna dalam semua kasus penggunaan.

Bandingkan fitur ini, misalnya, dengan fungsi append() bawaan. Tambahkan dibuat karena menambahkan ke irisan adalah kasus penggunaan yang sangat umum, dan meskipun dimungkinkan untuk melakukannya secara manual, juga mudah untuk melakukan kesalahan. Sekarang append() memungkinkan untuk menambahkan tidak hanya satu, tetapi banyak elemen, atau bahkan seluruh irisan, dan bahkan memungkinkan untuk menambahkan string ke irisan []byte. Ini cukup kuat untuk mencakup semua kasus penggunaan menambahkan ke irisan. Dan karenanya, tidak ada lagi yang menambahkan irisan secara manual.

Namun, try() berbeda. Itu tidak cukup kuat sehingga kami dapat menggunakannya dalam semua kasus penanganan kesalahan. Dan saya pikir itulah kelemahan paling serius dari proposal ini. Fungsi bawaan try() hanya benar-benar berguna, dalam arti mengurangi boilerplate, dalam kasus yang paling sederhana, yaitu hanya meneruskan kesalahan ke pemanggil, dan dengan pernyataan penangguhan, jika semua kesalahan fungsi harus ditangani dengan cara yang sama.

Untuk penanganan kesalahan yang lebih kompleks, kita masih perlu menggunakan if err != nil {} . Ini kemudian mengarah ke dua gaya berbeda untuk penanganan kesalahan, di mana sebelumnya hanya ada satu. Jika hanya proposal ini yang bisa kami bantu dengan penanganan kesalahan di Go, maka, saya pikir akan lebih baik untuk tidak melakukan apa-apa dan terus menangani penanganan kesalahan dengan if seperti yang selalu kami lakukan, karena setidaknya, ini konsisten dan mendapat manfaat dari "hanya ada satu cara untuk melakukannya".

@rsc , mohon maaf karena menyimpang dari topik!
Saya mengangkat penangan tingkat paket di #32437 (komentar)
dan menanggapi permintaan klarifikasi Anda di #32437 (komentar)

Saya melihat penangan tingkat paket sebagai fitur yang hampir semua orang bisa dapatkan.

Saya tidak melihat apa yang menyatukan konsep paket dengan penanganan kesalahan tertentu. Sulit membayangkan konsep handler tingkat paket berguna untuk, katakanlah, net/http . Dalam nada yang sama, meskipun menulis paket yang lebih kecil dari net/http secara umum, saya tidak dapat memikirkan satu kasus penggunaan di mana saya lebih suka konstruksi tingkat paket untuk melakukan penanganan kesalahan. Secara umum, saya telah menemukan bahwa asumsi bahwa setiap orang berbagi pengalaman, kasus penggunaan, dan pendapat adalah hal yang berbahaya :)

@beoran saya percaya proposal ini memungkinkan perbaikan lebih lanjut. Suka dekorator pada argumen terakhir try(..., func(err) error) , atau tryf(..., "context of my error: %w") ?

@flibustenet Meskipun ekstensi yang lebih baru dapat dimungkinkan, proposal seperti sekarang tampaknya tidak mendukung ekstensi tersebut, sebagian besar karena menambahkan penangan kesalahan akan berlebihan dengan penangguhan.

Saya kira masalah yang sulit adalah bagaimana memiliki penanganan kesalahan yang komprehensif tanpa menduplikasi fungsi defe. Mungkin pernyataan penangguhan itu sendiri dapat ditingkatkan entah bagaimana untuk memungkinkan penanganan kesalahan yang lebih mudah dalam kasus yang lebih kompleks ... Tapi, itu masalah yang berbeda.

https://github.com/golang/go/issues/32437#issuecomment-502975437

Ini kemudian mengarah ke dua gaya berbeda untuk penanganan kesalahan, di mana sebelumnya hanya ada satu. Jika hanya proposal ini yang bisa kami bantu dengan penanganan kesalahan di Go, maka, saya pikir akan lebih baik untuk tidak melakukan apa-apa dan terus menangani penanganan kesalahan dengan if seperti yang selalu kami lakukan, karena setidaknya, ini konsisten dan mendapat manfaat dari "hanya ada satu cara untuk melakukannya".

@beoran Setuju. Inilah mengapa saya menyarankan agar kita menyatukan sebagian besar kasus kesalahan di bawah kata kunci try ( try dan try / else ). Meskipun sintaks try / else tidak memberi kita pengurangan yang signifikan dalam panjang kode dibandingkan dengan gaya if err != nil yang ada, ini memberi kita konsistensi dengan try (tidak ada else ) kasus. Kedua kasus tersebut (coba dan coba-lain) cenderung mencakup sebagian besar kasus penanganan kesalahan. Saya meletakkannya sebagai lawan dari versi try bawaan yang hanya berlaku dalam kasus di mana programmer tidak benar-benar melakukan apa pun untuk menangani kesalahan selain mengembalikan (yang, seperti yang disebutkan orang lain di utas ini, belum tentu sesuatu yang benar-benar ingin kita dorong sejak awal).

Konsistensi penting untuk keterbacaan.

append adalah cara definitif untuk menambahkan elemen ke irisan. make adalah cara pasti untuk membangun saluran atau peta atau irisan baru (dengan pengecualian literal, yang tidak saya sukai). Tetapi try() (sebagai bawaan, dan tanpa else ) akan ditaburkan di seluruh basis kode, tergantung pada bagaimana programmer perlu menangani kesalahan yang diberikan, dengan cara yang mungkin agak kacau dan membingungkan untuk pembaca. Tampaknya tidak sesuai dengan semangat bawaan lainnya (yaitu, menangani kasus yang cukup sulit atau tidak mungkin dilakukan sebaliknya). Jika ini adalah versi try yang berhasil, konsistensi dan keterbacaan akan memaksa saya untuk tidak menggunakannya, sama seperti saya mencoba untuk menghindari peta/slice literal (dan menghindari new seperti wabah).

Jika idenya adalah untuk mengubah cara menangani kesalahan, tampaknya bijaksana untuk mencoba menyatukan pendekatan di sebanyak mungkin kasus, daripada menambahkan sesuatu yang, paling-paling, akan "ambil atau tinggalkan." Saya khawatir yang terakhir akan benar-benar menambah kebisingan daripada menguranginya.

@deanveloper menulis:

Saya pikir saya akan lebih menghargai proposal ini jika golint melarang panggilan percobaan bersarang.

Saya setuju bahwa try yang sangat bersarang mungkin sulit dibaca. Tetapi ini juga berlaku untuk panggilan fungsi standar, bukan hanya fungsi bawaan try . Jadi saya tidak mengerti mengapa golint harus melarang ini.

@brynbelomy menulis:

Meskipun sintaks try/else tidak memberi kita pengurangan yang signifikan dalam panjang kode dibandingkan dengan gaya if err != nil yang ada, ini memberi kita konsistensi dengan kasus try (tidak ada yang lain).

Tujuan unik dari fungsi try adalah untuk mengurangi boilerplate, jadi sulit untuk melihat mengapa kami harus mengadopsi sintaks try/else yang Anda usulkan ketika Anda mengakui bahwa itu "tidak memberi kami pengurangan yang signifikan dalam panjang kode".

Anda juga menyebutkan bahwa sintaks yang Anda usulkan membuat kasus percobaan konsisten dengan kasus coba/lain. Tapi itu juga menciptakan cara yang tidak konsisten untuk bercabang, ketika kita sudah memiliki if/else. Anda mendapatkan sedikit konsistensi pada kasus penggunaan tertentu tetapi kehilangan banyak inkonsistensi pada sisanya.

Saya merasa perlu untuk mengungkapkan pendapat saya untuk apa mereka layak. Meskipun tidak semua ini bersifat akademis dan teknis, saya pikir itu perlu dikatakan.

Saya percaya perubahan ini adalah salah satu kasus di mana rekayasa dilakukan demi rekayasa dan "kemajuan" digunakan untuk pembenaran. Penanganan kesalahan di Go tidak rusak dan proposal ini melanggar banyak filosofi desain yang saya sukai tentang Go.

Buatlah hal-hal yang mudah dipahami, tidak mudah dilakukan
Proposal ini memilih mengoptimalkan kemalasan daripada kebenaran. Fokusnya adalah membuat penanganan kesalahan lebih mudah dan sebagai imbalannya sejumlah besar keterbacaan hilang. Sifat penanganan kesalahan yang kadang-kadang membosankan dapat diterima karena perolehan keterbacaan dan kemampuan debugg.

Hindari menamai argumen pengembalian
Ada beberapa kasus tepi dengan pernyataan defer di mana penamaan argumen return valid. Di luar ini, itu harus dihindari. Proposal ini mempromosikan penggunaan argumen pengembalian penamaan. Ini tidak akan membantu membuat kode Go lebih mudah dibaca.

Enkapsulasi harus membuat semantik baru di mana seseorang benar-benar tepat
Tidak ada presisi dalam sintaks baru ini. Menyembunyikan variabel kesalahan dan pengembalian tidak membantu membuat segalanya lebih mudah dipahami. Faktanya, sintaksnya terasa sangat asing dari apa pun yang kita lakukan di Go hari ini. Jika seseorang menulis fungsi yang serupa, saya yakin komunitas akan setuju bahwa abstraksi menyembunyikan biaya dan tidak sebanding dengan kesederhanaan yang coba diberikannya.

Siapa yang kita coba bantu?
Saya khawatir perubahan ini diterapkan dalam upaya untuk menarik pengembang perusahaan menjauh dari bahasa mereka saat ini dan beralih ke Go. Menerapkan perubahan bahasa, hanya untuk menambah jumlah, menjadi preseden buruk. Saya pikir wajar untuk mengajukan pertanyaan ini dan mendapatkan jawaban atas masalah bisnis yang coba dipecahkan dan keuntungan yang diharapkan yang ingin dicapai?

Saya telah melihat ini sebelumnya beberapa kali sekarang. Tampaknya cukup jelas, dengan semua aktivitas baru-baru ini dari tim bahasa, proposal ini pada dasarnya sudah siap. Ada lebih banyak pembelaan implementasi daripada perdebatan aktual tentang implementasi itu sendiri. Semua ini dimulai 13 hari yang lalu. Kami akan melihat dampak perubahan ini pada bahasa, komunitas, dan masa depan Go.

Penanganan kesalahan di Go tidak rusak dan proposal ini melanggar banyak filosofi desain yang saya sukai tentang Go.

Bill mengungkapkan pikiran saya dengan sempurna.

Saya tidak dapat menghentikan try diperkenalkan, tetapi jika ya, saya tidak akan menggunakannya sendiri; Saya tidak akan mengajarkannya, dan saya tidak akan menerimanya dalam PR yang saya ulas. Itu hanya akan ditambahkan ke daftar 'hal-hal lain di Go yang tidak pernah saya gunakan' (lihat pembicaraan lucu Mat Ryer di YouTube untuk lebih banyak tentang ini).

@ardan-bkennedy, terima kasih atas komentar Anda.

Anda bertanya tentang "masalah bisnis yang coba dipecahkan". Saya tidak percaya kami menargetkan masalah bisnis tertentu kecuali mungkin "Pemrograman Go". Tetapi secara lebih umum kami mengartikulasikan masalah yang kami coba selesaikan Agustus lalu dalam kickoff diskusi draf desain Gophercon (lihat Ikhtisar Masalah terutama bagian Sasaran). Fakta bahwa percakapan ini telah berlangsung sejak Agustus lalu juga sangat bertentangan dengan klaim Anda bahwa "Semua ini dimulai 13 hari yang lalu."

Anda bukan satu-satunya orang yang menyarankan bahwa ini bukan masalah atau bukan masalah yang layak dipecahkan. Lihat https://swtch.com/try.html#nonissue untuk komentar serupa lainnya. Kami telah mencatatnya dan ingin memastikan bahwa kami memecahkan masalah yang sebenarnya. Salah satu cara untuk mengetahuinya adalah dengan mengevaluasi proposal pada basis kode nyata. Alat seperti Robert's tryhard membantu kami melakukannya. Sebelumnya saya meminta orang-orang untuk memberi tahu kami apa yang mereka temukan di basis kode mereka sendiri. Informasi itu akan sangat penting untuk mengevaluasi apakah perubahan itu bermanfaat atau tidak. Anda punya satu tebakan dan saya punya tebakan lain, dan itu bagus. Jawabannya adalah dengan mengganti data untuk tebakan tersebut.

Kami akan melakukan apa yang diperlukan untuk memastikan kami memecahkan masalah yang sebenarnya.

Sekali lagi, jalur ke depan adalah data eksperimental, bukan reaksi usus. Sayangnya, data membutuhkan lebih banyak upaya untuk dikumpulkan. Pada titik ini, saya akan mendorong orang-orang yang ingin membantu untuk keluar dan mengumpulkan data.

@ardan-bkennedy, maaf untuk tindak lanjut kedua tetapi mengenai:

Saya khawatir perubahan ini diterapkan dalam upaya untuk menarik pengembang perusahaan menjauh dari bahasa mereka saat ini dan beralih ke Go. Menerapkan perubahan bahasa, hanya untuk menambah jumlah, menjadi preseden buruk.

Ada dua masalah serius dengan jalur ini yang tidak bisa saya lewati.

Pertama, saya menolak klaim implisit bahwa ada kelas pengembang – dalam hal ini "pengembang perusahaan" – yang entah bagaimana tidak layak menggunakan Go atau mempertimbangkan masalah mereka. Dalam kasus khusus "perusahaan", kami melihat banyak contoh perusahaan kecil dan besar yang menggunakan Go dengan sangat efektif.

Kedua, sejak awal proyek Go, kami – Robert, Rob, Ken, Ian, dan saya – telah mengevaluasi perubahan bahasa dan fitur berdasarkan pengalaman kolektif kami dalam membangun banyak sistem. Kami bertanya "apakah ini akan bekerja dengan baik dalam program yang kami tulis?" Itu telah menjadi resep yang sukses dengan penerapan yang luas dan merupakan resep yang ingin kami terus gunakan, sekali lagi ditambah dengan data yang saya minta di komentar sebelumnya dan laporan pengalaman secara lebih umum. Kami tidak akan menyarankan atau mendukung perubahan bahasa yang tidak dapat kami lihat digunakan dalam program kami sendiri atau yang menurut kami tidak cocok dengan Go. Dan kami tentu tidak akan menyarankan atau mendukung perubahan buruk hanya untuk memiliki lebih banyak programmer Go. Kami juga menggunakan Go.

@rsc
Tidak akan ada kekurangan lokasi di mana kenyamanan ini dapat ditempatkan. Metrik apa yang sedang dicari yang akan membuktikan substansi mekanisme selain itu? Apakah ada daftar kasus penanganan kesalahan yang diklasifikasikan? Bagaimana nilai akan diperoleh dari data ketika sebagian besar proses publik didorong oleh sentimen?

Alat tryhard sangat informatif !
Saya dapat melihat bahwa saya sering menggunakan return ...,err , tetapi hanya ketika saya tahu bahwa saya memanggil fungsi yang sudah membungkus kesalahan (dengan pkg/errors ), sebagian besar di penangan http. Saya menang dalam keterbacaan dengan lebih sedikit baris kode.
Kemudian di tesis http handler saya akan menambahkan defer fmt.HandleErrorf(&err, "handler xyz") dan akhirnya menambahkan lebih banyak konteks dari sebelumnya.

Saya juga melihat banyak kasus di mana saya tidak peduli dengan kesalahan sama sekali fmt.Printf dan saya akan melakukannya dengan try .
Apakah mungkin misalnya untuk melakukan defer try(f.Close()) ?

Jadi, mungkin try akhirnya akan membantu menambahkan konteks dan mendorong praktik terbaik daripada sebaliknya.

Saya sangat tidak sabar untuk menguji secara nyata!

@flibustenet Proposal apa adanya tidak mengizinkan defer try(f()) (lihat alasannya ). Ada segala macam masalah dengan itu.

Saat menggunakan alat tryhard ini untuk melihat perubahan dalam basis kode, dapatkah kita juga membandingkan rasio if err != nil sebelum dan sesudah untuk melihat apakah lebih umum menambahkan konteks atau hanya meneruskan kesalahan?

Pemikiran saya adalah bahwa mungkin proyek besar hipotetis dapat melihat 1000 tempat di mana try() ditambahkan tetapi ada 10.000 if err != nil yang menambahkan konteks jadi meskipun 1000 terlihat besar, itu hanya 10% dari keseluruhan .

@Goodwine Ya. Saya mungkin tidak akan melakukan perubahan ini minggu ini, tetapi kodenya cukup sederhana dan mandiri. Jangan ragu untuk mencobanya (tidak ada permainan kata-kata), kloning, dan sesuaikan sesuai kebutuhan.

Bukankah defer try(f()) sama dengan

defer func() error {
    if err:= f(); err != nil { return err }
    return nil
}()

Ini (versi if) saat ini tidak dilarang, bukan? Menurut saya, Anda tidak boleh membuat pengecualian di sini -- dapatkah menghasilkan peringatan? Dan tidak jelas apakah kode penangguhan di atas salah. Bagaimana jika close(file) gagal dalam pernyataan defer ? Haruskah kita melaporkan kesalahan itu atau tidak?

Saya membaca alasan yang sepertinya berbicara tentang defer try(f) bukan defer try(f()) . Mungkin salah ketik?

Argumen serupa dapat dibuat untuk go try(f()) , yang diterjemahkan menjadi

go func() error {
    if err:= f(); err != nil { return err }
    return nil
}()

Di sini try tidak melakukan sesuatu yang berguna tetapi tidak berbahaya.

@ardan-bkennedy Terima kasih atas pemikiran Anda. Dengan segala hormat, saya yakin Anda telah salah mengartikan maksud dari proposal ini dan membuat beberapa klaim yang tidak berdasar .

Mengenai beberapa poin yang belum dibahas @rsc sebelumnya:

  • Kami tidak pernah mengatakan penanganan kesalahan rusak. Desainnya didasarkan pada pengamatan (oleh komunitas Go!) bahwa penanganan saat ini baik-baik saja, tetapi dalam banyak kasus, ini tidak terbantahkan. Ini adalah premis utama dari proposal.

  • Membuat sesuatu lebih mudah untuk dilakukan juga dapat membuatnya lebih mudah untuk dipahami - keduanya tidak saling mengecualikan, atau bahkan menyiratkan satu sama lain. Saya mendorong Anda untuk melihat kode ini sebagai contoh. Menggunakan try menghapus sejumlah besar boilerplate, dan boilerplate itu hampir tidak menambah pemahaman kode. Anjak keluar dari kode berulang adalah praktik pengkodean standar dan diterima secara luas untuk meningkatkan kualitas kode.

  • Mengenai "proposal ini melanggar banyak filosofi desain": Yang penting adalah kita tidak menjadi dogmatis tentang "filosofi desain" - yang sering kali merupakan kejatuhan ide-ide bagus (selain itu, saya pikir kita tahu satu atau dua hal tentang filosofi desain Go). Ada banyak "semangat agama" (karena tidak ada istilah yang lebih baik) di sekitar parameter hasil bernama vs tidak disebutkan namanya. Mantra seperti "Anda tidak boleh menggunakan parameter hasil bernama" di luar konteks tidak ada artinya. Mereka mungkin berfungsi sebagai pedoman umum, tetapi bukan kebenaran mutlak. Parameter hasil bernama tidak secara inheren "buruk". Parameter hasil yang diberi nama baik dapat ditambahkan ke dokumentasi API dengan cara yang berarti. Singkatnya, jangan gunakan slogan untuk membuat keputusan desain bahasa.

  • Ini adalah poin dari proposal ini untuk tidak memperkenalkan sintaks baru. Itu hanya mengusulkan fungsi baru. Kami tidak dapat menulis fungsi itu dalam bahasa, jadi built-in adalah tempat alami untuk itu di Go. Tidak hanya itu fungsi sederhana, itu juga didefinisikan dengan sangat tepat. Kami memilih pendekatan minimal ini daripada solusi yang lebih komprehensif karena pendekatan ini melakukan satu hal dengan sangat baik dan hampir tidak menyisakan apa pun untuk keputusan desain yang sewenang-wenang. Kami juga tidak keluar jalur karena bahasa lain (misalnya Rust) memiliki konstruksi yang sangat mirip. Menyarankan bahwa "komunitas akan setuju bahwa abstraksi menyembunyikan biaya dan tidak sebanding dengan kesederhanaan yang coba diberikannya" adalah memasukkan kata-kata ke mulut orang lain. Meskipun kami dapat dengan jelas mendengar lawan vokal dari proposal ini, ada persentase yang signifikan (diperkirakan 40%) orang yang menyatakan persetujuan untuk melanjutkan eksperimen. Mari kita tidak mencabut hak mereka dengan hiperbola.

Terima kasih.

return isset( $_COOKIE[ CookieNames::CART_ID ] )
    ? intval( $_COOKIE[ CookieNames::CART_ID ] )
    : null;

Cukup yakin ini seharusnya return intval( $_COOKIE[ CookieNames::CART_ID ] ) ?? null; FWIW. 😁

@bakul karena argumen dievaluasi segera, sebenarnya kira-kira setara dengan:

<result list> := f()
defer try(<result list>)

Ini mungkin perilaku yang tidak terduga bagi sebagian orang karena f() tidak ditangguhkan untuk nanti, itu langsung dieksekusi. Hal yang sama berlaku untuk go try(f()) .

@bakul Dokumen menyebutkan defer try(f) (daripada defer try(f()) karena try secara umum berlaku untuk ekspresi apa pun, bukan hanya panggilan fungsi (Anda dapat mengatakan try(err) for contoh, jika err bertipe error ). Jadi bukan salah ketik, tapi mungkin membingungkan pada awalnya. f adalah singkatan dari ekspresi, yang biasanya merupakan fungsi panggilan.

@deanveloper , @griesemer Sudahlah :-) Terima kasih.

@carl-mastrangelo

_"Cukup yakin ini seharusnya return intval( $_COOKIE[ CookieNames::CART_ID ] ) ?? null; _

Anda mengasumsikan PHP 7.x. Aku tidak. Tetapi sekali lagi, mengingat wajah Anda yang snarky, Anda tahu bukan itu intinya. :mengedip:

Saya sedang mempersiapkan demonstrasi singkat untuk menampilkan diskusi ini selama pertemuan yang berlangsung besok, dan mendengar beberapa pemikiran baru, karena saya yakin sebagian besar peserta di utas ini (kontributor atau pengamat), adalah mereka yang terlibat lebih dalam dalam bahasa, dan kemungkinan besar "bukan pengembang go biasa" (hanya firasat).

Saat melakukan itu, saya ingat kami benar-benar mengadakan pertemuan tentang kesalahan dan diskusi tentang dua pola:

  1. Perluas struct kesalahan sambil mendukung antarmuka kesalahan mystruct.Error()
  2. Sematkan kesalahan baik sebagai bidang atau bidang anonim dari struct
type ExtErr struct{
  error
  someOtherField string
}  

Ini digunakan dalam beberapa tumpukan yang dibuat oleh tim saya.

Proposal T&J menyatakan
T: Argumen terakhir yang diteruskan untuk mencoba harus dari kesalahan tipe. Mengapa argumen yang masuk tidak cukup untuk ditetapkan ke kesalahan?
A: "... Kami dapat meninjau kembali keputusan ini di masa mendatang jika diperlukan"

Adakah yang bisa mengomentari kasus penggunaan serupa sehingga kami dapat memahami jika kebutuhan ini umum untuk kedua opsi perluasan kesalahan di atas?

@mikeschinkel Saya bukan Carl yang Anda cari.

@daved , ulang:

Tidak akan ada kekurangan lokasi di mana kenyamanan ini dapat ditempatkan. Metrik apa yang sedang dicari yang akan membuktikan substansi mekanisme selain itu? Apakah ada daftar kasus penanganan kesalahan yang diklasifikasikan? Bagaimana nilai akan diperoleh dari data ketika sebagian besar proses publik didorong oleh sentimen?

Keputusan didasarkan pada seberapa baik ini bekerja dalam program nyata. Jika orang menunjukkan kepada kita bahwa try tidak efektif dalam sebagian besar kode mereka, itu data penting. Prosesnya didorong oleh data semacam itu. Ini _tidak_ didorong oleh sentimen.

Konteks Kesalahan

Kekhawatiran semantik terpenting yang diangkat dalam masalah ini adalah apakah try akan mendorong anotasi kesalahan yang lebih baik atau lebih buruk dengan konteks.

Tinjauan Masalah dari Agustus lalu memberikan urutan contoh implementasi CopyFile di bagian Masalah dan Tujuan. Ini adalah tujuan eksplisit, baik saat itu maupun hari ini, bahwa solusi apa pun membuatnya _lebih mungkin_ bahwa pengguna menambahkan konteks yang sesuai ke kesalahan. Dan kami pikir try bisa melakukan itu, atau kami tidak akan mengusulkannya.

Tetapi sebelum kita mencoba, ada baiknya memastikan kita semua berada di halaman yang sama tentang konteks kesalahan yang sesuai. Contoh kanoniknya adalah os.Open. Mengutip posting blog Go “ Penanganan kesalahan dan Go ”:

Ini adalah tanggung jawab implementasi kesalahan untuk meringkas konteksnya.
Kesalahan dikembalikan oleh format os.Open sebagai "buka /etc/passwd: izin ditolak," bukan hanya "izin ditolak."

Lihat juga bagian Go yang Efektif tentang Kesalahan .

Perhatikan bahwa konvensi ini mungkin berbeda dari bahasa lain yang Anda kenal, dan ini juga hanya diikuti secara tidak konsisten dalam kode Go. Tujuan eksplisit dari mencoba merampingkan penanganan kesalahan adalah untuk memudahkan orang mengikuti konvensi ini dan menambahkan konteks yang sesuai, dan dengan demikian membuatnya diikuti lebih konsisten.

Ada banyak kode yang mengikuti konvensi Go hari ini, tetapi ada juga banyak kode yang mengasumsikan konvensi yang berlawanan. Terlalu umum untuk melihat kode seperti:

f, err := os.Open(file)
if err != nil {
    log.Fatalf("opening %s: %v", file, err)
}

yang tentu saja mencetak hal yang sama dua kali (banyak contoh dalam diskusi ini terlihat seperti ini). Bagian dari upaya ini harus memastikan semua orang tahu dan mengikuti konvensi.

Dalam kode yang mengikuti konvensi konteks kesalahan Go, kami berharap bahwa sebagian besar fungsi akan menambahkan konteks yang sama dengan benar ke setiap pengembalian kesalahan, sehingga satu dekorasi berlaku secara umum. Misalnya, dalam contoh CopyFile, yang perlu ditambahkan dalam setiap kasus adalah detail tentang apa yang disalin. Pengembalian spesifik lainnya mungkin menambahkan lebih banyak konteks, tetapi biasanya sebagai tambahan daripada sebagai pengganti. Jika kita salah tentang harapan ini, itu bagus untuk diketahui. Bukti yang jelas dari basis kode nyata akan membantu.

Desain draft check/handle Gophercon akan menggunakan kode seperti:

func CopyFile(src, dst string) error {
    handle err {
        return fmt.Errorf("copy %s %s: %v", src, dst, err)
    }

    r := check os.Open(src)
    defer r.Close()

    w := check os.Create(dst)
    ...
}

Proposal ini telah merevisi itu, tetapi idenya sama:

func CopyFile(src, dst string) (err error) {
    defer func() {
        if err != nil {
            err = fmt.Errorf("copy %s %s: %v", src, dst, err)
        }
    }()

    r := try(os.Open(src))
    defer r.Close()

    w := try(os.Create(dst))
    ...
}

dan kami ingin menambahkan pembantu yang belum disebutkan namanya untuk pola umum ini:

func CopyFile(src, dst string) (err error) {
    defer HelperToBeNamedLater(&err, "copy %s %s", src, dst)

    r := try(os.Open(src))
    defer r.Close()

    w := try(os.Create(dst))
    ...
}

Singkatnya, kewajaran dan keberhasilan pendekatan ini bergantung pada asumsi dan langkah logis berikut:

  1. Orang harus mengikuti konvensi Go yang dinyatakan "callee menambahkan konteks relevan yang diketahuinya."
  2. Oleh karena itu sebagian besar fungsi hanya perlu menambahkan konteks tingkat fungsi yang menjelaskan keseluruhan
    operasi, bukan sub-bagian tertentu yang gagal (sub-bagian itu sudah dilaporkan sendiri).
  3. Banyak kode Go hari ini tidak menambahkan konteks tingkat fungsi karena terlalu berulang.
  4. Menyediakan cara untuk menulis konteks tingkat fungsi sekali akan membuatnya lebih mungkin bahwa
    pengembang melakukan itu.
  5. Hasil akhirnya akan lebih banyak kode Go mengikuti konvensi dan menambahkan konteks yang sesuai.

Jika ada asumsi atau langkah logis yang menurut Anda salah, kami ingin tahu. Dan cara terbaik untuk memberi tahu kami adalah dengan menunjukkan bukti dalam basis kode yang sebenarnya. Tunjukkan pada kami pola umum yang Anda miliki ketika mencoba tidak sesuai atau memperburuk keadaan. Tunjukkan pada kami contoh hal-hal di mana percobaan lebih efektif daripada yang Anda harapkan. Cobalah untuk mengukur seberapa banyak basis kode Anda berada di satu sisi atau sisi lainnya. Dan seterusnya. Data penting.

Terima kasih.

Terima kasih @rsc untuk info tambahan tentang praktik terbaik konteks kesalahan. Poin tentang praktik terbaik ini secara khusus telah menyinggung saya, tetapi secara signifikan meningkatkan hubungan try dengan konteks kesalahan.

Oleh karena itu sebagian besar fungsi hanya perlu menambahkan konteks tingkat fungsi yang menjelaskan keseluruhan
operasi, bukan sub-bagian tertentu yang gagal (sub-bagian itu sudah dilaporkan sendiri).

Jadi, tempat di mana try tidak membantu adalah saat kita perlu bereaksi terhadap kesalahan, bukan hanya mengontekstualisasikannya.

Untuk mengadaptasi contoh dari Cleaner, lebih elegan, dan salah , berikut contoh fungsi yang agak salah dalam penanganan kesalahannya. Saya telah mengadaptasinya ke Go menggunakan try dan defer -style error wrapping:

func AddNewGuy(name string) (guy Guy, err error) {
    defer func() {
        if err != nil {
            err = fmt.Errorf("adding guy %v: %v", name, err)
        }
    }()

    guy = Guy{name: name}
    guy.Team = ChooseRandomTeam()
    try(guy.Team.Add(guy))
    try(AddToLeague(guy))
    return guy, nil
}

Fungsi ini salah karena jika guy.Team.Add(guy) berhasil tetapi AddToLeague(guy) gagal, tim akan memiliki objek Guy yang tidak valid yang tidak ada dalam liga. Kode yang benar akan terlihat seperti ini, di mana kita memutar kembali guy.Team.Add(guy) dan tidak dapat lagi menggunakan try :

func AddNewGuy(name string) (guy Guy, err error) {
    defer func() {
        if err != nil {
            err = fmt.Errorf("adding guy %v: %v", name, err)
        }
    }()

    guy = Guy{name: name}
    guy.Team = ChooseRandomTeam()
    try(guy.Team.Add(guy))
    if err := AddToLeague(guy); err != nil {
        guy.Team.Remove(guy)
        return Guy{}, err
    }
    return guy, nil
}

Atau, jika kita ingin menghindari keharusan memberikan nilai nol untuk nilai pengembalian non-kesalahan, kita dapat mengganti return Guy{}, err dengan try(err) . Terlepas dari itu, fungsi defer -ed masih berjalan dan konteks ditambahkan, yang bagus.

Sekali lagi, ini berarti bahwa try memberikan reaksi terhadap kesalahan, tetapi tidak menambahkan konteks pada kesalahan tersebut. Itulah perbedaan yang telah menyinggung saya dan mungkin orang lain. Ini masuk akal karena cara suatu fungsi menambahkan konteks ke kesalahan bukanlah hal yang menarik bagi pembaca, tetapi cara suatu fungsi bereaksi terhadap kesalahan itu penting. Kita seharusnya membuat bagian yang kurang menarik dari kode kita menjadi tidak terlalu bertele-tele, dan itulah yang dilakukan try .

Anda bukan satu-satunya orang yang menyarankan bahwa ini bukan masalah atau bukan masalah yang layak dipecahkan. Lihat https://swtch.com/try.html#nonissue untuk komentar serupa lainnya. Kami telah mencatatnya dan ingin memastikan bahwa kami memecahkan masalah yang sebenarnya.

@rsc Saya juga berpikir tidak ada masalah dengan kode kesalahan saat ini. Jadi, tolong, hitung aku.

Alat seperti Robert's tryhard membantu kami melakukannya. Sebelumnya saya meminta orang-orang untuk memberi tahu kami apa yang mereka temukan di basis kode mereka sendiri. Informasi itu akan sangat penting untuk mengevaluasi apakah perubahan itu bermanfaat atau tidak. Anda punya satu tebakan dan saya punya tebakan lain, dan itu bagus. Jawabannya adalah dengan mengganti data untuk tebakan tersebut.

Saya melihat https://go-review.googlesource.com/c/go/+/182717/1/src/cmd/link/internal/ld/macho_combine_dwarf.go dan saya lebih suka kode lama. Sangat mengejutkan bagi saya bahwa panggilan fungsi try mungkin mengganggu eksekusi saat ini. Itu bukan cara kerja Go saat ini.

Saya menduga, Anda akan menemukan pendapat akan bervariasi. Menurut saya ini sangat subjektif.

Dan, saya menduga, mayoritas pengguna tidak berpartisipasi dalam debat ini. Mereka bahkan tidak tahu bahwa perubahan ini akan datang. Saya sendiri cukup terlibat dengan Go, tetapi saya tidak berpartisipasi dalam perubahan ini, karena saya tidak punya waktu luang.

Saya pikir kita perlu mendidik kembali semua pengguna Go yang ada untuk berpikir secara berbeda sekarang.

Kami juga perlu memutuskan apa yang harus dilakukan dengan beberapa pengguna/perusahaan yang akan menolak untuk menggunakan try dalam kode mereka. Pasti akan ada beberapa.

Mungkin kita harus mengubah gofmt untuk menulis ulang kode saat ini secara otomatis. Untuk memaksa pengguna "nakal" seperti itu menggunakan fungsi percobaan baru. Apakah mungkin untuk membuat gofmt melakukan itu?

Bagaimana kita menangani kesalahan kompilasi ketika orang menggunakan go1.13 dan sebelumnya untuk membuat kode dengan try?

Saya mungkin melewatkan banyak masalah lain yang harus kami atasi untuk menerapkan perubahan ini. Apakah itu sepadan dengan kesulitannya? Saya tidak percaya begitu.

Alex

@griesemer
Saat mencoba tryhard pada file dengan 97 err's none tertangkap, saya menemukan bahwa 2 pola tidak diterjemahkan
1:

    if err := updateItem(tx, fields, entityView.DataBinding, entityInstance); err != nil {
        tx.Rollback()
        return nil, err
    }

Tidak diganti, mungkin karena tx.Rollback() antara err := dan garis kembali,
Yang saya asumsikan hanya dapat ditangani oleh penangguhan - dan jika semua jalur kesalahan perlu tx.Rollback()
Apakah ini benar ?

  1. Itu juga tidak menyarankan:
if err := db.Error; err != nil {
        return nil, err
    } else if itemDb, err := GetItem(c, entity, entityView, ItemRequest{recNo}); err != nil {
        return nil, err
    } else {
        return itemDb, nil
    }

atau

    if err := db.Error; err != nil {
        return nil, err
    } else {
            if itemDb, err := GetItem(c, entity, entityView, ItemRequest{recNo}); err != nil {
                return nil, err
            } else {
                return itemDb, nil
            }
        return result, nil
    }

Apakah ini karena bayangan atau coba bersarang akan diterjemahkan? artinya - haruskah penggunaan ini dicoba atau disarankan untuk dibiarkan sebagai err := ... return err ?

@guybrand Re: dua pola yang Anda temukan:

1) ya, tryhard tidak berusaha terlalu keras. pemeriksaan tipe diperlukan untuk kasus yang lebih kompleks. Jika tx.Rollback() harus dilakukan di semua jalur, defer mungkin merupakan pendekatan yang tepat. Jika tidak, mempertahankan if mungkin merupakan pendekatan yang tepat. Itu tergantung pada kode tertentu.

2) Sama di sini: tryhard tidak mencari pola yang lebih kompleks ini. Mungkin bisa.

Sekali lagi, ini adalah alat eksperimental untuk mendapatkan jawaban cepat. Melakukannya dengan benar membutuhkan sedikit lebih banyak pekerjaan.

@alexbrainman

Bagaimana kita menangani kesalahan kompilasi ketika orang menggunakan go1.13 dan sebelumnya untuk membuat kode dengan try?

Pemahaman saya adalah versi bahasa itu sendiri akan dikendalikan oleh go versi bahasa direktif dalam file go.mod untuk setiap bagian kode yang sedang dikompilasi.

Dokumentasi go.mod dalam penerbangan menjelaskan direktif versi bahasa go seperti:

Versi bahasa yang diharapkan, diatur oleh direktif go , menentukan
fitur bahasa mana yang tersedia saat mengkompilasi modul.
Fitur bahasa yang tersedia dalam versi tersebut akan tersedia untuk digunakan.
Fitur bahasa dihapus di versi sebelumnya, atau ditambahkan di versi yang lebih baru,
tidak akan tersedia. Perhatikan bahwa versi bahasa tidak mempengaruhi
membangun tag, yang ditentukan oleh rilis Go yang digunakan.

Jika secara hipotetis sesuatu seperti try bawaan baru mendarat di sesuatu seperti Go 1.15, maka pada saat itu seseorang yang file go.mod membaca go 1.12 tidak akan memiliki akses ke try baru itu go.mod mereka dari go 1.12 menjadi alih-alih membaca go 1.15 jika mereka ingin menggunakan Go baru 1.15 fitur bahasa try .

Di sisi lain, jika Anda memiliki kode yang menggunakan try dan kode itu hidup dalam modul yang file go.mod mendeklarasikan versi bahasa Go-nya sebagai go 1.15 , tetapi kemudian seseorang mencoba untuk buat itu dengan toolchain Go 1.12, maka toolchain Go 1.12 akan gagal dengan kesalahan kompilasi. Toolchain Go 1.12 tidak tahu apa-apa tentang try , tetapi cukup tahu untuk mencetak pesan tambahan bahwa kode yang gagal dikompilasi diklaim memerlukan Go 1.15 berdasarkan apa yang ada di file go.mod . Anda sebenarnya dapat mencoba eksperimen ini sekarang menggunakan toolchain Go 1.12 hari ini, dan melihat pesan kesalahan yang dihasilkan:

.\hello.go:3:16: undefined: try
note: module requires Go 1.15

Ada diskusi yang lebih panjang dalam dokumen proposal transisi Go2 .

Yang mengatakan, detail yang tepat dari itu mungkin lebih baik dibahas di tempat lain (misalnya, mungkin di #30791, atau utas kacang golang baru- baru ini).

@griesemer , maaf jika saya melewatkan permintaan format yang lebih spesifik, tetapi saya ingin membagikan beberapa hasil, dan memiliki akses (mungkin izin) ke beberapa kode sumber perusahaan.
Di bawah ini adalah contoh nyata untuk proyek kecil, saya pikir hasil terlampir memberikan contoh yang baik, jika demikian, kami mungkin dapat membagikan beberapa tabel dengan hasil yang serupa:

Total = Jumlah baris kode
$find /path/to/repo -name '*.go' -exec cat {} \; | wc -l
Errs = jumlah baris dengan err := (ini mungkin meleset err = , dan myerr := , tapi saya pikir dalam banyak kasus itu mencakup)
$find /path/to/repo -name '*.go' -exec cat {} \; | grep "err :=" | wc -l
tryhard = jumlah baris yang ditemukan tryhard

kasus pertama yang saya uji untuk dipelajari kembali:
Jumlah = 5106
salah = 111
berusaha = 16

basis kode yang lebih besar
Jumlah = 131777
salah = 3289
usaha keras = 265

Jika format ini dapat diterima, beri tahu kami bagaimana Anda ingin mendapatkan hasilnya, saya berasumsi hanya membuangnya di sini tidak akan menjadi format yang benar
Juga, mungkin akan lebih cepat untuk mencoba menghitung baris, kesempatan err := (dan mungkin err = , hanya 4 pada basis kode yang saya coba pelajari)

Terima kasih.

Dari @griesemer di https://github.com/golang/go/issues/32437#issuecomment -503276339

Saya mendorong Anda untuk melihat kode ini sebagai contoh.

Sehubungan dengan kode itu, saya perhatikan bahwa file keluar yang dibuat di sini sepertinya tidak pernah ditutup. Selain itu, penting untuk memeriksa kesalahan dari menutup file yang telah Anda tulis, karena itu mungkin satu-satunya saat Anda diberi tahu bahwa ada masalah dengan penulisan.

Saya mengangkat ini bukan sebagai laporan bug (walaupun mungkin seharusnya demikian?), tetapi sebagai kesempatan untuk melihat apakah try berpengaruh pada cara memperbaikinya. Saya akan menghitung semua cara yang dapat saya pikirkan untuk memperbaikinya dan mempertimbangkan apakah penambahan try akan membantu atau merugikan. Berikut beberapa caranya:

  1. Tambahkan panggilan eksplisit ke outf.Close() tepat sebelum kesalahan kembali.
  2. Beri nama nilai pengembalian dan tambahkan penundaan untuk menutup file, catat kesalahan jika belum ada. misalnya
func foo() (err error) {
    outf := try(os.Create())
    defer func() {
        cerr := outf.Close()
        if err == nil {
            err = cerr
        }
    }()

    ...
}
  1. Pola "double close" di mana seseorang melakukan defer outf.Close() untuk memastikan pembersihan sumber daya, dan try(outf.Close()) sebelum kembali untuk memastikan tidak ada kesalahan.
  2. Refactor agar fungsi pembantu mengambil file yang dibuka daripada jalur sehingga pemanggil dapat memastikan file ditutup dengan tepat. misalnya
func foo() error {
    outf := try(os.Create())
    if err := helper(outf); err != nil {
        outf.Close()
        return err
    }
    try(outf.Close())
    return nil
}

Saya pikir dalam semua kasus kecuali kasus nomor 1, try paling buruk netral dan biasanya positif. Dan saya akan menganggap nomor 1 sebagai opsi yang paling tidak enak mengingat ukuran dan jumlah kemungkinan kesalahan dalam fungsi itu, jadi menambahkan try akan mengurangi daya tarik pilihan negatif.

Semoga analisa ini bermanfaat.

Jika secara hipotetis sesuatu seperti try bawaan baru mendarat di sesuatu seperti Go 1.15, maka pada saat itu seseorang yang file go.mod membaca go 1.12 tidak akan memiliki akses

@thepudds terima kasih sudah menjelaskan. Tapi saya tidak menggunakan modul. Jadi penjelasan Anda jauh di atas kepala saya.

Alex

@alexbrainman

Bagaimana kita menangani kesalahan kompilasi ketika orang menggunakan go1.13 dan sebelumnya untuk membuat kode dengan try?

Jika try secara hipotetis mendarat di sesuatu seperti Go 1.15, maka jawaban yang sangat singkat untuk pertanyaan Anda adalah bahwa seseorang yang menggunakan Go 1.13 untuk membuat kode dengan try akan melihat kesalahan kompilasi seperti ini:

.\hello.go:3:16: undefined: try
note: module requires Go 1.15

(Setidaknya sejauh yang saya mengerti apa yang telah dikemukakan tentang proposal transisi).

@alexbrainman Terima kasih atas tanggapan Anda.

Sejumlah besar komentar di utas ini dalam bentuk "ini tidak terlihat seperti Go", atau "Go tidak berfungsi seperti itu", atau "Saya tidak mengharapkan ini terjadi di sini". Itu semua benar, _existing_ Go tidak bekerja seperti itu.

Ini mungkin perubahan bahasa pertama yang disarankan yang memengaruhi nuansa bahasa dengan cara yang lebih substansial. Kami sadar akan hal itu, itulah sebabnya kami membuatnya sangat minim. (Saya kesulitan membayangkan kegemparan yang mungkin ditimbulkan oleh proposal generik konkret - berbicara tentang perubahan bahasa).

Tetapi kembali ke poin Anda: Pemrogram terbiasa dengan bagaimana bahasa pemrograman bekerja dan terasa. Jika saya telah belajar sesuatu selama sekitar 35 tahun pemrograman adalah bahwa seseorang terbiasa dengan hampir semua bahasa, dan itu terjadi dengan sangat cepat. Setelah mempelajari Pascal asli sebagai bahasa tingkat tinggi pertama saya, _tidak terbayangkan_ bahwa bahasa pemrograman tidak akan menggunakan huruf besar untuk semua kata kuncinya. Tapi hanya butuh sekitar seminggu untuk membiasakan diri dengan "lautan kata" yaitu C di mana "orang tidak dapat melihat struktur kode karena semuanya huruf kecil". Setelah hari-hari awal dengan C, kode Pascal tampak sangat keras, dan semua kode yang sebenarnya tampak terkubur dalam kekacauan kata kunci yang berteriak. Maju cepat ke Go, ketika kami memperkenalkan kapitalisasi untuk menandai pengidentifikasi yang diekspor, itu adalah perubahan yang mengejutkan dari sebelumnya, jika saya ingat dengan benar, pendekatan berbasis kata kunci (ini sebelum Go dipublikasikan). Sekarang kami pikir ini adalah salah satu keputusan desain yang lebih baik (dengan ide konkret yang sebenarnya datang dari luar Tim Go). Atau, pertimbangkan eksperimen pemikiran berikut: Imagine Go tidak memiliki pernyataan defer dan sekarang seseorang membuat alasan kuat untuk defer . defer tidak memiliki semantik seperti bahasa lainnya, bahasa baru tidak terasa seperti itu sebelum- defer Go lagi. Namun, setelah hidup dengan itu selama satu dekade tampaknya benar-benar "Go-like".

Intinya, reaksi awal terhadap perubahan bahasa hampir tidak ada artinya tanpa benar-benar mencoba mekanisme dalam kode nyata dan mengumpulkan umpan balik yang konkret. Tentu saja, kode penanganan kesalahan yang ada baik-baik saja dan terlihat lebih jelas daripada penggantinya menggunakan try - kami telah dilatih untuk mengabaikan pernyataan if itu selama satu dekade sekarang. Dan tentu saja kode try terlihat aneh dan memiliki semantik "aneh", kami belum pernah menggunakannya sebelumnya, dan kami tidak segera mengenalinya sebagai bagian dari bahasa.

Itulah sebabnya kami meminta orang-orang untuk benar-benar terlibat dengan perubahan dengan bereksperimen dengannya dalam kode Anda sendiri; yaitu, benar-benar menulisnya, atau tryhard menjalankan kode yang ada, dan pertimbangkan hasilnya. Saya akan merekomendasikan untuk membiarkannya sebentar, mungkin sekitar satu minggu. Lihat lagi, dan laporkan kembali.

Akhirnya, saya setuju dengan penilaian Anda bahwa mayoritas orang tidak tahu tentang proposal ini, atau tidak terlibat dengannya. Cukup jelas bahwa diskusi ini mungkin didominasi oleh sekitar selusin orang. Tapi ini masih awal, proposal ini baru keluar dua minggu, dan belum ada keputusan. Ada banyak waktu bagi lebih banyak orang untuk terlibat dengan ini.

https://github.com/golang/go/issues/32437#issuecomment -503297387 cukup banyak mengatakan jika Anda membungkus kesalahan dalam lebih dari satu cara dalam satu fungsi, Anda tampaknya melakukan kesalahan. Sementara itu, saya memiliki banyak kode yang terlihat seperti ini:

        if err := gen.Execute(tmp, s); err != nil {
                return fmt.Errorf("template error: %v", err)
        }

        if err := tmp.Close(); err != nil {
                return fmt.Errorf("cannot write temp file: %v", err)
        }
        closed = true

        if err := os.Rename(tmp.Name(), *genOutput); err != nil {
                return fmt.Errorf("cannot finalize file: %v", err)
        }
        removed = true

( closed dan removed digunakan oleh penangguhan untuk membersihkan, sebagaimana mestinya)

Saya benar-benar tidak berpikir semua ini harus diberikan konteks yang sama yang menggambarkan misi tingkat atas dari fungsi ini. Saya benar-benar tidak berpikir pengguna seharusnya hanya melihat

processing path/to/dir: template: gen:42:17: executing "gen" at <.Broken>: can't evaluate field Broken in type main.state

ketika template kacau, saya pikir itu adalah tanggung jawab penangan kesalahan saya untuk template Execute call untuk menambahkan "template pelaksana" atau sedikit tambahan semacam itu. (Itu bukan konteks terbesar, tetapi saya ingin menyalin-tempel kode asli alih-alih contoh yang dibuat-buat.)

Saya tidak berpikir pengguna harus melihat

processing path/to/dir: rename /tmp/blahDs3x42aD commands.gen.go: No such file or directory

tanpa petunjuk tentang _why_ program saya mencoba membuat penggantian nama itu terjadi, apa semantiknya, apa maksudnya. Saya percaya menambahkan sedikit "tidak dapat menyelesaikan file:" sangat membantu.

Jika contoh-contoh ini tidak cukup meyakinkan Anda, bayangkan keluaran kesalahan ini dari aplikasi baris perintah:

processing path/to/dir: open /some/path/here: No such file or directory

Apa artinya? Saya ingin menambahkan alasan mengapa aplikasi mencoba membuat file di sana (Anda bahkan tidak tahu itu buatan, bukan hanya os.Open! Ini ENOENT karena jalur perantara tidak ada.). Ini bukan sesuatu yang harus ditambahkan ke _every_ error return dari fungsi ini.

Jadi, apa yang saya lewatkan. Apakah saya "salah mengartikannya"? Apakah saya harus mendorong masing-masing hal itu ke dalam fungsi kecil terpisah yang semuanya menggunakan penangguhan untuk membungkus semua kesalahan mereka?

@guybrand Terima kasih atas angka- angka ini. Akan lebih baik untuk memiliki beberapa wawasan tentang mengapa angka tryhard adalah seperti itu. Mungkin ada banyak dekorasi kesalahan tertentu yang terjadi? Jika demikian, itu bagus dan pernyataan if adalah pilihan yang tepat.

Saya akan meningkatkan alat ketika saya sampai ke sana.

Terima kasih, @zeebo atas analisis Anda. Saya tidak tahu tentang kode ini secara spesifik, tetapi sepertinya outf adalah bagian dari loadCmdReader (baris 173) yang kemudian diteruskan ke baris 204. Mungkin itu alasannya outf tidak ditutup. (Maaf, saya tidak menulis kode ini).

@tv42 Dari contoh di https://github.com/golang/go/issues/32437#issuecomment -503340426 Anda, dengan asumsi Anda tidak melakukannya "salah", sepertinya menggunakan pernyataan if adalah cara untuk menangani kasus-kasus ini jika semuanya membutuhkan tanggapan yang berbeda. try tidak akan membantu, dan defer hanya akan mempersulit (proposal perubahan bahasa lainnya di utas ini yang mencoba membuat kode ini lebih sederhana untuk ditulis sangat dekat dengan if pernyataan bahwa tidak layak memperkenalkan mekanisme baru). Lihat juga FAQ dari proposal terperinci.

@griesemer Lalu yang bisa saya pikirkan adalah Anda dan @rsc tidak setuju. Atau bahwa saya, memang, "melakukannya salah", dan ingin berbicara tentang itu.

Ini adalah tujuan eksplisit, baik saat itu maupun hari ini, bahwa solusi apa pun membuatnya lebih mungkin bagi pengguna untuk menambahkan konteks yang sesuai ke kesalahan. Dan kami pikir try bisa melakukan itu, atau kami tidak akan mengusulkannya.

@tv42 @rsc posting adalah tentang keseluruhan struktur penanganan kesalahan dari kode yang baik, yang saya setujui. Jika Anda memiliki potongan kode yang tidak sesuai dengan pola ini dan Anda puas dengan kode tersebut, biarkan saja.

Menunda

Perubahan utama dari draft check/handle Gophercon ke proposal ini adalah menjatuhkan handle demi penggunaan kembali defer . Sekarang konteks kesalahan akan ditambahkan oleh kode seperti panggilan yang ditangguhkan ini (lihat komentar saya sebelumnya tentang konteks kesalahan):

func CopyFile(src, dst string) (err error) {
    defer HelperToBeNamedLater(&err, "copy %s %s", src, dst)

    r := check os.Open(src)
    defer r.Close()

    w := check os.Create(dst)
    ...
}

Kelangsungan penundaan sebagai mekanisme anotasi kesalahan dalam contoh ini tergantung pada beberapa hal.

  1. _Hasil kesalahan bernama._ Ada banyak kekhawatiran tentang menambahkan hasil kesalahan bernama. Memang benar bahwa kami telah mengecilkan bahwa di masa lalu di mana tidak diperlukan untuk tujuan dokumentasi, tetapi itu adalah konvensi yang kami pilih karena tidak adanya faktor penentu yang lebih kuat. Dan bahkan di masa lalu, faktor penentu yang lebih kuat seperti merujuk ke hasil spesifik dalam dokumentasi melebihi konvensi umum untuk hasil yang tidak disebutkan namanya. Sekarang ada faktor penentu kedua yang lebih kuat, yaitu ingin merujuk pada kesalahan dalam penundaan. Sepertinya itu seharusnya tidak lebih tidak menyenangkan daripada memberi nama hasil untuk digunakan dalam dokumentasi. Sejumlah orang bereaksi cukup negatif terhadap ini, dan sejujurnya saya tidak mengerti mengapa. Sepertinya orang-orang menggabungkan pengembalian tanpa daftar ekspresi (disebut "pengembalian telanjang") dengan hasil yang diberi nama. Memang benar bahwa pengembalian tanpa daftar ekspresi dapat menyebabkan kebingungan dalam fungsi yang lebih besar. Menghindari kebingungan itu dengan menghindari pengembalian itu dalam fungsi yang panjang sering kali masuk akal. Lukisan bernama hasil dengan kuas yang sama tidak.

  2. _Ekspresi alamat._ Beberapa orang telah menyampaikan kekhawatiran bahwa menggunakan pola ini akan mengharuskan pengembang Go untuk memahami ekspresi alamat. Menyimpan nilai apa pun dengan metode penunjuk ke dalam antarmuka sudah memerlukan itu, jadi ini sepertinya bukan kelemahan yang signifikan.

  3. _Defer sendiri._ Beberapa orang telah menyuarakan keprihatinan tentang penggunaan defer sebagai konsep bahasa, sekali lagi karena pengguna baru mungkin tidak terbiasa dengannya. Seperti halnya ekspresi sapaan, defer adalah konsep bahasa inti yang harus dipelajari pada akhirnya. Idiom standar seputar hal-hal seperti defer f.Close() dan defer l.mu.Unlock() sangat umum sehingga sulit untuk membenarkan menghindari penangguhan sebagai sudut bahasa yang tidak jelas.

  4. _Performance._ Kami telah membahas selama bertahun-tahun bekerja untuk membuat pola penangguhan umum seperti penangguhan di bagian atas suatu fungsi memiliki overhead nol dibandingkan dengan memasukkan panggilan itu dengan tangan pada setiap pengembalian. Kami rasa kami tahu bagaimana melakukannya dan akan menjelajahinya untuk rilis Go berikutnya. Meskipun tidak, overhead saat ini sekitar 50 ns seharusnya tidak menjadi penghalang untuk sebagian besar panggilan yang perlu menambahkan konteks kesalahan. Dan beberapa panggilan sensitif kinerja dapat terus menggunakan pernyataan if hingga penangguhan lebih cepat.

Tiga yang pertama menyangkut semua jumlah keberatan untuk menggunakan kembali fitur bahasa yang ada. Tetapi menggunakan kembali fitur bahasa yang ada adalah kemajuan dari proposal ini daripada pemeriksaan/penanganan: lebih sedikit yang ditambahkan ke bahasa inti, lebih sedikit bagian baru untuk dipelajari, dan lebih sedikit interaksi yang mengejutkan.

Namun, kami menghargai bahwa menggunakan penangguhan dengan cara ini adalah hal baru dan bahwa kami perlu memberi orang waktu untuk mengevaluasi apakah penangguhan bekerja cukup baik dalam praktiknya untuk idiom penanganan kesalahan yang mereka butuhkan.

Sejak kami memulai diskusi ini Agustus lalu, saya telah melakukan latihan mental "bagaimana tampilan kode ini dengan tanda centang/pegangan?" dan baru-baru ini "dengan coba/tunda?" setiap kali saya menulis kode baru. Biasanya jawabannya berarti saya menulis kode yang berbeda dan lebih baik, dengan konteks ditambahkan di satu tempat (penundaan) alih-alih di setiap pengembalian atau dihilangkan sama sekali.

Mengingat gagasan menggunakan penangan yang ditangguhkan untuk mengambil tindakan terhadap kesalahan, ada berbagai pola yang dapat kita aktifkan dengan paket pustaka sederhana. Saya telah mengajukan #32676 untuk lebih memikirkannya, tetapi menggunakan API paket dalam masalah itu kode kita akan terlihat seperti:

func CopyFile(src, dst string) (err error) {
    defer errd.Add(&err, "copy %s %s", src, dst)

    r := check os.Open(src)
    defer r.Close()

    w := check os.Create(dst)
    ...
}

Jika kami sedang men-debug CopyFile dan ingin melihat kesalahan yang dikembalikan dan jejak tumpukan (mirip dengan ingin memasukkan cetakan debug), kami dapat menggunakan:

func CopyFile(src, dst string) (err error) {
    defer errd.Trace(&err)
    defer errd.Add(&err, "copy %s %s", src, dst)

    r := check os.Open(src)
    defer r.Close()

    w := check os.Create(dst)
    ...
}

dan seterusnya.

Menggunakan penangguhan dengan cara ini akhirnya menjadi cukup kuat, dan itu mempertahankan keuntungan dari pemeriksaan/pegangan bahwa Anda dapat menulis "lakukan ini pada kesalahan apa pun" sekali di bagian atas fungsi dan kemudian tidak mengkhawatirkannya selama sisa tubuh. Ini meningkatkan keterbacaan dalam banyak cara yang sama seperti keluar cepat awal .

Apakah ini akan berhasil dalam praktik? Kami ingin mencari tahu.

Setelah melakukan percobaan mental tentang seperti apa penangguhan dalam kode saya sendiri selama beberapa bulan, saya pikir itu mungkin berhasil. Tapi tentu saja untuk menggunakannya dalam kode nyata tidak selalu sama. Kita perlu bereksperimen untuk mengetahuinya.

Orang-orang dapat bereksperimen dengan pendekatan ini hari ini dengan terus menulis pernyataan if err != nil tetapi menyalin pembantu penangguhan dan memanfaatkannya sebagaimana mestinya. Jika Anda cenderung melakukan ini, beri tahu kami apa yang Anda pelajari.

@tv42 , saya setuju dengan @griesemer. Jika Anda menemukan bahwa konteks tambahan diperlukan untuk memperlancar koneksi seperti mengubah nama menjadi langkah "menyelesaikan", tidak ada yang salah dengan menggunakan pernyataan if untuk menambahkan konteks tambahan. Namun, dalam banyak fungsi, ada sedikit kebutuhan untuk konteks tambahan semacam itu.

@guybrand , angka tryhard sangat bagus tetapi yang lebih baik adalah deskripsi mengapa contoh spesifik tidak dikonversi dan selanjutnya tidak pantas untuk ditulis ulang agar memungkinkan untuk dikonversi. Contoh dan penjelasan @ tv42 adalah contohnya.

@griesemer tentang kekhawatiran Anda tentang defer . Saya akan melakukannya emit atau dalam proposal awal handle . emit/handle akan dipanggil jika err tidak nihil. Dan akan dimulai pada saat itu alih-alih di akhir fungsi. Penundaan dipanggil di akhir. emit/handle AKAN mengakhiri fungsi berdasarkan apakah err adalah nihil atau tidak. Itu sebabnya penangguhan tidak akan berhasil.

beberapa data:

dari proyek LOC ~ 70k yang telah saya jajakan untuk menghilangkan "pengembalian kesalahan telanjang" secara religius, kami masih memiliki 612 pengembalian kesalahan telanjang. sebagian besar berurusan dengan kasus di mana kesalahan dicatat, tetapi pesan itu hanya penting secara internal (pesan kepada pengguna telah ditentukan sebelumnya). try() akan memiliki penghematan yang lebih besar daripada hanya dua baris per setiap pengembalian telanjang, karena dengan kesalahan yang telah ditentukan, kami dapat menunda penangan dan menggunakan coba di lebih banyak tempat.

lebih menariknya, di direktori vendor, dari ~620k+ LOC, kami hanya memiliki 1600 pengembalian kesalahan. perpustakaan yang kita pilih cenderung menghiasi kesalahan bahkan lebih religius daripada yang kita lakukan.

@rsc jika, nanti, penangan ditambahkan ke try apakah akan ada paket error/errc dengan fungsi seperti func Wrap(msg string) func(error) error sehingga Anda dapat melakukannya try(f(), errc.Wrap("f failed")) ?

@damienfamed75 Terima kasih atas penjelasan Anda. Jadi emit akan dipanggil ketika try menemukan kesalahan, dan dipanggil dengan kesalahan itu. Itu tampaknya cukup jelas.

Anda juga mengatakan bahwa emit akan mengakhiri fungsi jika ada kesalahan, dan tidak jika kesalahan itu ditangani. Jika Anda tidak mengakhiri fungsinya, di mana kodenya berlanjut? Agaknya dengan kembali dari try (jika tidak, saya tidak mengerti emit yang tidak mengakhiri fungsi). Bukankah lebih mudah dan lebih jelas dalam hal ini untuk hanya menggunakan if daripada try ? Menggunakan emit atau handle akan sangat mengaburkan aliran kontrol dalam kasus tersebut, terutama karena klausa emit dapat berada di bagian yang sama sekali berbeda (mungkin lebih awal) dalam fungsi. (Pada catatan itu, dapatkah seseorang memiliki lebih dari satu emit ? Jika tidak, mengapa tidak? Apa yang terjadi jika tidak ada emit ? Banyak pertanyaan yang sama yang mengganggu check asli handle rancangan desain.)

Hanya jika seseorang ingin kembali dari suatu fungsi tanpa banyak pekerjaan tambahan selain dekorasi kesalahan, atau dengan pekerjaan yang selalu sama, apakah masuk akal untuk menggunakan try , dan semacam penangan. Dan mekanisme handler itu, yang berjalan sebelum fungsi kembali, sudah ada di defer .

@guybrand (dan @griesemer) sehubungan dengan pola kedua Anda yang tidak dikenali, lihat https://github.com/griesemer/tryhard/issues/2

@daved

Bagaimana nilai akan diperoleh dari data ketika sebagian besar proses publik didorong oleh sentimen?

Mungkin orang lain mungkin memiliki pengalaman seperti yang saya laporkan di sini . Saya berharap untuk membolak-balik beberapa contoh try yang disisipkan oleh tryhard , menemukan mereka tampak kurang lebih seperti apa yang sudah ada di utas ini, dan melanjutkan. Sebaliknya, saya terkejut menemukan kasus di mana try menghasilkan kode yang jelas lebih baik, dengan cara yang belum pernah dibahas sebelumnya.

Jadi setidaknya ada harapan. :)

Untuk orang-orang yang mencoba tryhard , jika Anda belum melakukannya, saya akan mendorong Anda untuk tidak hanya melihat perubahan apa yang dibuat alat, tetapi juga untuk memahami sisa contoh err != nil dan melihat apa yang ditinggalkannya, dan mengapa.

(Dan perhatikan juga bahwa ada beberapa masalah dan PR di https://github.com/griesemer/tryhard/.)

@rsc inilah wawasan saya tentang mengapa saya pribadi tidak menyukai pola defer HandleFunc(&err, ...) . Bukan karena saya mengasosiasikannya dengan pengembalian telanjang atau apa, hanya saja terasa terlalu "pintar".

Ada proposal penanganan kesalahan beberapa bulan (mungkin setahun?) yang lalu, namun saya kehilangan jejaknya sekarang. Saya lupa apa yang dimintanya, namun seseorang telah menanggapi dengan sesuatu seperti:

func myFunction() (i int, err error) {
    defer func() {
        if err != nil {
            err = fmt.Errorf("wrapping the error: %s", err)
        }
    }()

    // ...
    return 0, err

    // ...
    return someInt, nil
}

Itu menarik untuk melihat untuk sedikitnya. Ini adalah pertama kalinya saya melihat defer digunakan untuk penanganan kesalahan, dan sekarang ditampilkan di sini. Saya melihatnya sebagai "pintar" dan "retas", dan, setidaknya dalam contoh yang saya kemukakan, itu tidak terasa seperti Go. Namun, membungkusnya dalam panggilan fungsi yang tepat dengan sesuatu seperti fmt.HandleErrorf memang membantunya terasa jauh lebih menyenangkan. Saya masih merasa negatif terhadapnya, meskipun.

Alasan lain saya dapat melihat orang tidak menyukainya adalah ketika seseorang menulis return ..., err , sepertinya err harus dikembalikan. Tapi itu tidak dikembalikan, sebaliknya nilainya diubah sebelum dikirim. Saya telah mengatakan sebelumnya bahwa return selalu tampak seperti operasi "suci" di Go, dan mendorong kode yang mengubah nilai yang dikembalikan sebelum benar-benar kembali terasa salah.

OK, angka dan data itu kemudian. :)

Saya menjalankan tryhard pada sumber beberapa layanan platform microservice kami, dan membandingkannya dengan hasil loccount dan grep 'if err'. Saya mendapatkan hasil berikut dalam urutan loccount / grep 'if err' | wc / usaha keras:

1382 / 64 / 14
108554 / 66 / 5
58401 / 22 / 5
2052/247/39
12024 / 1655 / 1

Beberapa layanan mikro kami melakukan banyak penanganan kesalahan dan beberapa hanya melakukan sedikit, tetapi sayangnya, tryhard hanya mampu meningkatkan kode secara otomatis, paling banter, 22% kasus, paling buruk kurang dari 1%. Sekarang, kita tidak akan menulis ulang penanganan kesalahan kita secara manual sehingga alat seperti tryhard akan menjadi penting untuk memperkenalkan try() dalam basis kode kita. Saya menghargai bahwa ini adalah alat awal yang sederhana, tetapi saya terkejut betapa jarangnya alat ini dapat membantu.

Tapi saya pikir sekarang, dengan nomor di tangan, saya dapat mengatakan bahwa untuk penggunaan kita, try() tidak benar-benar menyelesaikan masalah apa pun, atau, setidaknya tidak sampai tryhard menjadi jauh lebih baik.

Saya juga menemukan di basis kode kami bahwa kasus penggunaan if err != nil { return err } dari try() sebenarnya sangat jarang, tidak seperti di kompiler go, yang umum. Dengan segala hormat, tetapi saya pikir para desainer Go, yang melihat kode sumber kompiler Go jauh lebih sering daripada di basis kode lain, melebih-lebihkan kegunaan try() karena hal ini.

@beoran tryhard sangat sederhana saat ini. Apakah Anda memahami alasan paling umum mengapa try jarang ada di basis kode Anda? Misalnya karena Anda menghias kesalahan? Karena Anda melakukan pekerjaan tambahan lain sebelum kembali? Sesuatu yang lain?

@rsc , @griesemer

Adapun contoh , saya memberikan dua sampel berulang di sini yang terlewatkan oleh tryHard, yang satu mungkin akan tetap sebagai "jika Err :=", yang lain dapat diselesaikan

untuk error decoration , dua pola berulang yang saya lihat dalam kode adalah (saya menempatkan keduanya dalam satu cuplikan kode):

if v, err := someFunction(vars...) ; err != nil {
        return fmt.Errorf("extra data to help with where did error occur and params are %s , %d , err : %v",
            strParam, intParam, err)
    } else if v2, err := passToAnotherFunc(v,vars ...);err != nil {
        extraData := DoSomethingAccordingTo(v2,err)
        return formatError(err,extraData)
    } else {

    }

Dan seringkali formatError adalah standar untuk aplikasi atau repo silang, yang paling berulang adalah pemformatan DbError (satu fungsi di semua aplikasi/aplikasi, digunakan di lusinan lokasi), dalam beberapa kasus (tanpa masuk ke "apakah ini a pola yang benar") menyimpan beberapa data untuk dicatat (kueri sql gagal Anda tidak ingin melewatkan tumpukan) dan beberapa teks lain ke kesalahan.

Dengan kata lain, jika saya ingin "melakukan sesuatu yang cerdas dengan data tambahan seperti mencatat kesalahan A dan meningkatkan kesalahan B, selain itu saya menyebutkan dua opsi ini untuk memperpanjang penanganan kesalahan
Ini adalah opsi lain untuk "lebih dari sekadar mengembalikan kesalahan dan membiarkan 'orang lain' atau 'fungsi lain' menanganinya"

Yang berarti mungkin ada lebih banyak penggunaan untuk try() di "perpustakaan" daripada di "program yang dapat dieksekusi", mungkin saya akan mencoba menjalankan perbandingan Total/Errs/tryHard yang membedakan lib dari runnables ("aplikasi").

Saya menemukan diri saya persis dalam situasi yang dijelaskan di https://github.com/golang/go/issues/32437#issuecomment -503297387
Di beberapa level saya membungkus kesalahan satu per satu, saya tidak akan mengubah ini dengan try , tidak masalah dengan if err!=nil .
Di level lain saya hanya return err itu menyakitkan untuk menambahkan konteks yang sama untuk semua pengembalian, maka saya akan menggunakan try dan defer .
Saya bahkan sudah melakukan ini dengan logger spesifik yang saya gunakan di awal fungsi untuk berjaga-jaga jika terjadi kesalahan. Bagi saya try dan dekorasi berdasarkan fungsi sudah goish.

@thepudds

Jika try secara hipotetis mendarat di sesuatu seperti Go 1.15, maka jawaban yang sangat singkat untuk pertanyaan Anda adalah seseorang yang menggunakan Go 1.13

Go 1.13 bahkan belum dirilis, jadi saya tidak bisa menggunakannya. Dan, karena proyek saya tidak menggunakan modul Go, saya tidak akan dapat meningkatkan ke Go 1.13. (Saya percaya Go 1.13 akan mengharuskan semua orang untuk menggunakan modul Go)

untuk membuat kode dengan try akan melihat kesalahan kompilasi seperti ini:

.\hello.go:3:16: undefined: try
note: module requires Go 1.15

(Setidaknya sejauh yang saya mengerti apa yang telah dikemukakan tentang proposal transisi).

Itu semua hipotetis. Sulit bagi saya untuk berkomentar tentang hal-hal fiktif. Dan, mungkin Anda menyukai kesalahan itu, tetapi menurut saya itu membingungkan dan tidak membantu.

Jika try tidak terdefinisi, saya akan menerimanya. Dan saya tidak akan menemukan apa-apa. Apa yang harus saya lakukan?

Dan note: module requires Go 1.15 adalah bantuan terburuk dalam situasi ini. Mengapa module ? Mengapa Go 1.15 ?

@griesemer

Ini mungkin perubahan bahasa pertama yang disarankan yang memengaruhi nuansa bahasa dengan cara yang lebih substansial. Kami sadar akan hal itu, itulah sebabnya kami membuatnya sangat minim. (Saya kesulitan membayangkan kegemparan yang mungkin ditimbulkan oleh proposal generik konkret - berbicara tentang perubahan bahasa).

Saya lebih suka Anda menghabiskan waktu untuk obat generik, daripada mencoba. Mungkin ada manfaat memiliki obat generik di Go.

Tetapi kembali ke poin Anda: Pemrogram terbiasa dengan bagaimana bahasa pemrograman bekerja dan terasa. ...

Saya setuju dengan semua poin Anda. Tetapi kita berbicara tentang mengganti bentuk tertentu dari pernyataan if dengan pemanggilan fungsi try. Ini adalah bahasa yang membanggakan kesederhanaan dan ortogonalitas. Aku bisa terbiasa dengan semuanya, tapi apa gunanya? Untuk menyimpan beberapa baris kode?

Atau, pertimbangkan eksperimen pemikiran berikut: Imagine Go tidak memiliki pernyataan defer dan sekarang seseorang membuat alasan kuat untuk defer . defer tidak memiliki semantik seperti bahasa lainnya, bahasa baru tidak terasa seperti itu sebelum- defer Go lagi. Namun, setelah hidup dengan itu selama satu dekade tampaknya benar-benar "Go-like".

Setelah bertahun-tahun saya masih ditipu oleh defer body dan menutup variabel. Tapi defer membayar harganya dalam hal pengelolaan sumber daya. Saya tidak dapat membayangkan Pergi tanpa defer . Tetapi saya tidak siap untuk membayar harga yang sama untuk try , karena saya tidak melihat manfaat apa pun di sini.

Itulah sebabnya kami meminta orang-orang untuk benar-benar terlibat dengan perubahan dengan bereksperimen dengannya dalam kode Anda sendiri; yaitu, benar-benar menulisnya, atau tryhard menjalankan kode yang ada, dan pertimbangkan hasilnya. Saya akan merekomendasikan untuk membiarkannya sebentar, mungkin sekitar satu minggu. Lihat lagi, dan laporkan kembali.

Saya mencoba mengubah proyek kecil saya (sekitar 1200 baris kode). Dan itu terlihat mirip dengan perubahan Anda di https://go-review.googlesource.com/c/go/+/182717/1/src/cmd/link/internal/ld/macho_combine_dwarf.go Saya tidak melihat pendapat saya mengubah tentang ini setelah seminggu. Pikiranku selalu sibuk dengan sesuatu, dan aku akan melupakannya.

... Tapi ini masih awal, proposal ini baru keluar dua minggu, ...

Dan saya dapat melihat ada 504 pesan tentang proposal ini hanya di utas ini. Jika saya tertarik untuk mendorong perubahan ini, saya perlu berhari-hari jika tidak berminggu-minggu hanya untuk membaca dan memahami semua ini. Aku tidak iri dengan pekerjaanmu.

Terima kasih telah meluangkan waktu untuk membalas pesan saya. Maaf, jika saya tidak akan membalas utas ini - terlalu besar untuk saya pantau, apakah pesan ditujukan kepada saya atau tidak.

Alex

@griesemer Terima kasih atas proposal yang luar biasa dan usaha keras tampaknya lebih bermanfaat dari yang saya harapkan. Saya juga ingin menghargai.

@rsc terima kasih atas respons dan alat yang diartikulasikan dengan baik.

Telah mengikuti utas ini untuk sementara waktu dan komentar berikut oleh @beoran membuat saya merinding

Menyembunyikan variabel kesalahan dan pengembalian tidak membantu membuat segalanya lebih mudah dipahami

Telah mengelola beberapa bad written code sebelumnya dan saya dapat bersaksi bahwa ini adalah mimpi buruk terburuk bagi setiap pengembang.

Fakta dokumentasi mengatakan untuk menggunakan A suka tidak berarti itu akan diikuti, faktanya tetap jika memungkinkan untuk menggunakan AA , AB maka tidak ada batasan bagaimana itu bisa digunakan.

To my surprise, people already think the code below is cool ... Saya rasa it's an abomination dengan segala hormat mohon maaf kepada siapapun yang tersinggung.

parentCommitOne := try(try(try(r.Head()).Peel(git.ObjectCommit)).AsCommit())
parentCommitTwo := try(try(remoteBranch.Reference.Peel(git.ObjectCommit)).AsCommit())

Tunggu sampai Anda memeriksa AsCommit dan Anda melihat

func AsCommit() error(){
    return try(try(try(tail()).find()).auth())
}

Kegilaan terus berlanjut dan sejujurnya saya tidak mau percaya ini adalah definisi dari @robpike simplicity is complicated (Humor)

Berdasarkan contoh @rsc

// Example 1
headRef := try(r.Head())
parentObjOne := try(headRef.Peel(git.ObjectCommit))
parentObjTwo := try(remoteBranch.Reference.Peel(git.ObjectCommit))
parentCommitOne := parentObjOne.AsCommit()
parentCommitTwo := parentObjTwo.AsCommit()
treeOid := try(index.WriteTree())
tree := try(r.LookupTree(treeOid))

// Example 2 
try headRef := r.Head()
try parentObjOne := headRef.Peel(git.ObjectCommit)
try parentObjTwo := remoteBranch.Reference.Peel(git.ObjectCommit)
parentCommitOne := parentObjOne.AsCommit()
parentCommitTwo := parentObjTwo.AsCommit()
try treeOid := index.WriteTree()
try tree := r.LookupTree(treeOid)

// Example 3 
try (
    headRef := r.Head()
    parentObjOne := headRef.Peel(git.ObjectCommit)
    parentObjTwo := remoteBranch.Reference.Peel(git.ObjectCommit)
)
parentCommitOne := parentObjOne.AsCommit()
parentCommitTwo := parentObjTwo.AsCommit()
try (
    treeOid := index.WriteTree()
    tree := r.LookupTree(treeOid)
)

Saya mendukung Example 2 dengan sedikit else , Harap dicatat bahwa ini mungkin bukan pendekatan terbaik

  • Sangat mudah untuk melihat kesalahan dengan jelas
  • Paling tidak mungkin untuk bermutasi menjadi abomination yang dapat melahirkan orang lain
  • try tidak berperilaku seperti fungsi normal. untuk memberikan sintaks seperti fungsi adalah sedikit. go menggunakan if dan jika saya bisa mengubahnya menjadi try tree := r.LookupTree(treeOid) else { rasanya lebih alami
  • Kesalahan bisa sangat sangat mahal, mereka membutuhkan visibilitas sebanyak mungkin dan saya pikir itulah alasannya pergi tidak mendukung try tradisional & catch
try headRef := r.Head()
try parentObjOne := headRef.Peel(git.ObjectCommit)
try parentObjTwo := remoteBranch.Reference.Peel(git.ObjectCommit)

parentCommitOne := parentObjOne.AsCommit()
parentCommitTwo := parentObjTwo.AsCommit()

try treeOid := index.WriteTree()
try tree := r.LookupTree(treeOid) else { 
    // Heal the world 
   // I may return with return keyword 
   // I may not return but set some values to 0 
   // I may remember I need to log only this 
   // I may send a mail to let the cute monkeys know the server is on fire 
}

Sekali lagi saya ingin meminta maaf karena sedikit egois.

@josharian Saya tidak bisa membocorkan terlalu banyak di sini, namun alasannya cukup beragam. Seperti yang Anda katakan, kami mendekorasi kesalahan, dan atau juga melakukan pemrosesan yang berbeda, dan juga, kasus penggunaan yang penting adalah kami mencatatnya, di mana pesan log berbeda untuk setiap kesalahan yang dapat dikembalikan oleh suatu fungsi, atau karena kami menggunakan if err := foo() ; err != nil { /* various handling*/ ; return err } formulir, atau alasan lainnya.

Yang ingin saya tekankan adalah ini: kasus penggunaan sederhana yang dirancang untuk try() sangat jarang terjadi di basis kode kita. Jadi, bagi kami tidak banyak yang bisa diperoleh dengan menambahkan 'try()' ke bahasa tersebut.

EDIT: Jika try() akan diimplementasikan maka saya pikir langkah selanjutnya adalah membuat tryhard jauh lebih baik, sehingga dapat digunakan secara luas untuk meningkatkan basis kode yang ada.

@griesemer Saya akan mencoba untuk mengatasi semua kekhawatiran Anda satu per satu dari tanggapan terakhir Anda.
Pertama, Anda bertanya apakah pawang tidak kembali atau keluar dari fungsi dengan cara tertentu, lalu apa yang akan terjadi. Ya, mungkin ada contoh di mana klausa emit / handle tidak akan kembali atau keluar dari suatu fungsi, melainkan melanjutkan dari fungsi yang ditinggalkannya. Misalnya, jika kita mencoba menemukan pembatas atau sesuatu yang sederhana menggunakan pembaca dan kita mencapai EOF , kita mungkin tidak ingin mengembalikan kesalahan saat kita menekannya. Jadi saya membuat contoh singkat tentang seperti apa tampilannya:

func findDelimiter(r io.Reader) ([]byte, error) {
    emit err {
        // if this doesn't return then continue from where we left off
        // at the try function that was called last.
        if err != io.EOF {
            return nil, err
        }
    }

    bufReader := bufio.NewReader(r)

    token := try(bufReader.ReadSlice('|'))

    return token, nil
}

Atau bahkan bisa lebih disederhanakan menjadi ini:

func findDelimiter(r io.Reader) ([]byte, error) {
    emit err != io.EOF {
        return nil, err
    }

    bufReader := bufio.NewReader(r)

    token := try(bufReader.ReadSlice('|'))

    return token, nil
}

Kekhawatiran kedua adalah tentang gangguan aliran kontrol. Dan ya itu akan mengganggu aliran, tetapi untuk bersikap adil sebagian besar proposal agak mengganggu aliran untuk memiliki satu fungsi penanganan kesalahan pusat dan semacamnya. Ini tidak berbeda saya percaya.
Selanjutnya, Anda bertanya tentang apakah kami menggunakan emit / handle lebih dari sekali di mana saya katakan bahwa itu didefinisikan ulang.
Jika Anda menggunakan emit lebih dari sekali, itu akan menimpa yang terakhir dan seterusnya. Jika Anda tidak memilikinya maka try akan memiliki handler default yang hanya mengembalikan nilai nil dan kesalahan. Itu berarti bahwa contoh ini di sini:

func writeStuff(filename string) (io.ReadCloser, error) {
    emit err {
        return nil, err
    }

    f := try(os.Open(filename))

    try(fmt.Fprintf(f, "stuff\n"))

    return f, nil
}

Akan melakukan hal yang sama seperti contoh ini:

func writeStuff(filename string) (io.ReadCloser, error) {
    // when not defining a handler then try's default handler kicks in to
    // return nil valued then error as usual.

    f := try(os.Open(filename))

    try(fmt.Fprintf(f, "stuff\n"))

    return f, nil
}

Pertanyaan terakhir Anda adalah tentang mendeklarasikan fungsi handler yang dipanggil dalam defer dengan saya menganggap referensi ke error . Desain ini tidak bekerja dengan cara yang sama seperti proposal ini bekerja dengan alasan bahwa defer tidak dapat segera menghentikan fungsi yang diberikan kondisi itu sendiri.

Saya yakin saya menjawab semuanya dalam tanggapan Anda dan saya harap ini memperjelas proposal saya sedikit lagi. Jika ada kekhawatiran lagi, beri tahu saya karena saya pikir seluruh diskusi dengan semua orang ini cukup menyenangkan untuk merenungkan ide-ide baru. Pertahankan kerja hebat semuanya!

@velovix , kembali https://github.com/golang/go/issues/32437#issuecomment -503314834:

Sekali lagi, ini berarti bahwa try menyerang untuk bereaksi terhadap kesalahan, tetapi tidak untuk menambahkan konteks ke kesalahan tersebut. Itulah perbedaan yang telah menyinggung saya dan mungkin orang lain. Ini masuk akal karena cara suatu fungsi menambahkan konteks ke kesalahan bukanlah hal yang menarik bagi pembaca, tetapi cara suatu fungsi bereaksi terhadap kesalahan itu penting. Kita seharusnya membuat bagian kode yang kurang menarik menjadi tidak terlalu bertele-tele, dan itulah yang dilakukan try .

Ini adalah cara yang sangat bagus untuk meletakkannya. Terima kasih.

@olekukonko , kembali https://github.com/golang/go/issues/32437#issuecomment -503508478:

To my surprise, people already think the code below is cool ... Saya pikir it's an abomination dengan segala hormat mohon maaf kepada siapa pun yang tersinggung.

parentCommitOne := try(try(try(r.Head()).Peel(git.ObjectCommit)).AsCommit())
parentCommitTwo := try(try(remoteBranch.Reference.Peel(git.ObjectCommit)).AsCommit())

Grepping https://swtch.com/try.html , ekspresi itu telah muncul tiga kali di utas ini.
@goodwine membawanya sebagai kode yang buruk, saya setuju, dan @velovix berkata "meskipun jelek ... lebih baik daripada apa yang sering Anda lihat dalam bahasa try-catch ... karena Anda masih dapat mengetahui bagian kode mana yang mungkin dialihkan mengontrol aliran karena kesalahan dan yang tidak bisa."

Tidak ada yang mengatakan itu "keren" atau sesuatu untuk dikemukakan sebagai kode yang hebat. Sekali lagi, selalu mungkin untuk menulis kode yang buruk .

Saya juga hanya akan mengatakan re

Kesalahan bisa sangat sangat mahal, mereka membutuhkan visibilitas sebanyak mungkin

Kesalahan dalam Go dimaksudkan agar tidak mahal. Mereka sehari-hari, kejadian biasa dan dimaksudkan untuk menjadi ringan. (Ini berbeda dengan beberapa implementasi pengecualian pada khususnya. Kami pernah memiliki server yang menghabiskan terlalu banyak waktu CPU-nya untuk menyiapkan dan membuang objek pengecualian yang berisi jejak tumpukan untuk panggilan "pembukaan file" yang gagal dalam satu lingkaran memeriksa daftar yang diketahui lokasi untuk file tertentu.)

@alexbrainman , saya minta maaf atas kebingungan tentang apa yang terjadi jika versi yang lebih lama dari kode build Go yang berisi try. Jawaban singkatnya adalah seperti waktu lain kita mengubah bahasa: kompiler lama akan menolak kode baru dengan pesan yang sebagian besar tidak membantu (dalam hal ini "tidak terdefinisi: coba"). Pesan tersebut tidak membantu karena kompiler lama tidak mengetahui tentang sintaks baru dan tidak dapat lebih membantu. Orang-orang pada saat itu mungkin akan melakukan pencarian web untuk "mencoba tidak terdefinisi" dan mencari tahu tentang fitur baru.

Dalam contoh @thepudds , kode yang menggunakan try memiliki go.mod yang berisi baris 'go 1.15', artinya pembuat modul mengatakan bahwa kode tersebut ditulis dengan versi bahasa Go. Ini berfungsi sebagai sinyal untuk perintah go yang lebih lama untuk menyarankan setelah kesalahan kompilasi bahwa mungkin pesan yang tidak membantu ini disebabkan oleh versi Go yang terlalu lama. Ini secara eksplisit merupakan upaya untuk membuat pesan sedikit lebih bermanfaat tanpa memaksa pengguna untuk menggunakan pencarian web. Jika itu membantu, bagus; jika tidak, pencarian web tampaknya cukup efektif.

@guybrand , kembali https://github.com/golang/go/issues/32437#issuecomment -503287670 dan dengan permintaan maaf karena kemungkinan terlambat untuk pertemuan Anda:

Satu masalah secara umum dengan fungsi yang mengembalikan tipe yang tidak terlalu salah adalah bahwa untuk non-antarmuka, konversi ke kesalahan tidak mempertahankan nihil. Jadi misalnya jika Anda memiliki tipe beton *MyError kustom Anda sendiri (katakanlah, pointer ke struct) dan gunakan err == nil sebagai sinyal untuk sukses, itu bagus sampai Anda memilikinya

func f() (int, *MyError)
func g() (int, error) { x, err := f(); return x, err }

Jika f mengembalikan nil *MyError, g mengembalikan nilai yang sama sebagai kesalahan non-nil, yang kemungkinan bukan yang dimaksudkan. Jika *MyError adalah antarmuka alih-alih pointer struct, maka konversi mempertahankan nilness, tetapi meskipun demikian itu adalah kehalusan.

Untuk mencoba, Anda mungkin berpikir bahwa karena coba hanya akan memicu nilai bukan nil, tidak masalah. Misalnya, ini sebenarnya OK sejauh mengembalikan kesalahan non-nil ketika f gagal, dan juga OK sejauh mengembalikan kesalahan nihil ketika f berhasil:

func g() (int, error) {
    return try(f()), nil
}

Jadi itu sebenarnya baik-baik saja, tetapi kemudian Anda mungkin melihat ini dan berpikir untuk menulis ulang menjadi

func g() (int, error) {
    return f()
}

yang sepertinya harus sama tetapi tidak.

Ada cukup banyak rincian lain dari proposal percobaan yang membutuhkan pemeriksaan dan evaluasi yang cermat dalam pengalaman nyata sehingga sepertinya memutuskan tentang kehalusan khusus ini akan lebih baik untuk ditunda.

Terima kasih semuanya atas semua umpan balik sejauh ini . Pada titik ini, tampaknya kami telah mengidentifikasi manfaat utama, kekhawatiran, dan kemungkinan implikasi baik dan buruk dari try . Untuk membuat kemajuan, itu perlu dievaluasi lebih lanjut dengan melihat apa arti try untuk basis kode yang sebenarnya. Diskusi pada titik ini berputar-putar dan mengulangi poin-poin yang sama.

Pengalaman sekarang lebih berharga daripada diskusi lanjutan. Kami ingin mendorong orang-orang meluangkan waktu untuk bereksperimen dengan tampilan try dalam basis kode mereka sendiri dan menulis serta menautkan laporan pengalaman di laman umpan balik .

Untuk memberi semua orang waktu untuk bernapas dan bereksperimen, kami akan menghentikan sementara percakapan ini dan mengunci masalah selama satu setengah minggu ke depan.

Penguncian akan dimulai sekitar 1p PDT/4p EDT (sekitar 3 jam dari sekarang) untuk memberi orang kesempatan untuk mengirimkan posting yang tertunda. Kami akan membuka kembali masalah ini untuk diskusi lebih lanjut pada 1 Juli.

Yakinlah bahwa kami tidak berniat untuk terburu-buru fitur bahasa baru apa pun tanpa meluangkan waktu untuk memahaminya dengan baik dan memastikan bahwa mereka memecahkan masalah nyata dalam kode nyata. Kami akan meluangkan waktu yang diperlukan untuk memperbaikinya, seperti yang telah kami lakukan di masa lalu.

Halaman wiki itu penuh dengan tanggapan untuk diperiksa/ditangani. Saya sarankan Anda memulai halaman baru.

Bagaimanapun, saya tidak akan punya waktu untuk melanjutkan berkebun di wiki.

@networkimprov , terima kasih atas bantuan Anda berkebun. Saya membuat bagian atas baru di https://github.com/golang/go/wiki/Go2ErrorHandlingFeedback. Saya pikir itu harus lebih baik daripada halaman yang sama sekali baru.

Saya juga melewatkan catatan 1p PDT / 4p EDT Robert untuk kuncinya, jadi saya menguncinya terlalu cepat. Ini terbuka lagi, untuk sedikit lebih lama.

Saya berencana menulis ini, dan hanya ingin menyelesaikannya sebelum terkunci.

Saya harap tim go tidak melihat kritik dan merasa bahwa itu adalah indikasi sentimen mayoritas. Selalu ada kecenderungan minoritas vokal menguasai percakapan, dan saya merasa itu mungkin terjadi di sini. Ketika semua orang bersinggungan, itu membuat orang lain enggan untuk membicarakan proposal SEBAGAIMANA ADANYA.

Jadi - saya ingin mengartikulasikan posisi positif saya untuk apa nilainya.

Saya memiliki kode yang sudah menggunakan penangguhan untuk kesalahan dekorasi/anotasi, bahkan untuk mengeluarkan jejak tumpukan, tepatnya alasan ini.

Melihat:
https://github.com/ugorji/go-ndb/blob/master/ndb/ndb.go#L331
https://github.com/ugorji/go-serverapp/blob/master/app/baseapp.go#L129
https://github.com/ugorji/go-serverapp/blob/master/app/webrouter.go#L180

yang semuanya memanggil errorutil.OnError(*error)

https://github.com/ugorji/go-common/blob/master/errorutil/errors.go#L193

Ini sejalan dengan defer helper yang disebutkan oleh Russ/Robert sebelumnya.

Ini adalah pola yang sudah saya gunakan, FWIW. Ini bukan sihir. Ini benar-benar seperti IMHO.

Saya juga menggunakannya dengan parameter bernama, dan bekerja dengan sangat baik.

Saya mengatakan ini untuk membantah anggapan bahwa apapun yang direkomendasikan di sini adalah sihir.

Kedua, saya ingin menambahkan beberapa komentar pada try(...) sebagai sebuah fungsi.
Ini memiliki satu keunggulan yang jelas dibandingkan kata kunci, karena dapat diperluas untuk mengambil parameter.

Ada 2 mode ekstensi yang telah dibahas di sini:

  • memperpanjang mencoba untuk mengambil label untuk melompat ke
  • memperpanjang mencoba untuk mengambil penangan dari bentuk func(error) error

Untuk masing-masing dari mereka, diperlukan try sebagai fungsi yang mengambil satu parameter, dan dapat diperpanjang nanti untuk mengambil parameter kedua jika perlu.

Keputusan belum dibuat tentang apakah perpanjangan percobaan diperlukan, dan jika demikian, arah apa yang harus diambil. Akibatnya, arah pertama adalah memberikan upaya untuk menghilangkan sebagian besar gagap "jika err != nil { return err }" yang telah saya benci selamanya tetapi saya ambil sebagai biaya menjalankan bisnis.

Saya pribadi senang bahwa try adalah sebuah fungsi, yang dapat saya panggil inline, misalnya saya dapat menulis

var u User = db.loadUser(try(strconv.Atoi(stringId)))

Sebagai lawan:

var id int // i have to define this on its own if err is already defined in an enclosing block
id, err = strconv.Atoi(stringId)
if err != nil {
  return
}
var u User = db.loadUser(id)

Seperti yang Anda lihat, saya hanya menurunkan 6 baris menjadi 1. Dan 5 dari baris tersebut benar-benar boilerplate.
Ini adalah sesuatu yang telah saya tangani berkali-kali, dan saya telah menulis banyak kode dan paket go - Anda dapat memeriksa github saya untuk melihat beberapa yang telah saya posting online, atau perpustakaan go-codec saya.

Akhirnya, banyak komentar di sini tidak benar-benar menunjukkan masalah dengan proposal, sebanyak mereka telah mengemukakan cara yang mereka sukai untuk menyelesaikan masalah.

Saya pribadi senang bahwa try(...) masuk. Dan saya menghargai alasan mengapa try sebagai fungsi adalah solusi yang disukai. Saya jelas suka bahwa penangguhan digunakan di sini, karena itu masuk akal.

Mari kita ingat salah satu prinsip inti go - konsep ortogonal yang dapat digabungkan dengan baik. Proposal ini memanfaatkan banyak konsep ortogonal go (tunda, parameter pengembalian bernama, fungsi bawaan untuk melakukan apa yang tidak mungkin dilakukan melalui kode pengguna, dll.) untuk memberikan manfaat utama yang
go pengguna telah meminta secara universal selama bertahun-tahun yaitu mengurangi/menghilangkan if err != nil { return err } boilerplate. Survei Pengguna Go menunjukkan bahwa ini adalah masalah nyata. Tim go sadar bahwa ini adalah masalah nyata. Saya senang bahwa suara keras dari beberapa orang tidak terlalu mencondongkan posisi tim go.

Saya punya satu pertanyaan tentang try sebagai goto implisit if err != nil.

Jika kita memutuskan itu arahnya, apakah akan sulit untuk retrofit "coba lakukan pengembalian" menjadi "coba lakukan goto",
mengingat goto telah mendefinisikan semantik bahwa Anda tidak dapat melewati variabel yang tidak terisi?

Terima kasih atas catatan Anda, @ugorji.

Saya punya satu pertanyaan tentang try sebagai goto implisit if err != nil.

Jika kita memutuskan itu arahnya, apakah akan sulit untuk retrofit "coba lakukan pengembalian" menjadi "coba lakukan goto",
mengingat goto telah mendefinisikan semantik bahwa Anda tidak dapat melewati variabel yang tidak terisi?

Ya, tepat sekali. Ada beberapa diskusi tentang #26058.
Saya pikir 'try-goto' memiliki setidaknya tiga serangan terhadapnya:
(1) Anda harus menjawab variabel yang tidak terisi,
(2) Anda kehilangan informasi tumpukan tentang percobaan mana yang gagal, yang sebaliknya Anda masih dapat menangkap dalam kasus pengembalian+penundaan, dan
(3) semua orang suka membenci di goto.

Ya, try adalah caranya.
Saya sudah mencoba menambahkan try sekali, dan saya menyukainya.
Tambalan - https://github.com/ascheglov/go/pull/1
Topik tentang Reddit - https://www.reddit.com/r/golang/comments/6vt3el/the_try_keyword_proofofconcept/

@griesemer

Sambungan dari https://github.com/golang/go/issues/32825#issuecomment -507120860 ...

Sejalan dengan premis bahwa penyalahgunaan try akan dikurangi dengan tinjauan kode, pemeriksaan, dan/atau standar komunitas, saya dapat melihat kebijaksanaan dalam menghindari perubahan bahasa untuk membatasi fleksibilitas try . Saya tidak melihat kebijaksanaan memberikan fasilitas tambahan yang sangat mendorong manifestasi yang lebih sulit/tidak enak untuk dikonsumsi.

Dalam memecah beberapa ini, tampaknya ada dua bentuk aliran kontrol jalur kesalahan yang diungkapkan: Manual, dan Otomatis. Mengenai pembungkusan kesalahan, tampaknya ada tiga bentuk yang diungkapkan: Langsung, Tidak Langsung, dan Lewati. Ini menghasilkan enam "mode" total penanganan kesalahan.

Mode Manual Direct, dan Automatic Direct sepertinya cocok:

wrap := func(err error) error {
  return fmt.Errorf("failed to process %s: %v", filename, err)
}

f, err := os.Open(filename)
if err != nil {
    return nil, wrap(err)
}
defer f.Close()

info, err := f.Stat()
if err != nil {
    return nil, wrap(err)
}
// in errors, named better, and optimized
WrapfFunc := func(format string, args ...interface{}) func(error) error {
  return func(err error) error {
    if err == nil {
      return nil
    }
    s := fmt.Sprintf(format, args...)
    return errors.Errorf(s+": %w", err)
  }
}

``` pergi
bungkus := error.WrapfFunc("gagal memproses %s", nama file)

f, err := os.Open(nama file)
coba (bungkus (err))
tunda f.Tutup()

info, err := f.Stat()
coba (bungkus (err))

Manual Pass-through, and Automatic Pass-through modes are also simple enough to be agreeable (despite often being a code smell):
```go
f, err := os.Open(filename)
if err != nil {
    return nil, err
}
defer f.Close()

info, err := f.Stat()
if err != nil {
    return nil, err
}
f := try(os.Open(filename))
defer f.Close()

info := try(f.Stat())

Namun, mode Manual Tidak Langsung dan Otomatis Tidak Langsung keduanya cukup tidak menyenangkan karena kemungkinan besar kesalahan halus:

defer errd.Wrap(&err, "failed to do X for %s", filename)

var f *os.File
f, err = os.Open(filename)
if err != nil {
    return
}
defer f.Close()

var info os.FileInfo
info, err = f.Stat()
if err != nil {
    return
}
defer errd.Wrap(&err, "failed to do X for %s", filename)

f := try(os.Open(filename))
defer f.Close()

info := try(f.Stat())

Sekali lagi, saya bisa mengerti tidak melarang mereka, tetapi memfasilitasi/memberkati mode tidak langsung adalah di mana ini masih menimbulkan tanda bahaya yang jelas bagi saya. Cukup begitu, saat ini, bagi saya untuk tetap skeptis terhadap seluruh premis.

Coba tidak boleh menjadi fungsi untuk menghindari itu

info := try(try(os.Open(filename)).Stat())

kebocoran berkas.

Maksud saya pernyataan try tidak akan mengizinkan chaining. Dan itu lebih baik dilihat sebagai bonus. Ada masalah kompatibilitas sekalipun.

@sirkon Karena try khusus, bahasa tersebut dapat melarang try bersarang jika itu penting - bahkan jika try terlihat seperti sebuah fungsi. Sekali lagi, jika ini adalah satu-satunya penghalang jalan untuk try , itu dapat dengan mudah diatasi dengan berbagai cara ( go vet , atau batasan bahasa). Mari kita beralih dari ini - kita sudah sering mendengarnya sekarang. Terima kasih.

Mari kita beralih dari ini - kita sudah sering mendengarnya

“Ini sangat membosankan, mari kita lanjutkan dari ini”

Ada analog bagus lainnya:

- Teori Anda bertentangan dengan fakta!
- Lebih buruk untuk fakta!

Oleh Hegel

Maksud saya, Anda memecahkan masalah yang sebenarnya tidak ada. Dan cara yang jelek pada saat itu.

Mari kita lihat di mana sebenarnya masalah ini muncul: penanganan efek samping dari dunia luar, itu saja. Dan ini sebenarnya adalah salah satu bagian termudah secara logis dalam rekayasa perangkat lunak. Dan yang paling penting pada saat itu. Saya tidak mengerti mengapa kita membutuhkan penyederhanaan untuk hal termudah yang akan membuat kita kehilangan keandalan.

IMO masalah tersulit semacam itu adalah pelestarian konsistensi data dalam sistem terdistribusi (dan sebenarnya tidak begitu terdistribusi). Dan penanganan kesalahan bukanlah masalah yang saya hadapi di Go saat menyelesaikan ini. Kurangnya pemahaman irisan dan peta, kurangnya jumlah/aljabar/varians/jenis apa pun JAUH lebih menjengkelkan.

Karena perdebatan di sini tampaknya terus berlanjut, izinkan saya ulangi lagi:

Pengalaman sekarang lebih berharga daripada diskusi lanjutan. Kami ingin mendorong orang-orang meluangkan waktu untuk bereksperimen dengan tampilan try di basis kode mereka sendiri dan menulis serta menautkan laporan pengalaman di halaman umpan balik.

Jika pengalaman nyata memberikan bukti yang signifikan untuk mendukung atau menentang proposal ini, kami ingin mendengarnya di sini. Kekesalan hewan peliharaan pribadi, skenario hipotetis, desain alternatif, dll. Kami dapat mengakui tetapi mereka kurang dapat ditindaklanjuti.

Terima kasih.

Saya tidak ingin bersikap kasar di sini, dan saya menghargai semua moderasi Anda, tetapi komunitas telah berbicara sangat keras tentang penanganan kesalahan yang diubah. Mengubah sesuatu, atau menambahkan kode baru, akan mengecewakan _semua_ orang yang lebih menyukai sistem saat ini. Anda tidak bisa membuat semua orang bahagia, jadi mari kita fokus pada 88% yang bisa kita bahagiakan (angka didapat dari rasio suara di bawah).

Pada saat penulisan ini, utas "biarkan saja" berada di 1322 suara naik dan 158 turun. Utas ini berada di 158 ke atas dan 255 ke bawah. Jika itu bukan akhir langsung dari utas ini tentang penanganan kesalahan, maka kita harus memiliki alasan yang sangat bagus untuk terus mendorong masalah ini.

Adalah mungkin untuk selalu melakukan apa yang komunitas Anda teriakkan dan menghancurkan produk Anda pada waktu yang sama.

Paling tidak, saya pikir proposal khusus ini harus dianggap gagal.

Untungnya, go tidak dirancang oleh panitia. Kita perlu percaya bahwa pemelihara bahasa yang kita semua cintai akan terus membuat keputusan terbaik dengan semua data yang tersedia untuk mereka, dan tidak akan membuat keputusan berdasarkan opini populer dari massa. Ingat - mereka juga menggunakan go, sama seperti kita. Mereka merasakan titik sakitnya, sama seperti kita.

Jika Anda memiliki posisi, luangkan waktu untuk mempertahankannya seperti cara tim Go mempertahankan proposal mereka. Jika tidak, Anda hanya menenggelamkan percakapan dengan sentimen terbang-demi-malam yang tidak dapat ditindaklanjuti dan tidak meneruskan percakapan. Dan itu mempersulit orang-orang yang ingin terlibat, seperti yang dikatakan orang-orang mungkin hanya ingin menunggu sampai kebisingan mereda.

Ketika proses proposal dimulai, Russ membuat kesepakatan besar tentang penginjilan perlunya laporan pengalaman sebagai cara untuk mempengaruhi proposal atau membuat permintaan Anda didengar. Mari kita setidaknya mencoba untuk menghormati itu.

Tim go telah mempertimbangkan semua umpan balik yang dapat ditindaklanjuti. Mereka belum mengecewakan kita. Lihat dokumen rinci yang dihasilkan untuk alias, untuk modul, dll. Mari setidaknya beri mereka perhatian yang sama dan luangkan waktu untuk memikirkan keberatan kami, menanggapi posisi mereka atas keberatan Anda, dan mempersulit penolakan Anda untuk diabaikan.

Manfaat Go selalu adalah bahwa itu adalah bahasa yang kecil dan sederhana dengan konstruksi ortogonal yang dirancang oleh sekelompok kecil orang yang akan memikirkan ruang secara kritis sebelum berkomitmen pada suatu posisi. Mari kita bantu mereka di mana kita bisa, daripada hanya mengatakan "lihat, suara populer mengatakan tidak" - di mana banyak orang yang memilih bahkan mungkin tidak memiliki banyak pengalaman dalam go atau mengerti sepenuhnya. Saya telah membaca poster serial yang mengakui bahwa mereka tidak mengetahui beberapa konsep dasar dari bahasa yang kecil dan sederhana ini. Itu membuat sulit untuk menanggapi umpan balik Anda dengan serius.

Bagaimanapun, menyebalkan bahwa saya melakukan ini di sini - jangan ragu untuk menghapus komentar ini. Saya tidak akan tersinggung. Tapi seseorang harus mengatakan ini terus terang!

Keseluruhan proposal ke-2 ini terlihat sangat mirip dengan influencer digital yang mengorganisir rapat umum bagi saya. Kontes popularitas tidak mengevaluasi manfaat teknis.

Orang mungkin diam tetapi mereka masih mengharapkan Go 2. Saya pribadi menantikan ini dan sisanya dari Go 2. Go 1 adalah bahasa yang hebat dan cocok untuk berbagai jenis program. Saya harap Go 2 akan memperluasnya.

Akhirnya saya juga akan membalikkan preferensi saya untuk memiliki try sebagai pernyataan. Sekarang saya mendukung proposal apa adanya. Setelah bertahun-tahun di bawah janji compat "Go 1" orang berpikir Go telah diukir di batu. Karena asumsi bermasalah itu, tidak mengubah sintaks bahasa dalam contoh ini sepertinya kompromi yang jauh lebih baik di mata saya sekarang. Sunting: Saya juga berharap dapat melihat laporan pengalaman untuk pengecekan fakta.

PS: Saya ingin tahu oposisi seperti apa yang akan terjadi ketika obat generik diusulkan.

Kami memiliki sekitar selusin alat yang ditulis di perusahaan kami. Saya menjalankan alat tryhard terhadap basis kode kami dan menemukan 933 calon try() potensial. Secara pribadi, saya percaya fungsi try() adalah ide yang brilian karena ini memecahkan lebih dari sekadar masalah kode boilerplate.

Ini memaksa pemanggil dan fungsi/metode yang dipanggil untuk mengembalikan kesalahan sebagai parameter terakhir. Ini tidak akan diizinkan:

var file= try(parse())

func parse()(err, result) {
}

Ini memberlakukan satu cara untuk menangani kesalahan alih-alih mendeklarasikan variabel kesalahan dan secara longgar mengizinkan pola err!=nil err==nil, yang menghalangi keterbacaan, meningkatkan risiko kode rawan kesalahan di IMO:

func Foo() (err error) {
    var file, ferr = os.Open("file1.txt")
    if ferr == nil {
               defer file.Close()
        var parsed, perr = parseFile(file)
        if perr != nil {
            return
        }
        fmt.Printf("%s", parsed)
    }
    return nil
}

Dengan try(), kode lebih mudah dibaca, konsisten dan lebih aman menurut saya:

func Foo() (err error) {
    var file = try(os.Open("file.txt"))
        defer file.Close()
    var parsed = try(parseFile(file))
    fmt.Printf(parsed)
    return
}

Saya menjalankan beberapa eksperimen yang mirip dengan apa yang dilakukan @lpar pada semua repositori Go Heroku yang tidak diarsipkan (publik dan pribadi).

Hasilnya ada di Intisari ini: https://Gist.github.com/freeformz/55abbe5da61a28ab94dbb662bfc7f763

cc @davecheney

@ubikenobi Fungsi Anda yang lebih aman ~is~ bocor.

Juga, saya belum pernah melihat nilai yang dikembalikan setelah kesalahan. Padahal, saya bisa membayangkan masuk akal ketika suatu fungsi adalah semua tentang kesalahan dan nilai-nilai lain yang dikembalikan tidak bergantung pada kesalahan itu sendiri (mungkin mengarah ke dua pengembalian kesalahan dengan yang kedua "menjaga" nilai-nilai sebelumnya).

Terakhir, meskipun tidak umum, err == nil memberikan tes yang sah untuk beberapa pengembalian awal.

@Daved

Terima kasih telah menunjukkan tentang kebocoran, saya lupa menambahkan defer.Close() pada kedua contoh. (diperbarui sekarang).

Saya jarang melihat err kembali dalam urutan itu juga, tetapi masih bagus untuk dapat menangkapnya pada waktu kompilasi jika itu kesalahan daripada dengan desain.

Saya melihat kasus err==nil sebagai pengecualian daripada norma dalam banyak kasus. Ini dapat berguna dalam beberapa kasus seperti yang Anda sebutkan, tetapi yang tidak saya sukai adalah pengembang memilih secara tidak konsisten tanpa alasan yang valid. Untungnya, dalam basis kode kami, sebagian besar pernyataan adalah err!=nil, yang dapat dengan mudah memanfaatkan fungsi try().

  • Saya menjalankan tryhard terhadap Go API besar yang saya kelola dengan tim yang terdiri dari empat insinyur lainnya secara penuh waktu. Dalam 45580 baris kode Go, tryhard mengidentifikasi 301 kesalahan untuk ditulis ulang (jadi, itu akan menjadi +301/-903 perubahan), atau akan menulis ulang sekitar 2% dari kode dengan asumsi setiap kesalahan membutuhkan sekitar 3 baris. Mempertimbangkan komentar, spasi, impor, dll. yang terasa penting bagi saya.
  • Saya telah menggunakan alat baris tryhard untuk mengeksplorasi bagaimana try akan mengubah pekerjaan saya, dan secara subyektif itu mengalir dengan sangat baik kepada saya! Kata kerja try terasa lebih jelas bagi saya bahwa ada sesuatu yang salah dalam fungsi pemanggilan, dan menyelesaikannya dengan kompak. Saya sangat terbiasa menulis if err != nil , dan saya tidak keberatan, tetapi juga tidak keberatan untuk mengubahnya. Menulis dan memfaktorkan ulang variabel kosong sebelum kesalahan (yaitu membuat irisan/peta/variabel kosong untuk kembali) secara berulang mungkin lebih membosankan daripada err itu sendiri.
  • Agak sulit untuk mengikuti semua utas diskusi, tetapi saya ingin tahu apa artinya ini untuk membungkus kesalahan. Akan lebih baik jika try adalah variadic jika Anda ingin menambahkan konteks secara opsional seperti try(json.Unmarshal(b, &accountBalance), "failed to decode bank account info for user %s", user) . Sunting: poin ini mungkin di luar topik; dari melihat penulisan ulang non-coba di sinilah ini terjadi.
  • Saya sangat menghargai pemikiran dan perhatian yang dimasukkan ke dalam ini! Kompatibilitas dan stabilitas ke belakang sangat penting bagi kami dan upaya Go 2 hingga saat ini sangat lancar untuk memelihara proyek. Terima kasih!

Bukankah ini harus dilakukan pada sumber yang telah diperiksa oleh Gophers berpengalaman untuk memastikan bahwa penggantiannya rasional? Berapa banyak dari penulisan ulang "2%" yang seharusnya ditulis ulang dengan penanganan eksplisit? Jika kita tidak mengetahuinya, maka LOC tetap menjadi metrik yang relatif tidak berguna.

*Itulah mengapa posting saya pagi ini berfokus pada "mode" penanganan kesalahan. Lebih mudah dan lebih substantif untuk membahas mode penanganan kesalahan yang memfasilitasi try dan kemudian bergulat dengan potensi bahaya dari kode yang kemungkinan akan kita tulis daripada menjalankan penghitung baris yang agak sewenang-wenang.

@kingishb Berapa banyak tempat _try_ yang ditemukan dalam fungsi publik dari paket non-utama? Biasanya fungsi publik harus mengembalikan kesalahan paket-asli (yaitu dibungkus atau dihias)....

@networkimprov Itu formula yang terlalu sederhana untuk kepekaan saya. Di mana itu benar adalah dalam hal permukaan API yang mengembalikan kesalahan yang dapat diperiksa. Biasanya tepat untuk menambahkan konteks ke pesan kesalahan berdasarkan relevansi konteks, bukan posisinya di tumpukan panggilan.

Banyak positif palsu kemungkinan berhasil lolos dalam metrik saat ini. Dan bagaimana dengan kesalahan yang terjadi karena mengikuti praktik yang disarankan (https://blog.golang.org/errors-are-values)? try kemungkinan akan mengurangi penggunaan praktik semacam itu dan, dalam pengertian itu, mereka adalah target utama untuk penggantian (mungkin satu-satunya kasus penggunaan yang benar-benar menarik bagi saya). Jadi, sekali lagi, tampaknya tidak ada gunanya mengikis sumber yang ada tanpa lebih banyak uji tuntas.

Terima kasih @ubikenobi , @freeformz , dan @kingishb untuk mengumpulkan data Anda, sangat dihargai! Selain itu, jika Anda menjalankan tryhard dengan opsi -err="" if juga akan mencoba bekerja dengan kode di mana variabel kesalahan disebut selain err (seperti e ). Ini dapat menghasilkan beberapa kasus lagi, tergantung pada basis kode (tetapi juga mungkin meningkatkan kemungkinan positif palsu).

@griesemer jika Anda mencari lebih banyak poin data. Saya telah menjalankan tryhard terhadap dua layanan mikro kami, dengan hasil berikut:

cloc v 1.82 / coba keras
13280 Go baris kode / 148 diidentifikasi untuk dicoba (1%)

Layanan lain:
9768 Go baris kode / 50 diidentifikasi untuk dicoba (0,5%)

Selanjutnya tryhard memeriksa serangkaian layanan mikro yang lebih luas:

314343 Go baris kode / 1563 diidentifikasi untuk dicoba (0,5%)

Melakukan pemeriksaan cepat. Jenis paket yang dapat dioptimalkan oleh try biasanya adalah adapter/pembungkus layanan yang secara transparan mengembalikan kesalahan (GRPC) yang dikembalikan dari layanan yang dibungkus.

Semoga ini membantu.

Ini benar-benar ide yang buruk.

  • Kapan err var muncul untuk defer ? Bagaimana dengan "eksplisit lebih baik daripada implisit"?
  • Kami menggunakan aturan sederhana: Anda harus segera menemukan tepat satu tempat di mana Anda memiliki kesalahan kembali. Setiap kesalahan dibungkus dengan konteks untuk memahami apa dan di mana yang salah. defer akan membuat banyak kode jelek dan sulit dipahami.
  • @davecheney menulis posting bagus tentang kesalahan dan proposal sepenuhnya bertentangan dengan semua yang ada di posting ini.
  • Akhirnya, jika Anda menggunakan os.Exit , kesalahan Anda tidak akan dicentang.

Saya baru saja menjalankan tryhard pada sebuah paket (dengan vendor) dan dilaporkan 2478 dengan jumlah kode turun dari 873934 menjadi 851178 tetapi saya tidak yakin bagaimana menafsirkannya karena saya tidak tahu berapa banyak yang disebabkan oleh pembungkusan yang berlebihan (dengan stdlib kurang mendukung pembungkusan kesalahan pelacakan tumpukan) atau berapa banyak dari kode itu bahkan tentang penanganan kesalahan.

Apa yang saya tahu, bagaimanapun, adalah bahwa hanya minggu ini saja saya membuang banyak waktu karena copy-pasta seperti if err != nil { return nil } dan kesalahan yang terlihat seperti error: cannot process ....file: cannot parse ...file: cannot open ...file .

\ Saya tidak akan terlalu membebani jumlah suara kecuali Anda berpikir bahwa hanya ada ~3000 pengembang Go di luar sana. Penghitungan suara yang tinggi pada non-proposal lainnya hanya karena fakta bahwa masalah tersebut berhasil mencapai puncak HN dan Reddit — komunitas Go tidak diketahui secara pasti karena kurangnya dogma dan/atau penolakan jadi tidak -satu harus terkejut tentang jumlah suara.

Saya juga tidak akan menganggap upaya banding-ke-otoritas terlalu serius, karena otoritas yang sama ini diketahui menolak ide dan proposal baru bahkan setelah ketidaktahuan dan/atau kesalahpahaman mereka ditunjukkan.
\

Kami menjalankan tryhard -err="" pada layanan terbesar kami (±163k baris kode termasuk pengujian) - layanan ini telah menemukan 566 kemunculan. Saya menduga itu akan lebih dalam praktiknya, karena beberapa kode ditulis dengan if err != nil dalam pikiran, jadi itu dirancang di sekitarnya (artikel "kesalahan adalah nilai" Rob Pike tentang menghindari pengulangan muncul di benak).

@griesemer saya menambahkan file baru ke intinya. Itu dibuat dengan -err="". Saya memeriksanya dan ada beberapa perubahan. Saya juga memperbarui tryhard pagi ini, jadi versi yang lebih baru juga digunakan.

@griesemer Saya pikir tryhard akan lebih berguna jika bisa menghitung:

a) jumlah situs panggilan yang menghasilkan kesalahan
b) jumlah pernyataan tunggal if err != nil [&& ...] handler (kandidat untuk on err #32611)
c) jumlah yang mengembalikan sesuatu (kandidat untuk defer #32676)
d) jumlah yang mengembalikan err (kandidat untuk try() )
e) jumlah yang ada dalam fungsi ekspor paket non-utama (kemungkinan positif palsu)

Membandingkan total LoC dengan instance return err agak kurang konteks, IMO.

@networkimprov Setuju - saran serupa telah diajukan sebelumnya. Saya akan mencoba mencari waktu selama beberapa hari ke depan untuk meningkatkan ini.

Berikut adalah statistik menjalankan tryhard melalui basis kode internal kami (hanya kode kami, bukan dependensi):

Sebelum:

  • 882 .go file
  • lokasi 352434
  • 329909 lokasi tidak kosong

Setelah berusaha keras:

  • 2701 penggantian (rata-rata 3.1 penggantian / file)
  • 345364 lokasi (-2,0%)
  • 322838 lokasi tidak kosong (-2,1%)

Sunting: Sekarang @griesemer memperbarui tryhard untuk memasukkan statistik ringkasan, berikut adalah beberapa lagi:

  • 39,2% dari pernyataan if adalah if <err> != nil
  • 69,6% di antaranya adalah kandidat try

Melihat melalui penggantian yang ditemukan tryhard, pasti ada jenis kode di mana penggunaan try akan sangat lazim, dan jenis lain yang jarang digunakan.

Saya juga memperhatikan beberapa tempat yang tryhard tidak dapat mengubahnya, tetapi akan sangat diuntungkan dari try. Misalnya, berikut adalah beberapa kode yang kami miliki untuk mendekode pesan menurut protokol kabel sederhana (diedit untuk kesederhanaan/kejelasan):

func (req *Request) Decode(r Reader) error {
    typ, err := readByte(r)
    if err != nil {
        return err
    }
    req.Type = typ
    req.Body, err = readString(r)
    if err != nil {
        return unexpected(err)
    }

    req.ID, err = readID(r)
    if err != nil {
        return unexpected(err)
    }
    n, err := binary.ReadUvarint(r)
    if err != nil {
        return unexpected(err)
    }
    req.SubIDs = make([]ID, n)
    for i := range req.SubIDs {
        req.SubIDs[i], err = readID(r)
        if err != nil {
            return unexpected(err)
        }
    }
    return nil
}

// unexpected turns any io.EOF into an io.ErrUnexpectedEOF.
func unexpected(err error) error {
    if err == io.EOF {
        return io.ErrUnexpectedEOF
    }
    return err
}

Tanpa try , kami hanya menulis unexpected pada titik balik yang diperlukan karena tidak ada peningkatan besar dengan menanganinya di satu tempat. Namun, dengan try , kita dapat menerapkan transformasi kesalahan unexpected dengan penundaan dan kemudian secara dramatis mempersingkat kode, membuatnya lebih jelas dan lebih mudah untuk dibaca:

func (req *Request) Decode(r Reader) (err error) {
    defer func() { err = unexpected(err) }()

    req.Type = try(readByte(r))
    req.Body = try(readString(r))
    req.ID = try(readID(r))

    n := try(binary.ReadUvarint(r))
    req.SubIDs = make([]ID, n)
    for i := range req.SubIDs {
        req.SubIDs[i] = try(readID(r))
    }
    return nil
}

@cespare Laporan yang fantastis!

Cuplikan yang dikurangi sepenuhnya umumnya lebih baik, tetapi tanda kurung bahkan lebih buruk dari yang saya harapkan, dan try dalam loop sama buruknya dengan yang saya harapkan.

Kata kunci jauh lebih mudah dibaca dan agak tidak masuk akal bahwa itu adalah poin yang berbeda dari yang lain. Berikut ini dapat dibaca dan tidak membuat saya khawatir tentang seluk-beluk karena hanya satu nilai yang dikembalikan (meskipun, itu masih bisa muncul dalam fungsi yang lebih panjang dan/atau yang memiliki banyak sarang):

func (req *Request) Decode(r Reader) (err error) {
    defer func() { err = wrapEOF(err) }()

    req.Type = try readByte(r)
    req.Body = try readString(r)
    req.ID = try readID(r)

    n := try binary.ReadUvarint(r)
    req.SubIDs = make([]ID, n)
    for i := range req.SubIDs {
        req.SubIDs[i], err = readID(r)
        try err
    }
    return nil
}

* Bersikap adil tentang hal itu, penyorotan kode akan banyak membantu, tetapi itu sepertinya lipstik murahan.

Apakah Anda memahami bahwa keuntungan terbesar yang Anda dapatkan jika kode benar-benar buruk?

Jika Anda menggunakan unexpected() atau mengembalikan kesalahan apa adanya, Anda tidak tahu apa-apa tentang kode dan aplikasi Anda.

try tidak dapat membantu Anda menulis kode yang lebih baik, tetapi dapat menghasilkan lebih banyak kode buruk.

@cespare Dekoder juga bisa berupa struct dengan tipe kesalahan di dalamnya, dengan metode memeriksa err == nil sebelum setiap operasi dan mengembalikan boolean ok.

Karena ini adalah proses yang kami gunakan untuk codec, try sama sekali tidak berguna karena seseorang dapat dengan mudah membuat idiom non-ajaib, lebih pendek, dan lebih ringkas untuk menangani kesalahan untuk kasus khusus ini.

@makhov Dengan "kode yang sangat buruk", saya berasumsi maksud Anda kode yang tidak membungkus kesalahan.

Jika demikian, maka Anda dapat mengambil kode yang terlihat seperti ini:

a, b, c, err := someFn()
if err != nil {
  return ..., errors.Wrap(err, ...)
}

Dan ubah menjadi kode yang identik secara semantik[1] yang terlihat seperti ini:

a, b, c, err := someFn()
try(errors.Wrap(err, ...))

Proposal tidak mengatakan Anda harus menggunakan penangguhan untuk membungkus kesalahan, hanya menjelaskan mengapa kata kunci handle dari iterasi sebelumnya dari proposal tidak diperlukan, karena dapat diterapkan dalam hal penangguhan tanpa perubahan bahasa.

(Komentar Anda yang lain juga tampaknya didasarkan pada contoh atau kode semu dalam proposal, yang bertentangan dengan inti dari apa yang diusulkan)

Saya menjalankan tryhard pada basis kode saya dengan 54K LOC, 1116 instance ditemukan.
Saya melihat perbedaannya, dan saya harus mengatakan bahwa saya memiliki konstruksi yang sangat sedikit yang akan sangat diuntungkan dari try, karena hampir seluruh penggunaan tipe konstruksi if err != nil saya adalah blok tingkat tunggal sederhana yang hanya mengembalikan kesalahan dengan konteks tambahan. Saya pikir saya hanya menemukan beberapa contoh di mana try benar-benar akan mengubah konstruksi kode.

Dengan kata lain, pendapat saya adalah bahwa try dalam bentuknya saat ini memberi saya:

  • lebih sedikit mengetik (pengurangan kekalahan ~30 karakter per kemunculan, dilambangkan dengan "**" di bawah)
-       **if err := **json.NewEncoder(&buf).Encode(in)**; err != nil {**
-               **return err**
-       **}**
+       try(json.NewEncoder(&buf).Encode(in))

sementara itu memperkenalkan masalah ini untuk saya:

  • Cara Lain Untuk Menangani Kesalahan
  • isyarat visual yang hilang untuk pemisahan jalur eksekusi

Seperti yang saya tulis sebelumnya di utas ini, saya dapat hidup dengan try , tetapi setelah mencobanya pada kode saya, saya pikir saya pribadi lebih suka ini tidak diperkenalkan ke bahasa. $0,02 saya

fitur yang tidak berguna, itu menghemat pengetikan, tetapi bukan masalah besar.
Saya lebih memilih cara lama.
menulis lebih banyak error handler membuat program mudah untuk trouble shooting.

Hanya beberapa pemikiran...

Ungkapan itu berguna dalam go tetapi hanya itu: sebuah idiom yang harus Anda
mengajar kepada pendatang baru. Seorang programmer go baru harus mempelajari itu, jika tidak mereka
bahkan mungkin tergoda untuk memperbaiki penanganan kesalahan "tersembunyi". Juga
kode tidak lebih pendek menggunakan idiom itu (sebaliknya) kecuali Anda lupa
untuk menghitung metode.

Sekarang mari kita bayangkan try diimplementasikan, seberapa berguna idiom itu untuk
kasus penggunaan itu? Mempertimbangkan:

  • Cobalah menjaga implementasi lebih dekat daripada menyebar ke seluruh metode.
  • Pemrogram akan membaca dan menulis kode dengan mencoba lebih sering dari itu
    idiom tertentu (yang jarang digunakan kecuali untuk setiap tugas tertentu). SEBUAH
    lebih banyak idiom yang digunakan menjadi lebih alami dan mudah dibaca kecuali ada yang jelas
    kerugian, yang jelas tidak terjadi di sini jika kita membandingkan keduanya dengan
    pikiran terbuka.

Jadi mungkin idiom itu akan dianggap tergantikan oleh try.

Em ter, 2 de jul de 2019 18:06, sebagai [email protected] escreveu:

@cespare https://github.com/cespare Dekoder juga bisa berupa struct dengan
jenis kesalahan di dalamnya, dengan metode memeriksa err == nil sebelumnya
setiap operasi dan mengembalikan boolean ok.

Karena ini adalah proses yang kami gunakan untuk codec, try sama sekali tidak berguna
karena seseorang dapat dengan mudah membuat idiom non sihir, lebih pendek, dan lebih ringkas
untuk menangani kesalahan untuk kasus khusus ini.


Anda menerima ini karena Anda disebutkan.
Balas email ini secara langsung, lihat di GitHub
https://github.com/golang/go/issues/32437?email_source=notifications&email_token=AAT5WM3YDDRZXVXOLDQXKH3P5O7L5A5CNFSM4HTGCZ72YY3PNVWWK3TUL52HS4DFVREXG43VMVBWW63LNMVXHJKT48Zissue
atau matikan utasnya
https://github.com/notifications/unsubscribe-auth/AAT5WMYXLLO74CIM6H4Y2RLP5O7L5ANCNFSM4HTGCZ7Q
.

Verbositas dalam penanganan kesalahan adalah hal yang baik menurut saya. Dengan kata lain saya tidak melihat kasus penggunaan yang kuat untuk dicoba.

Saya terbuka untuk ide ini tetapi saya merasa itu harus mencakup beberapa mekanisme untuk menentukan di mana pemisahan eksekusi terjadi. Xerror/Is akan baik-baik saja untuk beberapa kasus (misalnya jika kesalahannya adalah ErrNotExists Anda dapat menyimpulkan itu terjadi pada Open), tetapi untuk yang lain - termasuk kesalahan warisan di perpustakaan - tidak ada penggantinya.

Bisakah built-in serupa untuk memulihkan mungkin dimasukkan untuk memberikan informasi konteks di mana aliran kontrol berubah? Mungkin, agar tetap murah, fungsi terpisah digunakan sebagai pengganti try().

Atau mungkin debug.Try dengan sintaks yang sama seperti try() tetapi dengan informasi debug yang ditambahkan? Dengan cara ini try() bisa sama bergunanya dengan kode yang menggunakan kesalahan lama, tanpa memaksa Anda menggunakan penanganan kesalahan lama.

Alternatifnya adalah try() untuk membungkus dan menambahkan konteks tetapi dalam banyak kasus ini akan mengurangi kinerja tanpa tujuan, maka saran fungsi tambahan.

Sunting: setelah menulis ini, terpikir oleh saya bahwa kompiler dapat menentukan varian try() mana yang akan digunakan berdasarkan apakah ada pernyataan penangguhan yang menggunakan fungsi penyediaan konteks ini yang mirip dengan "pulih". Tidak yakin dengan kerumitan ini

@lestrrat Saya tidak akan mengatakan pendapat saya dalam komentar ini tetapi jika ada kesempatan untuk menjelaskan kepada Anda bagaimana "coba" dapat berdampak baik bagi kami, itu akan menjadi dua atau lebih token yang dapat ditulis dalam pernyataan if. Jadi jika Anda menulis 200 kondisi dalam pernyataan if, Anda akan dapat mengurangi banyak baris.

if try(foo()) == 1 && try(bar()) == 2 {
  // err
}
n1, err := foo()
if err != nil {
  // err
}
n2, err := bar()
if err != nil {
  // err
}
if n1 == 1 && n2 == 2 {
  // err
}

@mattn itu masalahnya, _secara teoritis_ ​​Anda benar sekali. Saya yakin kita bisa menemukan kasus di mana try akan sangat cocok.

Saya baru saja memberikan data bahwa dalam kehidupan nyata, setidaknya _I_ hampir tidak menemukan kemunculan konstruksi semacam itu yang akan mendapat manfaat dari terjemahan untuk dicoba dalam _kode saya_.

Ada kemungkinan bahwa saya menulis kode secara berbeda dari bagian dunia lainnya, tetapi saya hanya berpikir bahwa layak bagi seseorang untuk mengikutinya, berdasarkan terjemahan PoC, bahwa beberapa dari kita sebenarnya tidak mendapatkan banyak dari pengenalan try ke dalam bahasa.

Sebagai tambahan, saya masih tidak akan menggunakan gaya Anda dalam kode saya. Saya akan menulisnya sebagai

n1 := try(foo())
n2 := try(bar())
if n1 == 1 && n2 == 2 {
   return errors.New(`boo`)
}

jadi saya masih akan menghemat jumlah pengetikan yang sama per instance dari n1/n2/....n(n)s tersebut

Mengapa memiliki kata kunci (atau fungsi) sama sekali?

Jika konteks panggilan mengharapkan nilai n+1, maka semuanya seperti sebelumnya.

Jika konteks pemanggilan mengharapkan nilai n, perilaku try akan dimulai.

(Ini sangat berguna dalam kasus n=1 yang merupakan asal dari semua kekacauan yang mengerikan.)

Ide saya sudah menyoroti nilai pengembalian yang diabaikan; akan sepele untuk menawarkan isyarat visual untuk ini jika diperlukan.

@balasanjay Ya, kesalahan pembungkus adalah kasusnya. Tetapi kami juga memiliki logging, reaksi yang berbeda pada kesalahan yang berbeda (apa yang harus kami lakukan dengan variabel kesalahan, misalnya sql.NoRows ?), kode yang dapat dibaca dan seterusnya. Kami menulis defer f.Close() segera setelah membuka file agar jelas bagi pembaca. Kami segera memeriksa kesalahan untuk alasan yang sama.

Yang terpenting, proposal ini melanggar aturan " kesalahan adalah nilai ". Ini adalah bagaimana Go dirancang. Dan proposal ini secara langsung bertentangan dengan aturan.

try(errors.Wrap(err, ...)) adalah bagian lain dari kode yang mengerikan karena bertentangan dengan proposal ini dan desain Go saat ini.

Saya cenderung setuju dengan @lestrrat
Seperti biasanya foo() dan bar() sebenarnya:
SomeFunctionWithGoodName(Parm1, Parms2)

maka sintaks @mattn yang disarankan sebenarnya adalah:

if  try(SomeFunctionWithGoodName(Parm1, Parms2)) == 1 && try(package.SomeOtherFunction(Parm1, Parms2,Parm3))) == 2 {


} 

Keterbacaan biasanya akan berantakan.

pertimbangkan nilai pengembalian:
someRetVal, err := SomeFunctionWithGoodName(Parm1, Parms2)
lebih sering digunakan daripada hanya membandingkan dengan const seperti 1 atau 2 dan tidak bertambah buruk tetapi membutuhkan fitur penugasan ganda:

if  a := try(SomeFunctionWithGoodName(Parm1, Parms2)) && b:= try(package.SomeOtherFunction(Parm1, Parms2,Parm3))) {


} 

Adapun semua kasus penggunaan ("berapa banyak tryhard membantu saya"):

  1. Saya pikir Anda akan melihat perbedaan besar antara perpustakaan dan yang dapat dieksekusi, akan menarik untuk melihat dari orang lain jika mereka mendapatkan perbedaan ini juga
  2. saran saya adalah untuk tidak membandingkan % simpan dalam baris dalam kode melainkan jumlah kesalahan dalam kode vs jumlah refactored.
    (pendapat saya tentang ini adalah
    $find /path/to/repo -name '*.go' -exec cat {} \; | grep "err :=" | wc -l
    )

@makhov

proposal ini melanggar aturan "kesalahan adalah nilai"

Tidak juga. Kesalahan masih menjadi nilai dalam proposal ini. try() hanya menyederhanakan aliran kontrol dengan menjadi jalan pintas untuk if err != nil { return ...,err } . Tipe error entah bagaimana sudah "khusus" dengan menjadi tipe antarmuka bawaan. Proposal ini hanya menambahkan fungsi bawaan yang melengkapi tipe error . Tidak ada yang luar biasa di sini.

@ngrilly Menyederhanakan? Bagaimana?

func (req *Request) Decode(r Reader) error {
    defer func() { err = unexpected(err) }()

    req.Type = try(readByte(r))
    req.Body = try(readString(r))
    req.ID = try(readID(r))

    n := try(binary.ReadUvarint(r))
    req.SubIDs = make([]ID, n)
    for i := range req.SubIDs {
        req.SubIDs[i] = try(readID(r))
    }
    return nil
}

Bagaimana saya harus memahami bahwa kesalahan dikembalikan di dalam loop? Mengapa itu ditugaskan ke err var, bukan ke foo ?
Apakah lebih mudah untuk mengingatnya dan tidak menyimpannya dalam kode?

@daved

kurung bahkan lebih buruk dari yang saya harapkan [...] Sebuah kata kunci jauh lebih mudah dibaca dan itu agak tidak nyata bahwa itu adalah titik yang banyak orang lain berbeda.

Memilih antara kata kunci dan fungsi bawaan sebagian besar merupakan masalah estetika dan sintaksis. Sejujurnya saya tidak mengerti mengapa ini sangat penting bagi mata Anda.

PS: Fungsi bawaan memiliki keuntungan karena kompatibel ke belakang, dapat diperluas dengan parameter lain di masa mendatang, dan menghindari masalah seputar prioritas operator. Kata kunci memiliki keuntungan... menjadi kata kunci, dan menandakan try adalah "istimewa".

@makhov

Menyederhanakan?

Oke. Kata yang tepat adalah "memperpendek".

try() mempersingkat kode kita dengan mengganti pola if err != nil { return ..., err } dengan panggilan ke fungsi try() bawaan.

Ini persis seperti ketika Anda mengidentifikasi pola berulang dalam kode Anda, dan Anda mengekstraknya dalam fungsi baru.

Kita sudah memiliki fungsi bawaan seperti append(), yang bisa kita ganti dengan menulis kode "in extenso" sendiri setiap kali kita perlu menambahkan sesuatu ke slice. Tetapi karena kami melakukannya sepanjang waktu, itu terintegrasi dalam bahasa. try() tidak berbeda.

Bagaimana saya harus memahami bahwa kesalahan dikembalikan di dalam loop?

try() dalam loop bertindak persis seperti try() di sisa fungsi, di luar loop. Jika readID() mengembalikan kesalahan, maka fungsi mengembalikan kesalahan (setelah didekorasi jika).

Mengapa itu ditugaskan ke err var, bukan ke foo?

Saya tidak melihat variabel foo dalam contoh kode Anda...

@makhov Saya pikir cuplikannya tidak lengkap karena kesalahan yang dikembalikan tidak diberi nama (saya dengan cepat membaca ulang proposal tetapi tidak dapat melihat apakah nama variabel err adalah nama default jika tidak ada yang disetel).

Harus mengganti nama parameter yang dikembalikan adalah salah satu poin yang tidak disukai orang yang menolak proposal ini.

func (req *Request) Decode(r Reader) (err error) {
    defer func() { err = unexpected(err) }()

    req.Type = try(readByte(r))
    req.Body = try(readString(r))
    req.ID = try(readID(r))

    n := try(binary.ReadUvarint(r))
    req.SubIDs = make([]ID, n)
    for i := range req.SubIDs {
        req.SubIDs[i] = try(readID(r))
    }
    return nil
}

@pierrec mungkin kita bisa memiliki fungsi seperti recover() untuk mengambil kesalahan jika tidak dalam parameter bernama?
defer func() {err = unexpected(tryError())}

@makhov Anda dapat membuatnya lebih eksplisit:

func (req *Request) Decode(r Reader) error {
    req.Type, err := readByte(r)
        try(err) // or add annotation like try(annotate(err, ...))
    req.Body, err := readString(r)
        try(err)
    req.ID, err := readID(r)
        try(err)

    n, err := binary.ReadUvarint(r)
        try(err)
    req.SubIDs = make([]ID, n)
    for i := range req.SubIDs {
        req.SubIDs[i], err := readID(r)
                try(err)
    }
    return nil
}

@pierrec Oke, mari kita ubah:

func (req *Request) Decode(r Reader) error {
        var errOne, errTwo error
    defer func() { err = unexpected(???) }()

    req.Type = try(readByte(r))
    …
}

@reusee Dan mengapa lebih baik dari ini?

func (req *Request) Decode(r Reader) error {
    req.Type, err := readByte(r)
        if err != nil { return err }
        …
}

Pada saat apa kita semua memutuskan bahwa kependekan itu lebih baik daripada keterbacaan?

@flibustenet Terima kasih telah memahami masalahnya. Kelihatannya jauh lebih baik tetapi saya masih tidak yakin bahwa kita memerlukan kompatibilitas mundur yang rusak untuk "perbaikan" kecil ini. Sangat menjengkelkan jika saya memiliki aplikasi yang berhenti membangun versi baru Go:

package main

func main() {
    // ...
   try("a", "b")
    // ...
}

func try(a, b string) {
    // ...
}

@makhov Saya setuju bahwa ini perlu diklarifikasi: apakah kompiler error ketika tidak dapat mengetahui variabel? Saya pikir itu akan terjadi.
Mungkin proposal perlu memperjelas poin ini? Atau apakah saya melewatkannya di dokumen?

@flibustenet ya itu salah satu cara menggunakan try() tetapi menurut saya itu bukan cara idiomatis menggunakan try.

@cespare Dari apa yang Anda tulis, tampaknya modifikasi nilai pengembalian dalam penundaan adalah fitur try tetapi Anda sudah dapat melakukan ini.

https://play.golang.com/p/ZMauFmt9ezJ

(Maaf jika saya salah mengartikan apa yang Anda katakan)

@jan-g Mengenai https://github.com/golang/go/issues/32437#issuecomment -507961463: Gagasan menangani kesalahan yang tidak terlihat telah muncul beberapa kali. Masalah dengan pendekatan implisit seperti itu adalah menambahkan pengembalian kesalahan ke fungsi yang dipanggil dapat menyebabkan fungsi pemanggil berperilaku berbeda secara diam-diam dan tidak terlihat. Kami benar-benar ingin menjadi eksplisit ketika kesalahan diperiksa. Pendekatan implisit juga bertentangan dengan prinsip umum di Go bahwa semuanya eksplisit.

@griesemer

Saya mencoba tryhand di salah satu proyek saya (https://github.com/komuw/meli) dan tidak ada perubahan.

gobin github.com/griesemer/tryhard
     Installed github.com/griesemer/[email protected] to ~/go/bin/tryhard

```bash
~/go/bin/tryhard -err "" -r
0

most of my err handling looks like;
```Go
import "github.com/pkg/errors"

func CreateDockerVolume(volName string) (string, error) {
    volume, err := VolumeCreate(volName)
    if err != nil {
        return "", errors.Wrapf(err, "unable to create docker volume %v", volName)
    }
    return volume.Name, nil
}

@komuw Pertama-tama, pastikan untuk memberikan nama file atau argumen direktori ke tryhard , seperti pada

tryhard -err="" -r .  // <<< note the dot
tryhard -err="" -r filename

Juga, kode seperti yang Anda miliki di komentar Anda tidak akan ditulis ulang karena menangani kesalahan khusus di blok if . Harap baca dokumentasi tryhard kapan itu berlaku. Terima kasih.

func CreateDockerVolume(volName string) (string, error) {
    volume, err := VolumeCreate(volName)
    if err != nil {
        return "", errors.Wrapf(err, "unable to create docker volume %v", volName)
    }
    return volume.Name, nil
}

Ini adalah contoh yang agak menarik. Reaksi pertama saya ketika melihatnya adalah menanyakan apakah ini akan menghasilkan string kesalahan gagap seperti:

unable to create docker volume: VolumeName: could not create volume VolumeName: actual problem

Jawabannya tidak, karena fungsi VolumeCreate (dari repo yang berbeda) adalah:

func (cli *Client) VolumeCreate(ctx context.Context, options volumetypes.VolumeCreateBody) (types.Volume, error) {
        var volume types.Volume
        resp, err := cli.post(ctx, "/volumes/create", nil, options, nil)
        defer ensureReaderClosed(resp)
        if err != nil {
                return volume, err
        }
        err = json.NewDecoder(resp.body).Decode(&volume)
        return volume, err
}

Dengan kata lain, dekorasi tambahan pada kesalahan berguna karena fungsi yang mendasarinya tidak menghiasi kesalahannya. Fungsi yang mendasarinya dapat sedikit disederhanakan dengan try .

Mungkin fungsi VolumeCreate benar-benar harus mendekorasi kesalahannya. Dalam hal ini, bagaimanapun, tidak jelas bagi saya bahwa fungsi CreateDockerVolume harus menambahkan dekorasi tambahan, karena tidak ada informasi baru untuk diberikan.

@neild
Bahkan jika VolumeCreate akan menghiasi kesalahan, kita masih memerlukan CreateDockerVolume untuk menambahkan dekorasinya, karena VolumeCreate dapat dipanggil dari berbagai fungsi lain, dan jika sesuatu gagal (dan semoga login) Anda ingin tahu apa yang gagal - yang dalam hal ini adalah CreateDockerVolume ,
Namun demikian, Mengingat VolumeCreate adalah bagian dari antarmuka APIclient.

Hal yang sama berlaku untuk perpustakaan lain - os.Open dapat dengan baik menghiasi nama file, alasan kesalahan, dll, tetapi
func ReadConfigFile(...
func WriteDataFile(...
dll - memanggil os.Open adalah bagian gagal sebenarnya yang ingin Anda lihat untuk mencatat, melacak, dan menangani kesalahan Anda - terutama, tetapi tidak hanya dalam env produksi.

@neild terima kasih.

Saya tidak ingin menggagalkan utas ini, tetapi ...

Mungkin fungsi VolumeCreate benar-benar harus mendekorasi kesalahannya.
Namun, dalam hal ini, tidak jelas bagi saya bahwa
Fungsi CreateDockerVolume
harus menambahkan dekorasi tambahan,

Masalahnya adalah, sebagai pembuat fungsi CreateDockerVolume saya mungkin tidak
tahu apakah penulis VolumeCreate telah menghiasi kesalahan mereka jadi saya
tidak perlu menghias milikku.
Dan bahkan jika saya tahu bahwa mereka telah melakukannya, mereka dapat memutuskan untuk menghapus dekorasi mereka
berfungsi pada versi yang lebih baru. Dan karena perubahan itu bukan perubahan api, mereka
akan merilisnya sebagai tambalan/versi kecil dan sekarang fungsi saya yang
tergantung pada fungsinya memiliki kesalahan yang didekorasi tidak memiliki semua
informasi yang saya butuhkan.
Jadi secara umum saya menemukan diri saya mendekorasi/membungkus bahkan jika perpustakaan saya
panggilan sudah dibungkus.

Saya memiliki pemikiran saat berbicara tentang try dengan rekan kerja. Mungkin try seharusnya hanya diaktifkan untuk pustaka standar di 1.14. @crawshaw dan @jimmyfrasche keduanya melakukan tur singkat melalui beberapa kasus dan memberikan beberapa perspektif, tetapi sebenarnya menulis ulang kode perpustakaan standar menggunakan try sebanyak mungkin akan sangat berharga.

Itu memberi tim Go waktu untuk menulis ulang proyek non-sepele menggunakannya, dan komunitas dapat memiliki laporan pengalaman tentang cara kerjanya. Kita akan tahu seberapa sering digunakan, seberapa sering perlu dipasangkan dengan defer , jika itu mengubah keterbacaan kode, seberapa berguna tryhard , dll.

Ini sedikit bertentangan dengan semangat perpustakaan standar, memungkinkannya untuk menggunakan sesuatu yang tidak bisa dilakukan oleh kode Go biasa, tetapi ini memberi kita taman bermain untuk melihat bagaimana try mempengaruhi basis kode yang ada.

Maaf jika orang lain sudah memikirkan ini; Saya melalui berbagai diskusi dan tidak melihat proposal serupa.

@jonbodner https://go-review.googlesource.com/c/go/+/182717 memberi Anda ide yang cukup bagus tentang seperti apa bentuknya.

Dan saya lupa mengatakan: Saya berpartisipasi dalam survei Anda dan saya memilih penanganan kesalahan yang lebih baik, bukan ini.

Maksud saya, saya ingin melihat kemungkinan yang lebih ketat untuk melupakan pemrosesan kesalahan.

@jonbodner https://go-review.googlesource.com/c/go/+/182717 memberi Anda ide yang cukup bagus tentang seperti apa bentuknya.

Untuk menyimpulkan:

  1. 1 baris secara universal menggantikan 4 baris (2 baris untuk mereka yang menggunakan if ... { return err } )
  2. Evaluasi hasil yang dikembalikan dapat dioptimalkan - hanya pada jalur yang gagal.

Sekitar 6.000 penggantian total dari apa yang tampaknya hanya perubahan kosmetik: tidak akan mengekspos kesalahan yang ada, mungkin tidak akan memperkenalkan yang baru (koreksi saya jika saya salah tentang keduanya.)

Apakah saya, sebagai pengelola, akan repot-repot melakukan hal seperti ini dengan kode saya sendiri? Tidak, kecuali saya sendiri yang menulis alat penggantinya. Yang membuatnya baik-baik saja untuk repositori golang/go .

PS Penafian yang menarik di CL:

... Some transformations may be incorrect due to the limitations of the tool (see https://github.com/griesemer/tryhard)...

Seperti xerrors , bagaimana dengan mengambil langkah pertama untuk menggunakannya sebagai paket pihak ketiga?

Misalnya, coba gunakan paket di bawah ini.

https://github.com/junpayment/gotry

  • Ini mungkin kependekan dari use case Anda karena saya yang membuatnya.

Namun, saya pikir mencoba itu sendiri adalah ide yang bagus, jadi saya pikir ada juga pendekatan yang benar-benar menggunakannya dengan pengaruh yang lebih kecil.

===

Sebagai tambahan, ada dua hal yang saya khawatirkan untuk dicoba.

1.Ada pendapat bahwa baris dapat dihilangkan, tetapi tampaknya tidak ada pertimbangan klausa penangguhan (atau penangan).

Misalnya, saat penanganan kesalahan dilakukan secara detail.

foo, err: = Foo ()
if err! = nil {
  if err.Error () = "AAA" {
    some action for AAA
  } else if err.Error () = "BBB" {
    some action for BBB
  } else if err.Error () = "CCC" {
    some action for CCC
  } else {
    return err
  }
}

Jika Anda cukup mengganti ini dengan coba, itu akan menjadi sebagai berikut.

handler: = func (err error) {
  if err.Error () = "AAA" {
    some action for AAA
  } else if err.Error () = "BBB" {
    some action for BBB
  } else if err.Error () = "CCC" {
    some action for CCC
  } else {
    return err
  }
}
foo: = try (Foo (), handler)

2.Mungkin ada paket buruk lainnya yang secara tidak sengaja mengimplementasikan antarmuka kesalahan.

type Bad struct {}
func (bad * Bad) Error () {
  return "i really do not intend to be an error"
}

@junpayment Terima kasih atas paket gotry Anda - Saya rasa itu salah satu cara untuk sedikit merasakan try tetapi akan sedikit mengganggu jika harus mengetik-menegaskan semua Try hasil dari interface{} dalam penggunaan sebenarnya.

Mengenai dua pertanyaan Anda:
1) Saya tidak yakin ke mana Anda akan pergi dengan ini. Apakah Anda menyarankan try harus menerima handler seperti pada contoh Anda? (dan seperti yang kami miliki di versi internal sebelumnya dari try ?)
2) Saya tidak terlalu khawatir tentang fungsi yang secara tidak sengaja mengimplementasikan antarmuka kesalahan. Masalah ini bukanlah hal baru dan sepertinya tidak menimbulkan masalah serius sejauh yang kami ketahui.

@jonbodner https://go-review.googlesource.com/c/go/+/182717 memberi Anda ide yang cukup bagus tentang seperti apa bentuknya.

Terima kasih telah melakukan latihan ini. Namun, ini menegaskan kepada saya apa yang saya curigai, kode sumber go itu sendiri memiliki banyak tempat di mana try() akan berguna karena kesalahannya baru saja diteruskan. Namun, seperti yang saya lihat dari percobaan dengan tryhard yang saya dan orang lain kirimkan di atas, untuk banyak basis kode lain try() tidak akan terlalu berguna karena dalam kesalahan kode aplikasi cenderung benar-benar ditangani, bukan baru saja diteruskan.

Saya pikir itu adalah sesuatu yang harus diingat oleh para desainer Go, kompiler go dan waktu proses adalah kode Go yang "unik", berbeda dari kode aplikasi Go. Oleh karena itu, saya pikir try() harus ditingkatkan agar juga berguna dalam kasus lain di mana kesalahan sebenarnya harus ditangani, dan di mana melakukan penanganan kesalahan dengan pernyataan penangguhan tidak terlalu diinginkan.

@griesemer

akan sedikit menjengkelkan jika harus mengetik-menegaskan semua hasil Coba dari antarmuka{} dalam penggunaan sebenarnya.

Kamu benar. Metode ini mengharuskan penelepon untuk mentransmisikan tipenya.

Saya tidak yakin ke mana Anda akan pergi dengan ini. Apakah Anda menyarankan try harus menerima handler seperti pada contoh Anda? (dan seperti yang kami lakukan di versi coba internal sebelumnya?)

Saya membuat kesalahan. Seharusnya dijelaskan menggunakan penangguhan daripada penangan. Saya minta maaf.

Apa yang ingin saya katakan adalah bahwa ada kasus di mana itu tidak berkontribusi pada jumlah kode sebagai akibat dari proses penanganan kesalahan yang dihilangkan perlu dijelaskan dalam penangguhan.

Dampaknya diharapkan akan lebih terasa ketika Anda ingin menangani kesalahan secara detail.

Jadi, daripada mengurangi jumlah baris kode, kita dapat memahami proposal, yang mengatur lokasi penanganan kesalahan.

Saya tidak terlalu khawatir tentang fungsi yang secara tidak sengaja mengimplementasikan antarmuka kesalahan. Masalah ini bukanlah hal baru dan sepertinya tidak menimbulkan masalah serius sejauh yang kami ketahui.

Persis itu adalah kasus yang jarang terjadi.

@beoran Saya melakukan beberapa analisis awal dari Go Corpus (https://github.com/rsc/corpus). Saya percaya tryhard dalam keadaan saat ini dapat menghilangkan 41,7% dari semua err != nil cek di korpus. Jika saya mengecualikan pola "_test.go", angka ini naik menjadi 51,1% ( tryhard hanya beroperasi pada fungsi yang mengembalikan kesalahan, dan cenderung tidak menemukan banyak kesalahan dalam pengujian). Peringatan, ambil angka-angka ini dengan sebutir garam, saya mendapatkan penyebutnya (yaitu jumlah tempat dalam kode yang kami lakukan pemeriksaan err != nil ) dengan menggunakan versi tryhard yang diretas, dan idealnya kami akan menunggu sampai tryhard melaporkan statistik ini sendiri.

Juga, jika tryhard menjadi tipe-sadar, secara teoritis dapat melakukan transformasi seperti ini:

// Before.
a, err := foo()
if err != nil {
  return 0, nil, errors.Wrapf(err, "some message %v", b)
}

// After.
a, err := foo()
try(errors.Wrapf(err, "some message %v", b))

Ini mengambil keuntungan dari error.Wrap's behavior mengembalikan nil ketika argumen kesalahan yang diteruskan adalah nil . (github.com/pkg/errors juga tidak unik dalam hal ini, perpustakaan internal yang saya gunakan untuk melakukan pembungkusan kesalahan juga mempertahankan kesalahan nil , dan juga akan bekerja dengan pola ini, seperti kebanyakan perpustakaan penanganan kesalahan post- try , saya bayangkan). Generasi baru perpustakaan dukungan mungkin juga akan memberi nama pembantu propagasi ini sedikit berbeda.

Mengingat bahwa ini akan berlaku untuk 50% dari centang err != nil non-tes, sebelum evolusi perpustakaan apa pun untuk mendukung polanya, sepertinya kompiler dan runtime Go tidak unik, seperti yang Anda sarankan .

Tentang contoh dengan CreateDockerVolume https://github.com/golang/go/issues/32437#issuecomment -508199875
Saya menemukan jenis penggunaan yang persis sama. Di lib saya membungkus kesalahan dengan konteks di setiap kesalahan, dalam penggunaan lib saya ingin menggunakan try dan menambahkan konteks di defer untuk seluruh fungsi.

Saya mencoba meniru ini dengan menambahkan fungsi penangan kesalahan di awal, ini berfungsi dengan baik:

func MyLib() error {
    return errors.New("Error from my lib")
}
func MyOtherLib() error {
    return errors.New("Error from my otherLib")
}

func Caller(a, b int) error {
    eh := func(err error) error {
        return fmt.Errorf("From Caller with %d and %d i found this error: %v", a, b, err)
    }

    err := MyLib()
    if err != nil {
        return eh(err)
    }

    err = MyOtherLib()
    if err != nil {
        return eh(err)
    }

    return nil
}

Itu akan terlihat bagus dan idiomatis dengan try+defer

func Caller(a, b int) (err error) {
    defer fmt.Errorf("From Caller with %d and %d i found this error: %v", a, b, &err)

    try(MyLib())
    try(MyOtherLib())

    return nil
}

@griesemer

Dokumen desain saat ini memiliki pernyataan berikut:

Jika fungsi terlampir mendeklarasikan parameter hasil bernama lainnya, parameter hasil tersebut mempertahankan nilai apa pun yang mereka miliki. Jika fungsi mendeklarasikan parameter hasil lain yang tidak disebutkan namanya, mereka menganggap nilai nol yang sesuai (yang sama dengan mempertahankan nilai yang sudah mereka miliki).

Ini menyiratkan bahwa program ini akan mencetak 1, bukan 0: https://play.golang.org/p/KenN56iNVg7.

Seperti yang ditunjukkan kepada saya di Twitter, ini membuat try berperilaku seperti pengembalian telanjang, di mana nilai yang dikembalikan tersirat; untuk mengetahui nilai aktual apa yang dikembalikan, seseorang mungkin perlu melihat kode pada jarak yang signifikan dari panggilan ke try itu sendiri.

Mengingat bahwa properti pengembalian telanjang ini (non-lokalitas) umumnya tidak disukai, apa pendapat Anda tentang memiliki try selalu mengembalikan nilai nol dari argumen non-kesalahan (jika mengembalikan sama sekali)?

Beberapa pertimbangan:

Ini mungkin membuat beberapa pola yang melibatkan penggunaan nilai pengembalian bernama tidak dapat digunakan try . Misalnya, untuk implementasi io.Writer , yang perlu mengembalikan hitungan byte yang ditulis, bahkan dalam situasi penulisan parsial. Yang mengatakan, sepertinya try adalah rawan kesalahan dalam kasus ini (misalnya n += try(wrappedWriter.Write(...)) tidak menetapkan n ke nomor yang benar jika terjadi kesalahan kembali). Tampaknya baik-baik saja bagi saya bahwa try akan dianggap tidak dapat digunakan untuk kasus penggunaan semacam ini, karena skenario di mana kita membutuhkan nilai dan kesalahan agak jarang, menurut pengalaman saya.

Jika ada fungsi dengan banyak kegunaan try , ini mungkin menyebabkan kode mengasapi, di mana ada banyak tempat dalam fungsi yang perlu menghilangkan variabel keluaran. Pertama, kompiler cukup bagus dalam mengoptimalkan penulisan yang tidak perlu akhir-akhir ini. Dan kedua, jika terbukti perlu, tampaknya seperti pengoptimalan langsung untuk memiliki semua try -generate blok goto ke label fungsi-lebar bersama yang umum, yang meniadakan nilai keluaran non-kesalahan.

Juga, seperti yang saya yakin Anda ketahui, tryhard sudah diterapkan dengan cara ini, jadi sebagai keuntungan sampingan ini akan membuat tryhard lebih benar secara surut.

@jonbodner https://go-review.googlesource.com/c/go/+/182717 memberi Anda ide yang cukup bagus tentang seperti apa bentuknya.

Terima kasih telah melakukan latihan ini. Namun, ini menegaskan kepada saya apa yang saya curigai, kode sumber go itu sendiri memiliki banyak tempat di mana try() akan berguna karena kesalahannya baru saja diteruskan. Namun, seperti yang saya lihat dari percobaan dengan tryhard yang saya dan orang lain kirimkan di atas, untuk banyak basis kode lain try() tidak akan terlalu berguna karena dalam kesalahan kode aplikasi cenderung benar-benar ditangani, bukan baru saja diteruskan.

Saya akan menafsirkan ini secara berbeda.

Kami belum memiliki obat generik, jadi akan sulit menemukan kode di alam bebas yang akan langsung mendapat manfaat dari obat generik berdasarkan kode yang ditulis. Itu tidak berarti bahwa obat generik tidak akan berguna.

Bagi saya, ada 2 pola yang saya gunakan dalam kode untuk penanganan kesalahan

  1. gunakan kepanikan di dalam paket, dan pulihkan kepanikan dan kembalikan hasil kesalahan dalam beberapa metode yang diekspor
  2. secara selektif menggunakan penangan yang ditangguhkan dalam beberapa metode sehingga saya dapat menghiasi kesalahan dengan informasi PC file/nomor baris yang kaya dan lebih banyak konteks

Pola-pola ini tidak tersebar luas tetapi berhasil. 1) digunakan di perpustakaan standar dalam fungsinya yang tidak diekspor dan 2) digunakan secara luas di basis kode saya selama beberapa tahun terakhir karena saya pikir itu adalah cara yang bagus untuk menggunakan fitur ortogonal untuk melakukan dekorasi kesalahan yang disederhanakan, dan proposal merekomendasikan dan telah memberkati pendekatan. Fakta bahwa mereka tidak tersebar luas tidak berarti bahwa mereka tidak baik. Namun seperti semua hal, pedoman dari tim Go yang merekomendasikannya akan membuat mereka lebih banyak digunakan dalam praktik, di masa mendatang .

Satu hal terakhir yang perlu diperhatikan adalah bahwa kesalahan dekorasi di setiap baris kode Anda bisa menjadi terlalu banyak. Akan ada beberapa tempat di mana masuk akal untuk menghias kesalahan, dan beberapa tempat di mana tidak. Karena kami tidak memiliki pedoman yang bagus sebelumnya, orang-orang memutuskan bahwa masuk akal untuk selalu menghiasi kesalahan. Tapi itu mungkin tidak menambah banyak nilai untuk selalu menghiasi setiap kali file tidak dibuka, karena mungkin cukup di dalam paket untuk hanya memiliki kesalahan sebagai "tidak dapat membuka file: conf.json", sebagai lawan dari: "tidak dapat untuk mendapatkan nama pengguna: tidak dapat memperoleh koneksi db: tidak dapat memuat file sistem: tidak dapat membuka file: conf.json".

Dengan kombinasi nilai kesalahan dan penanganan kesalahan yang ringkas, kami sekarang mendapatkan panduan yang lebih baik tentang cara menangani kesalahan. Preferensi tampaknya:

  • kesalahan akan sederhana misalnya "tidak dapat membuka file: conf.json"
  • bingkai kesalahan dapat dilampirkan yang mencakup konteks: GetUserName --> GetConnection --> LoadSystemFile.
  • Jika itu menambah konteks, Anda dapat membungkus kesalahan itu, misalnya MyAppError{error}

Saya cenderung merasa seperti kita terus mengabaikan tujuan dari proposal percobaan, dan hal-hal tingkat tinggi yang coba dipecahkannya:

  1. kurangi boilerplate dari if err != nil { return err } untuk tempat-tempat yang masuk akal untuk menyebarkan kesalahan agar ditangani lebih tinggi di tumpukan
  2. Izinkan penggunaan nilai pengembalian yang disederhanakan di mana err == nil
  3. izinkan solusi diperluas nanti untuk memungkinkan, misalnya, lebih banyak dekorasi kesalahan di situs, lompat ke penangan kesalahan, gunakan goto alih-alih mengembalikan semantik, dll.
  4. Izinkan penanganan kesalahan untuk tidak mengacaukan logika basis kode, yaitu meletakkannya agak ke samping dengan semacam penangan kesalahan.

Banyak orang masih memiliki 1). Banyak orang telah bekerja di sekitar 1) karena pedoman yang lebih baik tidak ada sebelumnya. Namun bukan berarti, setelah mereka mulai menggunakannya, reaksi negatif mereka tidak berubah menjadi lebih positif.

Banyak orang dapat menggunakan 2). Mungkin ada ketidaksepakatan tentang berapa banyak, tetapi saya memberi contoh di mana itu membuat kode saya lebih mudah.

var u user = try(db.LoadUser(try(strconv.ParseInt(stringId)))

Di Jawa di mana pengecualian adalah norma, kami akan memiliki:

User u = db.LoadUser(Integer.parseInt(stringId)))

Tidak ada yang akan melihat kode ini dan mengatakan bahwa kita harus melakukannya dalam 2 baris yaitu.

int id = Integer.parseInt(stringId)
User u = db.LoadUser(id))

Kita tidak harus melakukan itu di sini, di bawah pedoman yang mencoba HARUS tidak disebut sebaris dan HARUS selalu berada di jalurnya sendiri .

Selanjutnya, hari ini, sebagian besar kode akan melakukan hal-hal seperti:

var u user
var err error
var id int
id, err = strconv.ParseInt(stringId)
if err != nil {
  return u, errors.Wrap("cannot load userid from string: %s: %v", stringId, err)
}
u, err = db.LoadUser(id)
if err != nil {
  return u, errors.Wrap("cannot load user given user id: %d: %v", id, err)
}
// now work with u

Sekarang, seseorang yang membaca ini harus menguraikan 10 baris ini, yang di Jawa akan menjadi 1 baris, dan yang bisa menjadi 1 baris dengan proposal di sini. Saya secara visual harus mencoba secara mental untuk melihat baris apa di sini yang benar-benar relevan ketika saya membaca kode ini. Boilerplate membuat kode ini lebih sulit dibaca dan grok.

Saya ingat di kehidupan masa lalu saya mengerjakan/dengan pemrograman berorientasi aspek di Java. Di sana, tujuannya adalah untuk

Hal ini memungkinkan perilaku yang tidak penting bagi logika bisnis (seperti logging) untuk ditambahkan ke program tanpa mengacaukan kode, inti fungsionalitas. (dikutip dari wikipedia https://en.wikipedia.org/wiki/Aspect-oriented_programming ).
Penanganan kesalahan bukanlah pusat logika bisnis, tetapi merupakan pusat kebenaran. Idenya sama - kita tidak boleh mengacaukan kode kita dengan hal-hal yang tidak penting bagi logika bisnis karena " tetapi penanganan kesalahan sangat penting ". Ya itu, dan ya kita bisa meletakkannya di samping.

Mengenai 4), Banyak proposal telah menyarankan penangan kesalahan, yaitu kode ke sisi yang menangani kesalahan tetapi tidak mengacaukan logika bisnis. Proposal awal memiliki kata kunci pegangan untuk itu, dan orang-orang telah menyarankan hal-hal lain. Proposal ini mengatakan bahwa kita dapat memanfaatkan mekanisme penangguhan untuk itu, dan membuatnya lebih cepat yang sebelumnya merupakan kelemahannya. Saya tahu - Saya telah membuat keributan tentang kinerja mekanisme penangguhan berkali-kali kepada tim go.

Perhatikan bahwa tryhard tidak akan menandai kode ini sebagai sesuatu yang dapat disederhanakan. Tetapi dengan try dan pedoman baru, orang mungkin ingin menyederhanakan kode ini menjadi 1-liner dan membiarkan Error Frame menangkap konteks yang diperlukan.

Konteks, yang telah digunakan dengan sangat baik dalam bahasa berbasis pengecualian, akan menangkap bahwa seseorang mencoba kesalahan yang terjadi saat memuat pengguna karena id pengguna tidak ada, atau karena stringId tidak dalam format id integer. diuraikan dari itu.

Gabungkan itu dengan Error Formatter, dan sekarang kita dapat memeriksa bingkai kesalahan dan kesalahan itu sendiri dan memformat pesan dengan baik untuk pengguna, tanpa gaya a: b: c: d: e: underlying error yang sulit dibaca yang telah dilakukan banyak orang dan yang belum memiliki pedoman yang bagus untuk.

Ingatlah bahwa semua proposal ini bersama-sama memberi kami solusi yang kami inginkan: penanganan kesalahan yang ringkas tanpa boilerplate yang tidak perlu, sambil memberikan diagnostik yang lebih baik dan pemformatan kesalahan yang lebih baik bagi pengguna. Ini adalah konsep ortogonal tetapi bersama-sama menjadi sangat kuat.

Akhirnya, diberikan 3) di atas, sulit untuk menggunakan kata kunci untuk menyelesaikan ini. Menurut definisi, kata kunci tidak mengizinkan ekstensi di masa mendatang melewati penangan dengan nama, atau mengizinkan dekorasi kesalahan di tempat, atau mendukung semantik goto (bukan semantik kembali). Dengan kata kunci, kita harus memikirkan solusi lengkapnya terlebih dahulu. Dan kata kunci tidak kompatibel ke belakang. Tim go menyatakan ketika Go 2 dimulai, bahwa mereka ingin mencoba mempertahankan kompatibilitas mundur sebanyak mungkin. Fungsi try mempertahankannya, dan jika nanti kita melihat bahwa tidak ada ekstensi yang diperlukan, gofix sederhana dapat dengan mudah memodifikasi kode untuk mengubah fungsi try menjadi kata kunci.

2 sen saya lagi!

Pada 7/4/19, Sanjay Menakuru [email protected] menulis:

@griesemer

[ ... ]
Seperti yang ditunjukkan kepada saya di Twitter, ini membuat try berperilaku seperti telanjang
return, di mana nilai yang dikembalikan bersifat implisit; untuk mencari tahu apa
nilai aktual dikembalikan, orang mungkin perlu melihat kode di a
jarak yang signifikan dari panggilan ke try itu sendiri.

Mengingat bahwa properti pengembalian telanjang ini (non-lokalitas) umumnya
tidak disukai, apa pendapat Anda tentang try selalu mengembalikan nol
nilai argumen non-kesalahan (jika kembali sama sekali)?

Pengembalian telanjang hanya diizinkan ketika argumen pengembalian diberi nama. Dia
sepertinya try mengikuti aturan yang berbeda?

Saya menyukai gagasan keseluruhan untuk menggunakan kembali defer untuk mengatasi masalah tersebut. Namun, saya ingin tahu apakah kata kunci try adalah cara yang tepat untuk melakukannya. Bagaimana jika kita bisa menggunakan kembali pola yang sudah ada. Sesuatu yang sudah diketahui semua orang dari impor:

Penanganan eksplisit

res, err := doSomething()
if err != nil {
    return err
}

Pengabaian eksplisit

res, _ := doSomething()

Penanganan yang ditangguhkan

Perilaku serupa dengan apa yang akan dilakukan try .

res, . := doSomething()

@piotrkowalczuk
Ini mungkin sintaks yang lebih bagus untuk itu, tetapi saya tidak tahu betapa mudahnya mengadaptasi Go untuk membuat ini legal, baik di Go maupun di penyorot sintaks.

@balasanjay (dan @lootch): Per komentar Anda di sini , ya, program https://play.golang.org/p/KenN56iNVg7 akan mencetak 1.

Karena try hanya menyangkut dirinya sendiri dengan hasil kesalahan, ia membiarkan yang lainnya saja. Itu bisa mengatur nilai pengembalian lainnya ke nilai nolnya, tetapi tidak jelas mengapa itu akan lebih baik. Untuk satu hal itu bisa menyebabkan lebih banyak pekerjaan ketika nilai-nilai hasil diberi nama karena mereka mungkin harus disetel ke nol; namun penelepon (kemungkinan) akan mengabaikannya jika ada kesalahan. Tapi ini adalah keputusan desain yang bisa diubah jika ada alasan bagus untuk itu.

[edit: Perhatikan bahwa pertanyaan ini (tentang apakah akan menghapus hasil non-kesalahan setelah menemukan kesalahan) tidak spesifik untuk proposal try . Salah satu alternatif yang diusulkan yang tidak memerlukan return eksplisit harus menjawab pertanyaan yang sama.]

Mengenai contoh penulis Anda n += try(wrappedWriter.Write(...)) : Ya, dalam situasi di mana Anda perlu menambah n bahkan jika terjadi kesalahan, seseorang tidak dapat menggunakan try - bahkan jika try tidak nol nilai hasil non-kesalahan. Itu karena try hanya mengembalikan apa pun jika tidak ada kesalahan: try berperilaku bersih seperti fungsi (tetapi fungsi yang mungkin tidak kembali ke pemanggil, tetapi ke pemanggil pemanggil). Lihat penggunaan temporer dalam implementasi try .

Tetapi dalam kasus seperti contoh Anda, seseorang juga harus berhati-hati dengan pernyataan if dan pastikan untuk memasukkan jumlah byte yang dikembalikan ke n .

Tapi mungkin saya salah memahami kekhawatiran Anda.

@griesemer : Saya menyarankan bahwa lebih baik untuk mengatur nilai pengembalian lainnya ke nilai nolnya, karena dengan demikian jelas apa yang akan dilakukan try dari hanya memeriksa situs panggilan. Itu akan a) tidak melakukan apa-apa, atau b) kembali dari fungsi dengan nilai nol dan argumen untuk dicoba.

Seperti yang ditentukan, try akan mempertahankan nilai dari nilai pengembalian bernama non-kesalahan, dan karena itu seseorang perlu memeriksa seluruh fungsi untuk memperjelas nilai apa yang dikembalikan try .

Ini adalah masalah yang sama dengan pengembalian telanjang (harus memindai seluruh fungsi untuk melihat nilai apa yang dikembalikan), dan mungkin merupakan alasan untuk mengajukan https://github.com/golang/go/issues/21291 . Ini, bagi saya, menyiratkan bahwa try dalam fungsi besar dengan nilai pengembalian bernama, harus berkecil hati dengan dasar yang sama seperti pengembalian telanjang (https://github.com/golang/go/wiki/CodeReviewComments #named-hasil-parameter). Sebagai gantinya, saya menyarankan agar try ditentukan untuk selalu mengembalikan nilai nol dari argumen non-kesalahan.

bingung dan merasa tidak enak untuk tim go akhir-akhir ini. try adalah solusi yang bersih dan dapat dimengerti untuk masalah spesifik yang coba dipecahkan: verbositas dalam penanganan kesalahan.

proposalnya berbunyi: setelah diskusi panjang selama setahun, kami menambahkan ini built-in. gunakan jika Anda ingin kode yang lebih sedikit, jika tidak, lanjutkan melakukan apa yang Anda lakukan. reaksinya adalah beberapa penolakan yang tidak sepenuhnya dapat dibenarkan untuk fitur keikutsertaan yang anggota tim telah menunjukkan keuntungan yang jelas!

saya selanjutnya akan mendorong tim go untuk menjadikan try sebagai bawaan variadik jika itu mudah dilakukan

try(outf.Seek(linkstart, 0))
try(io.Copy(outf, exef))

menjadi

try(outf.Seek(linkstart, 0)), io.Copy(outf, exef)))

hal verbose berikutnya bisa menjadi panggilan berturut-turut ke try .

Saya setuju dengan nvictor untuk sebagian besar, kecuali untuk parameter variadic untuk try . Saya masih percaya itu harus memiliki tempat untuk penangan dan proposal variadic dapat mendorong batas keterbacaan untuk diri saya sendiri.

@nvictor Go adalah bahasa yang tidak menyukai fitur non-ortogonal. Itu berarti bahwa jika kita, di masa depan, mencari solusi penanganan kesalahan yang lebih baik yang bukan try , akan jauh lebih rumit untuk beralih (jika tidak ditolak mentah-mentah karena solusinya adalah "cukup baik").

Saya pikir ada solusi yang lebih baik di luar sana daripada try , dan saya lebih suka mengambilnya perlahan dan menemukan solusi itu daripada menerima yang ini.

Namun saya tidak akan marah jika ini ditambahkan. Ini bukan solusi yang buruk, saya hanya berpikir kita mungkin bisa menemukan solusi yang lebih baik.

Dalam pandangan saya, saya ingin mencoba kode blok, sekarang try seperti pegangan err func

Saat membaca diskusi ini (dan diskusi di Reddit), saya tidak selalu merasa semua orang berada di halaman yang sama.

Jadi, saya menulis posting blog kecil yang menunjukkan bagaimana try dapat digunakan: https://faiface.github.io/post/how-to-use-try/.

Saya mencoba menunjukkan banyak aspek dari proposal ini sehingga semua orang dapat melihat apa yang dapat dilakukannya dan membentuk opini yang lebih terinformasi (bahkan jika negatif).

Jika saya melewatkan sesuatu yang penting, beri tahu saya!

@faiface Saya cukup yakin Anda dapat mengganti

if err != nil {
    return resps, err
}

dengan try(err) .

Selain itu - artikel bagus!

@DmitriyMV Benar! Tapi saya rasa saya akan tetap seperti itu, sehingga setidaknya ada satu contoh klasik if err != nil , meskipun tidak terlalu bagus.

Saya memiliki dua kekhawatiran:

  • pengembalian bernama sangat membingungkan, dan ini mendorong mereka dengan kasus penggunaan baru dan penting
  • ini akan mencegah menambahkan konteks ke kesalahan

Dalam pengalaman saya, menambahkan konteks ke kesalahan segera setelah setiap situs panggilan sangat penting untuk memiliki kode yang dapat dengan mudah di-debug. Dan pengembalian bernama telah menyebabkan kebingungan bagi hampir setiap pengembang Go yang saya kenal di beberapa titik.

Masalah gaya yang lebih kecil adalah sangat disayangkan berapa banyak baris kode yang sekarang akan dibungkus try(actualThing()) . Saya dapat membayangkan melihat sebagian besar baris dalam basis kode yang dibungkus try() . Itu terasa disayangkan.

Saya pikir masalah ini akan diatasi dengan tweak:

a, b, err := myFunc()
check(err, "calling myFunc on %v and %v", a, b)

check() akan berperilaku seperti try() , tetapi akan menghilangkan perilaku melewati nilai pengembalian fungsi secara umum, dan sebagai gantinya akan memberikan kemampuan untuk menambahkan konteks. Itu masih akan memicu pengembalian.

Ini akan mempertahankan banyak keuntungan dari try() :

  • itu adalah bawaan
  • itu mengikuti aliran kontrol yang ada WRT untuk menunda
  • itu selaras dengan praktik yang ada untuk menambahkan konteks ke kesalahan dengan baik
  • itu sejajar dengan proposal dan pustaka saat ini untuk pembungkusan kesalahan, seperti errors.Wrap(err, "context message")
  • itu menghasilkan situs panggilan yang bersih: tidak ada boilerplate di baris a, b, err := myFunc()
  • menjelaskan kesalahan dengan defer fmt.HandleError(&err, "msg") masih dimungkinkan, tetapi tidak perlu didorong.
  • tanda tangan check sedikit lebih sederhana, karena tidak perlu mengembalikan sejumlah argumen dari fungsi yang dibungkusnya.

Ini bagus, saya pikir tim go benar-benar harus mengambil yang ini. Ini lebih baik daripada mencoba, lebih jelas!!!

@buchanae Saya akan tertarik dengan apa yang Anda pikirkan tentang posting blog saya karena Anda berpendapat bahwa try akan mencegah menambahkan konteks ke kesalahan, sementara saya berpendapat bahwa setidaknya dalam artikel saya itu bahkan lebih mudah dari biasanya.

Aku hanya akan membuang ini di luar sana pada tahap saat ini. Saya akan memikirkannya lagi, tetapi saya pikir saya memposting di sini untuk melihat apa yang Anda pikirkan. Mungkin saya harus membuka masalah baru untuk ini? Saya juga memposting ini di #32811

Jadi, bagaimana dengan melakukan semacam makro C generik alih-alih membuka lebih banyak fleksibilitas?

Seperti ini:

define returnIf(err error, desc string, args ...interface{}) {
    if (err != nil) {
        return fmt.Errorf("%s: %s: %+v", desc, err, args)
    }
}

func CopyFile(src, dst string) error {
    r, err := os.Open(src)
    :returnIf(err, "Error opening src", src)
    defer r.Close()

    w, err := os.Create(dst)
    :returnIf(err, "Error Creating dst", dst)
    defer w.Close()

    ...
}

Pada dasarnya returnIf akan diganti/digarisbawahi oleh yang didefinisikan di atas. Fleksibilitasnya adalah terserah Anda apa yang dilakukannya. Men-debug ini mungkin agak aneh, kecuali editor menggantinya di editor dengan cara yang bagus. Ini juga membuatnya kurang ajaib, karena Anda dapat membaca definisinya dengan jelas. Dan juga, ini memungkinkan Anda untuk memiliki satu baris yang berpotensi mengembalikan kesalahan. Dan dapat memiliki pesan kesalahan yang berbeda tergantung di mana itu terjadi (konteks).

Sunting: Juga menambahkan titik dua di depan makro untuk menyarankan bahwa mungkin itu bisa dilakukan untuk memperjelas itu makro dan bukan panggilan fungsi.

@nvictor

saya selanjutnya akan mendorong tim go untuk menjadikan try sebagai bawaan variadik

Apa try(foo(), bar()) yang akan dikembalikan jika foo dan bar tidak mengembalikan hal yang sama?

Aku hanya akan membuang ini di luar sana pada tahap saat ini. Saya akan memikirkannya lagi, tetapi saya pikir saya memposting di sini untuk melihat apa yang Anda pikirkan. Mungkin saya harus membuka masalah baru untuk ini? Saya juga memposting ini di #32811

Jadi, bagaimana dengan melakukan semacam makro C generik alih-alih membuka lebih banyak fleksibilitas?

@Chillance , IMHO, saya pikir sistem makro higienis seperti Rust (dan banyak bahasa lainnya) akan memberi orang kesempatan untuk bermain dengan ide-ide seperti try atau obat generik dan kemudian setelah pengalaman diperoleh, ide-ide terbaik bisa menjadi bagian dari bahasa dan perpustakaan. Tetapi saya juga berpikir bahwa sangat kecil kemungkinan hal seperti itu akan ditambahkan ke Go.

@jonbodner saat ini ada proposal untuk menambahkan makro higienis di Go. Belum ada sintaks yang diusulkan atau apa pun, namun belum banyak _melawan_ gagasan untuk menambahkan makro higienis. #32620

@Allenyn , mengenai saran @buchanae sebelumnya yang baru saja Anda kutip :

a, b, err := myFunc()
check(err, "calling myFunc on %v and %v", a, b)

Dari apa yang saya lihat dari diskusi, tebakan saya adalah ini akan menjadi hasil yang tidak mungkin di sini untuk semantik fmt untuk ditarik ke dalam fungsi bawaan. (Lihat misalnya tanggapan @josharian ).

Yang mengatakan, itu tidak benar-benar diperlukan, termasuk karena mengizinkan fungsi handler dapat menghindari menarik fmt semantik langsung ke builtin. Salah satu pendekatan tersebut diusulkan oleh @eihigh pada hari pertama atau lebih diskusi di sini, yang mirip dengan semangat saran @buchanae , dan yang menyarankan tweaking try builtin sebagai gantinya memiliki tanda tangan berikut:

func try(error, optional func(error) error)

Karena alternatif try ini tidak mengembalikan apa pun, tanda tangan itu menyiratkan:

  • itu tidak dapat bersarang di dalam panggilan fungsi lain
  • itu harus di awal baris

Saya tidak ingin memicu bikeshedding nama, tetapi bentuk try itu mungkin lebih baik dibaca dengan nama alternatif seperti check . Orang dapat membayangkan pembantu perpustakaan standar yang dapat membuat anotasi opsional di tempat menjadi nyaman, sementara defer dapat tetap menjadi opsi untuk anotasi seragam bila diinginkan.

Ada beberapa proposal terkait yang dibuat kemudian di #32811 ( catch sebagai bawaan) dan #32611 ( on kata kunci untuk mengizinkan on err, <statement> ). Itu mungkin tempat yang bagus untuk berdiskusi lebih lanjut, atau menambahkan jempol ke atas atau ke bawah, atau menyarankan kemungkinan penyesuaian pada proposal tersebut.

@jonbodner saat ini ada proposal untuk menambahkan makro higienis di Go. Belum ada sintaks yang diusulkan atau apa pun, namun belum banyak _melawan_ gagasan untuk menambahkan makro higienis. #32620

Sangat bagus bahwa ada proposal, tetapi saya menduga bahwa tim inti Go tidak bermaksud untuk menambahkan makro. Namun, saya akan senang jika salah tentang ini karena akan mengakhiri semua argumen tentang perubahan yang saat ini memerlukan modifikasi pada inti bahasa. Mengutip sebuah pewayangan terkenal, "Lakukan. Atau jangan. Tidak ada usaha."

@jonbodner Saya tidak berpikir bahwa menambahkan makro higienis akan mengakhiri argumen. Justru sebaliknya. Kritik umum adalah bahwa try "menyembunyikan" pengembalian. Makro akan lebih buruk dari sudut pandang ini, karena segala sesuatu mungkin terjadi dalam makro. Dan bahkan jika Go mengizinkan makro higienis yang ditentukan pengguna, kita masih harus berdebat apakah try harus menjadi makro bawaan yang dideklarasikan sebelumnya di blok semesta, atau tidak. Akan logis bagi mereka yang menentang try untuk lebih menentang makro higienis ;-)

@ngrilly ada beberapa cara untuk memastikan bahwa makro menonjol dan mudah dilihat. Cara Rust melakukannya adalah bahwa makro selalu dijalankan oleh ! (yaitu try!(...) dan println!(...) ).

Saya berpendapat bahwa jika makro higienis diadopsi dan mudah dilihat, dan tidak terlihat seperti panggilan fungsi normal, mereka akan lebih cocok. Kita harus memilih solusi yang lebih umum daripada memperbaiki masalah individu.

@thepudds Saya setuju bahwa menambahkan parameter opsional tipe func(error) error dapat bermanfaat (kemungkinan ini dibahas dalam proposal, dengan beberapa masalah yang perlu diselesaikan), tetapi saya tidak melihat gunanya try tidak mengembalikan apa pun. try yang diusulkan oleh tim Go adalah alat yang lebih umum.

@deanveloper Ya, ! di akhir makro di Rust pintar. Itu mengingatkan pengidentifikasi yang diekspor dimulai dengan huruf besar di Go :-)

Saya setuju memiliki makro higienis di Go jika dan hanya jika kita dapat mempertahankan kecepatan kompilasi dan menyelesaikan masalah kompleks terkait perkakas (alat refactoring perlu memperluas makro untuk memahami semantik kode, tetapi harus menghasilkan kode dengan makro yang tidak diperluas) . Sulit. Sementara itu, mungkin try bisa diganti namanya try! ? ;-)

Ide yang ringan: jika badan dari konstruk if/for berisi satu pernyataan, tidak perlu kurung kurawal asalkan pernyataan ini berada di baris yang sama dengan if atau for . Contoh:

fd, err := os.Open("foo")
if err != nil return err

Perhatikan bahwa saat ini tipe error hanyalah tipe antarmuka biasa. Kompiler tidak memperlakukannya sebagai sesuatu yang istimewa. try mengubah itu. Jika kompiler diizinkan untuk memperlakukan error sebagai spesial, saya lebih suka /bin/sh terinspirasi || :

fd, err := os.Open("foo") || return err

Arti dari kode tersebut akan cukup jelas bagi sebagian besar programmer, tidak ada aliran kontrol tersembunyi dan, karena saat ini kode ini ilegal, tidak ada kode kerja yang dirugikan.

Meskipun saya bisa membayangkan beberapa dari Anda mundur ketakutan.

@bakul Dalam if err != nil return err , bagaimana Anda tahu di mana ekspresi err != nil berakhir dan di mana pernyataan return err dimulai? Ide Anda akan menjadi perubahan besar pada tata bahasa, jauh lebih besar daripada yang diusulkan try .

Ide kedua Anda terlihat seperti catch |err| return err di Zig . Secara pribadi, saya tidak "mundur dalam ketakutan" dan saya akan mengatakan mengapa tidak? Tetapi perlu diperhatikan bahwa Zig juga memiliki kata kunci try , yang merupakan jalan pintas untuk catch |err| return err , dan hampir setara dengan apa yang diusulkan oleh tim Go di sini sebagai fungsi bawaan. Jadi mungkin try sudah cukup dan kita tidak membutuhkan kata kunci catch ? ;-)

@ngrilly , Saat ini <expr> <statement> tidak valid jadi saya tidak berpikir perubahan ini akan membuat tata bahasa menjadi lebih ambigu tetapi mungkin sedikit lebih rapuh.

Ini akan menghasilkan kode yang persis sama dengan proposal percobaan tetapi a) pengembaliannya eksplisit di sini b) tidak ada kemungkinan bersarang seperti dengan percobaan dan c) ini akan menjadi sintaks yang akrab bagi pengguna Shell (yang jauh melebihi jumlah pengguna zig). Tidak ada catch di sini.

Saya mengangkat ini sebagai alternatif tetapi sejujurnya saya baik-baik saja dengan apa pun yang diputuskan oleh perancang bahasa inti.

Saya telah mengunggah versi tryhard yang sedikit lebih baik. Sekarang melaporkan informasi yang lebih rinci tentang file input. Misalnya, berjalan melawan ujung repo Go yang dilaporkan sekarang:

$ tryhard $HOME/go/src
...
--- stats ---
  55620 (100.0% of   55620) function declarations
  14936 ( 26.9% of   55620) functions returning an error
 116539 (100.0% of  116539) statements
  27327 ( 23.4% of  116539) if statements
   7636 ( 27.9% of   27327) if <err> != nil statements
    119 (  1.6% of    7636) <err> name is different from "err" (use -l flag to list file positions)
   6037 ( 79.1% of    7636) return ..., <err> blocks in if <err> != nil statements
   1599 ( 20.9% of    7636) more complex error handler in if <err> != nil statements; prevent use of try (use -l flag to list file positions)
     17 (  0.2% of    7636) non-empty else blocks in if <err> != nil statements; prevent use of try (use -l flag to list file positions)
   5907 ( 77.4% of    7636) try candidates (use -l flag to list file positions)

Masih banyak yang harus dilakukan, tetapi ini memberikan gambaran yang lebih jelas. Secara khusus, 28% dari semua pernyataan if tampaknya untuk pemeriksaan kesalahan; ini menegaskan bahwa ada sejumlah besar kode berulang. Dari pemeriksaan kesalahan tersebut, 77% akan menerima try .

$ berusaha.
--- statistik ---
2930 (100.0% dari 2930) deklarasi fungsi
1408 ( 48,1% dari 2930) fungsi mengembalikan kesalahan
10497 (100.0% dari 10497) pernyataan
2265 (21,6% dari 10497) jika pernyataan
1383 ( 61,1% dari 2265) jika!= pernyataan nihil
0 ( 0,0% dari 1383)nama berbeda dari "err" (gunakan tanda -l
untuk membuat daftar posisi file)
645 ( 46,6% dari 1383) kembali ...,blok di jika!= nihil
pernyataan
738 ( 53,4% dari 1383) penangan kesalahan yang lebih kompleks di if!= nihil
pernyataan; cegah penggunaan try (gunakan flag -l untuk membuat daftar posisi file)
1 (0,1% dari 1383) blok else non-kosong di if!= nihil
pernyataan; cegah penggunaan try (gunakan flag -l untuk membuat daftar posisi file)
638 ( 46,1% dari 1383) mencoba kandidat (gunakan tanda -l untuk membuat daftar file
posisi)
$ pergi vendor mod
$ vendor yang berusaha keras
--- statistik ---
37757 (100.0% dari 37757) deklarasi fungsi
12557 (33,3% dari 37757) berfungsi mengembalikan kesalahan
88919 (100.0% dari 88919) pernyataan
20143 (22,7% dari 88919) jika pernyataan
6555 ( 32,5% dari 20143) jika!= pernyataan nihil
109 (1,7% dari 6555)nama berbeda dari "err" (gunakan tanda -l
untuk membuat daftar posisi file)
5545 ( 84,6% dari 6555) kembali ...,blok di jika!= nihil
pernyataan
1010 (15,4% dari 6555) penangan kesalahan yang lebih kompleks di if!= nihil
pernyataan; cegah penggunaan try (gunakan flag -l untuk membuat daftar posisi file)
12 (0,2% dari 6555) blok else non-kosong di if!= nihil
pernyataan; cegah penggunaan try (gunakan flag -l untuk membuat daftar posisi file)
5427 ( 82,8% dari 6555) mencoba kandidat (gunakan tanda -l untuk membuat daftar file
posisi)

Jadi, itulah mengapa saya menambahkan titik dua dalam contoh makro, sehingga akan menonjol dan tidak terlihat seperti pemanggilan fungsi. Tidak harus menjadi titik dua tentu saja. Itu hanya sebuah contoh. Juga, makro tidak menyembunyikan apa pun. Anda hanya melihat apa yang dilakukan makro, dan begitulah. Seperti jika itu adalah fungsi, tetapi itu akan digariskan. Ini seperti Anda melakukan pencarian dan mengganti dengan potongan kode dari makro ke fungsi Anda di mana penggunaan makro selesai. Secara alami, jika orang membuat makro dari makro dan mulai memperumit masalah, salahkan diri Anda sendiri karena membuat kode menjadi lebih rumit. :)

@mirtchovski

$ tryhard .
--- stats ---
   2930 (100.0% of    2930) function declarations
   1408 ( 48.1% of    2930) functions returning an error
  10497 (100.0% of   10497) statements
   2265 ( 21.6% of   10497) if statements
   1383 ( 61.1% of    2265) if <err> != nil statements
      0 (  0.0% of    1383) <err> name is different from "err" (use -l flag to list file positions)
    645 ( 46.6% of    1383) return ..., <err> blocks in if <err> != nil statements
    738 ( 53.4% of    1383) more complex error handler in if <err> != nil statements; prevent use of try (use -l flag to list file positions)
      1 (  0.1% of    1383) non-empty else blocks in if <err> != nil statements; prevent use of try (use -l flag to list file positions)
    638 ( 46.1% of    1383) try candidates (use -l flag to list file
positions)
$ go mod vendor
$ tryhard vendor
--- stats ---
  37757 (100.0% of   37757) function declarations
  12557 ( 33.3% of   37757) functions returning an error
  88919 (100.0% of   88919) statements
  20143 ( 22.7% of   88919) if statements
   6555 ( 32.5% of   20143) if <err> != nil statements
    109 (  1.7% of    6555) <err> name is different from "err" (use -l flag to list file positions)
   5545 ( 84.6% of    6555) return ..., <err> blocks in if <err> != nil statements
   1010 ( 15.4% of    6555) more complex error handler in if <err> != nil statements; prevent use of try (use -l flag to list file positions)
     12 (  0.2% of    6555) non-empty else blocks in if <err> != nil statements; prevent use of try (use -l flag to list file positions)
   5427 ( 82.8% of    6555) try candidates (use -l flag to list file
positions)
$

@av86743 ,

maaf, tidak menganggap bahwa "Balasan email tidak mendukung penurunan harga"

Beberapa orang berkomentar bahwa tidak adil menghitung kode vendor dalam hasil tryhard . Misalnya, di perpustakaan std kode vendor menyertakan paket syscall yang dihasilkan yang berisi banyak pemeriksaan kesalahan dan yang mungkin mendistorsi keseluruhan gambar. Versi terbaru tryhard sekarang mengecualikan jalur file yang berisi "vendor" secara default (ini juga dapat dikontrol dengan flag -ignore yang baru). Diterapkan ke perpustakaan std di tip:

tryhard $HOME/go/src
/Users/gri/go/src/cmd/go/testdata/src/badpkg/x.go:1:1: expected 'package', found pkg
/Users/gri/go/src/cmd/go/testdata/src/notest/hello.go:6:1: expected declaration, found Hello
/Users/gri/go/src/cmd/go/testdata/src/syntaxerror/x_test.go:3:11: expected identifier
--- stats ---
  45424 (100.0% of   45424) func declarations
   8346 ( 18.4% of   45424) func declarations returning an error
  71401 (100.0% of   71401) statements
  16666 ( 23.3% of   71401) if statements
   4812 ( 28.9% of   16666) if <err> != nil statements
     86 (  1.8% of    4812) <err> name is different from "err" (-l flag lists details)
   3463 ( 72.0% of    4812) return ..., <err> blocks in if <err> != nil statements
   1349 ( 28.0% of    4812) complex error handler in if <err> != nil statements; cannot use try (-l flag lists details)
     17 (  0.4% of    4812) non-empty else blocks in if <err> != nil statements; cannot use try (-l flag lists details)
   3345 ( 69.5% of    4812) try candidates (-l flag lists details)

Sekarang 29% (28,9%) dari semua if pernyataan tampaknya untuk pemeriksaan kesalahan (jadi sedikit lebih dari sebelumnya), dan sekitar 70% tampaknya menjadi kandidat untuk try (sedikit lebih sedikit dari sebelumnya).

Ubah https://golang.org/cl/185177 menyebutkan masalah ini: src: apply tryhard -err="" -ignore="vendor" -r $GOROOT/src

@griesemer Anda menghitung "penangan kesalahan kompleks" tetapi bukan "penangan kesalahan satu pernyataan".

Jika sebagian besar penangan "kompleks" adalah satu pernyataan, maka on err #32611 akan menghasilkan penghematan boilerplate sebanyak try() -- 2 baris vs 3 baris x 70%. Dan on err menambahkan manfaat dari pola yang konsisten untuk sebagian besar kesalahan.

@nvictor

try adalah solusi yang bersih dan dapat dimengerti untuk masalah spesifik yang coba dipecahkan:
verbositas dalam penanganan kesalahan.

Verbositas dalam penanganan kesalahan bukanlah _masalah_, itu adalah kekuatan Go.

proposalnya berbunyi: setelah diskusi panjang selama setahun, kami menambahkan ini built-in. gunakan jika Anda ingin kode yang lebih sedikit, jika tidak, lanjutkan melakukan apa yang Anda lakukan. reaksinya adalah beberapa penolakan yang tidak sepenuhnya dapat dibenarkan untuk fitur keikutsertaan yang anggota tim telah menunjukkan keuntungan yang jelas!

Anda _opt-in_ pada waktu menulis adalah _harus_ untuk semua pembaca, termasuk masa depan-Anda.

keuntungan yang jelas

Jika aliran kontrol berlumpur bisa disebut 'keuntungan', maka ya.

try , demi kebiasaan ekspatriat java dan C++, memperkenalkan keajaiban yang perlu dipahami oleh semua Gophers. Sementara itu menyisihkan sebagian kecil baris untuk ditulis di beberapa tempat (seperti yang ditunjukkan oleh tryhard run).

Saya berpendapat bahwa makro onErr cara saya yang lebih sederhana akan menghemat lebih banyak penulisan baris, dan untuk sebagian besar:

x, err = fa()
onErr break

r, err := fb(x)
onErr return 0, nil, err

if r, err := fc(x); onErr && triesleft > 0 {
  triesleft--
  continue retry
}

_(perhatikan bahwa saya berada di kamp 'tinggalkan if err!= nil sendirian' dan proposal di atas counter diterbitkan untuk menunjukkan solusi yang lebih sederhana yang dapat membuat lebih banyak pengeluh bahagia.)_

Sunting:

saya akan lebih mendorong tim go untuk membuat try variadic built-in jika itu mudah dilakukan
try(outf.Seek(linkstart, 0)), io.Copy(outf, exef)))

~Penulisan singkat, panjang dibaca, rawan terpeleset atau salah paham, terkelupas dan berbahaya pada tahap pemeliharaan.~

Saya salah. Sebenarnya variadic try akan jauh lebih baik daripada sarang, karena kita mungkin menulisnya dengan baris:

try( outf.Seek(linkstart, 0),
 io.Copy(outf, exef),
)

dan kembalikan try(…) setelah kesalahan pertama.

Saya tidak berpikir ini menangani kesalahan implisit (gula sintaksis) seperti try itu baik, karena Anda tidak dapat menangani banyak kesalahan secara intuitif terutama ketika Anda perlu menjalankan beberapa fungsi secara berurutan.

Saya akan menyarankan sesuatu seperti Elixir dengan pernyataan: https://www.openmymind.net/Elixirs-With-Statement/

Sesuatu seperti ini di bawah ini di golang:

switch a, b, err1 := go_func_01(),
       apple, banana, err2 := go_func_02(),
       fans, dissman, err3 := go_func_03()
{
   normal_func()
else
   err1 -> handle_err1()
   err2 -> handle_err2()
   _ -> handle_other_errs()
}

Apakah pelanggaran semacam ini terhadap "Go lebih menyukai lebih sedikit fitur" dan "menambahkan fitur ke Go tidak akan membuatnya lebih baik tetapi lebih besar"? Saya tidak yakin...

Saya hanya ingin mengatakan, secara pribadi saya sangat puas dengan cara lama

if err != nil {
    return …, err
}

Dan yang pasti saya tidak ingin membaca kode yang ditulis oleh orang lain menggunakan try ... Alasannya bisa dua kali lipat:

  1. terkadang sulit untuk menebak apa yang ada di dalamnya pada pandangan pertama
  2. try s dapat disarangkan, yaitu try( ... try( ... try ( ... ) ... ) ... ) , sulit dibaca

Jika Anda berpikir bahwa menulis kode dengan cara lama untuk meneruskan kesalahan itu membosankan, mengapa tidak menyalin dan menempel saja karena mereka selalu melakukan pekerjaan yang sama?

Nah, Anda mungkin berpikir bahwa, kami tidak selalu ingin melakukan pekerjaan yang sama, tetapi kemudian Anda harus menulis fungsi "handler" Anda. Jadi mungkin Anda tidak akan rugi apa-apa jika masih menulis dengan cara lama.

Bukankah kinerja penangguhan merupakan masalah dengan solusi yang diusulkan ini? Saya telah membandingkan fungsi dengan dan tanpa penundaan dan ada dampak kinerja yang signifikan. Saya baru saja mencari di Google orang lain yang melakukan benchmark seperti itu dan menemukan biaya 16x. Saya tidak ingat milik saya seburuk itu tetapi 4x lebih lambat membunyikan bel. Bagaimana sesuatu yang dapat menggandakan atau lebih buruk waktu menjalankan banyak fungsi dianggap sebagai solusi umum yang layak?

@eric-hawthorne Menunda kinerja adalah masalah terpisah. Coba tidak secara inheren memerlukan penangguhan dan tidak menghapus kemampuan untuk menangani kesalahan tanpanya.

@fabian-f Tapi proposal ini dapat mendorong penggantian kode di mana seseorang mendekorasi kesalahan secara terpisah untuk setiap kesalahan sebaris dalam lingkup blok if err != nil. Itu akan menjadi perbedaan kinerja yang signifikan.

@eric-hawthorne Mengutip dokumen desain:

T: Bukankah menggunakan penangguhan untuk membungkus kesalahan akan menjadi lambat?

A: Saat ini pernyataan penangguhan relatif mahal dibandingkan dengan aliran kontrol biasa. Namun, kami percaya bahwa adalah mungkin untuk membuat kasus penggunaan umum penangguhan untuk penanganan kesalahan yang sebanding dalam kinerja dengan pendekatan "manual" saat ini. Lihat juga CL 171758 yang diharapkan bisa meningkatkan performa defer sekitar 30%.

Inilah pembicaraan menarik dari Rust yang ditautkan di Reddit. Bagian yang paling relevan dimulai pada 47:55

Saya mencoba mencoba di repo publik terbesar saya, https://github.com/dpinela/mflg , dan mendapatkan yang berikut:

--- stats ---
    309 (100.0% of     309) func declarations
     36 ( 11.7% of     309) func declarations returning an error
    305 (100.0% of     305) statements
     73 ( 23.9% of     305) if statements
     29 ( 39.7% of      73) if <err> != nil statements
      0 (  0.0% of      29) <err> name is different from "err"
     19 ( 65.5% of      29) return ..., <err> blocks in if <err> != nil statements
     10 ( 34.5% of      29) complex error handler in if <err> != nil statements; cannot use try
      0 (  0.0% of      29) non-empty else blocks in if <err> != nil statements; cannot use try
     15 ( 51.7% of      29) try candidates

Sebagian besar kode dalam repo itu mengelola status editor internal dan tidak melakukan I/O apa pun, sehingga memiliki sedikit pemeriksaan kesalahan - sehingga tempat try dapat digunakan relatif terbatas. Saya melanjutkan dan menulis ulang kode secara manual untuk menggunakan try jika memungkinkan; git diff --stat mengembalikan yang berikut:

 application.go                  | 42 +++++++++++-------------------------------
 internal/atomicwrite/write.go   | 35 ++++++++++++++---------------------
 internal/clipboard/clipboard.go | 17 +++--------------
 internal/config/config.go       | 15 +++++++--------
 internal/termesc/term.go        |  5 +----
 render.go                       |  8 ++------
 6 files changed, 38 insertions(+), 84 deletions(-)

(Perbedaan penuh di sini .)

Dari 10 penangan yang tryhard laporkan sebagai "kompleks", 5 adalah negatif palsu di internal/atomicwrite/write.go; mereka menggunakan pkg/errors.WithMessage untuk membungkus kesalahan. Pembungkusnya persis sama untuk semuanya, jadi saya menulis ulang fungsi itu untuk menggunakan penangan coba dan penangguhan. Saya berakhir dengan perbedaan ini (+14, -21 baris):

@@ -20,21 +20,20 @@ const (
 // The file is created with mode 0644 if it doesn't already exist; if it does, its permissions will be
 // preserved if possible.
 // If some of the directories on the path don't already exist, they are created with mode 0755.
-func Write(filename string, contentWriter func(io.Writer) error) error {
+func Write(filename string, contentWriter func(io.Writer) error) (err error) {
+       defer func() { err = errors.WithMessage(err, errString(filename)) }()
+
        dir := filepath.Dir(filename)
-       if err := os.MkdirAll(dir, defaultDirPerms); err != nil {
-               return errors.WithMessage(err, errString(filename))
-       }
-       tf, err := ioutil.TempFile(dir, "mflg-atomic-write")
-       if err != nil {
-               return errors.WithMessage(err, errString(filename))
-       }
+       try(os.MkdirAll(dir, defaultDirPerms))
+       tf := try(ioutil.TempFile(dir, "mflg-atomic-write"))
        name := tf.Name()
-       if err = contentWriter(tf); err != nil {
-               os.Remove(name)
-               tf.Close()
-               return errors.WithMessage(err, errString(filename))
-       }
+       defer func() {
+               if err != nil {
+                       tf.Close()
+                       os.Remove(name)
+               }
+       }()
+       try(contentWriter(tf))
        // Keep existing file's permissions, when possible. This may race with a chmod() on the file.
        perms := defaultPerms
        if info, err := os.Stat(filename); err == nil {
@@ -42,14 +41,8 @@ func Write(filename string, contentWriter func(io.Writer) error) error {
        }
        // It's better to save a file with the default TempFile permissions than not save at all, so if this fails we just carry on.
        tf.Chmod(perms)
-       if err = tf.Close(); err != nil {
-               os.Remove(name)
-               return errors.WithMessage(err, errString(filename))
-       }
-       if err = os.Rename(name, filename); err != nil {
-               os.Remove(name)
-               return errors.WithMessage(err, errString(filename))
-       }
+       try(tf.Close())
+       try(os.Rename(name, filename))
        return nil
 }

Perhatikan penangguhan pertama, yang menjelaskan kesalahan - saya dapat memasukkannya dengan nyaman ke dalam satu baris berkat WithMessage yang mengembalikan nol untuk kesalahan nihil. Tampaknya pembungkus semacam ini berfungsi dengan baik dengan pendekatan ini seperti yang disarankan dalam proposal.

Dua dari penangan "kompleks" lainnya berada dalam implementasi ReadFrom dan WriteTo:

var line string
line, err = br.ReadString('\n')
b.lines = append(b.lines, line)
if err != nil {
  if err == io.EOF {
    err = nil
  }
  return
}
func (b *Buffer) WriteTo(w io.Writer) (int64, error) {
    var n int64
    for _, line := range b.lines {
        nw, err := w.Write([]byte(line))
        n += int64(nw)
        if err != nil {
            return n, err
        }
    }
    return n, nil
}

Ini benar-benar tidak bisa dicoba, jadi saya meninggalkannya sendirian.

Dua lainnya adalah kode seperti ini, di mana saya mengembalikan kesalahan yang sama sekali berbeda dari yang saya periksa (tidak hanya membungkusnya). Saya membiarkannya tidak berubah juga:

n, err := strconv.ParseInt(s[1:], 16, 32)
if err != nil {
    return Color{}, errors.WithMessage(err, fmt.Sprintf("color: parse %q", s))
}

Yang terakhir adalah fungsi untuk memuat file konfigurasi, yang selalu mengembalikan konfigurasi (bukan nol) bahkan jika ada kesalahan. Itu hanya memiliki satu pemeriksaan kesalahan ini, jadi tidak banyak manfaatnya jika sama sekali dari coba:

-func Load() (*Config, error) {
-       c := Config{
+func Load() (c *Config, err error) {
+       defer func() { err = errors.WithMessage(err, "error loading config file") }()
+
+       c = &Config{
                TabWidth:    4,
                ScrollSpeed: 1,
                Lang:        make(map[string]LangConfig),
        }
-       f, err := basedir.Config.Open(filepath.Join("mflg", "config.toml"))
-       if err != nil {
-               return &c, errors.WithMessage(err, "error loading config file")
-       }
+       f := try(basedir.Config.Open(filepath.Join("mflg", "config.toml")))
        defer f.Close()
-       _, err = toml.DecodeReader(f, &c)
+       _, err = toml.DecodeReader(f, c)
        if c.TextStyle.Comment == (Style{}) {
                c.TextStyle.Comment = Style{Foreground: &color.Color{R: 0, G: 200, B: 0}}
        }
        if c.TextStyle.String == (Style{}) {
                c.TextStyle.String = Style{Foreground: &color.Color{R: 0, G: 0, B: 200}}
        }
-       return &c, errors.WithMessage(err, "error loading config file")
+       return c, err
 }

Faktanya, mengandalkan perilaku try dalam menjaga nilai parameter pengembalian - seperti pengembalian telanjang - terasa, menurut pendapat saya, sedikit lebih sulit untuk diikuti; kecuali saya menambahkan lebih banyak pemeriksaan kesalahan, saya akan tetap menggunakan if err != nil dalam kasus khusus ini.

TL;DR: try hanya berguna dalam persentase yang cukup kecil (berdasarkan jumlah baris) dari kode ini, tetapi jika membantu, itu sangat membantu.

(Noob di sini). Ide lain untuk beberapa argumen. Bagaimana tentang:

package trytest

import "fmt"

func errorInner() (string, error) {
   return "", fmt.Errorf("inner error")
}

func errorOuter() (string, error) {
   tryreturn errorInner()
   return "", nil
}

func errorOuterWithArg() (string, error) {
   var toProcess string
   tryreturn toProcess, _ = errorOuter()
   return toProcess + "", nil
}

func errorOuterWithArgStretch() (bool, string, error) {
   var toProcess string
   tryreturn false, ( toProcess,_ = errorOuterWithArg() )
   return true, toProcess + "", nil
}

yaitu tryreturn memicu kembalinya semua nilai jika kesalahan terakhir
nilai, jika tidak, eksekusi berlanjut.

Prinsip yang saya setujui:
-

  • Kesalahan menangani panggilan fungsi layak mendapatkan salurannya sendiri. Go sengaja eksplisit dalam aliran kontrol, dan saya pikir mengemasnya ke dalam ekspresi bertentangan dengan keeksplisitannya.
  • Akan bermanfaat untuk memiliki metode penanganan kesalahan yang cocok pada satu baris. (Dan idealnya hanya membutuhkan satu kata atau beberapa karakter boilerplate sebelum penanganan kesalahan yang sebenarnya). 3 baris penanganan kesalahan untuk setiap panggilan fungsi adalah titik gesekan dalam bahasa yang patut dicintai dan diperhatikan.
  • Setiap bawaan yang mengembalikan (seperti try yang diusulkan) setidaknya harus berupa pernyataan, dan idealnya memiliki kata return di dalamnya. Sekali lagi, saya pikir aliran kontrol di Go harus eksplisit.
  • Kesalahan Go paling berguna ketika mereka memiliki konteks tambahan yang disertakan (saya hampir selalu menambahkan konteks ke kesalahan saya). Solusi untuk masalah ini juga harus mendukung kode penanganan kesalahan penambahan konteks.

Sintaks yang saya dukung:
-

  • pernyataan reterr _x_ (gula sintaksis untuk if err != nil { return _x_ } , secara eksplisit dinamai untuk menunjukkan itu akan kembali)

Jadi kasus umum bisa menjadi satu baris pendek dan eksplisit yang bagus:

func foo() error {
    a, err := bar()
    reterr err

    b, err := baz(a)
    reterr fmt.Errorf("getting the baz of %v: %v", a, err)

    return nil
}

Alih-alih 3 baris mereka sekarang:

func foo() error {
    a, err := bar()
    if err != nil {
        return err
    }

    b, err := baz()
    if err != nil {
        return fmt.Errorf("getting the baz of %v: %v", a, err)
    }

    return nil
}

Hal-hal yang Saya Tidak Setuju:



    • "Ini adalah perubahan yang terlalu kecil untuk mengubah bahasa"

      Saya tidak setuju, ini adalah perubahan kualitas hidup yang menghilangkan sumber gesekan terbesar yang saya miliki saat menulis kode Go. Saat memanggil suatu fungsi membutuhkan 4 baris

  • "Akan lebih baik menunggu solusi yang lebih umum"
    Saya tidak setuju, saya pikir masalah ini layak untuk solusi khusus sendiri. Versi umum dari masalah ini adalah mengurangi kode boilerplate, dan jawaban umum adalah makro - yang bertentangan dengan etos Go dari kode eksplisit. Jika Go tidak akan menyediakan fasilitas makro umum, maka Go seharusnya menyediakan beberapa makro spesifik yang sangat banyak digunakan seperti reterr (setiap orang yang menulis Go akan mendapat manfaat dari reterr).

@Qhesz Tidak jauh berbeda dengan try:

func foo() error {
    a, err := bar()
    try(err)

    b, err := baz(a)
    try(wrap(err, "getting the baz of %v", a))

    return nil
}

@reusee Saya menghargai saran itu, saya tidak menyadari itu bisa digunakan seperti itu. Tampaknya sedikit kisi-kisi bagi saya, saya mencoba untuk meletakkan jari saya mengapa.

Saya pikir "coba" adalah kata yang aneh untuk digunakan dengan cara itu. "try(action())" masuk akal dalam bahasa Inggris, sedangkan "try(value)" sebenarnya tidak. Saya akan lebih setuju jika itu adalah kata yang berbeda.

Juga try(wrap(...)) mengevaluasi wrap(...) terlebih dahulu bukan? Berapa banyak yang menurut Anda dioptimalkan oleh kompiler? (Dibandingkan dengan hanya menjalankan if err != nil ?)

Juga #32611 adalah proposal yang agak mirip, dan komentar memiliki beberapa pendapat yang mencerahkan dari tim inti Go dan anggota komunitas, khususnya seputar perbedaan antara kata kunci dan fungsi bawaan.

@Qhesz Saya setuju dengan Anda tentang penamaan. Mungkin check lebih tepat karena "check(action())" atau "check(err)" terbaca dengan baik.

@reusee Yang agak ironis, karena rancangan aslinya menggunakan check .

Pada 7/6/19, mirtchovski [email protected] menulis:

$ berusaha.
--- statistik ---
2930 (100.0% dari 2930) deklarasi fungsi
1408 ( 48,1% dari 2930) fungsi mengembalikan kesalahan
[ ... ]

Mau tidak mau saya nakal di sini: apakah itu "fungsi kembali dan
kesalahan sebagai argumen terakhir"?

Lucio.

Pemikiran terakhir pada pertanyaan saya di atas, saya masih lebih suka sintaks try(err, wrap("getting the baz of %v: %v", a, err)) , dengan wrap() hanya dieksekusi jika err tidak nihil. Alih-alih try(wrap(err, "getting the baz of %v", a)) .

@Qhesz Kemungkinan implementasi wrap dapat berupa:

func wrap(err error, format string, args ...interface{}) error {
    if err == nil {
        return nil
    }
    return fmt.Errorf("%s: %w", fmt.Sprintf(format, args...), err)
}

Jika kompiler dapat memasukkan wrap , maka tidak ada perbedaan kinerja antara klausa wrap dan if err != nil .

@reusee Saya pikir maksud Anda if err == nil ;)

@Qhesz Kemungkinan implementasi wrap dapat berupa:

func wrap(err error, format string, args ...interface{}) error {
  if err == nil {
      return nil
  }
  return fmt.Errorf("%s: %w", fmt.Sprintf(format, args...), err)
}

Jika kompiler dapat memasukkan wrap , maka tidak ada perbedaan kinerja antara klausa wrap dan if err != nil .

%w tidak valid kata kerja go

(Saya kira yang dia maksud adalah %v...)

Jadi sementara kata kunci lebih disukai untuk ditulis, saya mengerti bahwa builtin adalah cara yang lebih disukai untuk mengimplementasikannya.

Saya pikir saya akan setuju dengan proposal ini jika

  • itu check bukannya try
  • beberapa bagian dari perkakas Go yang diberlakukan itu hanya dapat digunakan seperti pernyataan (yaitu memperlakukannya seperti 'pernyataan' bawaan, bukan seperti 'fungsi' bawaan. Ini hanya bawaan untuk alasan kepraktisan, ia mencoba menjadi pernyataan tanpa menjadi diimplementasikan oleh bahasa.) Misalnya, jika tidak mengembalikan apa pun, maka tidak pernah valid di dalam ekspresi, seperti panic() .
  • ~mungkin beberapa indikator bahwa itu adalah makro dan memengaruhi aliran kontrol, sesuatu yang membedakannya dari pemanggilan fungsi. (misalnya check!(...) seperti yang dilakukan Rust, tetapi saya tidak memiliki pendapat yang kuat tentang sintaks tertentu)~ Berubah pikiran

Maka itu akan bagus, saya akan menggunakannya di setiap panggilan fungsi yang saya buat.

Dan permintaan maaf kecil untuk utasnya, saya baru sekarang menemukan komentar di atas yang menguraikan cukup banyak apa yang baru saja saya katakan.

@deanveloper diperbaiki, terima kasih.

@olekukonko @Qhesz %w baru ditambahkan di tip: https://tip.golang.org/pkg/fmt/#Errorf

Saya minta maaf karena tidak membaca semuanya dalam topik ini, tetapi saya ingin menyebutkan sesuatu yang belum saya lihat.

Saya melihat dua kasus terpisah di mana penanganan kesalahan Go1 dapat mengganggu: kode "baik" yang benar tetapi sedikit berulang; dan kode "buruk" yang salah, tetapi sebagian besar berfungsi.

Dalam kasus pertama, seharusnya ada beberapa logika di blok if-err, dan pindah ke konstruksi gaya coba menghambat praktik yang baik ini dengan mempersulit penambahan logika tambahan.

Dalam kasus kedua, kode buruk seringkali berbentuk:

..., _ := might_error()

atau hanya

might_error()

Di mana ini terjadi biasanya karena penulis tidak berpikir itu cukup penting untuk menghabiskan waktu pada penanganan kesalahan, dan hanya berharap semuanya bekerja. Kasus ini dapat ditingkatkan dengan sesuatu yang sangat mendekati nol-usaha, seperti:

..., XXX := might_error()

di mana XXX adalah simbol yang berarti "apa pun di sini harus menghentikan eksekusi entah bagaimana". Ini akan memperjelas bahwa ini bukan kode siap produksi - penulis mengetahui kasus kesalahan, tetapi tidak menginvestasikan waktu untuk memutuskan apa yang harus dilakukan.

Tentu saja ini tidak menghalangi solusi tipe returnif handle(err) .

Saya menentang mencoba, pada keseimbangan, dengan pujian kepada kontributor pada desain minimal yang bagus. Saya bukan ahli Go yang berat, tetapi merupakan pengadopsi awal dan memiliki kode dalam produksi di sana-sini. Saya bekerja di grup Tanpa Server di AWS dan sepertinya kami akan merilis layanan berbasis Go akhir tahun ini, check-in pertama yang sebagian besar ditulis oleh saya. Saya seorang pria yang sangat tua, jalan saya untuk pergi dipimpin melalui C, Perl, Java, dan Ruby. Masalah saya telah muncul sebelumnya dalam ringkasan debat yang sangat berguna, tetapi saya masih menganggapnya layak untuk diulang.

  1. Go adalah bahasa yang kecil dan sederhana dan dengan demikian telah mencapai keterbacaan yang tiada bandingnya. Saya secara refleks menentang menambahkan apa pun ke dalamnya kecuali manfaatnya benar-benar substansial secara kualitatif. Seseorang biasanya tidak menyadari lereng yang licin sampai seseorang berada di atasnya, jadi jangan mengambil langkah pertama.
  2. Saya sangat terpengaruh oleh argumen di atas tentang memfasilitasi debugging. Saya suka ritme visual, dalam kode infrastruktur tingkat rendah, dari bait kecil kode di sepanjang baris “Lakukan A. Periksa apakah itu berhasil. Lakukan B. Periksa apakah berhasil… dll" Karena baris "Periksa" adalah tempat Anda meletakkan printf atau breakpoint. Mungkin semua orang lebih pintar, tetapi saya akhirnya menggunakan idiom breakpoint itu secara teratur.
  3. Dengan asumsi nilai pengembalian bernama, "coba" kira-kira setara dengan if err != nil { return } (saya pikir?) Saya pribadi suka nilai pengembalian bernama dan, mengingat manfaat dari dekorator kesalahan, saya menduga proporsi nilai pengembalian bernama err akan meningkat secara monoton; yang melemahkan manfaat mencoba.
  4. Saya awalnya menyukai proposal untuk gofmt memberkati one-liner di baris di atas, tetapi pada keseimbangan, IDE pasti akan mengadopsi idiom tampilan ini, dan one-liner akan mengorbankan manfaat debug-sini.
  5. Tampaknya sangat mungkin bahwa beberapa bentuk ekspresi bersarang yang mengandung "coba" akan membuka pintu bagi para penyusup dalam profesi kita untuk mendatangkan malapetaka yang sama seperti yang mereka miliki dengan aliran Java dan spliterator dan sebagainya. Go lebih berhasil daripada kebanyakan bahasa lain dalam menolak kesempatan orang pintar di antara kita untuk menunjukkan keahlian merekaz.

Sekali lagi, selamat kepada komunitas atas proposal bersih yang bagus dan diskusi yang konstruktif.

Saya telah menghabiskan banyak waktu untuk melompat dan membaca perpustakaan atau potongan kode yang tidak dikenal selama beberapa tahun terakhir. Meskipun membosankan, if err != nil menyediakan idiom yang sangat mudah dibaca, meskipun verbose vertikal. Semangat dari apa yang try() coba capai adalah mulia, dan saya pikir ada sesuatu yang harus dilakukan, tetapi fitur ini terasa salah diprioritaskan dan bahwa proposal tersebut terlihat terlalu dini (yaitu seharusnya datang setelah xerr dan obat generik memiliki kesempatan untuk mengasinkan dalam rilis stabil selama 6-12 bulan).

Memperkenalkan try() tampaknya merupakan proposal yang mulia dan bermanfaat (misalnya 29% - ~40% dari pernyataan if adalah untuk pemeriksaan if err != nil ). Di permukaan, tampaknya pengurangan boilerplate yang terkait dengan penanganan kesalahan akan meningkatkan pengalaman pengembang. Pengorbanan dari pengenalan try() datang dalam bentuk beban kognitif dari kasus khusus semi-halus. Salah satu keunggulan terbesar Go adalah sederhana dan hanya ada sedikit beban kognitif yang diperlukan untuk menyelesaikan sesuatu (dibandingkan dengan C++ di mana spesifikasi bahasanya besar dan bernuansa). Mengurangi satu metrik kuantitatif (LoC if err != nil ) sebagai imbalan untuk meningkatkan metrik kuantitatif kompleksitas mental adalah pil yang sulit untuk ditelan (yaitu pajak mental atas sumber daya paling berharga yang kita miliki, kekuatan otak).

Khususnya kasus khusus baru untuk cara try() ditangani dengan go , defer , dan variabel pengembalian bernama membuat try() cukup ajaib untuk membuat kode lebih sedikit eksplisit sehingga semua penulis atau pembaca kode Go harus mengetahui kasus khusus baru ini agar dapat membaca atau menulis Go dengan benar dan beban seperti itu tidak ada sebelumnya. Saya suka bahwa ada kasus khusus yang eksplisit untuk situasi ini - terutama versus memperkenalkan beberapa bentuk perilaku yang tidak ditentukan, tetapi fakta bahwa mereka harus ada di tempat pertama menunjukkan ini tidak lengkap saat ini. Jika kasus khusus untuk apa pun kecuali penanganan kesalahan, itu mungkin dapat diterima, tetapi jika kita sudah berbicara tentang sesuatu yang dapat berdampak hingga 40% dari semua LoC, kasus khusus ini perlu dilatih ke seluruh komunitas dan yang menaikkan biaya beban kognitif proposal ini ke tingkat yang cukup tinggi untuk menjamin perhatian.

Ada contoh lain di Go di mana aturan kasus khusus sudah menjadi kemiringan kognitif yang licin, yaitu variabel yang disematkan dan yang tidak disematkan. Perlu untuk menyematkan variabel tidak sulit untuk dipahami dalam praktiknya, tetapi hal itu terlewatkan karena ada perilaku implisit di sini dan ini menyebabkan ketidakcocokan antara penulis, pembaca, dan apa yang terjadi dengan executable yang dikompilasi saat runtime. Bahkan dengan linter seperti scopelint banyak pengembang tampaknya masih tidak memahami gotcha ini (atau lebih buruk lagi, mereka mengetahuinya tetapi melewatkannya karena gotcha ini luput dari pikiran mereka). Beberapa bug runtime yang paling tidak terduga dan sulit didiagnosis dari program yang berfungsi berasal dari masalah khusus ini (misalnya N objek semua diisi dengan nilai yang sama alih-alih mengulangi sepotong dan mendapatkan nilai berbeda yang diharapkan). Domain kegagalan dari try() berbeda dari variabel yang disematkan, tetapi akan ada dampak pada cara orang menulis kode sebagai hasilnya.

IMNSHO, xerr dan proposal generik memerlukan waktu untuk mencapai produksi selama 6-12 bulan sebelum mencoba menaklukkan boilerplate dari if err != nil . Obat generik kemungkinan akan membuka jalan bagi penanganan kesalahan yang lebih kaya dan cara penanganan kesalahan idiomatis baru. Setelah penanganan kesalahan idiomatik dengan obat generik mulai muncul, maka masuk akal untuk meninjau kembali diskusi seputar try() atau apa pun.

Saya tidak berpura-pura tahu bagaimana obat generik akan memengaruhi penanganan kesalahan, tetapi tampaknya pasti bagi saya bahwa obat generik akan digunakan untuk membuat tipe kaya yang hampir pasti akan digunakan dalam penanganan kesalahan. Setelah obat generik meresap ke perpustakaan dan telah ditambahkan ke penanganan kesalahan, mungkin ada cara yang jelas untuk menggunakan kembali try() untuk meningkatkan pengalaman pengembang sehubungan dengan penanganan kesalahan.

Hal-hal yang menjadi perhatian saya adalah:

  1. try() tidak rumit secara terpisah, tetapi ini adalah overhead kognitif di mana tidak ada sebelumnya.
  2. Dengan memasukkan err != nil ke dalam asumsi perilaku try() , bahasa tersebut mencegah penggunaan err sebagai cara mengkomunikasikan status ke atas tumpukan.
  3. Secara estetika try() terasa seperti kepandaian yang dipaksakan tetapi tidak cukup pintar untuk memenuhi tes eksplisit dan nyata yang dinikmati sebagian besar bahasa Go. Seperti kebanyakan hal yang melibatkan kriteria subjektif, ini adalah masalah selera dan pengalaman pribadi dan sulit untuk diukur.
  4. Penanganan kesalahan dengan pernyataan switch / case dan pembungkusan kesalahan tampaknya tidak tersentuh oleh proposal ini, dan peluang yang terlewatkan, yang membuat saya percaya bahwa proposal ini sangat sulit untuk membuat hal yang tidak diketahui menjadi diketahui -diketahui (atau paling buruk, diketahui-tidak diketahui).

Terakhir, proposal try() terasa seperti terobosan baru di bendungan yang menahan banjir nuansa khusus bahasa seperti yang kami hindari dengan meninggalkan C++.

TL;DR: bukan respons #nevertry seperti itu, "tidak sekarang, belum, dan mari kita pertimbangkan ini lagi di masa depan setelah xerr dan obat generik matang di ekosistem. "

#32968 yang ditautkan di atas bukanlah proposal tandingan yang lengkap, tetapi ini didasarkan pada ketidaksetujuan saya dengan kemampuan berbahaya untuk bersarang yang dimiliki makro try . Tidak seperti #32946 yang satu ini adalah proposal yang serius, yang saya harap tidak memiliki kekurangan yang serius (tentu saja milik Anda untuk dilihat, dinilai dan dikomentari). Kutipan:

  • _Makro check bukan satu baris: ini paling membantu di mana banyak yang berulang
    pemeriksaan menggunakan ekspresi yang sama harus dilakukan dalam jarak dekat._
  • _Versi implisitnya sudah dikompilasi di taman bermain._

Kendala desain (terpenuhi)

Ini adalah bawaan, tidak bersarang dalam satu baris, memungkinkan lebih banyak aliran daripada try dan tidak memiliki harapan tentang bentuk kode di dalamnya. Itu tidak mendorong pengembalian telanjang.

contoh penggunaan

// built-in 'check' macro signature: 
func check(Condition bool) {}

check(err != nil) // explicit catch: label.
{
    ucred, err := getUserCredentials(user)
    remote, err := connectToApi(remoteUri)
    err, session, usertoken := remote.Auth(user, ucred)
    udata, err := session.getCalendar(usertoken)

  catch:               // sad path
    ucred.Clear()      // cleanup passwords
    remote.Close()     // do not leak sockets
    return nil, 0, err // dress before leaving
}
// happy path

// implicit catch: label is above last statement
check(x < 4) 
  {
    x, y = transformA(x, z)
    y, z = transformB(x, y)
    x, y = transformC(y, z)
    break // if x was < 4 after any of above
  }

Semoga ini bisa membantu, Selamat menikmati!

Saya telah membaca sebanyak yang saya bisa untuk mendapatkan pemahaman tentang utas ini. Saya mendukung untuk meninggalkan hal-hal persis seperti apa adanya.

Alasan saya:

  1. Saya, dan tidak ada seorang pun yang saya ajar Go memiliki _ever_ tidak mengerti penanganan kesalahan
  2. Saya menemukan diri saya tidak pernah melewatkan jebakan kesalahan karena sangat mudah untuk melakukannya saat itu juga

Juga, mungkin saya salah memahami proposal, tetapi biasanya, konstruksi try dalam bahasa lain menghasilkan beberapa baris kode yang semuanya berpotensi menghasilkan kesalahan, dan karenanya memerlukan jenis kesalahan. Menambahkan kompleksitas dan seringkali semacam arsitektur kesalahan di muka dan upaya desain.

Dalam kasus tersebut (dan saya telah melakukannya sendiri), beberapa blok percobaan ditambahkan. yang memperpanjang kode, dan membayangi implementasi.

Jika implementasi Go dari try berbeda dari implementasi bahasa lain, maka akan timbul lebih banyak kebingungan.

Saran saya adalah membiarkan kesalahan penanganan seperti itu

Saya tahu banyak orang telah mempertimbangkan, tetapi saya ingin menambahkan kritik saya terhadap spesifikasi apa adanya.

Bagian dari spesifikasi yang paling mengganggu saya adalah dua permintaan ini:

Oleh karena itu kami menyarankan untuk melarang try sebagai fungsi yang dipanggil dalam pernyataan go.
...
Oleh karena itu kami menyarankan untuk melarang try sebagai fungsi yang dipanggil dalam pernyataan defer juga.

Ini akan menjadi fungsi bawaan pertama yang benar (Anda bahkan dapat mengedit defer dan go a panic ) karena hasilnya tidak perlu dibuang. Membuat fungsi bawaan baru yang mengharuskan kompiler untuk memberikan pertimbangan aliran kontrol khusus tampaknya seperti permintaan besar dan merusak koherensi semantik dari go. Setiap token aliran kontrol lainnya yang masuk bukan merupakan fungsi.

Sebuah kontra-argumen untuk keluhan saya adalah bahwa mampu defer dan go a panic mungkin merupakan kecelakaan dan tidak terlalu berguna. Namun maksud saya adalah bahwa koherensi semantik dari fungsi-fungsi yang sedang berjalan rusak oleh proposal ini bukan karena defer dan go selalu masuk akal untuk digunakan. Mungkin ada banyak fungsi non-builtin yang tidak akan pernah masuk akal untuk digunakan dengan defer atau go , tetapi tidak ada alasan eksplisit, secara semantik, mengapa mereka tidak bisa. Mengapa builtin ini dapat membebaskan dirinya dari kontrak fungsi semantik saat berjalan?

Saya tahu @griesemer tidak ingin pendapat estetis tentang proposal ini dimasukkan ke dalam diskusi, tetapi saya pikir salah satu alasan orang menganggap proposal ini menjijikkan secara estetika adalah karena mereka dapat merasakannya tidak cukup berfungsi.

Proposal mengatakan:

Kami mengusulkan untuk menambahkan fungsi baru seperti built-in yang disebut coba dengan tanda tangan (kode semu)

func try(expr) (T1, T2, … Tn)

Kecuali ini bukan fungsi (yang pada dasarnya diakui oleh proposal). Ini, secara efektif, makro satu kali yang dibangun ke dalam spesifikasi bahasa (jika ingin diterima). Ada beberapa masalah dengan tanda tangan ini.

  1. Apa artinya suatu fungsi menerima ekspresi generik sebagai argumen, belum lagi ekspresi yang dipanggil. Setiap kali kata "ekspresi" digunakan dalam spesifikasi itu berarti sesuatu seperti fungsi yang tidak dipanggil. Bagaimana fungsi yang "dipanggil" dapat dianggap sebagai ekspresi, ketika dalam setiap konteks lain nilai baliknya adalah apa yang aktif secara semantik. IE kami menganggap fungsi yang dipanggil sebagai nilai pengembaliannya. Pengecualiannya, dengan jelas, adalah go dan defer , yang keduanya merupakan token mentah bukan fungsi bawaan.

  2. Juga proposal ini mendapatkan tanda tangan fungsinya sendiri yang salah, atau setidaknya tidak masuk akal, tanda tangan yang sebenarnya adalah:

func try(R1, R2, ... Rn) ((R|T)1, (R|T)2, ... (R|T)(n-1), ?Rn) 
// where T is the return params of the function that try is being called from
// where `R` is a return value from a function, `Rn` must be an error
// try will return the R values if Rn is nil and not return Tn at all
// if Rn is not nil then the T values will be returned as well as Rn at the end 
  1. Proposal tidak menyertakan apa yang terjadi dalam situasi di mana try dipanggil dengan argumen. Apa yang terjadi jika try dipanggil dengan argumen:
try(arg1, arg2,..., err)

Saya pikir alasan ini tidak ditangani, adalah karena try mencoba menerima argumen expr yang sebenarnya mewakili n jumlah argumen pengembalian dari suatu fungsi ditambah sesuatu yang lain, ilustrasi lebih lanjut dari fakta bahwa proposal ini mematahkan koherensi semantik tentang apa fungsi itu.

Keluhan terakhir saya terhadap proposal ini adalah bahwa itu semakin merusak makna semantik dari fungsi bawaan. Saya tidak acuh pada gagasan bahwa fungsi bawaan terkadang perlu dikecualikan dari aturan semantik fungsi "normal" (seperti tidak dapat menetapkannya ke variabel, dll), tetapi proposal ini membuat sejumlah besar pengecualian dari " normal" aturan yang tampaknya mengatur fungsi di dalam golang.

Proposal ini secara efektif menjadikan try hal baru yang belum dimiliki, itu bukan token dan bukan fungsi yang cukup, keduanya, yang sepertinya merupakan preseden buruk untuk ditetapkan dalam hal menciptakan koherensi semantik di seluruh bahasa.

Jika kita akan menambahkan hal-hal aliran kontrol baru, saya berpendapat bahwa lebih masuk akal untuk menjadikannya sebagai token mentah seperti goto , et al. Saya tahu kita tidak seharusnya membicarakan proposal dalam diskusi ini, tetapi sebagai contoh singkat, saya pikir hal seperti ini lebih masuk akal:

f, err := os.Open("/dev/stdout")
throw err

Meskipun ini menambahkan baris kode tambahan, saya pikir itu mengatasi setiap masalah yang saya angkat, dan juga menghilangkan seluruh kekurangan tanda tangan fungsi "alternatif" dengan try .

edit1 : catatan tentang pengecualian untuk kasus defer dan go di mana bawaan tidak dapat digunakan, karena hasil akan diabaikan, sedangkan dengan try bahkan tidak bisa mengatakan bahwa fungsi memiliki hasil.

@nathanjsweet proposal yang Anda cari adalah #32611 :-)

@nathanjsweet Beberapa dari apa yang Anda katakan ternyata tidak demikian. Bahasa tidak mengizinkan penggunaan defer atau go dengan fungsi yang dideklarasikan sebelumnya append cap complex imag len make new real . Itu juga tidak mengizinkan defer atau go dengan fungsi yang ditentukan spesifikasi unsafe.Alignof unsafe.Offsetof unsafe.Sizeof .

Terima kasih @nathanjsweet atas komentar ekstensif Anda - @ianlancetaylor telah menunjukkan bahwa argumen Anda secara teknis salah. Biarkan saya memperluas sedikit:

1) Anda menyebutkan bahwa bagian dari spesifikasi yang melarang try dengan go dan defer paling mengganggu Anda karena try akan menjadi bawaan pertama di mana ini benar. Ini tidak benar. Kompiler sudah tidak mengizinkan misalnya, defer append(a, 1) . Hal yang sama berlaku untuk built-in lain yang menghasilkan hasil yang kemudian dijatuhkan di lantai. Pembatasan ini juga akan berlaku untuk try dalam hal ini (kecuali jika try tidak memberikan hasil). (Alasan mengapa kami bahkan menyebutkan pembatasan ini dalam dokumen desain adalah untuk selengkap mungkin - mereka benar-benar tidak relevan dalam praktik. Juga, jika Anda membaca dokumen desain dengan tepat, itu tidak mengatakan bahwa kami tidak dapat menghasilkan try bekerja dengan go atau defer - itu hanya menunjukkan bahwa kami melarangnya; sebagian besar sebagai tindakan praktis Ini adalah "permintaan besar" - menggunakan kata-kata Anda - untuk menghasilkan try bekerja dengan go dan defer meskipun praktis tidak berguna.)

2) Anda menyarankan agar beberapa orang menganggap try "menjijikkan secara estetika" karena secara teknis bukan fungsi, dan kemudian Anda berkonsentrasi pada aturan khusus untuk tanda tangan. Pertimbangkan new , make , append , unsafe.Offsetof : mereka semua memiliki aturan khusus yang tidak dapat kita ekspresikan dengan fungsi Go biasa. Lihat unsafe.Offsetof yang memiliki persis jenis persyaratan sintaksis untuk argumennya (harus berupa bidang struct!) yang kita perlukan dari argumen untuk try (harus berupa nilai tipe tunggal error atau panggilan fungsi yang mengembalikan error sebagai hasil terakhir). Kami tidak mengungkapkan tanda tangan tersebut secara formal dalam spesifikasi, karena tidak satu pun dari bawaan ini karena tidak sesuai dengan formalisme yang ada - jika memang demikian, tanda tangan tersebut tidak harus ada di dalamnya. Sebaliknya kami mengungkapkan aturan mereka dalam prosa. Itulah _mengapa_ mereka adalah built-in yang _merupakan_ pintu keluar di Go, secara desain, sejak hari pertama. Perhatikan juga bahwa dokumen desain sangat eksplisit tentang ini.

3) Proposal juga membahas apa yang terjadi ketika try dipanggil dengan argumen (lebih dari satu): Tidak diizinkan. Dokumen desain menyatakan secara eksplisit bahwa try menerima (satu) ekspresi argumen yang masuk.

4) Anda menyatakan bahwa "proposal ini merusak makna semantik dari fungsi bawaan". Go tidak membatasi apa yang dapat dilakukan dan apa yang tidak dapat dilakukan oleh built-in. Kami memiliki kebebasan penuh di sini.

Terima kasih.

@griesemer

Perhatikan juga bahwa dokumen desain sangat eksplisit tentang ini.

Bisakah Anda menunjukkan ini. Saya terkejut membaca ini.

Anda menyatakan bahwa "proposal ini merusak makna semantik dari fungsi bawaan". Go tidak membatasi apa yang dapat dilakukan dan apa yang tidak dapat dilakukan oleh built-in. Kami memiliki kebebasan penuh di sini.

Saya pikir ini adalah poin yang adil. Namun, saya pikir ada apa yang dijabarkan dalam dokumen desain dan apa yang terasa seperti "pergi" (yang merupakan sesuatu yang banyak dibicarakan oleh Rob Pike). Saya pikir adil bagi saya untuk mengatakan bahwa proposal try memperluas cara fungsi bawaan melanggar aturan yang kami harapkan berfungsi untuk fungsi, dan saya mengakui bahwa saya mengerti mengapa ini diperlukan untuk bawaan lainnya , tapi menurut saya dalam hal ini perluasan dari melanggar aturan adalah:

  1. Kontra-intuitif dalam beberapa hal. Ini adalah fungsi pertama yang mengubah logika aliran kontrol dengan cara yang tidak melepaskan tumpukan (seperti yang dilakukan panic dan os.Exit )
  2. Pengecualian baru tentang cara kerja konvensi pemanggilan suatu fungsi. Anda memberi contoh unsafe.Offsetof sebagai kasus di mana ada persyaratan sintaksis untuk panggilan fungsi (mengejutkan bagi saya sebenarnya bahwa ini menyebabkan kesalahan waktu kompilasi, tapi itu masalah lain), tetapi persyaratan sintaksis , dalam hal ini, adalah persyaratan sintaksis yang berbeda dari yang Anda nyatakan. unsafe.Offsetof memerlukan satu argumen, sedangkan try memerlukan ekspresi yang akan terlihat, dalam setiap konteks lain, seperti nilai yang dikembalikan dari suatu fungsi (yaitu try(os.Open("/dev/stdout")) ) dan dapat diasumsikan dengan aman dalam setiap konteks lain untuk mengembalikan hanya satu nilai (kecuali ekspresi tampak seperti try(os.Open("/dev/stdout")...) ).

@nathanjsweet menulis:

Perhatikan juga bahwa dokumen desain sangat eksplisit tentang ini.

Bisakah Anda menunjukkan ini. Saya terkejut membaca ini.

Itu ada di bagian "Kesimpulan" dari proposal:

Di Go, built-in adalah mekanisme lolos bahasa pilihan untuk operasi yang tidak teratur dalam beberapa cara tetapi tidak membenarkan sintaks khusus.

Saya terkejut Anda melewatkannya ;-)

@ngrilly saya tidak bermaksud dalam proposal ini, maksud saya dalam spesifikasi bahasa go. Saya mendapat kesan bahwa @griesemer mengatakan bahwa spesifikasi bahasa go memanggil fungsi bawaan sebagai mekanisme yang secara khusus berguna untuk melanggar konvensi sintaksis.

@nathanjsweet

Kontra-intuitif dalam beberapa hal. Ini adalah fungsi pertama yang mengubah logika aliran kontrol dengan cara yang tidak melepaskan tumpukan (seperti panic dan os.Exit do)

Saya tidak berpikir bahwa os.Exit membuka tumpukan dalam arti yang berguna. Ini menghentikan program segera tanpa menjalankan fungsi yang ditangguhkan. Bagi saya os.Exit adalah yang aneh di sini, karena panic dan try menjalankan fungsi yang ditangguhkan dan naik ke tumpukan.

Saya setuju bahwa os.Exit adalah yang aneh, tetapi harus seperti itu. os.Exit menghentikan semua goroutine; tidak masuk akal untuk hanya menjalankan fungsi yang ditangguhkan hanya dari goroutine yang memanggil os.Exit . Itu harus menjalankan semua fungsi yang ditangguhkan, atau tidak sama sekali. Dan jauh lebih mudah untuk tidak menjalankannya.

Menjalankan tryhard pada basis kode kami dan inilah yang kami dapatkan:

--- stats ---
  15298 (100.0% of   15298) func declarations
   3026 ( 19.8% of   15298) func declarations returning an error
  33941 (100.0% of   33941) statements
   7765 ( 22.9% of   33941) if statements
   3747 ( 48.3% of    7765) if <err> != nil statements
    131 (  3.5% of    3747) <err> name is different from "err"
   1847 ( 49.3% of    3747) return ..., <err> blocks in if <err> != nil statements
   1900 ( 50.7% of    3747) complex error handler in if <err> != nil statements; cannot use try
     19 (  0.5% of    3747) non-empty else blocks in if <err> != nil statements; cannot use try
   1789 ( 47.7% of    3747) try candidates

Pertama, saya ingin mengklarifikasi bahwa karena Go (sebelum 1.13) tidak memiliki konteks dalam kesalahan, kami menerapkan jenis kesalahan kami sendiri yang mengimplementasikan antarmuka error , beberapa fungsi dideklarasikan sebagai mengembalikan foo.Error alih-alih error , dan sepertinya penganalisis ini tidak menangkapnya sehingga hasil ini tidak "adil".

Saya berada di kubu "ya! ayo lakukan ini", dan saya pikir ini akan menjadi eksperimen yang menarik untuk 1,13 atau 1,14 beta , tapi saya khawatir dengan _" 47,7% ... coba kandidat"_. Sekarang berarti ada 2 cara melakukan sesuatu, yang saya tidak suka. Namun ada juga 2 cara membuat pointer ( new(Foo) vs &Foo{} ) serta 2 cara membuat irisan atau peta dengan make([]Foo) dan []Foo{} .

Sekarang saya berada di kamp "ayo _coba_ ini" :^) dan melihat apa yang dipikirkan komunitas. Mungkin kita akan mengubah pola pengkodean kita menjadi malas dan berhenti menambahkan konteks, tapi mungkin tidak apa-apa jika kesalahan mendapatkan konteks yang lebih baik dari impl xerrors yang akan datang.

Terima kasih, @Goodwine telah memberikan lebih banyak data konkret!

(Sebagai tambahan, saya membuat perubahan kecil pada tryhard tadi malam sehingga ini membagi jumlah "penangan kesalahan kompleks" menjadi dua hitungan: penangan kompleks, dan pengembalian formulir return ..., expr di mana yang terakhir nilai hasil bukan <err> . Ini akan memberikan beberapa wawasan tambahan.)

Bagaimana dengan mengubah proposal menjadi variadik alih-alih argumen ekspresi aneh ini?

Itu akan menyelesaikan banyak masalah. Dalam kasus di mana orang hanya ingin mengembalikan kesalahan, satu-satunya hal yang akan berubah adalah variadic eksplisit ... . MISALNYA:

try(os.Open("/dev/stdout")...)

namun, orang yang menginginkan situasi yang lebih fleksibel dapat melakukan sesuatu seperti:

f, err := os.Open("/dev/stdout")
try(WrapErrorf(err, "whatever wrap does: %v"))

Satu hal yang dilakukan ide ini adalah membuat kata try kurang tepat, tetapi tetap mempertahankan kompatibilitas ke belakang.

@nathanjsweet menulis:

Maksud saya bukan dalam proposal ini, maksud saya dalam spesifikasi bahasa go.

Berikut adalah ekstrak yang Anda cari dalam spesifikasi bahasa:

Di bagian "Pernyataan ekspresi":

Fungsi bawaan berikut tidak diizinkan dalam konteks pernyataan: append cap complex imag len make new real unsafe.Alignof unsafe.Offsetof unsafe.Sizeof

Di bagian "Pernyataan Go" dan "Pernyataan Tunda":

Panggilan fungsi bawaan dibatasi seperti untuk pernyataan ekspresi.

Di bagian "Fungsi bawaan":

Fungsi bawaan tidak memiliki tipe Go standar, sehingga hanya dapat muncul dalam ekspresi panggilan; mereka tidak dapat digunakan sebagai nilai fungsi.

@nathanjsweet menulis:

Saya mendapat kesan bahwa @griesemer mengatakan bahwa spesifikasi bahasa go memanggil fungsi bawaan sebagai mekanisme yang secara khusus berguna untuk melanggar konvensi sintaksis .

Fungsi bawaan tidak melanggar konvensi sintaksis Go (kurung, koma di antara argumen, dll.). Mereka menggunakan sintaks yang sama dengan fungsi yang ditentukan pengguna, tetapi mereka mengizinkan hal-hal yang tidak dapat dilakukan dalam fungsi yang ditentukan pengguna.

@nathanjsweet Itu sudah dipertimbangkan (sebenarnya itu adalah kekeliruan) tetapi itu membuat try tidak dapat diperluas. Lihat https://go-review.googlesource.com/c/proposal/+/181878 .

Secara lebih umum, saya pikir Anda memfokuskan kritik Anda pada hal yang salah: Aturan khusus untuk argumen try sebenarnya bukan masalah - hampir setiap bawaan memiliki aturan khusus.

@griesemer terima kasih telah mengerjakan ini dan meluangkan waktu untuk menanggapi masalah komunitas. Saya yakin Anda telah menjawab banyak pertanyaan yang sama saat ini. Saya menyadari bahwa sangat sulit untuk memecahkan masalah ini dan mempertahankan kompatibilitas ke belakang pada saat yang bersamaan. Terima kasih!

@nathanjsweet Mengenai komentar Anda di sini :

Lihat bagian Kesimpulan yang secara menonjol berbicara tentang peran built-in di Go.

Mengenai komentar Anda tentang try memperluas built-in dengan cara yang berbeda: Ya, persyaratan yang diberikan unsafe.Offsetof pada argumennya berbeda dengan try . Tetapi keduanya mengharapkan ekspresi secara sintaksis. Keduanya memiliki beberapa batasan tambahan pada ekspresi itu. Persyaratan untuk try sangat mudah masuk ke dalam sintaks Go sehingga tidak ada alat pengurai front-end yang perlu disesuaikan. Saya mengerti bahwa saya merasa tidak biasa bagi Anda, tetapi itu tidak sama dengan alasan teknis yang menentangnya.

@griesemer _tryhard_ terbaru menghitung "penangan kesalahan kompleks" tetapi tidak "penangan kesalahan satu pernyataan". Bisakah itu ditambahkan?

@networkimprov Apa itu penangan kesalahan satu pernyataan? Blok if yang berisi satu pernyataan tidak kembali?

@griesemer , penangan kesalahan pernyataan tunggal adalah blok if err != nil yang berisi _any_ pernyataan tunggal, termasuk pengembalian.

@networkimprov Selesai. "penangan kompleks" sekarang dipecah menjadi "pernyataan tunggal lalu cabang" dan "kompleks lalu cabang".

Karena itu, perhatikan bahwa penghitungan ini mungkin menyesatkan: Misalnya, penghitungan ini menyertakan pernyataan if apa pun yang memeriksa variabel apa pun terhadap nol (jika -err="" yang sekarang menjadi default untuk tryhard ). Saya harus memperbaiki ini. Singkatnya, seperti tryhard melebih-lebihkan jumlah peluang handler pernyataan tunggal atau kompleks dengan banyak. Sebagai contoh, lihat archive/tar/common.go , baris 701.

@networkimprov tryhard sekarang memberikan penghitungan yang lebih akurat tentang mengapa pemeriksaan kesalahan bukan kandidat try . Jumlah keseluruhan dari try count tidak berubah, tetapi jumlah peluang untuk lebih banyak handler tunggal dan kompleks sekarang lebih akurat (dan kira-kira 50% lebih kecil daripada sebelumnya, karena sebelum kompleks then cabang dari pernyataan if dianggap selama if berisi cek <varname> != nil , apakah itu melibatkan pengecekan kesalahan atau tidak).

Jika ada yang ingin mencoba try dengan cara yang lebih praktis, saya telah membuat taman bermain WASM di sini dengan implementasi prototipe:

https://ccbrown.github.io/wasm-go-playground/experimental/try-builtin/

Dan jika ada yang benar-benar tertarik untuk mengkompilasi kode secara lokal dengan try, saya memiliki garpu Go dengan apa yang saya yakini sebagai implementasi yang berfungsi penuh/terkini di sini: https://github.com/ccbrown/go/pull/1

saya suka 'mencoba'. saya menemukan mengelola keadaan err lokal, dan menggunakan := vs = dengan err, bersama dengan impor terkait, menjadi gangguan biasa. juga, saya tidak melihat ini sebagai menciptakan dua cara untuk melakukan hal yang sama, lebih seperti dua kasus, satu di mana Anda ingin menyampaikan kesalahan tanpa bertindak di atasnya, yang lain di mana Anda secara eksplisit ingin menanganinya dalam fungsi panggilan misalnya. penebangan

Saya menjalankan tryhard terhadap proyek internal kecil yang saya kerjakan lebih dari setahun yang lalu. Direktori yang dimaksud memiliki kode untuk 3 server ("layanan mikro", saya kira), perayap yang berjalan secara berkala sebagai tugas cron, dan beberapa alat baris perintah. Ini juga memiliki unit test yang cukup komprehensif. (FWIW, berbagai bagian telah berjalan dengan lancar selama lebih dari setahun, dan terbukti mudah untuk men-debug dan menyelesaikan masalah apa pun yang muncul)

Berikut statistiknya:

--- stats ---
    370 (100.0% of     370) func declarations
    115 ( 31.1% of     370) func declarations returning an error
   1159 (100.0% of    1159) statements
    258 ( 22.3% of    1159) if statements
    123 ( 47.7% of     258) if <err> != nil statements
     64 ( 52.0% of     123) try candidates
      0 (  0.0% of     123) <err> name is different from "err"
--- non-try candidates (-l flag lists file positions) ---
     54 ( 43.9% of     123) { return ... zero values ..., expr }
      2 (  1.6% of     123) single statement then branch
      3 (  2.4% of     123) complex then branch; cannot use try
      1 (  0.8% of     123) non-empty else branch; cannot use try

Beberapa komentar:
1) 50% dari semua pernyataan if dalam basis kode ini melakukan pemeriksaan kesalahan, dan try dapat menggantikan ~setengahnya. Ini berarti seperempat dari semua if pernyataan dalam basis kode (kecil) ini adalah versi yang diketik dari try .

2) Saya harus mencatat bahwa ini sangat tinggi bagi saya, karena beberapa minggu sebelum memulai proyek ini, saya kebetulan membaca keluarga fungsi pembantu internal ( status.Annotate ) yang membubuhi keterangan pesan kesalahan tetapi mempertahankan kode status gRPC. Misalnya, jika Anda memanggil RPC dan itu mengembalikan kesalahan dengan kode status terkait PERMISSION_DENIED, kesalahan yang dikembalikan dari fungsi pembantu ini masih akan memiliki kode status terkait PERMISSION_DENIED (dan secara teoritis, jika kode status terkait itu disebarkan semua jauh ke penangan RPC, maka RPC akan gagal dengan kode status terkait itu). Saya telah memutuskan untuk menggunakan fungsi-fungsi ini untuk semua yang ada di proyek baru ini. Namun ternyata, untuk 50% dari semua kesalahan, saya hanya menyebarkan kesalahan tanpa catatan. (Sebelum menjalankan tryhard , saya telah memperkirakan 10%).

3) status.Annotate terjadi untuk mempertahankan kesalahan nil (yaitu status.Annotatef(err, "some message: %v", x) akan mengembalikan nil jika err == nil ). Saya melihat semua kandidat non-coba dari kategori pertama, dan sepertinya semua akan setuju dengan penulisan ulang berikut:

```
// Before
enc, err := keymaster.NewEncrypter(encKeyring)                                                     
if err != nil {                                                                                    
  return status.Annotate(err, "failed to create encrypter")                                        
}

// After
enc, err := keymaster.NewEncrypter(encKeyring)                                                                                                                                                                  
try(status.Annotate(err, "failed to create encrypter"))
```

To be clear, I'm not saying this transformation is always necessarily a good idea, but it seemed worth mentioning since it boosts the count significantly to a bit under half of all `if` statements.

4) defer - anotasi kesalahan berbasis tampaknya agak ortogonal dengan try , sejujurnya, karena ini akan berfungsi dengan dan tanpa try . Tetapi ketika melihat melalui kode untuk proyek ini, karena saya melihat lebih dekat pada penanganan kesalahan, saya kebetulan melihat beberapa contoh di mana kesalahan yang dihasilkan callee akan lebih masuk akal. Sebagai salah satu contoh, saya melihat beberapa contoh kode yang memanggil klien gRPC seperti ini:

```
resp, err := s.someClient.SomeMethod(ctx, req)
if err != nil {
  return ..., status.Annotate(err, "failed to call SomeMethod")
}
```

This is actually a bit redundant in retrospect, since gRPC already prefixes its errors with something like "/Service.Method to [ip]:port : ".

There was also code that called standard library functions using the same pattern:

```
hreq, err := http.NewRequest("GET", targetURL, nil)
if err != nil {
  return status.Annotate(err, "http.NewRequest failed")
}
```

In retrospect, this code demonstrates two issues: first, `http.NewRequest` isn't calling a gRPC API, so using `status.Annotate` was unnecessary, and second, assuming the standard library also return errors with callee context, this particular use of error annotation was unnecessary (although I am fairly certain the standard library does not consistently follow this pattern).

Bagaimanapun, saya pikir itu adalah latihan yang menarik untuk kembali ke proyek ini dan melihat dengan cermat bagaimana menangani kesalahan.

Satu hal, @griesemer : apakah tryhard memiliki penyebut yang tepat untuk "kandidat non-coba"?
Sunting: dijawab di bawah, saya salah membaca statistik.

EDIT: Apa yang dimaksudkan sebagai umpan balik yang dilimpahkan ke dalam proposal, yang secara eksplisit kami minta untuk tidak dilakukan di sini. Saya memindahkan komentar saya ke Gist .

@balasanjay Terima kasih atas komentar berdasarkan fakta Anda; itu sangat membantu.

Mengenai pertanyaan Anda tentang tryhard : "Kandidat non-coba" (diterima saran judul yang lebih baik) hanyalah jumlah kasus di mana pernyataan if memenuhi semua kriteria untuk "pemeriksaan kesalahan" (mis. , kami memiliki apa yang tampak seperti penugasan ke variabel kesalahan <err> , diikuti oleh if <err> != nil cek di sumbernya), tetapi di mana kami tidak dapat dengan mudah menggunakan try karena kode di blok if . Secara khusus, dalam urutan kemunculan dalam output "non-coba kandidat", ini adalah if pernyataan yang memiliki return pernyataan yang mengembalikan sesuatu selain <err> di akhir, if pernyataan dengan satu pernyataan return (atau lainnya) yang lebih kompleks, pernyataan if dengan banyak pernyataan di cabang "then", dan pernyataan if dengan cabang else tidak kosong. Beberapa dari pernyataan if ini mungkin memiliki beberapa kondisi ini yang terpenuhi secara bersamaan, jadi angka-angka ini tidak hanya bertambah. Mereka dimaksudkan untuk memberikan gambaran tentang apa yang salah agar try dapat digunakan.

Saya telah membuat penyesuaian terbaru untuk ini hari ini (Anda menjalankan versi terbaru). Sebelum perubahan terakhir, beberapa kondisi ini diperhitungkan meskipun tidak ada pemeriksaan kesalahan yang terlibat, yang tampaknya kurang masuk akal karena tampaknya try tidak dapat digunakan dalam lebih banyak kasus padahal sebenarnya try awalnya tidak masuk akal dalam kasus-kasus itu.

Namun yang terpenting, untuk basis kode tertentu, jumlah keseluruhan kandidat try tidak berubah dengan penyempurnaan ini, karena kondisi yang relevan untuk try tetap sama.

Jika Anda memiliki saran yang lebih baik tentang bagaimana dan/atau apa yang harus diukur, saya akan senang mendengarnya. Saya telah membuat beberapa penyesuaian berdasarkan umpan balik komunitas. Terima kasih.

@subfuzion Terima kasih atas komentar Anda, tetapi kami tidak mencari proposal alternatif. Silakan lihat https://github.com/golang/go/issues/32437#issuecomment -501878888 . Terima kasih.

Untuk kepentingan dihitung, terlepas dari hasilnya:

Saya berpandangan, bersama dengan tim saya, bahwa meskipun kerangka kerja try seperti yang diusulkan oleh Rob adalah ide yang masuk akal dan menarik, kerangka itu tidak mencapai tingkat yang sesuai sebagai bawaan. Paket perpustakaan standar akan menjadi pendekatan yang jauh lebih tepat sampai pola penggunaan ditetapkan dalam praktik. Jika try masuk ke dalam bahasa seperti itu, kami akan menggunakannya di sejumlah tempat berbeda.

Pada catatan yang lebih umum, kombinasi Go dari bahasa inti yang sangat stabil dan pustaka standar yang sangat kaya patut dipertahankan. Semakin lambat tim bahasa bergerak pada perubahan bahasa inti semakin baik. Pipa x -> stdlib tetap menjadi pendekatan yang kuat untuk hal semacam ini.

@griesemer Ah, maaf. Saya salah membaca statistik, ini menggunakan penghitung "if err != nil statement" (123) sebagai penyebut, bukan penghitung "calon coba" (64) sebagai penyebut. Saya akan mengajukan pertanyaan itu.

Terima kasih!

@mattpalmer Pola penggunaan telah terbentuk selama sekitar satu dekade. Pola penggunaan persis inilah yang secara langsung memengaruhi desain try . Pola penggunaan apa yang Anda maksud?

@griesemer Maaf, itu salah saya -- apa yang dimulai dalam pikiran saya sebagai menjelaskan apa yang mengganggu saya tentang try diserahkan ke proposal sendiri untuk membuat poin saya tentang tidak menambahkannya. Itu jelas bertentangan dengan aturan dasar yang dinyatakan (belum lagi bahwa tidak seperti proposal untuk fungsi bawaan baru ini, ini memperkenalkan operator baru). Apakah akan membantu untuk menghapus komentar agar percakapan tetap lancar (atau apakah itu dianggap sebagai bentuk yang buruk)?

@subfuzion Saya tidak akan khawatir tentang itu. Ini adalah saran yang kontroversial dan ada banyak proposal. Banyak yang aneh

Kami telah mengulangi desain itu beberapa kali dan meminta umpan balik dari banyak orang sebelum kami merasa cukup nyaman untuk mempostingnya dan merekomendasikan untuk memajukannya ke fase eksperimen yang sebenarnya, tetapi kami belum melakukan eksperimen tersebut. Masuk akal untuk kembali ke papan gambar jika eksperimen gagal, atau jika umpan balik memberi tahu kita sebelumnya bahwa itu jelas akan gagal.

@griesemer dapatkah Anda menguraikan metrik spesifik yang akan digunakan tim untuk menentukan keberhasilan atau kegagalan eksperimen?

@saya dan

Saya menanyakan ini kepada @rsc beberapa waktu lalu (https://github.com/golang/go/issues/32437#issuecomment-503245958):

@rsc
Tidak akan ada kekurangan lokasi di mana kenyamanan ini dapat ditempatkan. Metrik apa yang sedang dicari yang akan membuktikan substansi mekanisme selain itu? Apakah ada daftar kasus penanganan kesalahan yang diklasifikasikan? Bagaimana nilai akan diperoleh dari data ketika sebagian besar proses publik didorong oleh sentimen?

Jawabannya terarah, tetapi membosankan dan kurang substansi (https://github.com/golang/go/issues/32437#issuecomment-503295558):

Keputusan didasarkan pada seberapa baik ini bekerja dalam program nyata. Jika orang menunjukkan kepada kita bahwa try tidak efektif dalam sebagian besar kode mereka, itu data penting. Prosesnya didorong oleh data semacam itu. Itu tidak didorong oleh sentimen.

Sentimen tambahan ditawarkan (https://github.com/golang/go/issues/32437#issuecomment-503408184):

Saya terkejut menemukan kasus di mana try menghasilkan kode yang jelas lebih baik, dengan cara yang belum pernah dibahas sebelumnya.

Akhirnya, saya menjawab pertanyaan saya sendiri "Apakah ada daftar kasus penanganan kesalahan yang diklasifikasikan?". Secara efektif akan ada 6 mode penanganan kesalahan - Manual Direct, Manual Pass-through, Manual Indirect, Automatic Direct, Automatic Pass-through, Automatic Indirect. Saat ini, hanya umum untuk menggunakan 2 mode tersebut. Mode tidak langsung, yang memiliki sejumlah besar upaya yang dimasukkan ke dalam fasilitasi mereka, tampak sangat menghalangi sebagian besar Gopher veteran dan kekhawatiran itu tampaknya diabaikan. (https://github.com/golang/go/issues/32437#issuecomment-507332843).

Lebih lanjut, saya menyarankan agar transformasi otomatis diperiksa sebelum transformasi untuk mencoba memastikan nilai hasil (https://github.com/golang/go/issues/32437#issuecomment-507497656). Seiring waktu, untungnya, lebih banyak hasil yang ditawarkan tampaknya memiliki retrospektif yang lebih baik, tetapi ini masih tidak mengatasi dampak dari metode tidak langsung dengan cara yang bijaksana dan terpadu. Lagi pula (menurut saya), sama seperti pengguna harus diperlakukan sebagai musuh, pengembang harus diperlakukan sebagai pemalas.

Kegagalan pendekatan saat ini untuk melewatkan kandidat yang berharga juga ditunjukkan (https://github.com/golang/go/issues/32437#issuecomment-507505243).

Saya pikir ada baiknya bersikap ribut tentang proses ini yang umumnya kurang dan terutama tuli nada.

@iand Jawaban yang diberikan oleh @rsc masih valid. Saya tidak yakin bagian mana dari jawaban itu yang "kurang substansi" atau apa yang diperlukan untuk menjadi "menginspirasi". Tapi izinkan saya mencoba menambahkan lebih banyak "substansi":

Tujuan dari proses evaluasi proposal adalah untuk akhirnya mengidentifikasi "apakah perubahan telah memberikan manfaat yang diharapkan atau menciptakan biaya tak terduga" (langkah 5 dalam proses).

Kami telah melewati langkah 1: Tim Go telah memilih proposal tertentu yang tampaknya layak untuk diterima; usulan ini adalah salah satunya. Kami tidak akan memilihnya jika kami tidak memikirkannya cukup keras dan menganggapnya berharga. Secara khusus, kami percaya bahwa ada sejumlah besar boilerplate dalam kode Go yang hanya terkait dengan penanganan kesalahan. Proposal juga tidak keluar begitu saja - kami telah mendiskusikan ini selama lebih dari setahun dalam berbagai bentuk.

Kami saat ini berada di langkah 2, jadi masih agak jauh dari keputusan akhir. Langkah 2 adalah untuk mengumpulkan umpan balik dan kekhawatiran - yang tampaknya ada banyak. Tapi untuk memperjelas di sini: Sejauh ini hanya ada satu komentar yang menunjukkan kekurangan _teknis_ dengan desain, yang kami perbaiki . Ada juga beberapa komentar dengan data konkret berdasarkan kode nyata yang menunjukkan bahwa try memang akan mengurangi boilerplate dan menyederhanakan kode; dan ada beberapa komentar - juga berdasarkan data pada kode nyata - yang menunjukkan bahwa try tidak akan banyak membantu. Umpan balik konkret seperti itu, berdasarkan data aktual, atau menunjukkan kekurangan teknis, dapat ditindaklanjuti dan sangat membantu. Kami benar-benar akan mempertimbangkan ini.

Dan kemudian ada sejumlah besar komentar yang pada dasarnya adalah sentimen pribadi. Ini kurang bisa ditindaklanjuti. Bukan berarti kita mengabaikannya. Tetapi hanya karena kita berpegang teguh pada proses itu tidak berarti kita "tuli nada".

Mengenai komentar ini: Mungkin ada dua, mungkin tiga lusin lawan vokal dari proposal ini - Anda tahu siapa Anda. Mereka mendominasi diskusi ini dengan posting yang sering, terkadang beberapa hari. Ada sedikit informasi baru yang bisa diperoleh dari ini. Peningkatan jumlah postingan juga tidak mencerminkan sentimen yang “lebih kuat” dari masyarakat; itu hanya berarti bahwa orang-orang ini lebih vokal daripada yang lain.

@iand Jawaban yang diberikan oleh @rsc masih valid. Saya tidak yakin bagian mana dari jawaban itu yang "kurang substansi" atau apa yang diperlukan untuk menjadi "menginspirasi". Tapi izinkan saya mencoba menambahkan lebih banyak "substansi":

@griesemer Saya yakin itu tidak disengaja, tetapi saya ingin mencatat bahwa tidak ada kata-kata yang Anda kutip adalah milik saya tetapi komentar berikutnya.

Selain itu, saya berharap selain mengurangi boilerplate dan menyederhanakan keberhasilan try akan dinilai dari apakah ini memungkinkan kita untuk menulis kode yang lebih baik dan lebih jelas.

@iand Memang - itu hanya kelalaian saya. Permintaan maaf saya.

Kami percaya bahwa try memungkinkan kami untuk menulis kode yang lebih mudah dibaca - dan banyak bukti yang kami terima dari kode asli dan eksperimen kami sendiri dengan tryhard menunjukkan pembersihan yang signifikan. Tetapi keterbacaan lebih subjektif dan lebih sulit untuk diukur.

@griesemer

Pola penggunaan apa yang Anda maksud?

Saya mengacu pada pola penggunaan yang akan berkembang sekitar try dari waktu ke waktu, bukan pola nil-check yang ada untuk menangani kesalahan. Potensi penyalahgunaan dan penyalahgunaan tidak diketahui secara luas, terutama dengan masuknya pemrogram yang terus-menerus menggunakan versi try-catch yang berbeda secara semantik dalam bahasa lain.

Semua ini dan pertimbangan tentang stabilitas jangka panjang dari bahasa inti membuat saya berpikir bahwa memperkenalkan fitur ini pada tingkat paket x atau pustaka standar (baik sebagai paket errors/try atau sebagai errors.Try() ) akan lebih disukai daripada memperkenalkannya sebagai bawaan.

@mattparlmer Perbaiki saya jika saya salah, tetapi saya yakin proposal ini harus berada di runtime Go untuk menggunakan g's, m's (diperlukan untuk menimpa alur eksekusi).

@fabian-f

@mattparlmer Perbaiki saya jika saya salah, tetapi saya yakin proposal ini harus berada di runtime Go untuk menggunakan g's, m's (diperlukan untuk menimpa alur eksekusi).

Bukan itu masalahnya; sebagai catatan dokumen desain , ini dapat diimplementasikan sebagai transformasi pohon sintaksis waktu kompilasi.

Itu dimungkinkan karena semantik try dapat diekspresikan sepenuhnya dalam if dan return ; itu tidak benar-benar "mengganti aliran eksekusi" seperti yang dilakukan if dan return .

Berikut adalah laporan tryhard dari basis kode Go 300k-line perusahaan saya:

Jalankan awal:

--- stats ---
  13879 (100.0% of   13879) func declarations
   4381 ( 31.6% of   13879) func declarations returning an error
  38435 (100.0% of   38435) statements
   8028 ( 20.9% of   38435) if statements
   4496 ( 56.0% of    8028) if <err> != nil statements
    453 ( 10.1% of    4496) try candidates
      4 (  0.1% of    4496) <err> name is different from "err"
--- non-try candidates (-l flag lists file positions) ---
   3066 ( 68.2% of    4496) { return ... zero values ..., expr }
    356 (  7.9% of    4496) single statement then branch
    345 (  7.7% of    4496) complex then branch; cannot use try
     63 (  1.4% of    4496) non-empty else branch; cannot use try

Kami memiliki konvensi menggunakan paket errgo juju (https://godoc.org/github.com/juju/errgo) untuk menutupi kesalahan dan menambahkan informasi pelacakan tumpukan ke dalamnya, yang akan mencegah sebagian besar penulisan ulang terjadi. Itu berarti bahwa kami tidak mungkin untuk mengadopsi try , untuk alasan yang sama bahwa kami biasanya menghindari pengembalian kesalahan telanjang.

Karena sepertinya ini metrik yang membantu, saya menghapus panggilan errgo.Mask() (yang mengembalikan kesalahan tanpa anotasi) dan menjalankan kembali tryhard . Ini adalah perkiraan berapa banyak pemeriksaan kesalahan yang dapat ditulis ulang jika kami tidak menggunakan errgo:

--- stats ---
  13879 (100.0% of   13879) func declarations
   4381 ( 31.6% of   13879) func declarations returning an error
  38435 (100.0% of   38435) statements
   8028 ( 20.9% of   38435) if statements
   4496 ( 56.0% of    8028) if <err> != nil statements
   3114 ( 69.3% of    4496) try candidates
      7 (  0.2% of    4496) <err> name is different from "err"
--- non-try candidates (-l flag lists file positions) ---
    381 (  8.5% of    4496) { return ... zero values ..., expr }
    358 (  8.0% of    4496) single statement then branch
    345 (  7.7% of    4496) complex then branch; cannot use try
     63 (  1.4% of    4496) non-empty else branch; cannot use try

Jadi, saya kira ~70% pengembalian kesalahan akan kompatibel dengan try .

Terakhir, perhatian utama saya dengan proposal tersebut tampaknya tidak tercakup dalam komentar mana pun yang saya baca atau ringkasan diskusi:

Proposal ini secara signifikan meningkatkan biaya relatif kesalahan anotasi.

Saat ini, biaya marjinal untuk menambahkan beberapa konteks ke kesalahan sangat rendah; itu hampir tidak lebih dari mengetik string format. Jika proposal ini diadopsi, saya khawatir para insinyur akan semakin menyukai estetika yang ditawarkan oleh try , keduanya karena membuat kode mereka "terlihat lebih ramping" (yang sayangnya saya katakan merupakan pertimbangan bagi beberapa orang, dalam pengalaman saya), dan sekarang membutuhkan blok tambahan untuk menambahkan konteks. Mereka dapat membenarkannya berdasarkan argumen "keterbacaan", bagaimana menambahkan konteks memperluas metode dengan 3 baris lain dan mengalihkan pembaca dari poin utama. Saya pikir basis kode perusahaan tidak seperti perpustakaan standar Go dalam arti bahwa memudahkan untuk melakukan hal yang benar kemungkinan memiliki dampak terukur pada kualitas kode yang dihasilkan, ulasan kode memiliki kualitas yang bervariasi, dan praktik tim bervariasi secara independen satu sama lain . Bagaimanapun, seperti yang Anda katakan sebelumnya, kami selalu tidak dapat mengadopsi try untuk basis kode kami.

Terima kasih atas pertimbangannya

@mattparlmer

Semua ini dan pertimbangan tentang stabilitas jangka panjang dari bahasa inti membuat saya berpikir bahwa memperkenalkan fitur ini pada tingkat paket x atau pustaka standar (baik sebagai paket errors/try atau sebagai errors.Try() ) akan lebih disukai daripada memperkenalkannya sebagai bawaan.

try tidak dapat diimplementasikan sebagai fungsi perpustakaan; tidak ada cara bagi suatu fungsi untuk kembali dari pemanggilnya (mengaktifkan yang telah diusulkan sebagai #32473) dan, seperti kebanyakan bawaan lainnya, juga tidak ada cara untuk mengekspresikan tanda tangan try di Go. Bahkan dengan obat generik, itu tidak mungkin terjadi; lihat FAQ doc desain , menjelang akhir.

Juga, mengimplementasikan try sebagai fungsi perpustakaan akan membutuhkannya untuk memiliki nama yang lebih verbose, yang sebagian mengalahkan gunanya menggunakannya.

Namun, ini dapat - dan telah dua kali - diimplementasikan sebagai praprosesor kode sumber: lihat https://github.com/rhysd/trygo dan https://github.com/lunixbochs/og.

Sepertinya ~60% basis kode dari tegola akan dapat menggunakan fitur ini.

Ini adalah keluaran tryhard untuk proyek tegola: (http://github.com/go-spatial/tegola)

--- try candidates ---
      1  tegola/atlas/atlas.go:84
      2  tegola/atlas/map.go:232
      3  tegola/atlas/map.go:238
      4  tegola/atlas/map.go:248
      5  tegola/atlas/map.go:253
      6  tegola/basic/geometry_math.go:248
      7  tegola/basic/geometry_math.go:251
      8  tegola/basic/geometry_math.go:268
      9  tegola/basic/geometry_math.go:276
     10  tegola/basic/json_marshal.go:33
     11  tegola/basic/json_marshal.go:153
     12  tegola/basic/json_marshal.go:276
     13  tegola/cache/azblob/azblob.go:54
     14  tegola/cache/azblob/azblob.go:61
     15  tegola/cache/azblob/azblob.go:67
     16  tegola/cache/azblob/azblob.go:74
     17  tegola/cache/azblob/azblob.go:80
     18  tegola/cache/azblob/azblob.go:105
     19  tegola/cache/azblob/azblob.go:109
     20  tegola/cache/azblob/azblob.go:204
     21  tegola/cache/azblob/azblob.go:259
     22  tegola/cache/file/file.go:42
     23  tegola/cache/file/file.go:56
     24  tegola/cache/file/file.go:110
     25  tegola/cache/file/file.go:116
     26  tegola/cache/file/file.go:129
     27  tegola/cache/redis/redis.go:41
     28  tegola/cache/redis/redis.go:46
     29  tegola/cache/redis/redis.go:51
     30  tegola/cache/redis/redis.go:56
     31  tegola/cache/redis/redis.go:70
     32  tegola/cache/redis/redis.go:79
     33  tegola/cache/redis/redis.go:84
     34  tegola/cache/s3/s3.go:85
     35  tegola/cache/s3/s3.go:102
     36  tegola/cache/s3/s3.go:112
     37  tegola/cache/s3/s3.go:118
     38  tegola/cache/s3/s3.go:123
     39  tegola/cache/s3/s3.go:138
     40  tegola/cache/s3/s3.go:164
     41  tegola/cache/s3/s3.go:172
     42  tegola/cache/s3/s3.go:179
     43  tegola/cache/s3/s3.go:284
     44  tegola/cache/s3/s3.go:340
     45  tegola/cmd/tegola/cmd/cache/format.go:97
     46  tegola/cmd/tegola/cmd/cache/seed_purge.go:94
     47  tegola/cmd/tegola/cmd/cache/seed_purge.go:103
     48  tegola/cmd/tegola/cmd/cache/seed_purge.go:170
     49  tegola/cmd/tegola/cmd/cache/tile_list.go:51
     50  tegola/cmd/tegola/cmd/cache/tile_list.go:64
     51  tegola/cmd/tegola/cmd/cache/tile_name.go:35
     52  tegola/cmd/tegola/cmd/cache/tile_name.go:43
     53  tegola/cmd/tegola/cmd/root.go:58
     54  tegola/cmd/tegola/cmd/root.go:61
     55  tegola/cmd/xyz2svg/cmd/draw.go:62
     56  tegola/cmd/xyz2svg/cmd/draw.go:70
     57  tegola/cmd/xyz2svg/cmd/draw.go:214
     58  tegola/config/config.go:96
     59  tegola/internal/env/parse.go:30
     60  tegola/internal/env/parse.go:69
     61  tegola/internal/env/parse.go:116
     62  tegola/internal/env/parse.go:174
     63  tegola/internal/env/parse.go:221
     64  tegola/internal/env/types.go:67
     65  tegola/internal/env/types.go:86
     66  tegola/internal/env/types.go:105
     67  tegola/internal/env/types.go:124
     68  tegola/internal/env/types.go:143
     69  tegola/maths/makevalid/main.go:189
     70  tegola/maths/makevalid/main.go:207
     71  tegola/maths/makevalid/main.go:221
     72  tegola/maths/makevalid/main.go:295
     73  tegola/maths/makevalid/main.go:504
     74  tegola/maths/makevalid/makevalid.go:77
     75  tegola/maths/makevalid/makevalid.go:89
     76  tegola/maths/makevalid/makevalid.go:118
     77  tegola/maths/makevalid/makevalid_test.go:93
     78  tegola/maths/makevalid/makevalid_test.go:163
     79  tegola/maths/makevalid/plyg/ring.go:518
     80  tegola/maths/triangle.go:1023
     81  tegola/mvt/layer.go:73
     82  tegola/mvt/layer.go:79
     83  tegola/mvt/vector_tile/vector_tile.pb.go:64
     84  tegola/provider/gpkg/gpkg.go:138
     85  tegola/provider/gpkg/gpkg.go:223
     86  tegola/provider/gpkg/gpkg_register.go:46
     87  tegola/provider/gpkg/gpkg_register.go:51
     88  tegola/provider/gpkg/gpkg_register.go:186
     89  tegola/provider/gpkg/gpkg_register.go:227
     90  tegola/provider/gpkg/gpkg_register.go:240
     91  tegola/provider/gpkg/gpkg_register.go:245
     92  tegola/provider/gpkg/gpkg_register.go:256
     93  tegola/provider/gpkg/gpkg_register.go:377
     94  tegola/provider/postgis/postgis.go:112
     95  tegola/provider/postgis/postgis.go:117
     96  tegola/provider/postgis/postgis.go:122
     97  tegola/provider/postgis/postgis.go:127
     98  tegola/provider/postgis/postgis.go:136
     99  tegola/provider/postgis/postgis.go:142
    100  tegola/provider/postgis/postgis.go:148
    101  tegola/provider/postgis/postgis.go:153
    102  tegola/provider/postgis/postgis.go:158
    103  tegola/provider/postgis/postgis.go:163
    104  tegola/provider/postgis/postgis.go:181
    105  tegola/provider/postgis/postgis.go:198
    106  tegola/provider/postgis/postgis.go:264
    107  tegola/provider/postgis/postgis.go:441
    108  tegola/provider/postgis/postgis.go:446
    109  tegola/provider/postgis/postgis.go:529
    110  tegola/provider/postgis/postgis.go:559
    111  tegola/provider/postgis/postgis.go:603
    112  tegola/provider/postgis/util.go:31
    113  tegola/provider/postgis/util.go:36
    114  tegola/provider/postgis/util.go:200
    115  tegola/server/bindata/bindata.go:89
    116  tegola/server/bindata/bindata.go:109
    117  tegola/server/bindata/bindata.go:129
    118  tegola/server/bindata/bindata.go:149
    119  tegola/server/bindata/bindata.go:169
    120  tegola/server/bindata/bindata.go:189
    121  tegola/server/bindata/bindata.go:209
    122  tegola/server/bindata/bindata.go:229
    123  tegola/server/bindata/bindata.go:370
    124  tegola/server/bindata/bindata.go:374
    125  tegola/server/bindata/bindata.go:378
    126  tegola/server/bindata/bindata.go:382
    127  tegola/server/bindata/bindata.go:386
    128  tegola/server/bindata/bindata.go:402
    129  tegola/server/middleware_gzip.go:71
    130  tegola/server/middleware_gzip.go:78
    131  tegola/server/server_test.go:85

--- <err> name is different from "err" ---
      1  tegola/basic/json_marshal.go:276

--- { return ... zero values ..., expr } ---
      1  tegola/basic/geometry_math.go:214
      2  tegola/basic/geometry_math.go:222
      3  tegola/basic/geometry_math.go:230
      4  tegola/cache/azblob/azblob.go:131
      5  tegola/cache/azblob/azblob.go:140
      6  tegola/cache/azblob/azblob.go:149
      7  tegola/cache/azblob/azblob.go:171
      8  tegola/cache/file/file.go:47
      9  tegola/cache/s3/s3.go:92
     10  tegola/cmd/internal/register/maps.go:108
     11  tegola/cmd/tegola/cmd/cache/flags.go:20
     12  tegola/cmd/tegola/cmd/cache/tile_name.go:51
     13  tegola/cmd/tegola/cmd/cache/worker.go:112
     14  tegola/cmd/tegola/cmd/cache/worker.go:123
     15  tegola/cmd/tegola/cmd/root.go:73
     16  tegola/cmd/tegola/cmd/root.go:78
     17  tegola/cmd/xyz2svg/cmd/root.go:60
     18  tegola/provider/gpkg/gpkg.go:90
     19  tegola/provider/gpkg/gpkg.go:95
     20  tegola/provider/gpkg/gpkg_register.go:264
     21  tegola/provider/gpkg/gpkg_register.go:297
     22  tegola/provider/gpkg/gpkg_register.go:302
     23  tegola/provider/gpkg/gpkg_register.go:313
     24  tegola/provider/gpkg/gpkg_register.go:328
     25  tegola/provider/postgis/postgis.go:193
     26  tegola/provider/postgis/postgis.go:208
     27  tegola/provider/postgis/postgis.go:222
     28  tegola/provider/postgis/postgis.go:228
     29  tegola/provider/postgis/postgis.go:234
     30  tegola/provider/postgis/postgis.go:243
     31  tegola/provider/postgis/postgis.go:249
     32  tegola/provider/postgis/postgis.go:255
     33  tegola/provider/postgis/postgis.go:304
     34  tegola/provider/postgis/postgis.go:315
     35  tegola/provider/postgis/postgis.go:319
     36  tegola/provider/postgis/postgis.go:364
     37  tegola/provider/postgis/postgis.go:456
     38  tegola/provider/postgis/postgis.go:520
     39  tegola/provider/postgis/postgis.go:534
     40  tegola/provider/postgis/postgis.go:565
     41  tegola/provider/postgis/util.go:108
     42  tegola/provider/postgis/util.go:113
     43  tegola/server/bindata/bindata.go:29
     44  tegola/server/bindata/bindata.go:245
     45  tegola/server/bindata/bindata.go:271
     46  tegola/server/bindata/bindata.go:396

--- single statement then branch ---
      1  tegola/cache/azblob/azblob.go:241
      2  tegola/cache/file/file.go:87
      3  tegola/cache/s3/s3.go:321
      4  tegola/cmd/internal/register/caches.go:18
      5  tegola/cmd/internal/register/providers.go:43
      6  tegola/cmd/internal/register/providers.go:62
      7  tegola/cmd/internal/register/providers.go:75
      8  tegola/config/config.go:192
      9  tegola/config/config.go:207
     10  tegola/config/config.go:217
     11  tegola/internal/env/dict.go:43
     12  tegola/internal/env/dict.go:121
     13  tegola/internal/env/dict.go:197
     14  tegola/internal/env/dict.go:273
     15  tegola/internal/env/dict.go:348
     16  tegola/internal/env/parse.go:79
     17  tegola/internal/env/parse.go:126
     18  tegola/internal/env/parse.go:184
     19  tegola/internal/env/parse.go:231
     20  tegola/maths/makevalid/plyg/ring.go:541
     21  tegola/maths/maths.go:239
     22  tegola/maths/validate/validate.go:49
     23  tegola/maths/validate/validate.go:53
     24  tegola/maths/validate/validate.go:59
     25  tegola/maths/validate/validate.go:69
     26  tegola/mvt/feature.go:94
     27  tegola/mvt/feature.go:99
     28  tegola/mvt/feature.go:592
     29  tegola/mvt/feature.go:603
     30  tegola/mvt/layer.go:90
     31  tegola/mvt/tile.go:48
     32  tegola/provider/postgis/postgis.go:570
     33  tegola/provider/postgis/postgis.go:586
     34  tegola/tile.go:172

--- complex then branch; cannot use try ---
      1  tegola/cache/azblob/azblob.go:226
      2  tegola/cache/file/file.go:78
      3  tegola/cache/file/file.go:122
      4  tegola/cache/s3/s3.go:195
      5  tegola/cache/s3/s3.go:206
      6  tegola/cache/s3/s3.go:219
      7  tegola/cache/s3/s3.go:307
      8  tegola/provider/gpkg/gpkg.go:39
      9  tegola/provider/gpkg/gpkg.go:45
     10  tegola/provider/gpkg/gpkg.go:131
     11  tegola/provider/gpkg/gpkg.go:154
     12  tegola/provider/gpkg/gpkg_register.go:171
     13  tegola/provider/gpkg/gpkg_register.go:195

--- stats ---
   1294 (100.0% of    1294) func declarations
    246 ( 19.0% of    1294) func declarations returning an error
   2693 (100.0% of    2693) statements
    551 ( 20.5% of    2693) if statements
    238 ( 43.2% of     551) if <err> != nil statements
    131 ( 55.0% of     238) try candidates
      1 (  0.4% of     238) <err> name is different from "err"
--- non-try candidates ---
     46 ( 19.3% of     238) { return ... zero values ..., expr }
     34 ( 14.3% of     238) single statement then branch
     13 (  5.5% of     238) complex then branch; cannot use try
      0 (  0.0% of     238) non-empty else branch; cannot use try

Dan proyek pendamping: (http://github.com/go-spatial/geom)

--- try candidates ---
      1  geom/bbox.go:202
      2  geom/encoding/geojson/geojson.go:152
      3  geom/encoding/geojson/geojson.go:157
      4  geom/encoding/wkb/internal/tcase/symbol/symbol.go:73
      5  geom/encoding/wkb/internal/tcase/tcase.go:161
      6  geom/encoding/wkb/internal/tcase/tcase.go:172
      7  geom/encoding/wkb/wkb.go:50
      8  geom/encoding/wkb/wkb.go:110
      9  geom/encoding/wkt/internal/token/token.go:176
     10  geom/encoding/wkt/internal/token/token.go:252
     11  geom/internal/parsing/parsing.go:44
     12  geom/internal/parsing/parsing.go:85
     13  geom/internal/rtreego/rtree_test.go:110
     14  geom/multi_line_string.go:34
     15  geom/multi_polygon.go:35
     16  geom/planar/clip/linestring.go:82
     17  geom/planar/clip/linestring.go:181
     18  geom/planar/clip/point.go:23
     19  geom/planar/intersect/xsweep.go:106
     20  geom/planar/makevalid/makevalid.go:92
     21  geom/planar/makevalid/makevalid.go:191
     22  geom/planar/makevalid/setdiff/polygoncleaner.go:283
     23  geom/planar/makevalid/setdiff/polygoncleaner.go:345
     24  geom/planar/makevalid/setdiff/polygoncleaner.go:543
     25  geom/planar/makevalid/setdiff/polygoncleaner.go:554
     26  geom/planar/makevalid/setdiff/polygoncleaner.go:572
     27  geom/planar/makevalid/setdiff/polygoncleaner.go:578
     28  geom/planar/simplify/douglaspeucker.go:84
     29  geom/planar/simplify/douglaspeucker.go:88
     30  geom/planar/simplify.go:13
     31  geom/planar/triangulate/constraineddelaunay/triangle.go:186
     32  geom/planar/triangulate/constraineddelaunay/triangulator.go:134
     33  geom/planar/triangulate/constraineddelaunay/triangulator.go:138
     34  geom/planar/triangulate/constraineddelaunay/triangulator.go:142
     35  geom/planar/triangulate/constraineddelaunay/triangulator.go:173
     36  geom/planar/triangulate/constraineddelaunay/triangulator.go:176
     37  geom/planar/triangulate/constraineddelaunay/triangulator.go:203
     38  geom/planar/triangulate/constraineddelaunay/triangulator.go:248
     39  geom/planar/triangulate/constraineddelaunay/triangulator.go:396
     40  geom/planar/triangulate/constraineddelaunay/triangulator.go:466
     41  geom/planar/triangulate/constraineddelaunay/triangulator.go:553
     42  geom/planar/triangulate/constraineddelaunay/triangulator.go:583
     43  geom/planar/triangulate/constraineddelaunay/triangulator.go:667
     44  geom/planar/triangulate/constraineddelaunay/triangulator.go:672
     45  geom/planar/triangulate/constraineddelaunay/triangulator.go:677
     46  geom/planar/triangulate/constraineddelaunay/triangulator.go:814
     47  geom/planar/triangulate/constraineddelaunay/triangulator.go:818
     48  geom/planar/triangulate/constraineddelaunay/triangulator.go:823
     49  geom/planar/triangulate/constraineddelaunay/triangulator.go:865
     50  geom/planar/triangulate/constraineddelaunay/triangulator.go:870
     51  geom/planar/triangulate/constraineddelaunay/triangulator.go:875
     52  geom/planar/triangulate/constraineddelaunay/triangulator.go:897
     53  geom/planar/triangulate/constraineddelaunay/triangulator.go:901
     54  geom/planar/triangulate/constraineddelaunay/triangulator.go:907
     55  geom/planar/triangulate/constraineddelaunay/triangulator.go:1107
     56  geom/planar/triangulate/constraineddelaunay/triangulator.go:1146
     57  geom/planar/triangulate/constraineddelaunay/triangulator.go:1157
     58  geom/planar/triangulate/constraineddelaunay/triangulator.go:1202
     59  geom/planar/triangulate/constraineddelaunay/triangulator.go:1206
     60  geom/planar/triangulate/constraineddelaunay/triangulator.go:1216
     61  geom/planar/triangulate/delaunaytriangulationbuilder.go:66
     62  geom/planar/triangulate/incrementaldelaunaytriangulator.go:46
     63  geom/planar/triangulate/incrementaldelaunaytriangulator.go:78
     64  geom/planar/triangulate/quadedge/lastfoundquadedgelocator.go:65
     65  geom/planar/triangulate/quadedge/quadedgesubdivision.go:976
     66  geom/slippy/tile.go:133

--- { return ... zero values ..., expr } ---
      1  geom/internal/parsing/parsing.go:125
      2  geom/planar/triangulate/constraineddelaunay/triangulator.go:428
      3  geom/planar/triangulate/constraineddelaunay/triangulator.go:447
      4  geom/planar/triangulate/constraineddelaunay/triangulator.go:460

--- single statement then branch ---
      1  geom/bbox.go:259
      2  geom/encoding/wkb/internal/decode/decode.go:29
      3  geom/encoding/wkb/internal/decode/decode.go:55
      4  geom/encoding/wkb/internal/decode/decode.go:63
      5  geom/encoding/wkb/internal/decode/decode.go:70
      6  geom/encoding/wkb/internal/decode/decode.go:79
      7  geom/encoding/wkb/internal/decode/decode.go:84
      8  geom/encoding/wkb/internal/decode/decode.go:93
      9  geom/encoding/wkb/internal/decode/decode.go:99
     10  geom/encoding/wkb/internal/decode/decode.go:105
     11  geom/encoding/wkb/internal/decode/decode.go:114
     12  geom/encoding/wkb/internal/decode/decode.go:119
     13  geom/encoding/wkb/internal/decode/decode.go:135
     14  geom/encoding/wkb/internal/decode/decode.go:140
     15  geom/encoding/wkb/internal/decode/decode.go:149
     16  geom/encoding/wkb/internal/decode/decode.go:155
     17  geom/encoding/wkb/internal/decode/decode.go:161
     18  geom/encoding/wkb/internal/decode/decode.go:170
     19  geom/encoding/wkb/internal/decode/decode.go:176
     20  geom/encoding/wkb/internal/tcase/token/token.go:162
     21  geom/encoding/wkt/internal/token/token.go:136

--- complex then branch; cannot use try ---
      1  geom/encoding/wkb/internal/tcase/tcase.go:74
      2  geom/encoding/wkt/internal/symbol/symbol.go:125
      3  geom/planar/intersect/xsweep.go:165
      4  geom/planar/makevalid/makevalid.go:85
      5  geom/planar/makevalid/makevalid.go:172
      6  geom/planar/makevalid/triangulate.go:19
      7  geom/planar/makevalid/triangulate.go:28
      8  geom/planar/makevalid/triangulate.go:36
      9  geom/planar/makevalid/triangulate.go:58
     10  geom/planar/triangulate/constraineddelaunay/triangulator.go:358
     11  geom/planar/triangulate/constraineddelaunay/triangulator.go:373
     12  geom/planar/triangulate/constraineddelaunay/triangulator.go:453
     13  geom/planar/triangulate/constraineddelaunay/triangulator.go:1237
     14  geom/planar/triangulate/constraineddelaunay/triangulator.go:1243
     15  geom/planar/triangulate/constraineddelaunay/triangulator.go:1249

--- stats ---
    820 (100.0% of     820) func declarations
    146 ( 17.8% of     820) func declarations returning an error
   1715 (100.0% of    1715) statements
    391 ( 22.8% of    1715) if statements
    111 ( 28.4% of     391) if <err> != nil statements
     66 ( 59.5% of     111) try candidates
      0 (  0.0% of     111) <err> name is different from "err"
--- non-try candidates ---
      4 (  3.6% of     111) { return ... zero values ..., expr }
     21 ( 18.9% of     111) single statement then branch
     15 ( 13.5% of     111) complex then branch; cannot use try
      0 (  0.0% of     111) non-empty else branch; cannot use try

Mengenai biaya tak terduga, saya memposting ulang ini dari #32611...

Saya melihat tiga kelas biaya:

  1. biaya untuk spesifikasi, yang diuraikan dalam dokumen desain.
  2. biaya untuk perkakas (yaitu revisi perangkat lunak), juga dieksplorasi dalam dokumen desain.
  3. biaya ekosistem, yang telah dirinci secara panjang lebar oleh komunitas di atas dan di #32825.

ulang no. 1 & 2, biaya try() sederhana.

Untuk menyederhanakan no. 3, sebagian besar komentator percaya try() akan merusak kode kita dan/atau ekosistem kode tempat kita bergantung, dan dengan demikian mengurangi produktivitas dan kualitas produk kita. Persepsi yang tersebar luas dan beralasan ini tidak boleh diremehkan sebagai "non-faktual" atau "estetis".

Biaya ekosistem jauh lebih penting daripada biaya spesifikasi atau peralatan.

@griesemer jelas tidak adil untuk mengklaim bahwa "tiga lusin lawan vokal" adalah sebagian besar oposisi. Ratusan orang telah berkomentar di sini dan di #32825. Anda memberi tahu saya pada tanggal 12 Juni, "Saya menyadari bahwa sekitar 2/3 responden tidak senang dengan proposal tersebut." Sejak itu lebih dari 2.000 orang telah memilih "tinggalkan err != nil saja" dengan 90% jempol.

@gdey bisakah Anda mengubah posting Anda untuk hanya menyertakan _stats & kandidat yang tidak mencoba_ ?

@robfig , @gdey Terima kasih telah menyediakan data ini, terutama sebelum/sesudah perbandingan.

@griesemer
Anda pasti telah menambahkan beberapa substansi yang mengklarifikasi bahwa kekhawatiran saya (dan orang lain) dapat ditangani secara langsung. Pertanyaan saya, kemudian, adalah apakah tim Go melihat kemungkinan penyalahgunaan mode tidak langsung (yaitu pengembalian telanjang dan/atau mutasi kesalahan ruang lingkup pasca-fungsi melalui penangguhan) sebagai biaya yang layak didiskusikan selama langkah 5, dan bahwa itu layak secara potensial mengambil tindakan terhadap mitigasinya. Suasana saat ini adalah bahwa aspek proposal yang paling membingungkan ini dilihat sebagai fitur pintar/baru oleh tim Go (Kekhawatiran ini tidak ditangani oleh penilaian transformasi otomatis dan tampaknya didorong/didukung secara aktif. - errd , dalam percakapan, dll.).

edit untuk menambahkan... Kekhawatiran dengan tim Go mendorong apa yang dilihat oleh para veteran Gophers sebagai penghalang adalah apa yang saya maksud tentang tuli nada.
... Pengabaian adalah biaya yang banyak dari kita sangat prihatin sebagai masalah rasa sakit pengalaman. Ini mungkin bukan sesuatu yang dapat dijadikan tolok ukur dengan mudah (jika memang masuk akal), tetapi tidak jujur ​​untuk menganggap kekhawatiran ini sebagai sentimen itu sendiri. Sebaliknya, mengabaikan kebijaksanaan pengalaman bersama yang mendukung angka-angka sederhana tanpa penilaian kontekstual yang solid adalah jenis sentimen yang saya/kami coba lawan.

@networkimprov Mohon maaf karena kurang jelas. Apa yang saya katakan adalah:

Mungkin ada dua, mungkin tiga lusin lawan vokal dari proposal ini - Anda tahu siapa Anda. Mereka mendominasi diskusi ini dengan posting yang sering, terkadang beberapa hari.

Saya berbicara tentang komentar yang sebenarnya (seperti dalam "posting yang sering"), bukan emoji. Hanya ada sedikit orang yang memposting di sini _berulang kali_, yang menurut saya masih benar. Saya juga tidak berbicara tentang #32825; Saya berbicara tentang proposal ini.

Melihat emoji, situasinya hampir tidak berubah dari sebulan yang lalu: 1/3 emoji menunjukkan pendapat yang menguntungkan, dan 2/3 menunjukkan pendapat negatif.

@griesemer

Saya ingat sesuatu saat menulis komentar saya di atas: sementara dokumen desain mengatakan bahwa try dapat diimplementasikan sebagai transformasi pohon sintaksis langsung, dan dalam banyak kasus memang demikian, ada beberapa contoh di mana saya tidak melakukannya. melihat cara langsung untuk melakukannya. Sebagai contoh, misalkan kita memiliki yang berikut ini:

switch x {
case rand.Int():
  a()
case 5, try(strconv.Atoi(y)):
  b()
}

Mengingat urutan evaluasi switch , saya tidak melihat cara mengangkat strconv.Atoi(y) secara sepele dari klausa case sambil mempertahankan semantik yang dimaksud; yang terbaik yang bisa saya lakukan adalah menulis ulang switch sebagai rantai yang setara dengan pernyataan if / else , seperti ini:

if x == rand.Int() {
  a()
} else if x == 5 {
  b()
} else if _v, _err := strconv.Atoi(y); _err != nil {
  return _err
} else if x == _v {
  b()
}

(Ada situasi lain di mana ini bisa muncul, tetapi ini adalah salah satu contoh paling sederhana dan yang pertama muncul di pikiran.)

Sebenarnya, sebelum Anda menerbitkan proposal ini, saya telah mengerjakan penerjemah AST untuk mengimplementasikan operator check dari draf desain dan mengalami masalah ini. Namun, saya menggunakan versi yang diretas dari paket stdlib go/* ; mungkin front-end kompiler disusun sedemikian rupa sehingga membuatnya lebih mudah? Atau apakah saya melewatkan sesuatu dan benar-benar ada cara langsung untuk melakukan ini?

Lihat juga https://github.com/rhysd/trygo; menurut README itu tidak mengimplementasikan ekspresi try , dan pada dasarnya mencatat kekhawatiran yang sama yang saya kemukakan di sini; Saya menduga mungkin itu sebabnya penulis tidak mengimplementasikan fitur itu.

@daved Kode profesional tidak dikembangkan dalam ruang hampa - ada konvensi lokal, rekomendasi gaya, ulasan kode, dll. (Saya sudah mengatakan ini sebelumnya). Jadi, saya tidak melihat mengapa penyalahgunaan akan "mungkin" (itu mungkin, tapi itu berlaku untuk konstruksi bahasa apa pun).

Perhatikan bahwa menggunakan defer untuk menghias kesalahan dimungkinkan dengan atau tanpa try . Pasti ada alasan bagus untuk fungsi yang berisi banyak pemeriksaan kesalahan, yang semuanya menghiasi kesalahan dengan cara yang sama, untuk melakukan dekorasi itu sekali, misalnya menggunakan defer . Atau mungkin menggunakan fungsi pembungkus yang melakukan dekorasi. Atau mekanisme lain yang sesuai dengan tagihan dan rekomendasi pengkodean lokal. Lagi pula, "kesalahan hanyalah nilai" dan benar-benar masuk akal untuk menulis dan memfaktorkan kode yang menangani kesalahan.

Pengembalian telanjang bisa menjadi masalah bila digunakan dengan cara yang tidak disiplin. Itu tidak berarti mereka umumnya buruk. Misalnya, jika hasil fungsi hanya valid jika tidak ada kesalahan, tampaknya baik-baik saja untuk menggunakan pengembalian telanjang jika terjadi kesalahan - selama kita disiplin dalam mengatur kesalahan (karena nilai pengembalian lainnya tidak' tidak penting dalam hal ini). try memastikan hal itu. Saya tidak melihat ada "penyalahgunaan" di sini.

@dpinela Kompiler sudah menerjemahkan pernyataan switch seperti milik Anda sebagai urutan if-else-if , jadi saya tidak melihat masalah di sini. Juga, "pohon sintaksis" yang digunakan kompiler bukanlah pohon sintaksis "go/ast". Representasi internal kompiler memungkinkan kode yang jauh lebih fleksibel yang tidak perlu diterjemahkan kembali ke Go.

@griesemer
Ya, tentu saja, semua yang Anda katakan memiliki dasar. Namun, area abu-abu tidak sesederhana yang Anda bayangkan. Pengembalian telanjang biasanya diperlakukan dengan sangat hati-hati oleh kita yang mengajar orang lain (kita, yang berusaha untuk menumbuhkan/mempromosikan komunitas). Saya menghargai bahwa stdlib telah mengotorinya. Tapi, ketika mengajar orang lain, pengembalian eksplisit selalu ditekankan. Biarkan individu mencapai kedewasaan mereka sendiri untuk beralih ke pendekatan yang lebih "fantasi", tetapi mendorongnya dari awal pasti akan memupuk kode yang sulit dibaca (yaitu kebiasaan buruk). Ini, sekali lagi, adalah tuli nada yang saya coba ungkapkan.

Secara pribadi, saya tidak ingin melarang pengembalian atau manipulasi nilai yang ditangguhkan. Ketika mereka benar-benar cocok, saya senang kemampuan ini tersedia (meskipun, pengguna berpengalaman lainnya mungkin mengambil sikap yang lebih kaku). Meskipun demikian, mendorong penerapan fitur-fitur yang kurang umum dan umumnya rapuh ini dengan cara yang begitu meresap benar-benar berlawanan dengan arah yang pernah saya bayangkan akan diambil oleh Go. Apakah perubahan nyata dalam karakter menghindari sihir dan bentuk-bentuk tipuan yang berbahaya merupakan perubahan yang disengaja? Haruskah kita juga mulai menekankan penggunaan DIC dan mekanisme lain yang sulit di-debug?

ps Waktu Anda sangat dihargai. Tim Anda dan bahasanya memiliki rasa hormat dan perhatian saya. Saya tidak ingin ada kesedihan bagi siapa pun dalam berbicara; Saya harap Anda akan mendengar sifat keprihatinan saya/kami dan mencoba melihat segala sesuatunya dari perspektif "garis depan" kami.

Menambahkan beberapa komentar ke downvote saya.

Untuk proposal khusus yang ada:

1) Saya lebih suka ini menjadi kata kunci vs. fungsi bawaan untuk alasan aliran kontrol dan keterbacaan kode yang diartikulasikan sebelumnya.

2) Secara semantik, "coba" adalah penangkal petir. Dan, kecuali ada pengecualian yang dilemparkan, "coba" akan lebih baik diganti namanya menjadi sesuatu seperti guard atau ensure .

3) Selain dua poin ini, saya pikir ini adalah proposal terbaik yang pernah saya lihat untuk hal semacam ini.

Beberapa komentar lagi yang mengartikulasikan keberatan saya terhadap penambahan konsep try/guard/ensure vs. membiarkan if err != nil saja:

1) Ini bertentangan dengan salah satu mandat asli golang (setidaknya seperti yang saya rasakan) untuk menjadi eksplisit, mudah dibaca/dipahami, dengan sedikit 'keajaiban'.

2) Ini akan mendorong kemalasan pada saat yang tepat ketika pemikiran diperlukan: "apa hal terbaik yang harus dilakukan kode saya jika terjadi kesalahan ini?". Ada banyak kesalahan yang dapat muncul saat melakukan hal-hal "boilerplate" seperti membuka file, mentransfer data melalui jaringan, dll. Meskipun Anda mungkin memulai dengan sekelompok "percobaan" yang mengabaikan skenario kegagalan yang tidak umum, pada akhirnya banyak dari " trys" akan hilang karena Anda mungkin perlu menerapkan tugas backoff/coba lagi, logging/tracing, dan/atau pembersihan Anda sendiri. "Peristiwa dengan probabilitas rendah" dijamin dalam skala besar.

Berikut adalah beberapa statistik tryhard mentah lainnya. Ini hanya sedikit divalidasi, jadi jangan ragu untuk menunjukkan kesalahan. ;-)

20 "Paket Populer" pertama di godoc.org

Ini adalah repositori yang sesuai dengan 20 Paket Populer pertama di https://godoc.org , diurutkan berdasarkan persentase kandidat percobaan. Ini menggunakan pengaturan default tryhard , yang secara teori seharusnya mengecualikan direktori vendor .

Nilai median untuk kandidat percobaan di 20 repo ini adalah 58%.

| proyek | lokasi | jika stmts | jika != nil (% dari jika) | coba kandidat (% dari jika != nil) |
|---------|-----|---------------|----------------- -----|---------------
| github.com/google/uuid | 1714 | 12 | 16,7% | 0,0% |
| github.com/pkg/errors | 1886 | 10 | 0,0% | 0,0% |
| github.com/aws/aws-sdk-go | 1911309 | 32015 | 9,4% | 8,9% |
| github.com/jinzhu/gorm | 15246 | 44 | 11,4% | 20,0% |
| github.com/robfig/cron | 1911 | 20 | 35,0% | 28,6% |
| github.com/gorilla/websocket | 6959 | 212 | 32,5% | 39,1% |
| github.com/dgrijalva/jwt-go | 3270 | 118 | 29,7% | 40,0% |
| github.com/gomodule/redigo | 7119 | 187 | 34,8% | 41,5% |
| github.com/unixpickle/kahoot-hack | 1743 | 52 | 75,0% | 43,6% |
| github.com/lib/pq | 13396 | 239 | 30,1% | 55,6% |
| github.com/sirupsen/logrus | 5063 | 29 | 17,2% | 60,0% |
| github.com/prometheus/client_golang | 17791 | 194 | 49,0% | 62,1% |
| github.com/go-redis/redis | 21182 | 326 | 42,6% | 73,4% |
| github.com/mongodb/mongo-go-driver | 86605 | 2097 | 37,8% | 73,9% |
| github.com/uber-go/zap | 15363 | 84 | 36,9% | 74,2% |
| github.com/golang/protobuf | 42959 | 685 | 22,9% | 77,1% |
| github.com/gin-gonic/gin | 14574 | 96 | 53,1% | 86,3% |
| github.com/go-pg/pg | 26369 | 831 | 37,7% | 86,9% |
| github.com/Shopify/sarama | 36427 | 1369 | 68,2% | 91,0% |
| github.com/stretchr/testify | 13496 | 32 | 43,8% | 92,9% |

Kolom " if stmts " hanya menghitung pernyataan if dalam fungsi yang mengembalikan kesalahan, begitulah cara tryhard melaporkannya, dan yang diharapkan menjelaskan mengapa ini sangat rendah untuk sesuatu seperti gorm .

10 macam. Proyek Go "besar"

Mengingat paket populer di godoc.org cenderung berupa paket perpustakaan, saya juga ingin memeriksa statistik untuk beberapa proyek yang lebih besar.

Ini adalah misc. proyek besar yang kebetulan menjadi top-of-mind bagi saya (yaitu, tidak ada logika nyata di balik 10 ini). Ini sekali lagi diurutkan berdasarkan persentase kandidat percobaan.

Nilai median untuk kandidat percobaan di 10 repo ini adalah 59%.

| proyek | lokasi | jika stmts | jika != nil (% dari jika) | coba kandidat (% dari jika != nil) |
|---------|-----|---------------|----------------- -----|---------------------------------|
| github.com/juju/juju | 1026473 | 26904 | 51,9% | 17,5% |
| github.com/go-kit/kit | 38949 | 467 | 57,0% | 51,9% |
| github.com/boltdb/bolt | 12426 | 228 | 46,1% | 53,3% |
| github.com/hashicorp/consul | 249369 | 5477 | 47,6% | 54,5% |
| github.com/docker/docker | 251152 | 8690 | 48,7% | 56,8% |
| github.com/istio/istio | 429636 | 7564 | 40,4% | 61,9% |
| github.com/gohugoio/hugo | 94875 | 1853 | 42,4% | 64,8% |
| github.com/etcd-io/etcd | 209603 | 4657 | 38,3% | 65,5% |
| github.com/kubernetes/kubernetes | 1789172 | 40289 | 43,3% | 66,5% |
| github.com/cockroachdb/cockroach | 1038529 | 22018 | 39,9% | 74,0% |


Kedua tabel ini tentu saja hanya mewakili contoh proyek sumber terbuka, dan hanya yang cukup terkenal. Saya telah melihat orang berteori bahwa basis kode pribadi akan menunjukkan keragaman yang lebih besar, dan setidaknya ada beberapa bukti berdasarkan beberapa angka yang telah diposting oleh berbagai orang.

@thepudds , itu tidak terlihat seperti _tryhard_ terbaru, yang memberikan "kandidat non-coba".

@networkimprov Saya dapat mengonfirmasi bahwa setidaknya untuk gorm ini adalah hasil dari tryhard terbaru. "Kandidat non-coba" sama sekali tidak dilaporkan dalam tabel di atas.

@daved Pertama, izinkan saya meyakinkan Anda bahwa saya/kami mendengar Anda dengan keras dan jelas. Meskipun kami masih dalam proses awal dan banyak hal dapat berubah. Mari kita tidak melompat pistol.

Saya mengerti (dan menghargai) bahwa seseorang mungkin ingin memilih pendekatan yang lebih konservatif ketika mengajar Go. Terima kasih.

@griesemer FYI di sini adalah hasil menjalankan versi terbaru dari tryhard pada 233k baris kode yang saya ikuti, sebagian besar bukan open source:

--- stats ---
   8760 (100.0% of    8760) functions (function literals are ignored)
   2942 ( 33.6% of    8760) functions returning an error
  22991 (100.0% of   22991) statements in functions returning an error
   5548 ( 24.1% of   22991) if statements
   2929 ( 52.8% of    5548) if <err> != nil statements
    163 (  5.6% of    2929) try candidates
      0 (  0.0% of    2929) <err> name is different from "err"
--- non-try candidates (-l flag lists file positions) ---
   2213 ( 75.6% of    2929) { return ... zero values ..., expr }
    167 (  5.7% of    2929) single statement then branch
    253 (  8.6% of    2929) complex then branch; cannot use try
     14 (  0.5% of    2929) non-empty else branch; cannot use try

Sebagian besar kode menggunakan idiom yang mirip dengan:

 if err != nil {
     return ... zero values ..., errors.Wrap(err)
 }

Mungkin menarik jika tryhard dapat mengidentifikasi kapan semua ekspresi seperti itu dalam suatu fungsi menggunakan ekspresi yang identik - yaitu ketika dimungkinkan untuk menulis ulang fungsi dengan satu pengendali defer yang umum.

Berikut adalah statistik alat pembantu GCP kecil untuk mengotomatiskan pembuatan proyek dan pengguna:

$ tryhard -r .
--- stats ---
    129 (100.0% of     129) functions (function literals are ignored)
     75 ( 58.1% of     129) functions returning an error
    725 (100.0% of     725) statements in functions returning an error
    164 ( 22.6% of     725) if statements
     93 ( 56.7% of     164) if <err> != nil statements
     64 ( 68.8% of      93) try candidates
      0 (  0.0% of      93) <err> name is different from "err"
--- non-try candidates (-l flag lists file positions) ---
     17 ( 18.3% of      93) { return ... zero values ..., expr }
      7 (  7.5% of      93) single statement then branch
      1 (  1.1% of      93) complex then branch; cannot use try
      0 (  0.0% of      93) non-empty else branch; cannot use try

Setelah ini, saya melanjutkan dan memeriksa semua tempat dalam kode yang masih berhubungan dengan variabel err untuk melihat apakah saya dapat menemukan pola yang berarti.

Mengumpulkan err s

Di beberapa tempat, kami tidak ingin menghentikan eksekusi pada kesalahan pertama dan sebaliknya dapat melihat semua kesalahan yang terjadi sekali di akhir proses. Mungkin ada cara berbeda untuk melakukan ini yang terintegrasi dengan baik dengan try atau beberapa bentuk dukungan untuk multi-kesalahan ditambahkan ke Go itu sendiri.

var errs []error
for _, p := range toDelete {
    fmt.Println("delete:", p.ProjectID)
    if err := s.DeleteProject(ctx, p.ProjectID); err != nil {
        errs = append(errs, err)
    }
}

Tanggung Jawab Dekorasi Kesalahan

Setelah membaca komentar ini lagi, tiba-tiba ada banyak potensi kasus try yang menarik perhatian saya. Semuanya serupa karena fungsi panggilan mendekorasi kesalahan fungsi yang dipanggil dengan informasi bahwa fungsi yang dipanggil sudah bisa menambahkan kesalahan:

func run() error {
    key := "MY_ENV_VAR"
    client, err := ClientFromEnvironment(key)
    if err != nil {
        // "github.com/pkg/errors"
        return errors.Wrap(err, key)
    }
    // do something with `client`
}

func ClientFromEnvironment(key string) (*http.Client, error) {
    filename, ok := os.LookupEnv(key)
    if !ok {
        return nil, errors.New("environment variable not set")
    }
    return ClientFromFile(filename)
}

Mengutip bagian penting dari blog Go di sini lagi di sini untuk kejelasan:

Ini adalah tanggung jawab implementasi kesalahan untuk meringkas konteksnya. Kesalahan dikembalikan oleh format os.Open sebagai "buka /etc/passwd: izin ditolak," bukan hanya "izin ditolak." Kesalahan yang dikembalikan oleh Kotak kami adalah informasi yang hilang tentang argumen yang tidak valid.

Dengan mengingat hal ini, kode di atas sekarang menjadi:

func run() error {
    key := "MY_ENV_VAR"
    client := try(ClientFromEnvironment(key))
    // do something with `client`
}

func ClientFromEnvironment(key string) (*http.Client, error) {
    filename, ok := os.LookupEnv(key)
    if !ok {
        return nil, fmt.Errorf("environment variable not set: %s", key)
    }
    return ClientFromFile(filename)
}

Sekilas, ini tampak seperti perubahan kecil tetapi menurut perkiraan saya, itu bisa berarti bahwa try sebenarnya memberi insentif untuk mendorong kesalahan yang lebih baik dan lebih konsisten menangani rantai fungsi dan lebih dekat ke sumber atau paket.

Catatan Akhir

Secara keseluruhan, menurut saya nilai yang dibawa try dalam jangka panjang lebih tinggi daripada potensi masalah yang saat ini saya lihat dengannya, yaitu:

  1. Kata kunci mungkin "terasa" lebih baik karena try mengubah aliran kontrol.
  2. Menggunakan try berarti Anda tidak dapat lagi menempatkan penghenti debug dalam kasus return err .

Karena kekhawatiran tersebut sudah diketahui oleh tim Go, saya penasaran untuk melihat bagaimana ini akan terjadi di "dunia nyata". Terima kasih atas waktu Anda dalam membaca dan menanggapi semua pesan kami.

Memperbarui

Memperbaiki tanda tangan fungsi yang tidak mengembalikan error sebelumnya. Terima kasih @magical telah mengetahuinya!

func main() {
    key := "MY_ENV_VAR"
    client := try(ClientFromEnvironment(key))
    // do something with `client`
}

@mrkanister Nitpicking, tetapi Anda sebenarnya tidak dapat menggunakan try dalam contoh ini karena main tidak mengembalikan error .

Ini adalah komentar apresiasi;
terima kasih @griesemer untuk berkebun dan semuanya, yang telah Anda lakukan dalam masalah ini dan juga di tempat lain.

Jika Anda memiliki banyak baris seperti ini (dari https://github.com/golang/go/issues/32437#issuecomment-509974901):

if !ok {
    return nil, fmt.Errorf("environment variable not set: %s", key)
}

Anda bisa menggunakan fungsi pembantu yang hanya mengembalikan kesalahan non-nol jika beberapa kondisi benar:

try(condErrorf(!ok, "environment variable not set: %s", key))

Setelah pola umum diidentifikasi, saya pikir akan mungkin untuk menangani banyak dari mereka hanya dengan beberapa pembantu, pertama di tingkat paket, dan mungkin akhirnya mencapai perpustakaan standar. Tryhard hebat, ia melakukan pekerjaan yang luar biasa dan memberikan banyak informasi menarik, tetapi masih banyak lagi.

Baris tunggal kompak jika

Sebagai tambahan pada proposal if satu baris oleh @zeebo dan lainnya, pernyataan if dapat memiliki bentuk ringkas yang menghilangkan != nil dan kurung kurawal:

if err return err
if err return errors.Wrap(err, "foo: failed to boo")
if err return fmt.Errorf("foo: failed to boo: %v", err)

Saya pikir ini sederhana, ringan & mudah dibaca. Ada dua bagian:

  1. Apakah pernyataan if secara implisit memeriksa nilai kesalahan untuk nil (atau mungkin antarmuka lebih umum). IMHO ini meningkatkan keterbacaan dengan mengurangi kepadatan dan perilakunya cukup jelas.
  2. Tambahkan dukungan untuk if variable return ... . Karena return sangat dekat dengan sisi kiri, tampaknya masih cukup mudah untuk membaca sekilas kode - kesulitan ekstra untuk melakukannya menjadi salah satu argumen utama terhadap ifs satu baris (?) Go juga sudah memiliki preseden untuk menyederhanakan sintaks dengan misalnya menghapus tanda kurung dari pernyataan if-nya.

Gaya saat ini:

a, err := BusinessLogic(state)
if err != nil {
   return nil, err
}

Satu baris jika:

a, err := BusinessLogic(state)
if err != nil { return nil, err }

Kompak satu baris jika:

a, err := BusinessLogic(state)
if err return nil, err
a, err := BusinessLogic(state)
if err return nil, errors.Wrap(err, "some context")
func (c *Config) Build() error {
    pkgPath, err := c.load()
    if err return nil, errors.WithMessage(err, "load config dir")

    b := bytes.NewBuffer(nil)
    err = templates.ExecuteTemplate(b, "main", c)
    if err return nil, errors.WithMessage(err, "execute main template")

    buf, err := format.Source(b.Bytes())
    if err return nil, errors.WithMessage(err, "format main template")

    target := fmt.Sprintf("%s.go", filename(pkgPath))
    err = ioutil.WriteFile(target, buf, 0644)
    if err return nil, errors.WithMessagef(err, "write file %s", target)

    // ...
}

@eug48 lihat #32611

Berikut adalah statistik tryhard untuk monorepo (baris kode go, tidak termasuk kode vendor: 2.282.731):

--- stats ---
 117551 (100.0% of  117551) functions (function literals are ignored)
  35726 ( 30.4% of  117551) functions returning an error
 263725 (100.0% of  263725) statements in functions returning an error
  50690 ( 19.2% of  263725) if statements
  25042 ( 49.4% of   50690) if <err> != nil statements
  12091 ( 48.3% of   25042) try candidates
     36 (  0.1% of   25042) <err> name is different from "err"
--- non-try candidates (-l flag lists file positions) ---
   3561 ( 14.2% of   25042) { return ... zero values ..., expr }
   3304 ( 13.2% of   25042) single statement then branch
   4966 ( 19.8% of   25042) complex then branch; cannot use try
    296 (  1.2% of   25042) non-empty else branch; cannot use try

Mengingat bahwa orang-orang masih mengusulkan alternatif, saya ingin mengetahui lebih detail fungsionalitas apa yang sebenarnya diinginkan komunitas Go yang lebih luas dari fitur penanganan kesalahan baru yang diusulkan.

Saya telah mengumpulkan survei yang mencantumkan banyak fitur berbeda, bagian dari fungsi penanganan kesalahan yang saya lihat orang usulkan. Saya telah dengan hati-hati _menghilangkan penamaan atau sintaks yang diusulkan_, dan tentu saja mencoba membuat survei netral daripada mendukung pendapat saya sendiri.

Jika orang ingin berpartisipasi, ini tautannya, disingkat untuk berbagi:

https://forms.gle/gaCBgxKRE4RMCz7c7

Setiap orang yang berpartisipasi harus dapat melihat hasil ringkasan. Mungkin ini bisa membantu memfokuskan diskusi?

if err := os.Setenv("GO111MODULE", "on"); err != nil {
    return err
}

Penangguhan penangguhan menambahkan konteks tidak berfungsi dalam kasus ini atau apakah itu? Jika tidak maka akan lebih baik untuk membuatnya lebih terlihat, jika memungkinkan karena itu terjadi cukup cepat, terutama karena itulah standar yang digunakan sampai sekarang.

Oh, dan tolong perkenalkan try , temukan banyak kasus penggunaan di sini juga.

--- stats ---
    929 (100.0% of     929) functions (function literals are ignored)
    230 ( 24.8% of     929) functions returning an error
   1480 (100.0% of    1480) statements in functions returning an error
    320 ( 21.6% of    1480) if statements
    206 ( 64.4% of     320) if <err> != nil statements
    109 ( 52.9% of     206) try candidates
      2 (  1.0% of     206) <err> name is different from "err"
--- non-try candidates (-l flag lists file positions) ---
     53 ( 25.7% of     206) { return ... zero values ..., expr }
     18 (  8.7% of     206) single statement then branch
     17 (  8.3% of     206) complex then branch; cannot use try
      2 (  1.0% of     206) non-empty else branch; cannot use try

@lpar Anda dipersilakan untuk mendiskusikan alternatif tetapi tolong jangan lakukan ini dalam masalah ini. Ini tentang proposal try . Tempat terbaik sebenarnya adalah salah satu milis, misalnya go-nuts. Pelacak masalah benar-benar terbaik untuk melacak dan mendiskusikan masalah tertentu daripada diskusi umum. Terima kasih.

@fabstu The defer handler akan bekerja dalam contoh Anda baik-baik saja, baik dengan dan tanpa try . Memperluas kode Anda dengan fungsi terlampir:

func f() (err error) {
    defer func() {
       if err != nil {
          err = decorate(err, "msg") // here you can modify the result error as you please
       }
    }()
    ...
    if err := os.Setenv("GO111MODULE", "on"); err != nil {
        return err
    }
    ...
}

(perhatikan bahwa hasil err akan ditetapkan oleh return err ; dan err yang digunakan oleh return adalah yang dideklarasikan secara lokal dengan if - ini hanya aturan pelingkupan normal dalam tindakan).

Atau, menggunakan try , yang akan menghilangkan kebutuhan akan variabel err lokal:

func f() (err error) {
    defer func() {
       if err != nil {
          err = decorate(err, "msg") // here you can modify the result error as you please
       }
    }()
    ...
    try(os.Setenv("GO111MODULE", "on"))
    ...
}

Dan kemungkinan besar, Anda ingin menggunakan salah satu dari fungsi errors/errd yang diusulkan :

func f() (err error) {
    defer errd.Wrap(&err, ... )
    ...
    try(os.Setenv("GO111MODULE", "on"))
    ...
}

Dan jika Anda tidak perlu membungkusnya, itu hanya:

func f() error {
    ...
    try(os.Setenv("GO111MODULE", "on"))
    ...
}

@fastu Dan akhirnya, Anda dapat menggunakan errors/errd juga tanpa try dan kemudian Anda mendapatkan:

func f() (err error) {
    defer errd.Wrap(&err, ... )
    ...
    if err := os.Setenv("GO111MODULE", "on"); err != nil {
        return err
    }
    ...
}

Semakin saya berpikir semakin saya menyukai proposal ini.
Satu-satunya hal yang mengganggu saya adalah menggunakan pengembalian bernama di mana-mana. Apakah ini akhirnya praktik yang baik dan saya harus menggunakannya (tidak pernah mencoba)?

Bagaimanapun, sebelum mengubah semua kode saya, apakah akan berfungsi seperti itu?

func f() error {
  var err error
  defer errd.Wrap(&err,...)
  try(...)
}

@flibustenet Parameter hasil yang dinamai sendiri bukanlah praktik yang buruk sama sekali; perhatian umum dengan hasil bernama adalah bahwa mereka mengaktifkan naked returns ; yaitu, seseorang dapat dengan mudah menulis return tanpa kebutuhan untuk menentukan hasil aktual _dengan return _. Secara umum (tetapi tidak selalu!) praktik seperti itu membuat lebih sulit untuk membaca dan bernalar tentang kode karena orang tidak bisa begitu saja melihat pernyataan return dan menyimpulkan apa hasilnya. Kita harus memindai kode untuk parameter hasil. Seseorang mungkin melewatkan untuk menetapkan nilai hasil, dan sebagainya. Jadi di beberapa basis kode, pengembalian telanjang tidak disarankan.

Tapi, seperti yang saya sebutkan sebelumnya , jika hasilnya tidak valid jika terjadi kesalahan, tidak apa-apa untuk mengatur kesalahan dan mengabaikan sisanya. Pengembalian telanjang dalam kasus seperti itu benar-benar baik-baik saja selama hasil kesalahan ditetapkan secara konsisten. try akan memastikan hal itu.

Terakhir, parameter hasil bernama hanya diperlukan jika Anda ingin menambah pengembalian kesalahan dengan defer . Dokumen desain secara singkat juga membahas kemungkinan untuk menyediakan built-in lain untuk mengakses hasil kesalahan. Itu akan menghilangkan kebutuhan untuk pengembalian bernama sepenuhnya.

Mengenai contoh kode Anda: Ini tidak akan berfungsi seperti yang diharapkan karena try _always_ menyetel _result error_ (yang tidak disebutkan namanya dalam kasus ini). Tetapi Anda mendeklarasikan variabel lokal yang berbeda err dan errd.Wrap beroperasi pada variabel itu. Itu tidak akan disetel oleh try .

Laporan pengalaman cepat: Saya sedang menulis penangan permintaan HTTP yang terlihat seperti ini:

func Handler(w http.ResponseWriter, r *http.Request) {
        ctx := r.Context()
        id := chi.URLParam(r, "id")

        var err error
        // starts as bad request, then it's an internal server error after we parse inputs
        var statusCode = http.StatusBadRequest

        defer func() {
            if err != nil {
                wrap := xerrors.Errorf("handler fail: %w", err)
                logger.With(zap.Error(wrap)).Error("error")
                http.Error(w, wrap.Error(), statusCode)
            }
        }()
        var c Thingie
        err = unmarshalBody(r, &c)
        if err != nil {
            return
        }
        statusCode = http.StatusInternalServerError
        s, err := DoThing(ctx, c)
        if err != nil {
            return
        }
        d, err := DoThingWithResult(ctx, id, s)
        if err != nil {
            return
        }
        data, err := json.Marshal(detail)
        if err != nil {
            return
        }
        w.Header().Set("Content-Type", "application/json")
        w.WriteHeader(http.StatusCreated)
        _, err = w.Write(data)
        if err != nil {
            return
        }
}

Sepintas, sepertinya ini adalah kandidat ideal untuk try karena ada banyak penanganan kesalahan di mana tidak ada yang bisa dilakukan kecuali mengembalikan pesan, yang semuanya dapat dilakukan penundaan. Tetapi Anda tidak dapat menggunakan try karena penangan permintaan tidak mengembalikan error . Untuk menggunakannya, saya harus membungkus tubuh dengan penutup dengan tanda tangan func() error . Itu terasa...tidak elegan dan saya menduga bahwa kode yang terlihat seperti ini adalah pola yang agak umum.

@jonbodner

Ini berfungsi (https://play.golang.org/p/NaBZe-QShpu):

package main

import (
    "errors"
    "fmt"

    "golang.org/x/xerrors"
)

func main() {
    var err error
    defer func() {
        filterCheck(recover())
        if err != nil {
            wrap := xerrors.Errorf("app fail (at count %d): %w", ct, err)
            fmt.Println(wrap)
        }
    }()

    check(retNoErr())

    n, err := intNoErr()
    check(err)

    n, err = intErr()
    check(err)

    check(retNoErr())

    check(retErr())

    fmt.Println(n)
}

func check(err error) {
    if err != nil {
        panic(struct{}{})
    }
}

func filterCheck(r interface{}) {
    if r != nil {
        if _, ok := r.(struct{}); !ok {
            panic(r)
        }
    }
}

var ct int

func intNoErr() (int, error) {
    ct++
    return 0, nil
}

func retNoErr() error {
    ct++
    return nil
}

func intErr() (int, error) {
    ct++
    return 0, errors.New("oops")
}

func retErr() error {
    ct++
    return errors.New("oops")
}

Ah, downvote pertama! Bagus. Biarkan pragmatisme mengalir melalui Anda.

Menjalankan tryhard pada beberapa basis kode saya. Sayangnya, beberapa paket saya memiliki kandidat percobaan 0 meskipun cukup besar karena metode di dalamnya menggunakan implementasi kesalahan khusus. Misalnya, ketika membangun server, saya suka metode lapisan logika bisnis saya hanya memancarkan SanitizedError s daripada error s untuk memastikan pada waktu kompilasi bahwa hal-hal seperti jalur sistem file atau info sistem tidak bocor ke pengguna dalam pesan kesalahan.

Misalnya, metode yang menggunakan pola ini mungkin terlihat seperti ini:

func (a *App) GetFriendsOfUser(userId model.Id) ([]*model.User, SanitizedError) {
    if user, err := a.GetUserById(userId); err != nil {
        // (*App).GetUserById returns (*model.User, SanitizedError)
        // This could be a try() candidate.
        return err
    } else if user == nil {
        return NewUserError("The specified user doesn't exist.")
    }

    friends, err := a.Store.GetFriendsOfUser(userId)
    // (*Store).GetFriendsOfUser returns ([]*model.User, error)
    // This could be a SQL error or a network error or who knows what.
    return friends, NewInternalError(err)
}

Apakah ada alasan mengapa kami tidak dapat mengendurkan proposal saat ini untuk berfungsi selama nilai pengembalian terakhir dari fungsi terlampir dan kesalahan implementasi ekspresi fungsi try dan merupakan tipe yang sama? Ini masih akan menghindari kebingungan antarmuka yang konkret -> nihil, tetapi itu akan memungkinkan coba dalam situasi seperti di atas.

Terima kasih, @jonbodner , untuk contoh Anda. Saya akan menulis kode itu sebagai berikut (terlepas dari kesalahan terjemahan):

func Handler(w http.ResponseWriter, r *http.Request) {
    statusCode, err := internalHandler(w, r)
    if err != nil {
        wrap := xerrors.Errorf("handler fail: %w", err)
        logger.With(zap.Error(wrap)).Error("error")
        http.Error(w, wrap.Error(), statusCode)
    }
}

func internalHandler(w http.ResponseWriter, r *http.Request) (statusCode int, err error) {
    ctx := r.Context()
    id := chi.URLParam(r, "id")

    // starts as bad request, then it's an internal server error after we parse inputs
    statusCode = http.StatusBadRequest
    var c Thingie
    try(unmarshalBody(r, &c))

    statusCode = http.StatusInternalServerError
    s := try(DoThing(ctx, c))
    d := try(DoThingWithResult(ctx, id, s))
    data := try(json.Marshal(detail))

    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusCreated)
    try(w.Write(data))

    return
}

Ini menggunakan dua fungsi tetapi jauh lebih pendek (29 baris vs 40 baris) - dan saya menggunakan spasi yang bagus - dan kode ini tidak memerlukan defer . defer khususnya, bersama dengan statusCode yang diubah saat turun dan digunakan dalam defer membuat kode asli lebih sulit untuk diikuti daripada yang diperlukan. Kode baru, sementara menggunakan hasil bernama dan pengembalian telanjang (Anda dapat dengan mudah menggantinya dengan return statusCode, nil jika Anda mau) lebih sederhana karena memisahkan penanganan kesalahan dari "logika bisnis".

Cukup posting ulang komentar saya di edisi lain https://github.com/golang/go/issues/32853#issuecomment -510340544

Saya pikir jika kami dapat memberikan parameter lain funcname , itu akan bagus, jika tidak, kami masih tidak tahu kesalahan dikembalikan oleh fungsi mana.

func foo() error {
    handler := func(err error, funcname string) error {
        return fmt.Errorf("%s: %v", funcname, err) // wrap something
        //return nil // or dismiss
    }

    a, b := try(bar1(), handler) 
    c, d := try(bar2(), handler) 
}

@ccbrown Saya ingin tahu apakah contoh Anda dapat menerima perlakuan yang sama seperti di atas ; yaitu, jika masuk akal untuk memfaktorkan kode sedemikian rupa sehingga kesalahan internal dibungkus sekali (dengan fungsi terlampir) sebelum keluar (daripada membungkusnya di mana-mana). Tampaknya bagi saya (tanpa mengetahui banyak tentang sistem Anda) bahwa itu akan lebih disukai karena akan memusatkan pembungkusan kesalahan di satu tempat daripada di mana-mana.

Tetapi mengenai pertanyaan Anda: Saya harus berpikir untuk membuat try menerima jenis kesalahan yang lebih umum (dan mengembalikannya juga). Saya tidak melihat masalah dengan itu saat ini (kecuali lebih rumit untuk dijelaskan) - tetapi mungkin ada masalah.

Sejalan dengan itu, kami telah bertanya-tanya sejak awal apakah try dapat digeneralisasi sehingga tidak hanya berfungsi untuk tipe error , tetapi semua tipe, dan pengujian err != nil kemudian akan menjadi x != zero di mana x setara dengan err (hasil terakhir), dan zero masing-masing nilai nol untuk jenis x . Sayangnya, ini tidak bekerja untuk kasus umum boolean (dan hampir semua tipe dasar lainnya), karena nilai nol dari bool adalah false dan ok != false persis kebalikan dari apa yang ingin kita uji.

@lunny Versi try yang diusulkan tidak menerima fungsi handler.

@griesemer Oh. Sayang sekali! Kalau tidak, saya dapat menghapus github.com/pkg/errors dan semua errors.Wrap .

@ccbrown Saya ingin tahu apakah contoh Anda dapat menerima perlakuan yang sama seperti di atas; yaitu, jika masuk akal untuk memfaktorkan kode sedemikian rupa sehingga kesalahan internal dibungkus sekali (dengan fungsi terlampir) sebelum keluar (daripada membungkusnya di mana-mana). Tampaknya bagi saya (tanpa mengetahui banyak tentang sistem Anda) bahwa itu akan lebih disukai karena akan memusatkan pembungkusan kesalahan di satu tempat daripada di mana-mana.

@griesemer Mengembalikan error alih-alih ke fungsi terlampir akan memungkinkan untuk lupa mengkategorikan setiap kesalahan sebagai kesalahan internal, kesalahan pengguna, kesalahan otorisasi, dll. Apa adanya, kompiler menangkapnya, dan menggunakan try tidak akan berguna untuk memperdagangkan cek waktu kompilasi tersebut dengan cek run-time.

Saya ingin mengatakan bahwa saya menyukai desain try , tetapi masih ada pernyataan if di defer handler saat Anda menggunakan try . Saya tidak berpikir itu akan lebih sederhana daripada pernyataan if tanpa try dan defer handler. Mungkin hanya menggunakan try akan jauh lebih baik.

@ccbrown Mengerti. Dalam retrospeksi, saya pikir relaksasi yang Anda sarankan seharusnya tidak menjadi masalah. Saya percaya kita dapat bersantai try untuk bekerja dengan semua jenis antarmuka (dan jenis hasil yang cocok), dalam hal ini, bukan hanya error , selama tes yang relevan tetap x != nil . Sesuatu untuk dipikirkan. Ini bisa dilakukan lebih awal, atau secara retroaktif karena saya yakin ini akan menjadi perubahan yang kompatibel dengan mundur.

@jonbodner example , dan cara @griesemer menulis ulang , persis seperti kode yang saya miliki di mana saya benar-benar ingin menggunakan try .

Apakah tidak ada yang terganggu dengan jenis penggunaan try ini:

data := coba(json.Marshal(detail))

Terlepas dari kenyataan bahwa kesalahan penyusunan dapat mengakibatkan menemukan baris yang benar dalam kode tertulis, saya hanya merasa tidak nyaman mengetahui bahwa ini adalah kesalahan telanjang yang dikembalikan tanpa nomor baris/informasi penelepon yang disertakan. Mengetahui file sumber, nama fungsi, dan nomor baris biasanya yang saya sertakan saat menangani kesalahan. Mungkin saya salah paham tentang sesuatu.

@griesemer Saya tidak berencana untuk membahas alternatif di sini. Fakta bahwa setiap orang terus menyarankan alternatif adalah mengapa saya pikir survei untuk mengetahui apa yang sebenarnya diinginkan orang akan menjadi ide yang bagus. Saya baru saja mempostingnya di sini untuk mencoba menangkap sebanyak mungkin orang yang tertarik dengan kemungkinan meningkatkan penanganan kesalahan Go.

@trende-jp Saya benar-benar bergantung pada konteks baris kode ini - dengan sendirinya tidak dapat dinilai dengan cara apa pun yang berarti. Jika ini adalah satu-satunya panggilan ke json.Marshal dan Anda ingin menambah kesalahan, pernyataan if mungkin yang terbaik. Jika ada banyak panggilan json.Marshal , menambahkan konteks ke kesalahan dapat dilakukan dengan baik dengan defer ; atau mungkin dengan membungkus semua panggilan ini di dalam penutupan lokal yang mengembalikan kesalahan. Ada banyak cara bagaimana ini dapat diperhitungkan jika perlu (yaitu, jika ada banyak panggilan seperti itu dalam fungsi yang sama). "Kesalahan adalah nilai" juga benar di sini: gunakan kode untuk membuat penanganan kesalahan dapat dikelola.

try tidak akan menyelesaikan semua masalah penanganan kesalahan Anda - bukan itu maksudnya. Ini hanyalah alat lain di kotak alat. Dan itu juga bukan mesin yang benar-benar baru, ini adalah bentuk gula sintaksis untuk pola yang sering kita amati selama hampir satu dekade. Kami memiliki beberapa bukti bahwa itu akan bekerja dengan sangat baik di beberapa kode, dan itu juga tidak akan banyak membantu dalam kode lain.

@trende-jp

Tidak bisakah itu diselesaikan dengan defer ?

defer fmt.HandleErrorf(&err, "decoding %q", path)

Nomor baris dalam pesan kesalahan juga dapat diselesaikan seperti yang saya tunjukkan di blog saya: Cara menggunakan 'coba' .

@trende-jp @faiface Selain nomor baris, Anda dapat menyimpan string dekorator dalam variabel. Ini akan memungkinkan Anda mengisolasi panggilan fungsi tertentu yang gagal.

Saya benar-benar berpikir ini seharusnya tidak menjadi fungsi bawaan .

Telah disebutkan beberapa kali bahwa panic() dan recover() juga mengubah aliran kontrol. Baiklah, mari kita tidak menambahkan lebih banyak.

@networkimprov menulis https://github.com/golang/go/issues/32437#issuecomment -498960081:

Itu tidak membaca seperti Go.

Saya sangat setuju.

Jika ada, saya percaya mekanisme apa pun untuk mengatasi masalah root (dan saya tidak yakin ada), itu harus dipicu oleh kata kunci (atau simbol kunci?).

Bagaimana perasaan Anda jika go func() menjadi go(func()) ?

Bagaimana kalau menggunakan bang(!) daripada fungsi try . Ini dapat memungkinkan rantai fungsi:

func foo() {
    f := os.Open!("")
    defer f.Close()
    // etc
}

func bar() {
    count := mustErr!().Read!()
}

@syr

Bagaimana perasaan Anda jika go func() menjadi go(func()) ?

Ayolah, itu cukup bisa diterima.

@syr Terima kasih, tetapi kami tidak meminta proposal alternatif di utas ini. Lihat juga ini untuk tetap fokus.

Mengenai komentar Anda: Go adalah bahasa pragmatis - menggunakan built-in di sini adalah pilihan pragmatis. Ini memiliki beberapa keunggulan dibandingkan menggunakan kata kunci seperti yang dijelaskan panjang lebar dalam dokumen desain . Perhatikan bahwa try hanyalah gula sintaksis untuk pola umum (berbeda dengan go yang mengimplementasikan fitur utama Go dan tidak dapat diimplementasikan dengan mekanisme Go lainnya), seperti append , copy , dll. Menggunakan built-in adalah pilihan yang bagus.

(Tapi seperti yang telah saya katakan sebelumnya, jika _that_ adalah satu-satunya hal yang mencegah try diterima, kami dapat mempertimbangkan untuk menjadikannya sebagai kata kunci.)

Saya baru saja merenungkan sepotong kode saya sendiri, dan bagaimana tampilannya dengan try :

slurp, err := ioutil.ReadFile(path)
if err != nil {
    return err
}
return ioutil.WriteFile(path, append(copyrightText, slurp...), 0666)

Bisa menjadi:

return ioutil.WriteFile(path, append(copyrightText, try(ioutil.ReadFile(path))...), 0666)

Saya tidak yakin apakah ini lebih baik. Tampaknya membuat kode jauh lebih sulit untuk dibaca. Tapi itu mungkin hanya masalah membiasakan diri.

@gbbr Anda punya pilihan di sini. Anda bisa menulisnya sebagai:

slurp := try(ioutil.ReadFile(path))
return ioutil.WriteFile(path, append(copyrightText, slurp...), 0666)

yang masih menghemat banyak boilerplate namun membuatnya lebih jelas. Ini tidak melekat pada try . Hanya karena Anda dapat memeras semuanya menjadi satu ekspresi, bukan berarti Anda harus melakukannya. Itu berlaku secara umum.

@griesemer Contoh ini _is_ melekat untuk dicoba, Anda mungkin tidak membuat kode yang mungkin gagal hari ini - Anda dipaksa untuk menangani kesalahan dengan aliran kontrol. Saya ingin sesuatu dibersihkan dari https://github.com/golang/go/issues/32825#issuecomment -507099786 / https://github.com/golang/go/issues/32825#issuecomment -507136111 yang Anda menjawab https://github.com/golang/go/issues/32825#issuecomment-507358397 . Kemudian masalah yang sama dibahas lagi di https://github.com/golang/go/issues/32825#issuecomment -508813236 dan https://github.com/golang/go/issues/32825#issuecomment -508937177 - yang terakhir yang saya nyatakan:

Senang Anda membaca argumen utama saya yang menentang try: implementasinya tidak cukup membatasi. Saya percaya bahwa implementasinya harus cocok dengan semua contoh penggunaan proposal yang ringkas dan mudah dibaca.

_Atau_ proposal harus berisi contoh yang sesuai dengan implementasi sehingga semua orang yang mempertimbangkannya dapat mengetahui apa yang pasti akan muncul dalam kode Go. Seiring dengan semua kasus sudut yang mungkin kita hadapi saat memecahkan masalah yang kurang dari perangkat lunak yang ditulis secara ideal, yang terjadi dalam bahasa / lingkungan apa pun. Itu harus menjawab pertanyaan seperti apa jejak tumpukan akan terlihat dengan beberapa tingkat bersarang, apakah lokasi kesalahan mudah dikenali? Bagaimana dengan nilai metode, literal fungsi anonim? Jenis pelacakan tumpukan apa yang dihasilkan di bawah ini jika baris yang berisi panggilan ke fn() gagal?

fn := func(n int) (int, error) { ... }
return try(func() (int, error) { 
    mu.Lock()
    defer mu.Unlock()
    return try(try(fn(111111)) + try(fn(101010)) + try(func() (int, error) {
       // yea...
    })(2))
}(try(fn(1)))

Saya sangat menyadari akan ada banyak kode masuk akal yang ditulis, tetapi kami sekarang menyediakan alat yang belum pernah ada sebelumnya: kemampuan untuk berpotensi menulis kode tanpa aliran kontrol yang jelas. Jadi saya ingin membenarkan mengapa kami mengizinkannya sejak awal, saya tidak ingin waktu saya terbuang sia-sia untuk men-debug kode semacam ini. Karena saya tahu saya akan melakukannya, pengalaman telah mengajari saya bahwa seseorang akan melakukannya jika Anda mengizinkannya. Bahwa seseorang sering menjadi saya yang tidak tahu apa-apa.

Go menyediakan cara yang paling tidak mungkin bagi pengembang lain dan saya untuk saling membuang waktu dengan membatasi kami untuk menggunakan konstruksi biasa yang sama. Saya tidak ingin kehilangan itu tanpa manfaat yang luar biasa. Saya tidak percaya "karena coba diimplementasikan sebagai fungsi" menjadi manfaat yang luar biasa. Bisakah Anda memberikan alasan mengapa demikian?

Memiliki jejak tumpukan yang menunjukkan di mana kegagalan di atas akan berguna, mungkin menambahkan literal komposit dengan bidang yang memanggil fungsi itu ke dalam campuran? Saya menanyakan ini karena saya tahu bagaimana jejak tumpukan terlihat hari ini untuk jenis masalah ini, Go tidak memberikan informasi kolom yang mudah dicerna dalam informasi tumpukan hanya alamat entri fungsi heksadesimal. Beberapa hal mengkhawatirkan saya tentang hal ini, seperti konsistensi pelacakan tumpukan di seluruh arsitektur, misalnya pertimbangkan kode ini:

package main
import "fmt"
func dopanic(b bool) int { if b { panic("panic") }; return 1 }
type bar struct { a, b, d int; b *bar }
func main() {
    fmt.Println(&bar{
        a: 1,
        c: 1,
        d: 1,
        b: &bar{
            a: 1,
            c: 1,
            d: dopanic(true) + dopanic(false),
        },
    })
}

Perhatikan bagaimana taman bermain pertama gagal di sebelah kiri dopanic, yang kedua di sebelah kanan, namun keduanya mencetak jejak tumpukan yang identik:
https://play.golang.org/p/SYs1r4hBS7O
https://play.golang.org/p/YMKkflcQuav

panic: panic

goroutine 1 [running]:
main.dopanic(...)
    /tmp/sandbox709874298/prog.go:7
main.main()
    /tmp/sandbox709874298/prog.go:27 +0x40

Saya mengharapkan yang kedua menjadi +0x41 atau beberapa offset setelah 0x40, yang dapat digunakan untuk menentukan panggilan aktual yang gagal dalam kepanikan. Bahkan jika kita mendapatkan offset heksadesimal yang benar, saya tidak akan dapat menentukan di mana kegagalan terjadi tanpa debugging tambahan. Hari ini ini adalah kasus tepi, sesuatu yang jarang dihadapi orang. Jika Anda merilis versi try yang dapat disarangkan, itu akan menjadi norma, karena bahkan proposal menyertakan a try() + try() strconv yang menunjukkan bahwa mungkin dan dapat diterima untuk menggunakan try dengan cara ini.

1) Berdasarkan informasi di atas, perubahan apa pada pelacakan tumpukan yang Anda rencanakan (jika ada) sehingga saya masih dapat mengetahui di mana kode saya gagal?

2) Apakah mencoba bersarang diperbolehkan karena Anda yakin itu seharusnya? Jika demikian, apa yang Anda lihat sebagai manfaat dari mencoba bersarang dan bagaimana Anda mencegah penyalahgunaan? Saya pikir tryhard harus disesuaikan untuk melakukan percobaan bersarang di mana Anda membayangkannya dapat diterima sehingga orang dapat membuat keputusan yang lebih tepat tentang bagaimana hal itu memengaruhi kode mereka, karena saat ini kami hanya mendapatkan contoh penggunaan terbaik/ketat. Ini akan memberi kita gambaran tentang jenis batasan vet yang akan dikenakan, mulai sekarang Anda telah mengatakan bahwa dokter hewan akan menjadi pertahanan terhadap percobaan yang tidak masuk akal, tetapi bagaimana hal itu akan terwujud?

3) Apakah coba bersarang karena kebetulan merupakan konsekuensi dari implementasi? Jika demikian, bukankah ini tampak seperti argumen yang sangat lemah untuk perubahan bahasa yang paling menonjol sejak Go dirilis?

Saya pikir perubahan ini perlu lebih banyak pertimbangan seputar mencoba bersarang. Setiap kali saya memikirkannya beberapa titik rasa sakit baru muncul di suatu tempat, saya sangat khawatir bahwa semua potensi negatif tidak akan muncul sampai terungkap di alam liar. Bersarang juga menyediakan cara mudah untuk membocorkan sumber daya seperti yang disebutkan di https://github.com/golang/go/issues/32825#issuecomment -506882164 yang tidak mungkin dilakukan hari ini. Saya pikir cerita "dokter hewan" membutuhkan rencana yang jauh lebih konkret dengan contoh bagaimana itu akan memberikan umpan balik jika itu akan digunakan sebagai pertahanan terhadap try() berbahaya contoh yang saya berikan di sini, atau implementasinya harus memberikan kesalahan waktu kompilasi untuk penggunaan di luar praktik terbaik ideal Anda.

edit: Saya bertanya di gophers tentang arsitektur play.golang.org dan seseorang menyebutkannya dikompilasi melalui NaCl, jadi mungkin hanya konsekuensi/bug dari itu. Tapi saya bisa melihat ini menjadi masalah di lengkungan lain, saya pikir banyak masalah yang bisa muncul dari memperkenalkan beberapa pengembalian per baris belum sepenuhnya dieksplorasi karena sebagian besar pusat penggunaan di sekitar penggunaan satu baris yang waras & bersih.

Oh tidak, tolong jangan tambahkan 'keajaiban' ini ke dalam bahasa.
Ini tidak terlihat dan terasa seperti bahasa lainnya.
Saya sudah melihat kode seperti ini muncul di mana-mana.

a, b := try( f() )
if a != 0 && b != "" {
...
}
...

dari pada

a,b,err := f()
if err != nil {
...
}
...

atau

a,b,_:= f()

Pola call if err.... awalnya agak tidak wajar bagi saya, tetapi sekarang saya sudah terbiasa
Saya merasa lebih mudah untuk menangani kesalahan karena mereka mungkin tiba di aliran eksekusi daripada menulis pembungkus/penangan yang harus melacak beberapa jenis keadaan untuk bertindak setelah dipecat.
Dan jika saya memutuskan untuk mengabaikan kesalahan untuk menyelamatkan hidup keyboard saya, saya sadar saya akan panik suatu hari nanti.

saya bahkan mengubah kebiasaan saya di vbscript menjadi:

on error resume next
a = f()
if er.number <> 0 then
    ...
end if
...

Saya suka proposal ini

Semua masalah yang saya miliki (misalnya idealnya harus berupa kata kunci dan bukan bawaan) ditangani oleh dokumen mendalam

Ini tidak 100% sempurna, tetapi ini adalah solusi yang cukup baik sehingga a) memecahkan masalah aktual dan b) melakukannya sambil mempertimbangkan banyak kompatibilitas mundur dan masalah lainnya

Tentu itu melakukan beberapa 'keajaiban' tetapi begitu juga defer . Satu-satunya perbedaan adalah kata kunci vs. bawaan, dan pilihan untuk menghindari kata kunci di sini masuk akal.

Saya merasa semua umpan balik penting terhadap proposal try() sudah disuarakan. Tapi izinkan saya mencoba meringkas:

1) try() memindahkan kompleksitas kode vertikal ke horizontal
2) Panggilan try() bersarang sama sulitnya untuk dibaca seperti operator ternary
3) Memperkenalkan aliran kontrol 'kembali' yang tidak terlihat yang tidak berbeda secara visual (dibandingkan dengan blok indentasi yang dimulai dengan kata kunci return )
4) Memperburuk praktik pembungkusan kesalahan (konteks fungsi alih-alih tindakan tertentu)
5) Membagi komunitas #golang & gaya kode (anti-gofmt)
6) Akan membuat devs menulis ulang try() ke if-err-nil dan sebaliknya sering (tryhard vs menambahkan logika pembersihan / log tambahan / konteks kesalahan yang lebih baik)

@VojtechVitek Saya pikir poin yang Anda buat bersifat subjektif dan hanya dapat dievaluasi setelah orang mulai menggunakannya dengan serius.

Namun saya yakin ada satu hal teknis yang belum banyak dibahas. Pola penggunaan defer untuk pembungkusan/dekorasi kesalahan memiliki implikasi kinerja di luar biaya sederhana defer itu sendiri karena fungsi yang menggunakan defer tidak dapat disejajarkan.

Ini berarti bahwa mengadopsi try dengan pembungkusan kesalahan membebankan dua biaya potensial dibandingkan dengan mengembalikan kesalahan yang dibungkus secara langsung setelah pemeriksaan err != nil :

  1. penangguhan untuk semua jalur melalui fungsi, bahkan yang berhasil
  2. kehilangan inlining

Meskipun ada beberapa peningkatan kinerja mendatang yang mengesankan untuk defer , biayanya tetap tidak nol.

try memiliki banyak potensi sehingga akan lebih baik jika tim Go dapat meninjau kembali desain untuk memungkinkan semacam pembungkusan dilakukan pada titik kegagalan alih-alih secara pre-emptive melalui defer .

cerita dokter hewan membutuhkan rencana yang jauh lebih konkret

cerita dokter hewan adalah dongeng. Ini hanya akan berfungsi untuk jenis yang dikenal dan tidak akan berguna pada yang khusus.

Halo semuanya,

Tujuan kami dengan proposal seperti ini adalah untuk mengadakan diskusi di seluruh komunitas tentang implikasi, pengorbanan, dan bagaimana melanjutkan, dan kemudian menggunakan diskusi itu untuk membantu memutuskan jalan ke depan.

Berdasarkan tanggapan masyarakat yang luar biasa dan diskusi ekstensif di sini, kami menandai proposal ini ditolak lebih cepat dari jadwal .

Sejauh umpan balik teknis, diskusi ini telah membantu mengidentifikasi beberapa pertimbangan penting yang kami lewatkan, terutama implikasi untuk menambahkan cetakan debug dan menganalisis cakupan kode.

Lebih penting lagi, kami telah mendengar dengan jelas banyak orang yang berpendapat bahwa proposal ini tidak menargetkan masalah yang berharga. Kami masih percaya bahwa penanganan kesalahan di Go tidak sempurna dan dapat ditingkatkan secara berarti, tetapi jelas bahwa kami sebagai komunitas perlu berbicara lebih banyak tentang aspek spesifik apa dari penanganan kesalahan yang menjadi masalah yang harus kami atasi.

Sejauh membahas masalah yang harus dipecahkan, kami mencoba untuk memaparkan visi masalah kami Agustus lalu di " Ikhtisar masalah penanganan kesalahan Go 2 , " tetapi dalam retrospeksi kami tidak cukup menarik perhatian ke bagian itu dan tidak cukup mendorong diskusi tentang apakah masalah khusus itu benar. Proposal try mungkin merupakan solusi yang bagus untuk masalah yang diuraikan di sana, tetapi bagi banyak dari Anda itu bukan masalah untuk dipecahkan. Di masa depan kita perlu melakukan pekerjaan yang lebih baik dengan menarik perhatian pada pernyataan masalah awal ini dan memastikan bahwa ada kesepakatan luas tentang masalah yang perlu dipecahkan.

(Mungkin juga bahwa pernyataan masalah penanganan kesalahan sepenuhnya dikalahkan dengan menerbitkan draf desain generik pada hari yang sama.)

Pada topik yang lebih luas tentang apa yang harus ditingkatkan tentang penanganan kesalahan Go, kami akan sangat senang melihat laporan pengalaman tentang aspek penanganan kesalahan apa di Go yang paling bermasalah bagi Anda di basis kode dan lingkungan kerja Anda sendiri dan seberapa besar pengaruh solusi yang baik akan miliki dalam pengembangan Anda sendiri. Jika Anda memang menulis laporan seperti itu, harap pasang tautan di laman Go2ErrorHandlingFeedback .

Terima kasih kepada semua orang yang berpartisipasi dalam diskusi ini, di sini dan di tempat lain. Seperti yang telah ditunjukkan oleh Russ Cox sebelumnya, diskusi di seluruh komunitas seperti ini adalah yang terbaik dari sumber terbuka . Kami sangat menghargai bantuan semua orang untuk memeriksa proposal khusus ini dan secara lebih umum dalam membahas cara terbaik untuk meningkatkan status penanganan kesalahan di Go.

Robert Griesemer, untuk Komite Peninjau Proposal.

Terima kasih, Go Team, untuk pekerjaan yang masuk ke proposal percobaan. Dan terima kasih kepada para komentator yang berjuang dengannya dan mengusulkan alternatif. Terkadang hal-hal ini mengambil kehidupan mereka sendiri. Terima kasih Go Team telah mendengarkan dan menanggapi dengan tepat.

Ya!

Terima kasih semuanya untuk hashing ini sehingga kami bisa mendapatkan hasil terbaik!

Panggilan ini untuk daftar masalah dan pengalaman negatif dengan penanganan kesalahan Go. Namun,
Saya dan Tim sangat senang dengan xerrors.As, xerrors.Is dan xerrors.Errorf dalam produksi. Penambahan baru ini sepenuhnya mengubah penanganan kesalahan dengan cara yang luar biasa bagi kami sekarang karena kami telah sepenuhnya menerima perubahan tersebut. Saat ini kami tidak menemukan masalah atau kebutuhan yang tidak ditangani.

@griesemer Hanya ingin mengucapkan terima kasih (dan mungkin banyak orang lain yang bekerja dengan Anda) atas kesabaran dan upaya Anda.

bagus!

@griesemer Terima kasih dan semua orang di tim Go karena tanpa lelah mendengarkan semua umpan balik dan menerima semua pendapat kami yang beragam.

Jadi mungkin sekarang adalah saat yang tepat untuk mengakhiri utas ini dan beralih ke hal-hal yang akan datang?

@griesemer @rsc dan @all , keren, terima kasih semuanya. bagi saya, ini adalah diskusi/identifikasi/klarifikasi yang bagus. peningkatan beberapa bagian seperti masalah 'kesalahan' dalam perjalanan, perlu diskusi lebih terbuka (dalam proposal dan komentar ...) untuk mengidentifikasi / memperjelas masalah inti terlebih dahulu.

ps, x/xerrors bagus untuk saat ini.

(mungkin masuk akal untuk mengunci utas ini juga ...)

Terima kasih kepada tim dan komunitas yang telah terlibat dalam hal ini. Saya suka berapa banyak orang yang peduli dengan Go.

Saya sangat berharap masyarakat melihat terlebih dahulu upaya dan keterampilan yang masuk ke dalam proposal percobaan, dan kemudian semangat keterlibatan yang mengikuti yang membantu kami mencapai keputusan ini. Masa depan Go sangat cerah jika kita bisa terus seperti ini, apalagi jika kita semua bisa menjaga sikap positif.

func M() (Data, kesalahan){
a, err1 := A()
b, err2 := B()
kembali b, nihil
} => (jika err1 != nil){ mengembalikan a, err1}.
(jika err2 != nil){ kembali b, err2}

Oke... Saya menyukai proposal ini, tetapi saya menyukai cara komunitas dan tim Go bereaksi dan terlibat dalam diskusi yang konstruktif, meskipun terkadang agak kasar.

Saya punya 2 pertanyaan tentang hasil ini:
1/ Apakah "penanganan kesalahan" masih menjadi bidang penelitian?
2/ Apakah penangguhan perbaikan diprioritaskan ulang?

Ini membuktikan sekali lagi bahwa komunitas Go didengar dan mampu mendiskusikan proposal perubahan bahasa yang kontroversial. Seperti perubahan yang masuk ke dalam bahasa, perubahan yang tidak merupakan perbaikan. Terima kasih, tim dan komunitas Go, atas kerja keras dan diskusi beradab seputar proposal ini!

Bagus sekali!

luar biasa,cukup membantu

Mungkin saya terlalu terikat pada Go, tapi saya pikir ada satu poin yang ditunjukkan di sini, seperti
Russ menjelaskan: ada satu titik di mana komunitas tidak hanya
ayam tanpa kepala, itu adalah kekuatan yang harus diperhitungkan dan harus
dimanfaatkan untuk kebaikannya sendiri.

Berkat koordinasi yang diberikan oleh Tim Go, kami dapat
semua bangga bahwa kami sampai pada suatu kesimpulan, yang dapat kami jalani dan
akan mengunjungi kembali, tidak diragukan lagi, ketika kondisinya lebih matang.

Mari berharap rasa sakit yang dirasakan di sini akan melayani kita dengan baik di masa depan
(bukankah itu menyedihkan, jika tidak?).

Lucio.

Saya tidak setuju dengan keputusan itu. Namun saya sangat mendukung pendekatan yang telah dilakukan oleh tim go. Memiliki diskusi yang luas di komunitas dan mempertimbangkan umpan balik dari pengembang adalah apa yang dimaksud dengan open source.

Saya bertanya-tanya apakah optimasi penangguhan akan datang juga. Saya suka membuat anotasi kesalahan dengannya dan xerrors bersama-sama cukup banyak dan itu terlalu mahal sekarang.

@pierrec Saya pikir kita perlu pemahaman yang lebih jelas tentang perubahan apa yang berguna dalam penanganan kesalahan. Beberapa perubahan nilai kesalahan akan ada di rilis 1.13 mendatang (https://tip.golang.org/doc/go1.13#errors), dan kami akan mendapatkan pengalaman dengannya. Selama diskusi ini kita telah melihat banyak banyak proposal penanganan kesalahan sintaksis, dan akan sangat membantu jika orang dapat memilih dan mengomentari salah satu yang tampaknya sangat berguna. Secara umum, seperti yang dikatakan @griesemer , laporan pengalaman akan sangat membantu.

Ini juga akan berguna untuk lebih memahami sejauh mana sintaks penanganan kesalahan bermasalah bagi orang yang baru mengenal bahasa tersebut, meskipun itu akan sulit ditentukan.

Ada pekerjaan aktif untuk meningkatkan kinerja defer di https://golang.org/cl/183677 , dan kecuali ada kendala besar yang dihadapi, saya berharap itu bisa masuk ke rilis 1.14.

@griesemer @ianlancetaylor @rsc Apakah Anda masih berencana untuk mengatasi kesalahan penanganan verbositas, dengan proposal lain memecahkan beberapa atau semua masalah yang diangkat di sini?

Jadi, terlambat ke pesta, karena ini telah ditolak, tetapi untuk diskusi selanjutnya tentang topik ini, bagaimana dengan sintaks pengembalian bersyarat seperti ternary? (Saya tidak melihat sesuatu yang mirip dengan ini dalam pemindaian topik saya atau melihat tampilannya yang diposting Russ Cox di Twitter.) Contoh:

f, err := Foo()
return err != nil ? nil, err

Mengembalikan nil, err jika err bukan nihil, melanjutkan eksekusi jika err adalah nihil. Bentuk pernyataannya adalah

return <boolean expression> ? <return values>

dan ini akan menjadi gula sintaksis untuk:

if <boolean expression> {
    return <return values>
}

Manfaat utamanya adalah ini lebih fleksibel daripada kata kunci check atau fungsi bawaan try , karena dapat memicu lebih dari sekadar kesalahan (mis. return err != nil || f == nil ? nil, fmt.Errorf("failed to get Foo") , pada lebih daripada hanya kesalahan yang bukan nihil (mis. return err != nil && err != io.EOF ? nil, err ), dll, sementara masih cukup intuitif untuk dipahami saat dibaca (terutama bagi mereka yang terbiasa membaca operator ternary dalam bahasa lain).

Ini juga memastikan bahwa penanganan kesalahan _masih terjadi di lokasi panggilan_, daripada terjadi secara otomatis berdasarkan beberapa pernyataan penangguhan. Salah satu keluhan terbesar yang saya miliki dengan proposal asli adalah bahwa ia mencoba, dalam beberapa hal, membuat _penanganan_ kesalahan yang sebenarnya menjadi proses implisit yang terjadi secara otomatis ketika kesalahan tidak nol, tanpa indikasi yang jelas bahwa aliran kontrol akan kembali jika panggilan fungsi mengembalikan kesalahan non-nol. Seluruh _point_ Go menggunakan pengembalian kesalahan eksplisit alih-alih sistem seperti pengecualian adalah untuk mendorong pengembang untuk secara eksplisit dan sengaja memeriksa dan menangani kesalahan mereka, daripada hanya membiarkan mereka menyebarkan tumpukan untuk, secara teori, ditangani di beberapa titik lebih tinggi ke atas. Setidaknya pernyataan pengembalian yang eksplisit, jika bersyarat, dengan jelas menjelaskan apa yang terjadi.

@ngrilly Seperti yang dikatakan @griesemer , saya pikir kita perlu lebih memahami aspek penanganan kesalahan yang menurut programmer Go paling bermasalah.

Berbicara secara pribadi, saya tidak berpikir proposal yang menghilangkan sedikit verbositas layak dilakukan. Bagaimanapun, bahasanya bekerja dengan cukup baik hari ini. Setiap perubahan membawa biaya. Jika kita akan melakukan perubahan, kita membutuhkan manfaat yang signifikan. Saya pikir proposal ini memang memberikan manfaat yang signifikan dalam mengurangi verbositas, tetapi jelas ada segmen signifikan dari programmer Go yang merasa bahwa biaya tambahan yang dikenakan terlalu tinggi. Saya tidak tahu apakah ada jalan tengah di sini. Dan saya tidak tahu apakah masalahnya layak untuk ditangani sama sekali.

@kaedys Masalah tertutup dan sangat bertele-tele ini jelas bukan tempat yang tepat untuk membahas sintaks alternatif khusus untuk penanganan kesalahan.

@ianlancetaylor

Saya pikir proposal ini memang memberikan manfaat yang signifikan dalam mengurangi verbositas, tetapi jelas ada segmen signifikan dari programmer Go yang merasa bahwa biaya tambahan yang dikenakan terlalu tinggi.

Saya khawatir ada bias seleksi diri. Go dikenal dengan penanganan kesalahan verbose, dan kurangnya obat generik. Ini secara alami menarik pengembang yang tidak peduli dengan dua masalah ini. Sementara itu, pengembang lain tetap menggunakan bahasa mereka saat ini (Java, C++, C#, Python, Ruby, dll.) dan/atau beralih ke bahasa yang lebih modern (Rust, TypeScript, Kotlin, Swift, Elixir, dll.) karena ini . Saya tahu banyak pengembang yang menghindari Go kebanyakan karena alasan ini.

Saya juga berpikir ada bias konfirmasi yang bermain. Gophers telah digunakan untuk mempertahankan penanganan kesalahan verbose dan kurangnya penanganan kesalahan ketika orang mengkritik Go. Ini membuat lebih sulit untuk menilai proposal secara objektif seperti try.

Steve Klabnik menerbitkan komentar menarik di Reddit beberapa hari yang lalu. Dia menentang memperkenalkan ? di Rust, karena itu "dua cara untuk menulis hal yang sama" dan itu "terlalu implisit". Tapi sekarang, setelah menulis lebih dari beberapa baris kode, ? adalah salah satu fitur favoritnya.

@ngrilly Saya setuju dengan komentar Anda. Bias tersebut sangat sulit untuk dihindari. Apa yang akan sangat membantu adalah pemahaman yang lebih jelas tentang berapa banyak orang yang menghindari Go karena penanganan kesalahan verbose. Saya yakin jumlahnya bukan nol, tetapi sulit diukur.

Yang mengatakan, itu juga benar bahwa try memperkenalkan perubahan baru dalam aliran kontrol yang sulit untuk dilihat, dan bahwa meskipun try dimaksudkan untuk membantu menangani kesalahan itu tidak membantu dengan kesalahan anotasi .

Terima kasih atas kutipan dari Steve Klabnik. Meskipun saya menghargai dan setuju dengan sentimen tersebut, perlu dipertimbangkan bahwa sebagai bahasa Rust tampaknya agak lebih bergantung pada detail sintaksis daripada Go.

Sebagai pendukung proposal ini, saya tentu saja kecewa karena sekarang telah ditarik, meskipun menurut saya tim Go telah melakukan hal yang benar dalam situasi tersebut.

Satu hal yang sekarang tampak cukup jelas adalah bahwa mayoritas pengguna Go tidak menganggap verbositas penanganan kesalahan sebagai masalah dan saya pikir itu adalah sesuatu yang kita semua harus jalani meskipun itu menunda calon pengguna baru. .

Saya telah kehilangan hitungan berapa banyak proposal alternatif yang telah saya baca dan, sementara beberapa cukup bagus, saya belum melihat satu pun yang menurut saya layak untuk diadopsi jika try benar-benar hilang. Jadi peluang beberapa proposal jalan tengah yang sekarang muncul tampaknya jauh bagi saya.

Pada catatan yang lebih positif, diskusi saat ini telah menunjukkan cara di mana semua kesalahan potensial dalam suatu fungsi dapat didekorasi dengan cara yang sama dan di tempat yang sama (menggunakan defer atau bahkan goto ) yang sebelumnya tidak saya pertimbangkan dan saya berharap tim Go setidaknya akan mempertimbangkan untuk mengubah go fmt untuk memungkinkan satu pernyataan if ditulis pada satu baris yang setidaknya akan membuat penanganan kesalahan _look_ lebih ringkas meskipun tidak benar-benar menghapus boilerplate apa pun.

@pierrec

1/ Apakah "penanganan kesalahan" masih menjadi bidang penelitian?

Sudah, selama lebih dari 50 tahun! Tampaknya tidak ada teori keseluruhan atau bahkan panduan praktis untuk penanganan kesalahan yang konsisten dan sistematis. Di tanah Go (seperti untuk bahasa lain) bahkan ada kebingungan tentang apa itu kesalahan. Misalnya, EOF mungkin merupakan kondisi luar biasa ketika Anda mencoba membaca file tetapi mengapa itu adalah kesalahan? Apakah itu kesalahan yang sebenarnya atau tidak benar-benar tergantung pada konteksnya. Dan ada masalah lain seperti itu.

Mungkin diskusi tingkat yang lebih tinggi diperlukan (meskipun tidak di sini).

Terima kasih @griesemer @rsc dan semua orang yang terlibat dalam pengusulan. Banyak orang lain telah mengatakannya di atas, dan perlu diulangi bahwa upaya Anda dalam memikirkan masalah, menulis proposal, dan mendiskusikannya dengan itikad baik, dihargai. Terima kasih.

@ianlancetaylor

Terima kasih atas kutipan dari Steve Klabnik. Meskipun saya menghargai dan setuju dengan sentimen tersebut, perlu dipertimbangkan bahwa sebagai bahasa Rust tampaknya agak lebih bergantung pada detail sintaksis daripada Go.

Saya setuju secara umum tentang Rust lebih mengandalkan daripada Go pada detail sintaksis, tapi saya rasa ini tidak berlaku untuk diskusi khusus tentang penanganan kesalahan verbositas.

Kesalahan adalah nilai di Rust seperti di Go. Anda dapat menanganinya menggunakan aliran kontrol standar, seperti di Go. Di Rust versi pertama, itu adalah satu-satunya cara untuk menangani kesalahan, seperti di Go. Kemudian mereka memperkenalkan makro try! , yang secara mengejutkan mirip dengan proposal fungsi bawaan try . Mereka akhirnya menambahkan operator ? , yang merupakan variasi sintaksis dan generalisasi dari makro try! , tetapi ini tidak perlu untuk menunjukkan kegunaan try , dan faktanya bahwa komunitas Rust tidak menyesal telah menambahkannya.

Saya sangat menyadari perbedaan besar antara Go dan Rust, tetapi pada topik penanganan kesalahan verbositas, saya pikir pengalaman mereka dapat dialihkan ke Go. RFC dan diskusi yang terkait dengan try! dan ? sangat layak untuk dibaca. Saya benar-benar terkejut dengan betapa miripnya masalah dan argumen yang mendukung dan menentang perubahan bahasa.

@griesemer , Anda mengumumkan keputusan untuk menolak proposal try , dalam bentuknya saat ini, tetapi Anda tidak mengatakan apa yang akan dilakukan tim Go selanjutnya.

Apakah Anda masih berencana untuk mengatasi kesalahan penanganan verbositas, dengan proposal lain yang akan menyelesaikan masalah yang diangkat dalam diskusi ini (men-debug cetakan, cakupan kode, dekorasi kesalahan yang lebih baik, dll.)?

Saya setuju secara umum tentang Rust lebih mengandalkan daripada Go pada detail sintaksis, tapi saya rasa ini tidak berlaku untuk diskusi khusus tentang penanganan kesalahan verbositas.

Karena orang lain masih menambahkan dua sen mereka, saya kira masih ada ruang bagi saya untuk melakukan hal yang sama.

Meskipun saya telah memprogram sejak 1987, saya baru bekerja dengan Go selama sekitar satu tahun. Kembali sekitar 18 bulan yang lalu ketika saya sedang mencari bahasa baru untuk memenuhi kebutuhan tertentu, saya melihat Go dan Rust. Saya memutuskan Go karena saya merasa kode Go lebih mudah dipelajari dan digunakan, dan kode Go jauh lebih mudah dibaca karena Go tampaknya lebih memilih kata-kata untuk menyampaikan makna daripada simbol singkat.

Jadi saya sendiri akan sangat tidak senang melihat Go menjadi lebih seperti Rust , termasuk penggunaan tanda seru ( ! ) dan tanda tanya ( ? ) untuk menyiratkan makna.

Dalam nada yang sama, saya pikir pengenalan makro akan mengubah sifat Go dan akan menghasilkan ribuan dialek Go seperti halnya dengan Ruby. Jadi saya harap makro tidak pernah ditambahkan Go, meskipun tebakan saya ada sedikit kemungkinan itu terjadi, untungnya IMO.

jmtcw

@ianlancetaylor

Apa yang akan sangat membantu adalah pemahaman yang lebih jelas tentang berapa banyak orang yang menghindari Go karena penanganan kesalahan verbose. Saya yakin jumlahnya bukan nol, tetapi sulit diukur.

Ini bukan "ukuran" semata, tetapi diskusi Berita Peretas ini memberikan puluhan komentar dari pengembang yang tidak senang dengan penanganan kesalahan Go karena verbositasnya (dan beberapa komentar menjelaskan alasan mereka dan memberikan contoh kode): https://news.ycombinator. com/item?id=20454966.

Pertama-tama, terima kasih kepada semua orang atas umpan balik yang mendukung pada keputusan akhir, bahkan jika keputusan itu tidak memuaskan bagi banyak orang. Ini benar-benar upaya tim, dan saya sangat senang bahwa kita semua berhasil melewati diskusi yang intens dengan cara yang sopan dan hormat secara keseluruhan.

@ngrilly Berbicara hanya untuk diri saya sendiri, saya masih berpikir akan lebih baik untuk mengatasi kesalahan penanganan verbositas di beberapa titik. Yang mengatakan, kami baru saja mendedikasikan sedikit waktu dan energi untuk ini selama setengah tahun terakhir dan terutama 3 bulan terakhir, dan kami cukup senang dengan proposal tersebut, namun kami jelas meremehkan kemungkinan reaksi terhadapnya. Sekarang sangat masuk akal untuk mundur, mencerna dan menyaring umpan balik, dan kemudian memutuskan langkah terbaik berikutnya.

Juga, secara realistis, karena kami tidak memiliki sumber daya yang tidak terbatas, saya melihat pemikiran tentang dukungan bahasa untuk penanganan kesalahan sedikit lebih baik untuk mendukung lebih banyak kemajuan di bidang lain, terutama bekerja pada obat generik, setidaknya untuk beberapa bulan ke depan. if err != nil mungkin mengganggu, tapi itu bukan alasan untuk tindakan segera.

Jika Anda ingin melanjutkan diskusi, saya ingin dengan lembut menyarankan kepada semua orang untuk pindah dari sini dan melanjutkan diskusi di tempat lain, dalam masalah terpisah (jika ada proposal yang jelas), atau di forum lain yang lebih cocok untuk diskusi terbuka. Masalah ini ditutup, setelah semua. Terima kasih.

Saya khawatir ada bias seleksi diri.

Saya ingin menciptakan istilah baru di sini dan sekarang: "bias kreator". Jika seseorang bersedia menempatkan pekerjaan, mereka harus diberi manfaat dari keraguan.

Sangat mudah bagi galeri kacang untuk berteriak keras dan lebar di forum yang tidak terkait bagaimana mereka tidak menyukai solusi yang diusulkan untuk suatu masalah. Ini juga sangat mudah bagi semua orang untuk menulis upaya tidak lengkap 3 paragraf untuk solusi yang berbeda (tanpa pekerjaan nyata yang disajikan di sampingan). Jika seseorang setuju dengan status quo, ok. Poin yang adil. Menyajikan hal lain sebagai solusi tanpa proposal lengkap memberi Anda -10k poin.

Saya tidak mendukung atau menentang percobaan, tetapi saya percaya penilaian Go Teams tentang masalah ini, sejauh ini penilaian mereka telah memberikan bahasa yang sangat baik, jadi saya pikir apa pun yang mereka putuskan akan berhasil untuk saya, coba atau tidak coba, saya pertimbangkan kita perlu memahami sebagai orang luar, bahwa pengelola memiliki visibilitas yang lebih luas atas masalah ini. sintaks yang bisa kita diskusikan sepanjang hari. Saya ingin mengucapkan terima kasih kepada semua orang yang telah bekerja atau sedang mencoba untuk meningkatkan go pada saat ini atas upaya mereka, kami berterima kasih dan berharap untuk perbaikan baru (non-mundur-melanggar) di perpustakaan bahasa dan Runtime jika ada yang dianggap bermanfaat oleh kalian.

Ini juga sangat mudah bagi semua orang untuk menulis upaya tidak lengkap 3 paragraf untuk solusi yang berbeda (tanpa pekerjaan nyata yang disajikan di sampingan).

Satu-satunya hal yang saya (dan sejumlah orang lain) ingin membuat try berguna adalah argumen opsional untuk memungkinkannya mengembalikan versi kesalahan yang dibungkus alih-alih kesalahan yang tidak berubah. Saya tidak berpikir itu membutuhkan banyak pekerjaan desain.

Oh tidak.

Saya melihat. Pergi ingin membuat sesuatu yang berbeda dari bahasa lain.

Mungkin seseorang harus mengunci masalah ini? Diskusi mungkin lebih cocok di tempat lain.

Masalah ini sudah begitu lama sehingga mengunci sepertinya tidak ada gunanya.

Semuanya, perlu diketahui bahwa masalah ini telah ditutup, dan komentar yang Anda buat di sini hampir pasti akan diabaikan selamanya. Jika itu baik-baik saja dengan Anda, tinggalkan komentar.

Jika seseorang membenci kata coba yang membuat mereka memikirkan bahasa Java, C*, saya sarankan untuk tidak menggunakan 'coba' tetapi kata lain seperti 'bantuan' atau 'harus' atau 'checkError'.. (abaikan saya)

Jika seseorang membenci kata coba yang membuat mereka memikirkan bahasa Java, C*, saya sarankan untuk tidak menggunakan 'coba' tetapi kata lain seperti 'bantuan' atau 'harus' atau 'checkError'.. (abaikan saya)

Akan selalu ada kata kunci dan konsep yang tumpang tindih yang memiliki perbedaan semantik kecil atau besar dalam bahasa yang cukup dekat satu sama lain (seperti bahasa keluarga C). Fitur bahasa tidak boleh menimbulkan kebingungan di dalam bahasa itu sendiri, perbedaan antar bahasa akan selalu terjadi.

buruk. ini anti pola, tidak menghormati penulis proposal itu

@alersenkevich Harap sopan. Silakan lihat https://golang.org/conduct.

Saya pikir saya senang tentang keputusan untuk tidak melangkah lebih jauh dengan ini. Bagi saya ini terasa seperti peretasan cepat untuk menyelesaikan masalah kecil tentang jika err != nil berada di banyak baris. Kami tidak ingin mengasapi Go dengan kata kunci kecil untuk menyelesaikan hal-hal kecil seperti ini bukan? Inilah mengapa proposal dengan makro higienis https://github.com/golang/go/issues/32620 terasa lebih baik. Ini mencoba menjadi solusi yang lebih umum untuk membuka lebih banyak fleksibilitas dengan lebih banyak hal. Sintaks dan diskusi penggunaan sedang berlangsung di sana, jadi jangan hanya berpikir jika itu adalah makro C/C++. Intinya ada untuk membahas cara yang lebih baik untuk melakukan macro. Dengan itu, Anda dapat menerapkan percobaan Anda sendiri.

Saya akan menyukai umpan balik pada proposal serupa yang membahas masalah dengan penanganan kesalahan saat ini https://github.com/golang/go/issues/33161.

Jujur ini harus dibuka kembali, dari semua proposal penanganan err, ini yang paling waras.

@OneOfOne dengan hormat, saya tidak setuju bahwa ini harus dibuka kembali. Utas ini telah menetapkan bahwa ada batasan nyata dengan sintaksis. Mungkin Anda benar bahwa ini adalah proposal yang paling "waras": tetapi saya percaya bahwa status quo masih lebih waras.

Saya setuju bahwa if err != nil ditulis terlalu sering di Go- tetapi memiliki satu cara untuk kembali dari suatu fungsi sangat meningkatkan keterbacaan. Meskipun saya biasanya dapat mendukung proposal yang mengurangi kode boilerplate, biayanya tidak boleh mudah dibaca IMHO.

Saya tahu banyak pengembang menyesali kesalahan "tulisan tangan" saat memeriksa, tetapi sejujurnya kerapian sering bertentangan dengan keterbacaan. Go memiliki banyak pola mapan di sini dan di tempat lain yang mendorong cara tertentu dalam melakukan sesuatu, dan, menurut pengalaman saya, hasilnya adalah kode yang andal yang menua dengan baik. Ini sangat penting: kode dunia nyata harus dibaca dan dipahami berkali-kali sepanjang masa pakainya, tetapi hanya pernah ditulis sekali. Overhead kognitif adalah biaya nyata, bahkan untuk pengembang berpengalaman.

Dari pada:

f := try(os.Open(filename))

saya harapkan:

f := try os.Open(filename)

Semuanya, perlu diketahui bahwa masalah ini telah ditutup, dan komentar yang Anda buat di sini hampir pasti akan diabaikan selamanya. Jika itu baik-baik saja dengan Anda, tinggalkan komentar.
—@ianlancetaylor

Alangkah baiknya jika kita bisa menggunakan try untuk blok kode bersama dengan cara menangani kesalahan saat ini.
Sesuatu seperti ini:

// Generic Error Handler
handler := func(err error) error {
    return fmt.Errorf("We encounter an error: %v", err)  
}
a := "not Integer"
b := "not Integer"

try(handler){
    f := os.Open(filename)
    x := strconv.Atoi(a)
    y, err := strconv.Atoi(b) // <------ If you want a specific error handler
    if err != nil {
        panic("We cannot covert b to int")   
    }
}

Kode di atas tampaknya lebih bersih daripada komentar awal. Saya berharap saya bisa tujuan ini.

Saya membuat proposal baru #35179

val := coba f() (err){
panik (err)
}

Saya harap begitu:

i, err := strconv.Atoi("1")
if err {
    println("ERROR")
} else {
    println(i)
}

atau

i, err := strconv.Atoi("1")
if err {
    io.EOF:
        println("EOF")
    io.ErrShortWrite:
        println("ErrShortWrite")
} else {
    println(i)
}

@myroid Saya tidak keberatan jika contoh kedua Anda dibuat sedikit lebih umum dalam bentuk pernyataan switch-else :

``` pergi
saya, err := strconv.Atoi("1")
beralih err != nihil; salah {
kasus io.EOF:
println("EOF")
kasus io.ErrShortWrite:
println("ErrShortWrite")
} lain {
println(i)
}

@piotrkowalczuk Kode Anda terlihat jauh lebih baik daripada saya. Saya pikir kodenya bisa lebih ringkas.

i, err := strconv.Atoi("1")
switch err {
case io.EOF:
    println("EOF")
case io.ErrShortWrite:
    println("ErrShortWrite")
} else {
    println(i)
}

Ini tidak mempertimbangkan opsi akan ada mata dari tipe yang berbeda

Perlu ada
Kasus err! = nihil

Untuk kesalahan yang tidak ditangkap oleh pengembang secara eksplisit

Pada Jumat, 8 November 2019, 12:06 Yang Fan, [email protected] menulis:

@piotrkowalczuk https://github.com/piotrkowalczuk Kode Anda terlihat banyak
lebih baik dari milikku. Saya pikir kodenya bisa lebih ringkas.

saya, err := strconv.Atoi("1")switch err {case io.EOF:
println("EOF")case io.ErrShortWrite:
println("ErrShortWrite")
} lain {
println(i)
}


Anda menerima ini karena Anda disebutkan.
Balas email ini secara langsung, lihat di GitHub
https://github.com/golang/go/issues/32437?email_source=notifications&email_token=ABNEY4VH4KS2OX4M5BVH673QSU24DA5CNFSM4HTGCZ72YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNVH673QSU24DA5CNFSM4HTGCZ72YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LN5WZissueCOMEDPTYW150XHJKTDN5
atau berhenti berlangganan
https://github.com/notifications/unsubscribe-auth/ABNEY4W4XIIHGUGIW2KXRPTQSU24DANCNFSM4HTGCZ7Q
.

switch tidak membutuhkan else , ia memiliki default .

Saya telah membuka https://github.com/golang/go/issues/39890 yang mengusulkan sesuatu yang mirip dengan guard Swift harus mengatasi beberapa masalah dengan proposal ini:

  • aliran kontrol
  • lokasi penanganan kesalahan
  • keterbacaan

Itu belum mendapatkan banyak daya tarik tetapi mungkin menarik bagi mereka yang berkomentar di sini.

Apakah halaman ini membantu?
0 / 5 - 0 peringkat