Go: proposal: Go 2: sederhanakan penanganan kesalahan dengan || akhiran err

Dibuat pada 25 Jul 2017  ·  519Komentar  ·  Sumber: golang/go

Sudah banyak usulan untuk menyederhanakan penanganan error di Go, semua berdasarkan keluhan umum bahwa kode Go terlalu banyak berisi baris.

if err != nil {
    return err
}

Saya tidak yakin ada masalah di sini yang harus diselesaikan, tetapi karena masalah itu terus muncul, saya akan mengeluarkan ide ini.

Salah satu masalah inti dengan sebagian besar saran untuk menyederhanakan penanganan kesalahan adalah bahwa mereka hanya menyederhanakan dua cara menangani kesalahan, tetapi sebenarnya ada tiga:

  1. abaikan kesalahannya
  2. kembalikan kesalahan tanpa dimodifikasi
  3. kembalikan kesalahan dengan informasi kontekstual tambahan

Sudah mudah (mungkin terlalu mudah) untuk mengabaikan kesalahan (lihat #20803). Banyak proposal yang ada untuk penanganan kesalahan membuatnya lebih mudah untuk mengembalikan kesalahan yang tidak dimodifikasi (misalnya, #16225, #18721, #21146, #21155). Beberapa membuatnya lebih mudah untuk mengembalikan kesalahan dengan informasi tambahan.

Proposal ini secara longgar didasarkan pada bahasa shell Perl dan Bourne, sumber ide bahasa yang subur. Kami memperkenalkan jenis pernyataan baru, mirip dengan pernyataan ekspresi: ekspresi panggilan diikuti oleh || . Tata bahasanya adalah:

PrimaryExpr Arguments "||" Expression

Demikian pula kami memperkenalkan jenis baru dari pernyataan tugas:

ExpressionList assign_op PrimaryExpr Arguments "||" Expression

Meskipun tata bahasa menerima jenis apa pun setelah || dalam kasus non-tugas, satu-satunya jenis yang diizinkan adalah jenis yang dideklarasikan sebelumnya error . Ekspresi berikut || harus memiliki tipe yang dapat ditetapkan ke error . Ini mungkin bukan tipe boolean, bahkan bukan tipe boolean bernama yang dapat ditetapkan ke error . (Pembatasan terakhir ini diperlukan untuk membuat proposal ini kompatibel dengan bahasa yang ada.)

Jenis pernyataan baru ini hanya diizinkan di badan fungsi yang memiliki setidaknya satu parameter hasil, dan tipe parameter hasil terakhir harus tipe yang dideklarasikan sebelumnya error . Fungsi yang dipanggil juga harus memiliki setidaknya satu parameter hasil, dan tipe parameter hasil terakhir harus tipe yang dideklarasikan sebelumnya error .

Saat menjalankan pernyataan ini, ekspresi panggilan dievaluasi seperti biasa. Jika itu adalah pernyataan penugasan, hasil panggilan ditugaskan ke operan sisi kiri seperti biasa. Kemudian hasil panggilan terakhir, yang seperti dijelaskan di atas harus bertipe error , dibandingkan dengan nil . Jika hasil panggilan terakhir bukan nil , pernyataan return akan dieksekusi secara implisit. Jika fungsi panggilan memiliki beberapa hasil, nilai nol dikembalikan untuk semua hasil kecuali yang terakhir. Ekspresi setelah || dikembalikan sebagai hasil terakhir. Seperti dijelaskan di atas, hasil terakhir dari fungsi pemanggilan harus memiliki tipe error , dan ekspresi harus dapat ditetapkan untuk tipe error .

Dalam kasus non-tugas, ekspresi dievaluasi dalam lingkup di mana variabel baru err diperkenalkan dan disetel ke nilai hasil terakhir dari pemanggilan fungsi. Ini memungkinkan ekspresi untuk dengan mudah merujuk ke kesalahan yang dikembalikan oleh panggilan. Dalam kasus penugasan, ekspresi dievaluasi dalam lingkup hasil panggilan, dan dengan demikian dapat merujuk ke kesalahan secara langsung.

Itu proposal lengkapnya.

Misalnya, fungsi os.Chdir saat ini

func Chdir(dir string) error {
    if e := syscall.Chdir(dir); e != nil {
        return &PathError{"chdir", dir, e}
    }
    return nil
}

Di bawah proposal ini, dapat ditulis sebagai

func Chdir(dir string) error {
    syscall.Chdir(dir) || &PathError{"chdir", dir, err}
    return nil
}

Saya menulis proposal ini terutama untuk mendorong orang-orang yang ingin menyederhanakan penanganan kesalahan Go untuk memikirkan cara-cara mempermudah membungkus konteks di sekitar kesalahan, bukan hanya mengembalikan kesalahan yang tidak dimodifikasi.

FrozenDueToAge Go2 LanguageChange NeedsInvestigation Proposal error-handling

Komentar yang paling membantu

Ide sederhana, dengan dukungan untuk dekorasi kesalahan, tetapi membutuhkan perubahan bahasa yang lebih drastis (jelas bukan untuk go1.10) adalah pengenalan kata kunci check .

Itu akan memiliki dua bentuk: check A dan check A, B .

Baik A dan B harus error . Bentuk kedua hanya akan digunakan saat mendekorasi kesalahan; orang yang tidak membutuhkan atau ingin menghias kesalahan mereka akan menggunakan bentuk yang lebih sederhana.

Formulir 1 (centang A)

check A mengevaluasi A . Jika nil , itu tidak menghasilkan apa-apa. Jika bukan nil , check bertindak seperti return {<zero>}*, A .

Contoh

  • Jika suatu fungsi baru saja mengembalikan kesalahan, itu dapat digunakan sebaris dengan check , jadi
err := UpdateDB()    // signature: func UpdateDb() error
if err != nil {
    return err
}

menjadi

check UpdateDB()
  • Untuk fungsi dengan beberapa nilai kembalian, Anda harus menetapkan, seperti yang kita lakukan sekarang.
a, b, err := Foo()    // signature: func Foo() (string, string, error)
if err != nil {
    return "", "", err
}

// use a and b

menjadi

a, b, err := Foo()
check err

// use a and b

Formulir ke-2 (centang A, B)

check A, B mengevaluasi A . Jika nil , itu tidak menghasilkan apa-apa. Jika tidak nil , check bertindak seperti return {<zero>}*, B .

Ini untuk kebutuhan dekorasi kesalahan. Kami masih memeriksa A , tetapi B yang digunakan dalam return implisit.

Contoh

a, err := Bar()    // signature: func Bar() (string, error)
if err != nil {
    return "", &BarError{"Bar", err}
}

menjadi

a, err := Foo()
check err, &BarError{"Bar", err}

Catatan

Ini adalah kesalahan kompilasi untuk

  • gunakan pernyataan check untuk hal-hal yang tidak bernilai error
  • gunakan check dalam fungsi dengan nilai kembalian tidak dalam bentuk { type }*, error

Formulir dua-expr check A, B dihubung pendek. B tidak dievaluasi jika A adalah nil .

Catatan tentang kepraktisan

Ada dukungan untuk kesalahan dekorasi, tetapi Anda membayar sintaks check A, B kikuk hanya ketika Anda benar-benar perlu mendekorasi kesalahan.

Untuk if err != nil { return nil, nil, err } boilerplate (yang sangat umum) check err sesingkat mungkin tanpa mengorbankan kejelasan (lihat catatan pada sintaks di bawah).

Catatan tentang sintaks

Saya berpendapat bahwa sintaks semacam ini ( check .. , di awal baris, mirip dengan return ) adalah cara yang baik untuk menghilangkan kesalahan pengecekan boilerplate tanpa menyembunyikan gangguan aliran kontrol yang pengembalian implisit memperkenalkan.

Kelemahan dari ide seperti <do-stuff> || <handle-err> dan <do-stuff> catch <handle-err> atas, atau a, b = foo()? diusulkan di utas lain, adalah mereka menyembunyikan modifikasi aliran kontrol dengan cara yang membuat aliran lebih sulit mengikuti; yang pertama dengan mesin || <handle-err> ditambahkan di akhir baris yang tampak biasa, yang terakhir dengan simbol kecil yang dapat muncul di mana-mana, termasuk di tengah dan di akhir baris kode yang tampak biasa, mungkin beberapa kali.

Pernyataan check akan selalu menjadi level teratas di blok saat ini, memiliki keunggulan yang sama dengan pernyataan lain yang mengubah aliran kontrol (misalnya, return ).

Semua 519 komentar

    syscall.Chdir(dir) || &PathError{"chdir", dir, e}

Dari mana datangnya e ? Salah ketik?

Atau apakah yang Anda maksud:

func Chdir(dir string) (e error) {
    syscall.Chdir(dir) || &PathError{"chdir", dir, e}
    return nil
}

(Yaitu, apakah err != nil check implisit terlebih dahulu menetapkan kesalahan ke parameter hasil, yang dapat dinamai untuk memodifikasinya lagi sebelum pengembalian implisit?)

Sigh, mengacaukan contoh saya sendiri. Sekarang diperbaiki: e seharusnya err . Proposal menempatkan err dalam ruang lingkup untuk menampung nilai kesalahan pemanggilan fungsi saat tidak dalam pernyataan penetapan.

Meskipun saya tidak yakin apakah saya setuju dengan ide atau sintaksnya, saya harus menghargai Anda karena memberikan perhatian untuk menambahkan konteks ke kesalahan sebelum mengembalikannya.

Ini mungkin menarik bagi @davecheney , yang menulis https://github.com/pkg/errors.

Apa yang terjadi dalam kode ini:

if foo, err := thing.Nope() || &PathError{"chdir", dir, err}; err == nil || ignoreError {
}

(Saya minta maaf jika ini bahkan tidak mungkin tanpa bagian || &PathError{"chdir", dir, e} ; Saya mencoba untuk mengungkapkan bahwa ini terasa seperti penggantian yang membingungkan dari perilaku yang ada, dan pengembalian implisit adalah ... licik?)

@object88 Saya akan baik-baik saja dengan tidak mengizinkan kasus baru ini di SimpleStmt seperti yang digunakan dalam pernyataan if dan for dan switch . Itu mungkin yang terbaik meskipun akan sedikit memperumit tata bahasa.

Tetapi jika kita tidak melakukannya, maka yang terjadi adalah jika thing.Nope() mengembalikan kesalahan non-nil, maka fungsi panggilan kembali dengan &PathError{"chdir", dir, err} (di mana err adalah variabel yang disetel oleh panggilan ke thing.Nope() ). Jika thing.Nope() mengembalikan kesalahan nil , maka kita tahu pasti bahwa err == nil benar dalam kondisi pernyataan if , dan juga isi dari jika pernyataan dijalankan. Variabel ignoreError tidak pernah dibaca. Tidak ada ambiguitas atau mengesampingkan perilaku yang ada di sini; penanganan || diperkenalkan di sini hanya diterima jika ekspresi setelah || bukan merupakan nilai boolean, yang berarti tidak akan dikompilasi saat ini.

Saya setuju bahwa pengembalian implisit itu licik.

Ya, contoh saya sangat buruk. Tetapi tidak mengizinkan operasi di dalam if , for , atau switch akan menyelesaikan banyak potensi kebingungan.

Karena bilah pertimbangan umumnya adalah sesuatu yang sulit dilakukan dalam bahasa apa adanya, saya memutuskan untuk melihat seberapa sulit varian ini untuk dikodekan dalam bahasa tersebut. Tidak jauh lebih sulit dari yang lain: https://play.golang.org/p/9B3Sr7kj39

Saya sangat tidak menyukai semua proposal ini untuk membuat satu jenis nilai dan satu posisi dalam argumen pengembalian menjadi istimewa. Yang ini dalam beberapa hal sebenarnya lebih buruk karena juga membuat err nama khusus dalam konteks khusus ini.

Meskipun saya tentu setuju bahwa orang (termasuk saya!) harus lebih lelah mengembalikan kesalahan tanpa konteks tambahan.

Ketika ada nilai kembalian lainnya, seperti

if err != nil {
  return 0, nil, "", Struct{}, wrap(err)
}

itu pasti bisa melelahkan untuk dibaca. Saya agak menyukai saran @nigeltao untuk return ..., err di https://github.com/golang/go/issues/19642#issuecomment -288559297

Jika saya mengerti dengan benar, untuk membangun pohon sintaksis, pengurai perlu mengetahui jenis variabel untuk membedakannya

boolean := BoolFunc() || BoolExpr

dan

err := FuncReturningError() || Expr

Ini tidak terlihat bagus.

kurang itu lebih...

Ketika kembali ExpressionList berisi dua elemen lebih banyak, bagaimana cara kerjanya?

BTW, saya ingin panicIf sebagai gantinya.

err := doSomeThing()
panicIf(err)

err = doAnotherThing()
panicIf(err)

@ianlancetaylor Dalam contoh proposal Anda err masih belum dideklarasikan secara eksplisit, dan ditarik sebagai 'ajaib' (bahasa yang telah ditentukan), bukan?

Atau akankah itu seperti

func Chdir(dir string) error {
    return (err := syscall.Chdir(dir)) || &PathError{"chdir", dir, err}
}

?

Di sisi lain (karena sudah ditandai sebagai "perubahan bahasa"...)
Perkenalkan operator baru (!! atau ??) yang melakukan pintasan pada kesalahan != nil (atau nullable apa pun?)

func DirCh(dir string) (string, error) {
    return dir, (err := syscall.Chdir(dir)) !! &PathError{"chdir", dir, err}
}

Maaf jika ini terlalu jauh :)

Saya setuju bahwa penanganan kesalahan di Go bisa berulang. Saya tidak keberatan dengan pengulangan, tetapi terlalu banyak yang memengaruhi keterbacaan. Ada alasan mengapa "Kompleksitas Cyclomatic" (apakah Anda percaya atau tidak) menggunakan aliran kontrol sebagai ukuran kompleksitas. Pernyataan "jika" menambahkan kebisingan ekstra.

Namun, sintaks yang diusulkan "||" tidak terlalu intuitif untuk dibaca, terutama karena simbol tersebut umumnya dikenal sebagai operator OR. Selain itu, bagaimana Anda menangani fungsi yang mengembalikan banyak nilai dan kesalahan?

Saya hanya melemparkan beberapa ide di sini. Bagaimana kalau alih-alih menggunakan error sebagai output, kita menggunakan error sebagai input? Contoh: https://play.golang.org/p/rtfoCIMGAb

Terima kasih untuk semua komentar.

@opennota Poin bagus. Itu masih bisa bekerja tetapi saya setuju bahwa aspek itu canggung.

@mattn Saya tidak berpikir ada ExpressionList yang kembali, jadi saya tidak yakin apa yang Anda tanyakan. Jika fungsi panggilan memiliki beberapa hasil, semua kecuali yang terakhir dikembalikan sebagai nilai nol dari tipe tersebut.

@mattn panicif tidak membahas salah satu elemen kunci dari proposal ini, yang merupakan cara mudah untuk mengembalikan kesalahan dengan konteks tambahan. Dan, tentu saja, seseorang dapat menulis panicif hari ini dengan cukup mudah.

@tandr Ya, err didefinisikan secara ajaib, yang cukup mengerikan. Kemungkinan lain adalah mengizinkan ekspresi kesalahan untuk menggunakan error untuk merujuk ke kesalahan, yang mengerikan dengan cara yang berbeda.

@tandr Kami dapat menggunakan operator yang berbeda tetapi saya tidak melihat keuntungan besar. Tampaknya tidak membuat hasilnya lebih mudah dibaca.

@henryas Saya pikir proposal menjelaskan bagaimana menangani banyak hasil.

@henryas Terima kasih untuk contohnya. Hal yang saya tidak suka tentang pendekatan semacam itu adalah membuat kesalahan menangani aspek kode yang paling menonjol. Saya ingin penanganan kesalahan hadir dan terlihat tetapi saya tidak ingin itu menjadi hal pertama yang dipertaruhkan. Itu benar hari ini, dengan idiom if err != nil dan lekukan kode penanganan kesalahan, dan itu akan tetap benar jika ada fitur baru yang ditambahkan untuk penanganan kesalahan.

Terima kasih lagi.

@ianlancetaylor Saya tidak tahu apakah Anda memeriksa tautan taman bermain saya tetapi ada "panicIf" yang memungkinkan Anda menambahkan konteks tambahan.

Saya akan mereproduksi versi yang agak disederhanakan di sini:

func panicIf(err error, transforms ...func(error) error) {
  if err == nil {
    return
  }
  for _, transform := range transforms {
    err = transform(err)
  }
  panic(err)
}

Secara kebetulan, saya baru saja memberikan ceramah kilat di GopherCon di mana saya menggunakan (tetapi tidak secara serius mengusulkan) sedikit sintaks untuk membantu memvisualisasikan kode penanganan kesalahan. Idenya adalah untuk meletakkan kode itu ke samping untuk mengeluarkannya dari aliran utama, tanpa menggunakan trik sulap apa pun untuk mempersingkat kode. Hasilnya terlihat seperti

func DirCh(dir string) (string, error) {
    dir := syscall.Chdir(dir)        =: err; if err != nil { return "", err }
}

di mana =: adalah sintaks baru, cerminan dari := yang ditetapkan ke arah lain. Jelas kami juga membutuhkan sesuatu untuk = juga, yang memang bermasalah. Tetapi ide umumnya adalah untuk memudahkan pembaca memahami jalan bahagia, tanpa kehilangan informasi.

Di sisi lain, cara penanganan kesalahan saat ini memang memiliki beberapa kelebihan karena berfungsi sebagai pengingat yang mencolok bahwa Anda mungkin melakukan terlalu banyak hal dalam satu fungsi, dan beberapa pemfaktoran ulang mungkin terlambat.

Saya sangat suka sintaks yang diusulkan oleh @billyh di sini

func Chdir(dir string) error {
    e := syscall.Chdir(dir) catch: &PathError{"chdir", dir, e}
    return nil
}

atau contoh yang lebih kompleks menggunakan https://github.com/pkg/errors

func parse(input io.Reader) (*point, error) {
    var p point

    err := read(&p.Longitude) catch: nil, errors.Wrap(err, "Failed to read longitude")
    err = read(&p.Latitude) catch: nil, errors.Wrap(err, "Failed to read Latitude")
    err = read(&p.Distance) catch: nil, errors.Wrap(err, "Failed to read Distance")
    err = read(&p.ElevationGain) catch: nil, errors.Wrap(err, "Failed to read ElevationGain")
    err = read(&p.ElevationLoss) catch: nil, errors.Wrap(err, "Failed to read ElevationLoss")

    return &p, nil
}

Ide sederhana, dengan dukungan untuk dekorasi kesalahan, tetapi membutuhkan perubahan bahasa yang lebih drastis (jelas bukan untuk go1.10) adalah pengenalan kata kunci check .

Itu akan memiliki dua bentuk: check A dan check A, B .

Baik A dan B harus error . Bentuk kedua hanya akan digunakan saat mendekorasi kesalahan; orang yang tidak membutuhkan atau ingin menghias kesalahan mereka akan menggunakan bentuk yang lebih sederhana.

Formulir 1 (centang A)

check A mengevaluasi A . Jika nil , itu tidak menghasilkan apa-apa. Jika bukan nil , check bertindak seperti return {<zero>}*, A .

Contoh

  • Jika suatu fungsi baru saja mengembalikan kesalahan, itu dapat digunakan sebaris dengan check , jadi
err := UpdateDB()    // signature: func UpdateDb() error
if err != nil {
    return err
}

menjadi

check UpdateDB()
  • Untuk fungsi dengan beberapa nilai kembalian, Anda harus menetapkan, seperti yang kita lakukan sekarang.
a, b, err := Foo()    // signature: func Foo() (string, string, error)
if err != nil {
    return "", "", err
}

// use a and b

menjadi

a, b, err := Foo()
check err

// use a and b

Formulir ke-2 (centang A, B)

check A, B mengevaluasi A . Jika nil , itu tidak menghasilkan apa-apa. Jika tidak nil , check bertindak seperti return {<zero>}*, B .

Ini untuk kebutuhan dekorasi kesalahan. Kami masih memeriksa A , tetapi B yang digunakan dalam return implisit.

Contoh

a, err := Bar()    // signature: func Bar() (string, error)
if err != nil {
    return "", &BarError{"Bar", err}
}

menjadi

a, err := Foo()
check err, &BarError{"Bar", err}

Catatan

Ini adalah kesalahan kompilasi untuk

  • gunakan pernyataan check untuk hal-hal yang tidak bernilai error
  • gunakan check dalam fungsi dengan nilai kembalian tidak dalam bentuk { type }*, error

Formulir dua-expr check A, B dihubung pendek. B tidak dievaluasi jika A adalah nil .

Catatan tentang kepraktisan

Ada dukungan untuk kesalahan dekorasi, tetapi Anda membayar sintaks check A, B kikuk hanya ketika Anda benar-benar perlu mendekorasi kesalahan.

Untuk if err != nil { return nil, nil, err } boilerplate (yang sangat umum) check err sesingkat mungkin tanpa mengorbankan kejelasan (lihat catatan pada sintaks di bawah).

Catatan tentang sintaks

Saya berpendapat bahwa sintaks semacam ini ( check .. , di awal baris, mirip dengan return ) adalah cara yang baik untuk menghilangkan kesalahan pengecekan boilerplate tanpa menyembunyikan gangguan aliran kontrol yang pengembalian implisit memperkenalkan.

Kelemahan dari ide seperti <do-stuff> || <handle-err> dan <do-stuff> catch <handle-err> atas, atau a, b = foo()? diusulkan di utas lain, adalah mereka menyembunyikan modifikasi aliran kontrol dengan cara yang membuat aliran lebih sulit mengikuti; yang pertama dengan mesin || <handle-err> ditambahkan di akhir baris yang tampak biasa, yang terakhir dengan simbol kecil yang dapat muncul di mana-mana, termasuk di tengah dan di akhir baris kode yang tampak biasa, mungkin beberapa kali.

Pernyataan check akan selalu menjadi level teratas di blok saat ini, memiliki keunggulan yang sama dengan pernyataan lain yang mengubah aliran kontrol (misalnya, return ).

@ALTree , saya tidak mengerti bagaimana contoh Anda:

a, b, err := Foo()
check err

Mencapai tiga pengembalian bernilai dari aslinya:

return "", "", err

Apakah itu hanya mengembalikan nilai nol untuk semua pengembalian yang dinyatakan kecuali kesalahan terakhir? Bagaimana dengan kasus di mana Anda ingin mengembalikan nilai yang valid bersama dengan kesalahan, misalnya jumlah byte yang ditulis ketika Write() gagal?

Solusi apa pun yang kita gunakan harus membatasi secara minimal penanganan kesalahan secara umum.

Mengenai nilai memiliki check di awal baris, preferensi pribadi saya adalah melihat aliran kontrol utama di awal setiap baris dan memiliki penanganan kesalahan yang mengganggu keterbacaan aliran kontrol utama itu sedikit mungkin. Juga, jika penanganan kesalahan dipisahkan oleh Word yang dicadangkan seperti check atau catch , maka hampir semua editor modern akan menyoroti sintaksis Word yang dicadangkan dalam beberapa cara dan membuatnya terlihat bahkan jika di sebelah kanan.

@billyh ini dijelaskan di atas, pada baris yang mengatakan:

Jika tidak nihil, centang bertindak seperti return {<zero>}*, A

check akan mengembalikan nilai nol dari nilai pengembalian apa pun, kecuali kesalahan (di posisi terakhir).

Bagaimana dengan kasus di mana Anda ingin mengembalikan nilai yang valid bersama dengan kesalahan?

Kemudian Anda akan menggunakan idiom if err != nil { .

Ada banyak kasus di mana Anda memerlukan prosedur pemulihan kesalahan yang lebih canggih. Misalnya Anda mungkin perlu, setelah menangkap kesalahan, untuk memutar kembali sesuatu, atau untuk menulis sesuatu pada file log. Dalam semua kasus ini, Anda masih memiliki idiom if err yang biasa di kotak peralatan Anda, dan Anda dapat menggunakannya untuk memulai blok baru, di mana segala jenis operasi terkait penanganan kesalahan, tidak peduli seberapa diartikulasikan, dapat dilakukan.

Solusi apa pun yang kita gunakan harus membatasi secara minimal penanganan kesalahan secara umum.

Lihat jawaban saya di atas. Anda masih memiliki if dan apa pun yang diberikan bahasa tersebut kepada Anda sekarang.

hampir semua editor modern akan menyoroti kata yang dicadangkan

Mungkin. Tetapi memperkenalkan sintaksis buram, yang membutuhkan penyorotan sintaks agar dapat dibaca, tidak ideal.

bug khusus ini dapat diperbaiki dengan memperkenalkan fasilitas pengembalian ganda ke bahasa tersebut.
dalam hal ini fungsi a() mengembalikan 123:

fungsi a() int {
B()
kembali 456
}
fungsi b() {
kembalikan kembali int (123)
}

Fasilitas ini dapat digunakan untuk mempermudah penanganan error sebagai berikut:

func handle(var *foo, err error )(var *foo, err error ) {
jika salah != nihil {
kembali kembali nihil, err
}
kembali var, nihil
}

func client_code() (*klien_objek, kesalahan) {
var obj, err = handle(something_that_can_fail())
// ini hanya tercapai jika sesuatu tidak gagal
// jika tidak, fungsi client_code akan menyebarkan kesalahan ke atas tumpukan
menegaskan (err == nihil)
}

Ini memungkinkan orang untuk menulis fungsi penangan kesalahan yang dapat menyebarkan kesalahan ke atas tumpukan
fungsi penanganan kesalahan seperti itu dapat dipisahkan dari kode utama

Maaf jika saya salah, tetapi saya ingin memperjelas suatu hal, fungsi di bawah ini akan menghasilkan kesalahan, vet peringatan atau apakah itu akan diterima?

func Chdir(dir string) (err error) {
    syscall.Chdir(dir) || err
    return nil
}

@rodcorsi Di bawah proposal ini, contoh Anda akan diterima tanpa peringatan dokter hewan. Itu akan setara dengan

if err := syscall.Chdir(dir); err != nil {
    return err
}

Bagaimana dengan memperluas penggunaan Konteks untuk menangani kesalahan? Misalnya, diberikan definisi berikut:
type ErrorContext interface { HasError() bool SetError(msg string) Error() string }
Sekarang dalam fungsi rawan kesalahan ...
func MyFunction(number int, ctx ErrorContext) int { if ctx.HasError() { return 0 } return number + 1 }
Dalam fungsi perantara ...
func MyIntermediateFunction(ctx ErrorContext) int { if ctx.HasError() { return 0 } number := 0 number = MyFunction(number, ctx) number = MyFunction(number, ctx) number = MyFunction(number, ctx) return number }
Dan di fungsi tingkat atas
func main() { ctx := context.New() no := MyIntermediateFunction(ctx) if ctx.HasError() { log.Fatalf("Error: %s", ctx.Error()) return } fmt.Printf("%d\n", no) }
Ada beberapa manfaat menggunakan pendekatan ini. Pertama, itu tidak mengalihkan perhatian pembaca dari jalur eksekusi utama. Ada pernyataan "jika" minimal untuk menunjukkan penyimpangan dari jalur eksekusi utama.

Kedua, itu tidak menyembunyikan kesalahan. Jelas dari tanda tangan metode bahwa jika menerima ErrorContext, maka fungsi tersebut mungkin memiliki kesalahan. Di dalam fungsi, ia menggunakan pernyataan percabangan normal (mis. "jika") yang menunjukkan bagaimana kesalahan ditangani menggunakan kode Go normal.

Ketiga, kesalahan secara otomatis digelembungkan ke pihak yang berkepentingan, yang dalam hal ini adalah pemilik konteks. Jika ada pemrosesan kesalahan tambahan, itu akan ditampilkan dengan jelas. Misalnya, mari buat beberapa perubahan pada fungsi perantara untuk membungkus kesalahan yang ada:
func MyIntermediateFunction(ctx ErrorContext) int { if ctx.HasError() { return 0 } number := 0 number = MyFunction(number, ctx) number = MyFunction(number, ctx) number = MyFunction(number, ctx) if ctx.HasError() { ctx.SetError(fmt.Sprintf("wrap msg: %s", ctx.Error()) return } number *= 20 number = MyFunction(number, ctx) return number }
Pada dasarnya, Anda tinggal menulis kode penanganan kesalahan sesuai kebutuhan. Anda tidak perlu menggelembungkannya secara manual.

Terakhir, Anda sebagai penulis fungsi memiliki pendapat apakah kesalahan harus ditangani. Menggunakan pendekatan Go saat ini, mudah untuk melakukan ini ...
````
//diberikan definisi berikut
func MyFunction (angka int) kesalahan

//lalu lakukan ini
MyFunction(8) //tanpa memeriksa kesalahan
With the ErrorContext, you as the function owner can make the error checking optional with this:
func Fungsiku(ctx ErrorContext) {
jika ctx != nil && ctx.HasError() {
kembali
}
//...
}
Or make it compulsory with this:
func Fungsiku(ctx ErrorContext) {
if ctx.HasError() { //akan panik jika ctx nil
kembali
}
//...
}
If you make error handling compulsory and yet the user insists on ignoring error, they can still do that. However, they have to be very explicit about it (to prevent accidental ignore). For instance:
func Fungsi Atas(ctx ErrorContext) {
diabaikan := konteks.Baru()
MyFunction(diabaikan) //yang ini diabaikan

 MyFunction(ctx) //this one is handled

}
````
Pendekatan ini tidak mengubah apa pun pada bahasa yang ada.

@ALTree Alberto, bagaimana dengan mencampur check dan apa yang diusulkan @ianlancetaylor ?

jadi

func F() (int, string, error) {
   i, s, err := OhNo()
   if err != nil {
      return i, s, &BadStuffHappened(err, "oopsie-daisy")
   }
   // all is good
   return i+1, s+" ok", nil
}

menjadi

func F() (int, string, error) {
   i, s, err := OhNo()
   check i, s, err || &BadStuffHappened(err, "oopsie-daisy")
   // all is good
   return i+1, s+" ok", nil
}

Selain itu, kami dapat membatasi check hanya untuk menangani jenis kesalahan, jadi jika Anda memerlukan beberapa nilai pengembalian, nilai tersebut perlu diberi nama dan ditetapkan, jadi ini entah bagaimana menetapkan "di tempat" dan berperilaku seperti "pengembalian" sederhana

func F() (a int, s string, err error) {
   i, s, err = OhNo()
   check err |=  &BadStuffHappened(err, "oopsy-daisy")  // assigns in place and behaves like simple "return"
   // all is good
   return i+1, s+" ok", nil
}

Jika return akan diterima dalam ekspresi suatu hari, maka check tidak diperlukan, atau menjadi fungsi standar

func check(e error) bool {
   return e != nil
}

func F() (a int, s string, err error) {
   i, s, err = OhNo()
   check(err) || return &BadStuffHappened(err, "oopsy-daisy")
   // all is good
   return i+1, s+" ok", nil
}

solusi terakhir terasa seperti Perl

Saya tidak ingat siapa yang awalnya mengusulkannya, tapi inilah ide sintaks lainnya (bikeshed favorit semua orang :-). Saya tidak mengatakan itu bagus, tetapi jika kita menuangkan ide ke dalam pot...

x, y := try foo()

akan setara dengan:

x, y, err := foo()
if err != nil {
    return (an appropriate number of zero values), err
}

dan

x, y := try foo() catch &FooErr{E:$, S:"bad"}

akan setara dengan:

x, y, err := foo()
if err != nil {
    return (an appropriate number of zero values), &FooErr{E:err, S:"bad"}
}

Bentuk try pasti telah diusulkan sebelumnya, beberapa kali, perbedaan sintaksis modulo superfisial. Formulir try ... catch lebih jarang diusulkan, tetapi jelas mirip dengan konstruksi check A, B @ALTree dan saran tindak lanjut

z(try foo() catch &FooErr{E:$, S:"bad"})

Anda dapat memiliki beberapa percobaan/tangkapan dalam satu pernyataan:

p = try q(0) + try q(1)
a = try b(c, d() + try e(), f, try g() catch &GErr{E:$}, h()) catch $BErr{E:$}

meskipun saya tidak berpikir kami ingin mendorong itu. Anda juga harus berhati-hati di sini tentang urutan evaluasi. Misalnya, apakah h() dievaluasi untuk efek samping jika e() mengembalikan kesalahan bukan nol.

Jelas, kata kunci baru seperti try dan catch akan merusak kompatibilitas Go 1.x.

Saya menyarankan agar kita menekan target proposal ini. Masalah apa yang akan diperbaiki oleh proposal ini? Kurangi tiga baris berikut menjadi dua atau satu? Ini mungkin perubahan bahasa pengembalian/jika.

if err != nil {
    return err
}

Atau kurangi berapa kali untuk memeriksa err? Ini mungkin solusi coba/tangkap untuk ini.

Saya ingin menyarankan bahwa sintaks pintasan apa pun yang masuk akal untuk penanganan kesalahan memiliki tiga properti:

  1. Seharusnya tidak muncul sebelum kode yang diperiksa, sehingga jalur non-kesalahan menonjol.
  2. Seharusnya tidak memasukkan variabel implisit ke dalam ruang lingkup, sehingga pembaca tidak bingung ketika ada variabel eksplisit dengan nama yang sama.
  3. Seharusnya tidak membuat satu tindakan pemulihan (misalnya, return err ) lebih mudah daripada yang lain. Terkadang tindakan yang sama sekali berbeda mungkin lebih disukai (seperti memanggil t.Fatal ). Kami juga tidak ingin mencegah orang menambahkan konteks tambahan.

Mengingat kendala itu, tampaknya satu sintaks yang hampir minimal adalah seperti

STMT SEPARATOR_TOKEN VAR BLOCK

Sebagai contoh,

syscall.Chdir(dir) :: err { return err }

yang setara dengan

if err := syscall.Chdir(dir); err != nil {
    return err
}
````
Even though it's not much shorter, the new syntax moves the error path out of the way. Part of the change would be to modify `gofmt` so it doesn't line-break one-line error-handling blocks, and it indents multi-line error-handling blocks past the opening `}`.

We could make it a bit shorter by declaring the error variable in place with a special marker, like

syscall.Chdir(dir) :: { kembali @err }
```

Saya bertanya-tanya bagaimana ini berperilaku untuk nilai non-nol dan kesalahan keduanya dikembalikan. Misalnya, bufio.Peek mungkin mengembalikan nilai bukan nol dan ErrBufferFull keduanya dalam waktu yang bersamaan.

@mattn Anda masih dapat menggunakan sintaks lama.

@nigeltao Ya, saya mengerti. Saya menduga perilaku ini mungkin membuat bug pada kode pengguna karena bufio.Peek juga mengembalikan bukan nol dan nihil. Kode tidak boleh menyiratkan nilai dan kesalahan keduanya. Jadi nilai dan kesalahan keduanya harus dikembalikan ke pemanggil (dalam hal ini).

ret, err := doSomething() :: err { return err }
return ret, err

@jba Apa yang Anda gambarkan terlihat seperti operator komposisi fungsi yang dialihkan:

syscall.Chdir(dir) ⫱ func (err error) { return &PathError{"chdir", dir, err} }

Tetapi fakta bahwa kita sedang menulis sebagian besar kode imperatif mengharuskan kita untuk tidak menggunakan fungsi di posisi kedua, karena sebagian dari intinya adalah untuk dapat kembali lebih awal.

Jadi sekarang saya memikirkan tiga pengamatan yang semuanya terkait:

  1. Penanganan kesalahan seperti komposisi fungsi, tetapi cara kami melakukan sesuatu di Go adalah kebalikan dari Haskell's Error monad : karena kami kebanyakan menulis imperatif daripada kode berurutan, kami ingin mengubah kesalahan (untuk menambahkan konteks) daripada nilai non-kesalahan (yang lebih baik kita ikat ke variabel).

  2. Go fungsi yang mengembalikan (x, y, error) biasanya benar-benar berarti sesuatu yang lebih seperti gabungan (#19412) dari (x, y) | error .

  3. Dalam bahasa yang membongkar atau menggabungkan pencocokan pola, kasus adalah cakupan yang terpisah, dan banyak masalah yang kami hadapi dengan kesalahan di Go adalah karena bayangan tak terduga dari variabel yang dideklarasikan ulang yang mungkin ditingkatkan dengan memisahkan cakupan tersebut (#21114).

Jadi mungkin yang kita inginkan sebenarnya adalah seperti operator =: , tetapi dengan semacam kondisi pencocokan serikat pekerja:

syscall.Chdir(dir) =? err { return &PathError{"chdir", dir, err} }

``` pergi
n := io.WriteString(w, s) =? err { kembalikan err }

and perhaps a boolean version for `, ok` index expressions and type assertions:
```go
y := m[x] =! { return ErrNotFound }

Kecuali untuk pelingkupan, itu tidak jauh berbeda dari hanya mengubah gofmt menjadi lebih dapat menerima satu baris:

err := syscall.Chdir(dir); if err != nil { return &PathError{"chdir", dir, err} }

``` pergi
n, err := io.WriteString(w, s); if err != nil { kembalikan err }

```go
y, ok := m[x]; if !ok { return ErrNotFound }

Tapi pelingkupan adalah masalah besar! Masalah pelingkupan adalah ketika kode semacam ini melewati batas dari "agak jelek" menjadi "bug halus".

@ianlancetaylor
Meskipun saya penggemar ide keseluruhan, saya bukan pendukung besar sintaks seperti perl yang samar untuk itu. Mungkin sintaks yang lebih bertele-tele akan kurang membingungkan, seperti:

syscall.Chdir(dir) or dump(err): errors.Wrap(err, "chdir failed")

syscall.Chdir(dir) or dump

Juga, saya tidak mengerti apakah argumen terakhir muncul jika ada penugasan, Misalnya:

resp := http.Get("https://example.com") or dump

Jangan lupa bahwa kesalahan adalah nilai yang masuk dan bukan tipe khusus.
Tidak ada yang bisa kita lakukan untuk struct lain yang tidak bisa kita lakukan untuk kesalahan dan sebaliknya. Ini berarti bahwa jika Anda memahami struct secara umum, Anda memahami kesalahan dan cara menanganinya (bahkan jika menurut Anda itu bertele-tele)

Sintaks ini akan mengharuskan pengembang baru dan lama untuk mempelajari sedikit informasi baru sebelum mereka dapat mulai memahami kode yang menggunakannya.

Itu saja membuat proposal ini tidak layak IMHO.

Secara pribadi saya lebih suka sintaks ini

err := syscall.Chdir(dir)
if err != nil {
    return err
}
return nil

lebih

if err := syscall.Chdir(dir); err != nil {
    return err
}
return nil

Ini satu baris lagi tetapi memisahkan tindakan yang dimaksudkan dari penanganan kesalahan. Formulir ini adalah yang paling mudah dibaca bagi saya.

@bcmils :

Kecuali untuk pelingkupan, itu tidak jauh berbeda dari hanya mengubah gofmt menjadi lebih setuju dengan satu baris

Bukan hanya pelingkupan; ada juga tepi kiri. Saya pikir itu sangat memengaruhi keterbacaan. kupikir

syscall.Chdir(dir) =: err; if err != nil { return &PathError{"chdir", dir, err} } 

jauh lebih jelas daripada

err := syscall.Chdir(dir); if err != nil { return &PathError{"chdir", dir, err} } 

terutama ketika itu terjadi pada beberapa baris berturut-turut, karena mata Anda dapat memindai tepi kiri untuk mengabaikan penanganan kesalahan.

Mencampur ide @bcmils kami dapat memperkenalkan operator penerusan pipa bersyarat.

Fungsi F2 akan dijalankan jika nilai terakhir bukan nil .

func F1() (foo, bar){}

first := F1() ?> last: F2(first, last)

Kasus khusus penerusan pipa dengan pernyataan pengembalian

func Chdir(dir string) error {
    syscall.Chdir(dir) ?> err: return &PathError{"chdir", dir, err}
    return nil
}

Contoh nyata yang dibawakan oleh @urandom di edisi lain
Bagi saya jauh lebih mudah dibaca dengan fokus pada aliran utama

func configureCloudinit(icfg *instancecfg.InstanceConfig, cloudcfg cloudinit.CloudConfig) (cloudconfig.UserdataConfig, error) {
    // When bootstrapping, we only want to apt-get update/upgrade
    // and setup the SSH keys. The rest we leave to cloudinit/sshinit.
    udata := cloudconfig.NewUserdataConfig(icfg, cloudcfg) ?> err: return nil, err
    if icfg.Bootstrap != nil {
        udata.ConfigureBasic() ?> err: return nil, err
        return udata, nil
    }
    udata.Configure() ?> err: return nil, err
    return udata, nil
}

func ComposeUserData(icfg *instancecfg.InstanceConfig, cloudcfg cloudinit.CloudConfig, renderer renderers.ProviderRenderer) ([]byte, error) {
    if cloudcfg == nil {
        cloudcfg = cloudinit.New(icfg.Series) ?> err: return nil, errors.Trace(err)
    }
    _ = configureCloudinit(icfg, cloudcfg) ?> err: return nil, errors.Trace(err)
    operatingSystem := series.GetOSFromSeries(icfg.Series) ?> err: return nil, errors.Trace(err)
    udata := renderer.Render(cloudcfg, operatingSystem) ?> err: return nil, errors.Trace(err)
    logger.Tracef("Generated cloud init:\n%s", string(udata))
    return udata, nil
}

Saya setuju penanganan kesalahan tidak ergonomis. Yaitu, ketika Anda membaca kode di bawah ini, Anda harus menyuarakannya menjadi if error not nil then -yang diterjemahkan menjadi if there is an error then .

if err != nil {
    // handle error
}

Saya ingin memiliki kemampuan untuk mengekspresikan kode di atas sedemikian rupa - yang menurut saya lebih mudah dibaca.

if err {
    // handle error
}

Hanya saran sederhana saya :)

Itu memang terlihat seperti Perl, bahkan memiliki variabel ajaib
Untuk referensi, di Perl Anda akan melakukannya

open (FILE, $file) or die("tidak dapat membuka $file: $!");

IMHO, itu tidak layak, satu hal yang saya suka tentang pergi adalah penanganan kesalahan itu
eksplisit dan 'di wajah Anda'

Jika kita tetap menggunakannya, saya ingin tidak memiliki variabel ajaib, kita harus
dapat memberi nama variabel kesalahan

e := syscall.Chdir(dir) ?> e: &PathError{"chdir", dir, e}

Dan sebaiknya kita menggunakan simbol yang berbeda dari || khusus untuk tugas ini,
Saya kira simbol teks seperti 'atau' tidak mungkin karena mundur
kesesuaian

n, _, err, _ = panggilan(...) ?> err: &PathError{"panggilan", n, err}

Pada Selasa, 1 Agustus 2017 pukul 14:47, Rodrigo [email protected] menulis:

Mencampur ide @bcmils https://github.com/bcmills dapat kami perkenalkan
operator penerusan pipa bersyarat.

Fungsi F2 akan dijalankan jika nilai terakhir tidak nil .

func F1() (foo, bilah){}
pertama := F1() ?> terakhir: F2(pertama, terakhir)

Kasus khusus penerusan pipa dengan pernyataan pengembalian

func Chdir(dir string) kesalahan {
syscall.Chdir(dir) ?> err: return &PathError{"chdir", dir, err}
kembali nihil
}

Contoh nyata
https://github.com/juju/juju/blob/01b24551ecdf20921cf620b844ef6c2948fcc9f8/cloudconfig/providerinit/providerinit.go
dibawa oleh @urandom https://github.com/urandom di edisi lain
Bagi saya jauh lebih mudah dibaca dengan fokus pada aliran utama

func configureCloudinit(icfg *instancecfg.InstanceConfig, cloudcfg cloudinit.CloudConfig) (cloudconfig.UserdataConfig, kesalahan) {
// Saat bootstrap, kita hanya ingin apt-get update/upgrade
// dan atur kunci SSH. Selebihnya kita serahkan ke cloudinit/sshinit.
udata := cloudconfig.NewUserdataConfig(icfg, cloudcfg) ?> err: return nil, err
if icfg.Bootstrap != nil {
udata.ConfigureBasic() ?> err: return nil, err
kembalikan udata, nihil
}
udata.Configure() ?> err: kembali nihil, err
kembalikan udata, nihil
}
func ComposeUserData(icfg *instancecfg.InstanceConfig, cloudcfg cloudinit.CloudConfig, renderer renderers.ProviderRenderer) ([]byte, error) {
jika cloudcfg == nihil {
cloudcfg = cloudinit.New(icfg.Series) ?> err: return nil, errors.Trace(err)
}
configureCloudinit(icfg, cloudcfg) ?> err: return nil, errors.Trace(err)
operatingSystem := series.GetOSFromSeries(icfg.Series) ?> err: return nil, errors.Trace(err)
udata := renderer.Render(cloudcfg, operatingSystem) ?> err: return nil, errors.Trace(err)
logger.Tracef("Init cloud yang dihasilkan:\n%s", string(udata))
kembalikan udata, nihil
}


Anda menerima ini karena Anda berlangganan utas ini.
Balas email ini secara langsung, lihat di GitHub
https://github.com/golang/go/issues/21161#issuecomment-319359614 , atau bisukan
benang
https://github.com/notifications/unsubscribe-auth/AbwRO_J0h2dQHqfysf2roA866vFN4_1Jks5sTx5hgaJpZM4Oi1c-
.

Apakah saya satu-satunya yang berpikir semua perubahan yang diusulkan ini akan lebih rumit daripada bentuk saat ini.

Saya pikir kesederhanaan dan singkatnya tidak sama atau dapat dipertukarkan. Ya, semua perubahan ini akan menjadi satu atau lebih baris yang lebih pendek tetapi akan memperkenalkan operator atau kata kunci yang harus dipelajari oleh pengguna bahasa tersebut.

@rodcorsi Saya tahu ini tampaknya kecil, tetapi saya pikir penting untuk bagian kedua menjadi Blok : pernyataan if dan for yang ada keduanya menggunakan blok, dan select dan switch keduanya menggunakan sintaks yang dibatasi kurung kurawal, jadi tampaknya aneh untuk menghilangkan kurung kurawal untuk operasi aliran kontrol yang satu ini.

Juga lebih mudah untuk memastikan bahwa pohon parse tidak ambigu jika Anda tidak perlu khawatir tentang ekspresi arbitrer yang mengikuti simbol baru.

Sintaks dan semantik yang saya pikirkan untuk sketsa saya adalah:


NonZeroGuardStmt = ( Expression | IdentifierList ":=" Expression |
                     ExpressionList assign_op Expression ) "=?" [ identifier ] Block .
ZeroGuardStmt = ( Expression | IdentifierList ":=" Expression |
                  ExpressionList assign_op Expression ) "=!" Block .

A NonZeroGuardStmt mengeksekusi Block jika nilai terakhir dari Expression tidak sama dengan nilai nol dari tipenya. Jika identifier ada, itu terikat ke nilai itu dalam Block . ZeroGuardStmt mengeksekusi Block jika nilai terakhir dari Expression sama dengan nilai nol dari tipenya.

Untuk formulir := , nilai (utama) lainnya dari Expression terikat ke IdentifierList seperti dalam ShortVarDecl . Pengidentifikasi dideklarasikan dalam lingkup yang berisi, yang menyiratkan bahwa mereka juga terlihat dalam Block .

Untuk formulir assign_op , setiap operan sisi kiri harus dapat dialamatkan, ekspresi indeks peta, atau (hanya untuk penetapan = ) pengidentifikasi kosong. Operand dapat diberi tanda kurung. Nilai (utama) lainnya dari ruas kanan Expression dievaluasi seperti dalam Assignment . Penetapan terjadi sebelum eksekusi Block dan terlepas dari apakah Block kemudian dieksekusi.


Saya percaya tata bahasa yang diusulkan di sini kompatibel dengan Go 1: ? bukan pengidentifikasi yang valid dan tidak ada operator Go yang menggunakan karakter itu, dan meskipun ! adalah operator yang valid, tidak ada produksi yang ada yang mungkin diikuti oleh { .

@bcmils LGTM, dengan perubahan seiring dengan gofmt.

Saya akan berpikir Anda akan membuat =? dan =! masing-masing sebagai token dalam dirinya sendiri, yang akan membuat tata bahasanya sangat kompatibel.

Saya akan berpikir Anda akan membuat =? dan =! masing-masing token dalam dirinya sendiri, yang akan membuat tata bahasa yang sepele kompatibel.

Kita bisa melakukannya di tata bahasa, tapi tidak di lexer: urutan "=!" dapat muncul dalam kode Go 1 yang valid (https://play.golang.org/p/pMTtUWgBN9).

Kurung kurawal inilah yang membuat penguraian tidak ambigu dalam proposal saya: =! saat ini hanya dapat muncul dalam deklarasi atau penetapan ke variabel boolean, dan deklarasi serta penetapan saat ini tidak dapat langsung muncul sebelum kurung kurawal (https ://play.golang.org/p/ncJyg-GMuL) kecuali dipisahkan oleh titik koma implisit (https://play.golang.org/p/lhcqBhr7Te).

@romainmenke Tidak. Kamu bukanlah satu - satunya. Saya gagal melihat nilai penanganan kesalahan satu baris. Anda dapat menyimpan satu baris tetapi menambahkan lebih banyak kerumitan. Masalahnya adalah bahwa dalam banyak proposal ini, bagian penanganan kesalahan menjadi tersembunyi. Idenya bukan untuk membuatnya kurang terlihat karena penanganan kesalahan itu penting, tetapi untuk membuat kode lebih mudah dibaca. Singkat tidak sama dengan mudah dibaca. Jika Anda harus membuat perubahan pada sistem penanganan kesalahan yang ada, menurut saya, coba-tangkap-akhirnya konvensional jauh lebih menarik daripada banyak tujuan ide di sini.

Saya suka proposal check karena Anda juga dapat memperpanjangnya untuk ditangani

f, err := os.Open(myfile)
check err
defer check f.Close()

Proposal lain sepertinya tidak dapat digabungkan dengan defer juga. check juga sangat mudah dibaca, dan mudah dibaca oleh Google jika Anda tidak mengetahuinya. Saya tidak berpikir itu perlu dibatasi pada tipe error . Apa pun yang merupakan parameter pengembalian di posisi terakhir dapat menggunakannya. Jadi, seorang iterator mungkin memiliki check untuk Next() bool .

Saya pernah menulis Pemindai yang terlihat seperti

func (s *Scanner) Next() bool {
    if s.Error != nil || s.pos >= s.RecordCount {
        return false
    }
    s.pos++

    var rt uint8
    if !s.read(&rt) {
        return false
    }
...

Bit terakhir itu bisa menjadi check s.read(&rt) sebagai gantinya.

@carlmjohnson

Proposal lain sepertinya tidak dapat digabungkan dengan defer juga.

Jika Anda berasumsi bahwa kami akan memperluas defer untuk memungkinkan pengembalian dari fungsi luar menggunakan sintaks baru, Anda dapat menerapkan asumsi itu dengan baik ke proposal lain.

defer f.Close() =? err { return err }

Karena @ALTree 's check proposal memperkenalkan pernyataan terpisah, saya tidak melihat bagaimana Anda bisa mencampurnya dengan defer yang melakukan apa pun selain hanya mengembalikan kesalahan.

defer func() {
  err := f.Close()
  check err, fmt.Errorf(…, err) // But this func() doesn't return an error!
}()

Kontras:

defer f.Close() =? err { return fmt.Errorf(…, err) }

Pembenaran untuk banyak dari proposal ini adalah "ergonomis" yang lebih baik tetapi saya tidak benar-benar melihat bagaimana semua ini lebih baik selain membuatnya sehingga ada sedikit lebih sedikit untuk diketik. Bagaimana ini meningkatkan pemeliharaan kode? Komposabilitas? Keterbacaan? Kemudahan memahami aliran kontrol?

@jimmyfrasche

Bagaimana ini meningkatkan pemeliharaan kode? Komposabilitas? Keterbacaan? Kemudahan memahami aliran kontrol?

Seperti yang saya sebutkan sebelumnya, keuntungan utama dari proposal ini mungkin perlu datang dari pelingkupan tugas yang lebih jelas dan variabel err : lihat #19727, #20148, #5634, #21114, dan mungkin lainnya untuk berbagai cara orang menghadapi masalah pelingkupan dalam kaitannya dengan penanganan kesalahan.

@bcmills terima kasih telah memberikan motivasi dan maaf saya melewatkannya di posting Anda sebelumnya.

Namun, mengingat premis itu, bukankah lebih baik menyediakan fasilitas yang lebih umum untuk "pelingkupan tugas yang lebih jelas" yang dapat digunakan oleh semua variabel? Saya tidak sengaja membayangi bagian saya dari variabel non-kesalahan juga, tentu saja.

Saya ingat ketika perilaku := ini diperkenalkan—banyak utas yang sedang berlangsung† menuntut cara untuk secara eksplisit membubuhi keterangan nama mana yang akan digunakan kembali alih-alih "penggunaan kembali hanya jika variabel itu ada di persis cakupan saat ini" yang merupakan tempat semua masalah halus yang sulit dilihat terwujud, dalam pengalaman saya.

Saya tidak dapat menemukan utas itu apakah ada yang punya tautan?

Ada banyak hal yang menurut saya dapat ditingkatkan tentang Go tetapi perilaku := selalu menurut saya sebagai satu kesalahan serius. Mungkin meninjau kembali perilaku := adalah cara untuk menyelesaikan akar masalah atau setidaknya untuk mengurangi kebutuhan akan perubahan lain yang lebih ekstrem?

@jimmyfrasche

Namun, mengingat premis itu, bukankah lebih baik menyediakan fasilitas yang lebih umum untuk "pelingkupan tugas yang lebih jelas" yang dapat digunakan oleh semua variabel?

Ya. Itulah salah satu hal yang saya suka tentang operator =? atau :: yang @jba dan saya usulkan: itu juga meluas dengan baik ke (bagian yang diakui terbatas dari) non-kesalahan.

Secara pribadi, saya kira saya akan lebih bahagia dalam jangka panjang dengan fitur tipe data tagged-union/varint/algebraic yang lebih eksplisit (lihat juga #19412), tetapi itu adalah perubahan yang jauh lebih besar pada bahasa: sulit untuk melihat bagaimana kami akan melakukan retrofit itu ke API yang ada di lingkungan campuran Go 1 / Go 2.

Kemudahan memahami aliran kontrol?

Dalam proposal saya dan @bcmils , mata Anda dapat memindai ke sisi kiri dan dengan mudah memahami aliran kontrol non-kesalahan.

@bcmils Saya pikir saya bertanggung jawab untuk setidaknya setengah dari kata-kata di # 19412 sehingga Anda tidak perlu menjual saya pada jenis jumlah ;)

Ketika datang untuk mengembalikan barang dengan kesalahan ada empat kasus

  1. hanya kesalahan (tidak perlu melakukan apa pun, cukup kembalikan kesalahan)
  2. hal DAN kesalahan (Anda akan menanganinya persis seperti yang Anda lakukan sekarang)
  3. satu hal ATAU kesalahan (Anda bisa menggunakan tipe sum! :tada: )
  4. dua atau lebih hal ATAU kesalahan

Jika Anda menekan 4 di situlah segalanya menjadi rumit. Tanpa memperkenalkan tipe Tuple (tipe produk tidak berlabel untuk digunakan dengan tipe produk berlabel struct), Anda harus mengurangi masalah menjadi kasus 3 dengan menggabungkan semuanya dalam struct jika Anda ingin menggunakan tipe sum untuk memodelkan "ini atau kesalahan".

Memperkenalkan jenis tupel akan menyebabkan semua jenis masalah dan masalah kompatibilitas dan tumpang tindih yang aneh (apakah func() (int, string, error) merupakan tupel yang didefinisikan secara implisit atau beberapa nilai kembalian merupakan konsep yang terpisah? Jika itu adalah tupel yang ditentukan secara implisit maka apakah itu berarti func() (n int, msg string, err error) adalah struct yang didefinisikan secara implisit!? Jika itu struct, bagaimana cara mengakses bidang jika saya tidak berada dalam paket yang sama!)

Saya masih berpikir tipe jumlah memberikan banyak manfaat, tetapi mereka tidak melakukan apa pun untuk memperbaiki masalah dengan pelingkupan, tentu saja. Jika ada yang bisa memperburuknya karena Anda bisa membayangi seluruh jumlah 'hasil atau kesalahan' alih-alih hanya membayangi kasus kesalahan ketika Anda memiliki sesuatu dalam kasus hasil.

@jba Saya tidak melihat bagaimana itu adalah properti yang diinginkan. Selain kurangnya kemudahan secara umum dengan konsep membuat aliran kontrol dua dimensi, jadi untuk berbicara, saya tidak dapat benar-benar memikirkan mengapa itu tidak terjadi. Bisakah Anda menjelaskan manfaatnya kepada saya?

Tanpa memperkenalkan tipe Tuple […] Anda harus [menggabungkan] semuanya dalam sebuah struct jika Anda ingin menggunakan tipe sum untuk memodelkan "ini atau kesalahan".

Saya setuju dengan itu: Saya pikir kami akan memiliki lebih banyak situs panggilan yang dapat dibaca seperti itu (tidak ada lagi binding posisi yang dialihkan secara tidak sengaja!), dan #12854 akan mengurangi banyak overhead yang saat ini terkait dengan pengembalian struct.

Masalah besarnya adalah migrasi: bagaimana kita mendapatkan model "nilai dan kesalahan" dari Go 1 ke model "nilai atau kesalahan" potensial di Go 2, terutama mengingat API seperti io.Writer yang benar-benar mengembalikan "nilai" dan kesalahan"?

Saya masih berpikir tipe jumlah memberikan banyak manfaat, tetapi mereka tidak melakukan apa pun untuk memperbaiki masalah dengan pelingkupan, tentu saja.

Itu tergantung pada bagaimana Anda membongkar mereka, yang saya kira membawa kita kembali ke tempat kita hari ini. Jika Anda lebih suka serikat pekerja, mungkin Anda dapat membayangkan versi =? sebagai API "pencocokan pola asimetris":

i := match strconv.Atoi(str) | err error { return err }

Di mana match akan menjadi operasi pencocokan pola gaya ML tradisional, tetapi dalam kasus kecocokan yang tidak lengkap akan mengembalikan nilai (sebagai interface{} jika gabungan memiliki lebih dari satu alternatif yang tidak cocok) daripada panik dengan kegagalan pertandingan yang tidak lengkap.

Saya baru saja memeriksa paket di https://github.com/mpvl/errd yang membahas masalah yang dibahas di sini secara terprogram (tidak ada perubahan bahasa). Aspek terpenting dari paket ini adalah tidak hanya mempersingkat penanganan kesalahan, tetapi juga membuatnya lebih mudah untuk melakukannya dengan benar. Saya memberikan contoh dalam dokumen tentang bagaimana penanganan kesalahan idiomatik tradisional lebih rumit daripada yang terlihat, terutama dalam interaksi dengan penangguhan.

Saya menganggap ini sebagai paket "pembakar"; tujuannya adalah untuk mendapatkan pengalaman dan wawasan yang baik tentang cara terbaik untuk memperluas bahasa. Ini berinteraksi cukup baik dengan obat generik, btw, jika ini akan menjadi sesuatu.

Masih mengerjakan beberapa contoh lagi, tetapi paket ini siap untuk bereksperimen.

@bcmils satu juta :+1: untuk #12854

Seperti yang Anda catat, ada "kembalikan X dan kesalahan" dan "kembalikan X atau kesalahan" sehingga Anda tidak dapat benar-benar menyiasatinya tanpa beberapa makro yang menerjemahkan cara lama ke cara baru sesuai permintaan (dan tentu saja akan ada bug atau setidaknya panik runtime ketika itu pasti digunakan untuk fungsi "X dan kesalahan").

Saya benar-benar tidak suka ide memperkenalkan makro khusus ke dalam bahasa, terutama jika itu hanya untuk penanganan kesalahan, yang merupakan masalah utama saya dengan banyak proposal ini.

Pergi tidak besar pada gula atau sihir dan itu plus.

Ada terlalu banyak inersia dan terlalu sedikit informasi yang dikodekan dalam praktik saat ini untuk menangani lompatan massal ke paradigma penanganan kesalahan yang lebih fungsional.

Jika Go 2 mendapatkan tipe sum—yang sejujurnya akan mengejutkan saya (dalam cara yang baik!)—itu, jika ada, harus menjadi proses bertahap yang sangat lambat untuk pindah ke "gaya baru" dan sementara itu akan ada bahkan lebih banyak fragmentasi dan kebingungan tentang cara menangani kesalahan, jadi saya tidak melihatnya sebagai hal yang positif. (Namun saya akan segera mulai menggunakannya untuk hal-hal seperti chan union { Msg1 T; Msg2 S; Err error } alih-alih tiga saluran).

Jika ini adalah pra-Go1 dan tim Go dapat mengatakan "kami hanya akan menghabiskan enam bulan ke depan untuk memindahkan semuanya dan ketika itu merusak barang, teruskan" itu akan menjadi satu hal, tetapi saat ini kami pada dasarnya terjebak bahkan jika kita mendapatkan tipe jumlah.

Seperti yang Anda catat, ada "kembalikan X dan kesalahan" dan "kembalikan X atau kesalahan" sehingga Anda tidak dapat benar-benar menyiasatinya tanpa beberapa makro yang menerjemahkan cara lama ke cara baru sesuai permintaan

Seperti yang saya coba katakan di atas, saya rasa tidak perlu cara baru, apa pun itu, untuk menutupi "mengembalikan X dan kesalahan". Jika sebagian besar kasus adalah "kembalikan X atau kesalahan", dan cara baru hanya meningkatkan itu, maka itu bagus, dan Anda masih dapat menggunakan cara lama, Go 1 yang kompatibel untuk "kembalikan X dan kesalahan" yang lebih jarang.

@nigeltao Benar, tetapi kami masih memerlukan beberapa cara untuk membedakannya selama transisi, kecuali jika Anda mengusulkan agar kami menyimpan seluruh pustaka standar dalam gaya yang ada.

@jimmyfrasche Saya rasa saya tidak bisa membuat argumen untuk itu. Anda dapat menonton ceramah saya atau melihat contohnya di repo README . Tetapi jika bukti visualnya tidak menarik bagi Anda, maka tidak ada yang bisa saya katakan.

@jba menonton ceramah dan membaca README. Saya sekarang mengerti dari mana Anda berasal dengan hal kurung/catatan kaki/catatan akhir/catatan samping (dan saya penggemar catatan samping (dan tanda kurung)).

Jika tujuannya adalah untuk menempatkan, karena tidak ada istilah yang lebih baik, jalan yang tidak menyenangkan ke samping, maka plugin $EDITOR akan bekerja tanpa perubahan bahasa dan akan bekerja dengan semua kode yang ada, terlepas dari preferensi pembuat kode.

Perubahan bahasa membuat sintaks agak lebih kompak. @bcmills menyebutkan ini meningkatkan pelingkupan tetapi saya tidak benar-benar melihat bagaimana itu bisa terjadi kecuali memiliki aturan pelingkupan yang berbeda dari := tetapi sepertinya itu akan menyebabkan lebih banyak kebingungan.

@bcmils Saya tidak mengerti komentar Anda. Anda jelas dapat membedakan mereka. Cara lama terlihat seperti ini:

err := foo()
if err != nil {
  return n, err  // n can be non-zero
}

Cara baru sepertinya

check foo()

atau

foo() || &FooError{err}

atau apapun warna bikeshednya. Saya kira sebagian besar perpustakaan standar dapat bertransisi, tetapi tidak semuanya harus.

Untuk menambah persyaratan @ianlancetaylor : menyederhanakan pesan kesalahan seharusnya tidak hanya mempersingkat, tetapi juga lebih mudah untuk menangani kesalahan dengan benar. Meneruskan kepanikan dan kesalahan hilir hingga menunda fungsi sulit dilakukan dengan benar.

Pertimbangkan misalnya, menulis ke file Google Cloud Storage, tempat kami ingin membatalkan penulisan file pada kesalahan apa pun:

func writeToGS(ctx context.Context, bucket, dst string, r io.Reader) (err error) {
    client, err := storage.NewClient(ctx)
    if err != nil {
        return err
    }
    defer client.Close()

    w := client.Bucket(bucket).Object(dst).NewWriter(ctx)
    defer func() {
        if r := recover(); r != nil {
            w.CloseWithError(fmt.Errorf("panic: %v", r))
            panic(r)
        }
        if err != nil {
            _ = w.CloseWithError(err)
        } else {
            err = w.Close()
        }
    }
    _, err = io.Copy(w, r)
    return err
}

Seluk-beluk kode ini meliputi:

  • Kesalahan dari Salin secara diam-diam diteruskan melalui argumen pengembalian bernama ke fungsi penangguhan.
  • Agar sepenuhnya aman, kami menangkap kepanikan dari r dan memastikan kami membatalkan penulisan sebelum melanjutkan kepanikan.
  • Mengabaikan kesalahan Penutupan pertama disengaja, tetapi agak terlihat seperti artefak pemrogram malas.

Menggunakan paket errd, kode ini terlihat seperti:

func writeToGS(ctx context.Context, bucket, dst, src string, r io.Reader) error {
    return errd.Run(func(e *errd.E) {
        client, err := storage.NewClient(ctx)
        e.Must(err)
        e.Defer(client.Close, errd.Discard)

        w := client.Bucket(bucket).Object(dst).NewWriter(ctx)
        e.Defer(w.CloseWithError)

        _, err = io.Copy(w, r)
        e.Must(err)
    })
}

errd.Discard adalah penangan kesalahan. Penangan kesalahan juga dapat digunakan untuk membungkus, mencatat, kesalahan apa pun.

e.Must setara dengan foo() || wrapError

e.Defer adalah tambahan dan menangani kesalahan penerusan ke penangguhan.

Menggunakan obat generik, potongan kode ini bisa terlihat seperti:

func writeToGS(ctx context.Context, bucket, dst, src string, r io.Reader) error {
    return errd.Run(func(e *errd.E) {
        client := e.Must(storage.NewClient(ctx))
        e.Defer(client.Close, errd.Discard)

        w := client.Bucket(bucket).Object(dst).NewWriter(ctx)
        e.Defer(w.CloseWithError)

        _ = e.Must(io.Copy(w, r))
    })
}

Jika kita membakukan metode yang digunakan untuk Defer, itu bahkan bisa terlihat seperti:

func writeToGS(ctx context.Context, bucket, dst, src string, r io.Reader) error {
    return errd.Run(func(e *errd.E) {
        client := e.DeferClose(e.Must(storage.NewClient(ctx)), errd.Discard)
       e.Must(io.Copy(e.DeferClose(client.Bucket(bucket).Object(dst).NewWriter(ctx)), r)
    })
}

Di mana DeferClose memilih Close atau CloseWithError. Tidak mengatakan ini lebih baik, tetapi hanya menunjukkan kemungkinan.

Bagaimanapun, saya memberikan presentasi di pertemuan Amsterdam minggu lalu tentang topik ini dan tampaknya kemampuan untuk mempermudah melakukan penanganan kesalahan dengan benar dianggap lebih berguna daripada membuatnya lebih pendek.

Solusi yang memperbaiki kesalahan harus fokus setidaknya sama pada membuatnya lebih mudah untuk memperbaiki sesuatu daripada membuat segalanya lebih pendek.

@ALTree errd menangani "pemulihan kesalahan yang canggih" di luar kotak.

@jimmyfrasche : errd melakukan kira-kira seperti yang dilakukan contoh taman bermain Anda, tetapi juga menjalin kesalahan lewat dan panik untuk menunda.

@jimmyfrasche : Saya setuju bahwa sebagian besar proposal tidak menambahkan banyak pada apa yang sudah dapat dicapai dalam kode.

@romainmenke : setuju bahwa ada terlalu banyak fokus pada singkatnya. Untuk membuatnya lebih mudah untuk melakukan sesuatu dengan benar harus memiliki fokus yang lebih besar.

@jba : pendekatan errd membuatnya cukup mudah untuk memindai aliran kesalahan versus non-kesalahan hanya dengan melihat sisi kiri (apa pun yang dimulai dengan e. adalah kesalahan atau penangguhan penanganan). Ini juga membuatnya sangat mudah untuk memindai nilai pengembalian mana yang ditangani untuk kesalahan atau penundaan dan mana yang tidak.

@bcmills : meskipun errd tidak memperbaiki masalah pelingkupan itu sendiri, itu menghilangkan kebutuhan untuk meneruskan kesalahan hilir ke variabel kesalahan yang dideklarasikan sebelumnya dan semuanya, sehingga mengurangi masalah secara signifikan untuk penanganan kesalahan, AFAICT.

errd tampaknya bergantung sepenuhnya pada kepanikan dan pemulihan. kedengarannya seperti itu datang dengan penalti kinerja yang signifikan. Saya tidak yakin ini adalah solusi keseluruhan karena ini.

@urandom : Di bawah tenda itu diimplementasikan sebagai penangguhan yang lebih mahal, tetapi tunggal.
Jika kode asli:

  • tidak menggunakan penangguhan: hukuman menggunakan errd besar, sekitar 100ns*.
  • menggunakan penundaan idiomatik: waktu berjalan atau kesalahan memiliki urutan yang sama, meskipun agak lebih lambat
  • menggunakan penanganan kesalahan yang tepat untuk penangguhan: waktu berjalan hampir sama; errd bisa lebih cepat jika jumlah penangguhan> 1

Overhead lainnya:

  • Meneruskan penutupan (w.Close) ke Defer saat ini juga menambahkan sekitar 25ns* overhead, dibandingkan dengan menggunakan DeferClose atau DeferFunc API (lihat rilis v0.1.0). Setelah berdiskusi dengan @rsc, saya menghapusnya agar API tetap sederhana dan khawatir tentang pengoptimalan nanti.
  • Membungkus string kesalahan sebaris sebagai penangan ( e.Must(err, msg("oh noes!") ) berharga sekitar 30ns dengan Go 1.8. Dengan tip (1.9), bagaimanapun, meskipun masih alokasi, saya mencatat biaya di 2ns. Tentu saja untuk pesan kesalahan yang dideklarasikan sebelumnya, biayanya masih dapat diabaikan.

(*) semua nomor berjalan di MacBook Pro 2016 saya.

Secara keseluruhan, biayanya tampaknya dapat diterima jika kode asli Anda menggunakan penangguhan. Jika tidak, Austin sedang berupaya mengurangi biaya penangguhan secara signifikan, sehingga biayanya bahkan bisa turun seiring waktu.

Bagaimanapun, inti dari paket ini adalah untuk mendapatkan pengalaman tentang bagaimana menggunakan penanganan kesalahan alternatif akan terasa dan berguna sekarang sehingga kita dapat membangun tambahan bahasa terbaik di Go 2. Contoh kasus adalah diskusi saat ini, terlalu fokus pada pengurangan a beberapa baris untuk kasus sederhana, sedangkan ada lebih banyak keuntungan dan bisa dibilang poin lain lebih penting.

@jimmyfrasche :

maka plugin $EDITOR akan berfungsi tanpa perubahan bahasa

Ya, itulah yang saya bantah dalam pembicaraan. Di sini saya berpendapat bahwa jika kita membuat perubahan bahasa, itu harus sesuai dengan konsep "sidenote".

@nigeltao

Anda jelas dapat membedakan mereka. Cara lama terlihat seperti ini:

Saya sedang berbicara tentang titik deklarasi, bukan titik penggunaan.

Beberapa proposal yang dibahas di sini tidak membedakan keduanya di situs panggilan, tetapi beberapa melakukannya. Jika kita memilih salah satu opsi yang mengasumsikan "nilai atau kesalahan" — seperti || , try … catch , atau match — maka itu harus menjadi kesalahan waktu kompilasi untuk digunakan sintaks itu dengan fungsi "nilai dan kesalahan", dan itu harus tergantung pada pelaksana fungsi untuk menentukan yang mana itu.

Pada titik deklarasi, saat ini tidak ada cara untuk membedakan antara "nilai dan kesalahan" dan "nilai atau kesalahan":

func Atoi(string) (int, error)

dan

func WriteString(Writer, String) (int, error)

memiliki tipe pengembalian yang sama, tetapi semantik kesalahan yang berbeda.

@mpvl Saya sedang melihat dokumen dan src untuk kesalahan. Saya pikir saya mulai memahami cara kerjanya tetapi sepertinya memiliki banyak API yang menghalangi pemahaman yang sepertinya dapat diimplementasikan dalam paket terpisah. Saya yakin semuanya membuatnya lebih berguna dalam praktik, tetapi sebagai ilustrasi, ini menambahkan banyak noise.

Jika kita mengabaikan pembantu umum seperti fungsi tingkat atas untuk beroperasi pada hasil WithDefault(), dan menganggap, demi kesederhanaan bahwa kita selalu menggunakan konteks, dan mengabaikan keputusan apa pun yang dibuat untuk kinerja, API barebone minimal absolut akan berkurang menjadi di bawah operasi?

type Handler = func(ctx context.Context, panicing bool, err error) error
Run(context.Context, func(*E), defaults ...Handler) //egregious style but most minimal
type struct E {...}
func (*E) Must(err error, handlers ...Handler)
func (*E) Defer(func() error, handlers ...Handler)

Melihat kode saya melihat beberapa alasan bagus itu tidak didefinisikan seperti di atas, tetapi saya mencoba untuk mendapatkan semantik inti untuk lebih memahami konsepnya. Misalnya, saya tidak yakin apakah IsSentinel ada di inti atau tidak.

@jimmyfrasche

@bcmils menyebutkan ini meningkatkan pelingkupan tetapi saya tidak benar-benar melihat bagaimana itu bisa

Peningkatan utama adalah menjaga variabel err keluar dari ruang lingkup. Itu akan menghindari bug seperti yang ditautkan dari https://github.com/golang/go/issues/19727. Untuk mengilustrasikan dengan cuplikan dari salah satunya:

    res, err := ctxhttp.Get(ctx, c.HTTPClient, dirURL)
    if err != nil {
        return Directory{}, err
    }
    defer res.Body.Close()
    c.addNonce(res.Header)
    if res.StatusCode != http.StatusOK {
        return Directory{}, responseError(res)
    }

    var v struct {
        …
    }
    if json.NewDecoder(res.Body).Decode(&v); err != nil {
        return Directory{}, err
    }

Bug terjadi pada pernyataan if terakhir: kesalahan dari Decode dihilangkan, tetapi tidak jelas karena err dari pemeriksaan sebelumnya masih dalam cakupan. Sebaliknya, dengan menggunakan operator :: atau =? , akan ditulis:

    res := ctxhttp.Get(ctx, c.HTTPClient, dirURL) =? err { return Directory{}, err }
    defer res.Body.Close()
    c.addNonce(res.Header)
    (res.StatusCode == http.StatusOK) =! { return Directory{}, responseError(res) }

    var v struct {
        …
    }
    json.NewDecoder(res.Body).Decode(&v) =? err { return Directory{}, err }

Di sini ada dua peningkatan pelingkupan yang membantu:

  1. err (dari panggilan Get ) hanya dalam cakupan untuk blok return , sehingga tidak dapat digunakan secara tidak sengaja pada pemeriksaan berikutnya.
  2. Karena err dari Decode dideklarasikan dalam pernyataan yang sama di mana ia diperiksa untuk nol, tidak boleh ada kemiringan antara deklarasi dan cek.

(1) saja sudah cukup untuk mengungkapkan kesalahan pada waktu kompilasi, tetapi (2) membuatnya mudah untuk dihindari saat menggunakan pernyataan penjaga dengan cara yang jelas.

@bcmils terima kasih atas klarifikasinya

Jadi, di res := ctxhttp.Get(ctx, c.HTTPClient, dirURL) =? err { return Directory{}, err } =? makro

var res *http.Reponse
{
  var err error
  res, err = ctxhttp.Get(ctx, c.HTTPClient, dirURL)
  if err != nil {
    return Directory{}, err 
  }
}

Jika itu benar, itulah yang saya maksud ketika saya mengatakan itu harus memiliki semantik yang berbeda dari := .

Sepertinya itu akan menyebabkan kebingungannya sendiri seperti:

func f() error {
  var err error
  g() =? err {
    if err != io.EOF {
      return err
    }
  }
  //one could expect that err could be io.EOF here but it will never be so
}

Kecuali saya telah salah memahami sesuatu.

Yap, itu ekspansi yang benar. Anda benar bahwa itu berbeda dari := , dan itu disengaja.

Sepertinya itu akan menyebabkan kebingungannya sendiri

Itu benar. Tidak jelas bagi saya apakah itu akan membingungkan dalam praktik. Jika ya, kami dapat menyediakan varian ":" dari pernyataan penjaga untuk deklarasi (dan menetapkan varian "=" saja).

(Dan sekarang itu membuat saya berpikir operator harus dieja ? dan ! bukannya =? dan =! .)

res := ctxhttp.Get(ctx, c.HTTPClient, dirURL) ?: err { return Directory{}, err }

tetapi

func f() error {
  var err error
  g() ?= err { (err == io.EOF) ! { return err } }
  // err may be io.EOF here.
}

@mpvl Perhatian utama saya tentang errd adalah dengan antarmuka Handler: tampaknya mendorong saluran pipa panggilan balik gaya fungsional, tetapi pengalaman saya dengan kode gaya panggilan balik/kelanjutan (keduanya dalam bahasa imperatif seperti Go dan C++ dan dalam fungsional bahasa seperti ML dan Haskell) sering kali jauh lebih sulit untuk diikuti daripada gaya sekuensial / imperatif yang setara, yang juga kebetulan disejajarkan dengan idiom Go lainnya.

Apakah Anda membayangkan rantai Handler -style sebagai bagian dari API, atau apakah Handler merupakan pengganti untuk beberapa sintaks lain yang Anda pertimbangkan (seperti sesuatu yang beroperasi pada Block S?)

@bcmils Saya masih belum memahami fitur ajaib yang memperkenalkan lusinan konsep ke dalam bahasa ke dalam satu baris dan hanya berfungsi dengan satu hal, tetapi akhirnya saya mengerti mengapa mereka lebih dari sekadar cara yang sedikit lebih pendek untuk menulis x, err := f(); if err != nil { return err } . Terima kasih telah membantu saya memahami dan maaf butuh waktu lama.

@bcmills Saya menulis ulang contoh motivasi @mpvl , yang memiliki beberapa penanganan kesalahan yang parah, menggunakan proposal =? yang tidak selalu mendeklarasikan variabel err baru:

func writeToGS(ctx context.Context, bucket, dst string, r io.Reader) (err error) {
        client := storage.NewClient(ctx) =? err { return err }
        defer client.Close()

        w := client.Bucket(bucket).Object(dst).NewWriter(ctx)

        defer func() {
                if r := recover(); r != nil { // r is interface{} not error so we can't use it here
                        _ = w.CloseWithError(fmt.Errorf("panic: %v", r))
                        panic(r)
                }

                if err != nil { // could use =! here but I don't see how that simplifies anything
                        _ = w.CloseWithError(err)
                } else {
                        err = w.Close()
                }
        }()

        io.Copy(w, r) =? err { return err } // what about n? does this need to be prefixed by a '_ ='?
        return nil
}

Sebagian besar penanganan kesalahan tidak berubah. Saya hanya bisa menggunakan =? di dua tempat. Pertama-tama itu tidak benar-benar memberikan manfaat apa pun yang bisa saya lihat. Dalam detik itu membuat kode lebih panjang dan mengaburkan fakta bahwa io.Copy mengembalikan dua hal, jadi mungkin akan lebih baik untuk tidak menggunakannya di sana.

@jimmyfrasche Kode itu adalah pengecualian, bukan aturannya. Kita seharusnya tidak mendesain fitur untuk membuatnya lebih mudah untuk menulis.

Juga, saya mempertanyakan apakah recover seharusnya ada di sana. Jika w.Write atau r.Read (atau io.Copy !) panik, mungkin lebih baik untuk menghentikannya.

Tanpa recover , tidak ada kebutuhan nyata untuk defer , dan bagian bawah fungsi bisa menjadi

_ = io.Copy(w, r) =? err { _ = w.CloseWithError(err); return err }
return w.Close()

@jimmyfrasche

// r is interface{} not error so we can't use it here

Perhatikan bahwa kata-kata spesifik saya (dalam https://github.com/golang/go/issues/21161#issuecomment-319434101) adalah tentang nilai nol, bukan kesalahan secara khusus.

// what about n? does this need to be prefixed by a '_ ='?

Tidak, meskipun saya bisa lebih eksplisit tentang itu.

Saya tidak terlalu suka @mpvl menggunakan recover dalam contoh itu: ini mendorong penggunaan panik atas aliran kontrol idiomatik, sedangkan jika ada yang saya pikir kita harus menghilangkan panggilan recover asing ( seperti yang ada di fmt ) dari pustaka standar di Go 2.

Dengan pendekatan itu, saya akan menulis kode itu sebagai:

func writeToGS(ctx context.Context, bucket, dst string, r io.Reader) (err error) {
        client := storage.NewClient(ctx) =? err { return err }
        defer client.Close()

        w := client.Bucket(bucket).Object(dst).NewWriter(ctx)
        io.Copy(w, r) =? err {
                w.CloseWithError(err)
                return err
        }
        return w.Close()
}

Di sisi lain, Anda benar bahwa dengan pemulihan unidiomatik ada sedikit peluang untuk menerapkan fitur yang dimaksudkan untuk mendukung penanganan kesalahan idiomatik. Namun, memisahkan pemulihan dari operasi Close memang menghasilkan kode IMO yang lebih bersih.

func writeToGS(ctx context.Context, bucket, dst string, r io.Reader) (err error) {
        client := storage.NewClient(ctx) =? err { return err }
        defer client.Close()

        w := client.Bucket(bucket).Object(dst).NewWriter(ctx)
        defer func() {
                if err != nil {
                        _ = w.CloseWithError(err)
                } else {
                        err = w.Close()
                }
        }()
        defer func() {
                recover() =? r {
                        err = fmt.Errorf("panic: %v", r)
                        panic(r)
                }
        }()

        io.Copy(w, r) =? err { return err }
        return nil
}

@jba penangan penangguhan kembali panik: itu ada di sana untuk mencoba memberi tahu proses di komputer lain sehingga tidak secara tidak sengaja melakukan transaksi yang buruk (dengan asumsi itu masih mungkin dalam keadaan kesalahan potensial). Jika itu tidak cukup umum mungkin seharusnya begitu. Saya setuju bahwa Baca/Tulis/Salin tidak perlu panik, tetapi jika ada kode lain di sana yang cukup membuat panik, untuk alasan apa pun, kami akan segera kembali ke awal.

@bcmils bahwa revisi terakhir memang terlihat lebih baik (bahkan jika Anda mengeluarkan =? , sungguh)

@jba :

_ = io.Copy(w, r) =? err { _ = w.CloseWithError(err); return err }
return w.Close()

itu masih belum menutupi kasus kepanikan pada pembaca. Memang ini adalah kasus yang jarang terjadi, tetapi cukup penting: menelepon Tutup di sini jika terjadi kepanikan sangat buruk.

Kode itu adalah pengecualian, bukan aturannya. Kita seharusnya tidak mendesain fitur untuk membuatnya lebih mudah untuk menulis.

@jba : Saya sepenuh hati tidak setuju dalam hal ini. Penting untuk mendapatkan penanganan kesalahan dengan benar. Membiarkan kasus sederhana menjadi lebih mudah akan mendorong orang untuk berpikir lebih sedikit tentang penanganan kesalahan yang tepat. Saya ingin melihat beberapa pendekatan yang, seperti errd , membuat penanganan kesalahan konservatif menjadi mudah, sementara membutuhkan beberapa upaya untuk melonggarkan aturan, bukan apa pun yang bergerak sedikit ke arah lain.

@jimmyfrasche : mengenai penyederhanaan Anda: Anda kira-kira benar.

  • IsSentinel tidak penting, hanya berguna dan umum. Aku menjatuhkannya, setidaknya untuk saat ini.
  • Err in State berbeda dari err, jadi API Anda menghapus ini. Namun, itu tidak penting untuk dipahami.
  • Handler dapat berupa fungsi, tetapi sebagian besar merupakan antarmuka karena alasan kinerja. Saya hanya tahu bahwa banyak orang tidak akan menggunakan paket jika tidak dioptimalkan. (lihat beberapa komentar pertama tentang errd dalam edisi ini)
  • Konteksnya sangat disayangkan. AppEngine membutuhkannya, tetapi saya rasa tidak banyak lagi. Saya akan baik-baik saja menghapus dukungan untuk itu sampai orang-orang menolak.

@mpvl Saya hanya mencoba menguranginya menjadi beberapa hal sehingga lebih mudah untuk memahami cara kerjanya, cara menggunakannya, dan membayangkan bagaimana itu akan cocok dengan kode yang saya tulis.

@jimmyfrasche : mengerti, meskipun bagus jika API tidak mengharuskan Anda melakukan itu. :)

@bcmils : Penangan melayani beberapa tujuan, misalnya, dalam urutan kepentingan:

  • membungkus kesalahan
  • definisikan untuk mengabaikan kesalahan (untuk membuatnya eksplisit. Lihat contoh)
  • kesalahan log
  • metrik kesalahan

Sekali lagi dalam urutan kepentingan, ini perlu dicakup oleh:

  • memblokir
  • garis
  • kemasan

Kesalahan default hanya ada untuk membuatnya lebih mudah untuk menjamin bahwa kesalahan ditangani di suatu tempat,
tapi saya bisa hidup dengan level blok saja. Saya awalnya memiliki API dengan Opsi alih-alih Penangan. Itu menghasilkan API yang lebih besar dan lebih canggung selain lebih lambat.

Saya tidak melihat masalah panggilan balik seburuk itu di sini. Pengguna mendefinisikan Runner dengan memberikan Handler yang dipanggil jika ada kesalahan. Pelari spesifik secara eksplisit ditentukan di blok tempat kesalahan ditangani. Dalam banyak kasus, handler hanya akan berupa string literal terbungkus yang diteruskan sebaris. Saya akan mengambil beberapa bermain-main untuk melihat apa yang berguna dan apa yang tidak.

BTW, Jika kita seharusnya tidak mendorong kesalahan logging di Handler, maka dukungan Konteks mungkin dapat dibatalkan.

@jba :

Juga, saya mempertanyakan apakah pemulihan harus ada di sana. Jika w.Write atau r.Read (atau io.Copy!) panik, mungkin lebih baik untuk menghentikannya.

writeToGS masih berhenti jika ada kepanikan, sebagaimana mestinya(!!!), itu hanya memastikan itu memanggil CloseWithError dengan kesalahan non-nihil. Jika kepanikan tidak ditangani, penangguhan tetap dipanggil, tetapi dengan err == nil, yang mengakibatkan file yang berpotensi rusak muncul di Cloud Storage. Hal yang benar untuk dilakukan di sini adalah memanggil CloseWithError dengan beberapa kesalahan sementara dan kemudian melanjutkan kepanikan.

Saya menemukan banyak contoh seperti ini di kode Go. Berurusan dengan io.Pipes juga sering menghasilkan kode yang terlalu halus. Menangani kesalahan seringkali tidak semudah yang Anda lihat sekarang.

@bcmils

Saya tidak terlalu suka penggunaan pemulihan @mpvl dalam contoh itu: ini mendorong penggunaan kepanikan atas aliran kontrol idiomatik,

Tidak mencoba untuk mendorong penggunaan panik sedikit pun. Perhatikan bahwa kepanikan meningkat kembali tepat setelah CloseWithError dan dengan demikian tidak mengubah aliran kontrol. Panik tetap panik.
Tidak menggunakan pemulihan di sini adalah salah, karena kepanikan akan menyebabkan penangguhan dipanggil dengan kesalahan nihil, menandakan bahwa apa yang telah ditulis sejauh ini dapat dilakukan.

Satu-satunya argumen yang agak valid untuk tidak menggunakan pemulihan di sini adalah bahwa sangat tidak mungkin terjadi kepanikan, bahkan untuk Pembaca yang sewenang-wenang (Pembaca adalah tipe yang tidak dikenal dalam contoh ini karena suatu alasan :) ).
Namun, untuk kode produksi, ini adalah sikap yang tidak dapat diterima. Terutama ketika memprogram dalam skala yang cukup besar, hal ini pasti akan terjadi kadang-kadang (kepanikan dapat disebabkan oleh hal-hal lain selain bug dalam kode).

BTW, perhatikan bahwa paket errd menghilangkan kebutuhan pengguna untuk memikirkan hal ini. Mekanisme lain apa pun yang menandakan kesalahan jika terjadi kepanikan untuk ditunda tidak masalah. Tidak memanggil penangguhan karena panik akan berhasil juga, tetapi itu datang dengan masalahnya sendiri.

Pada titik deklarasi, saat ini tidak ada cara untuk membedakan antara "nilai dan kesalahan" dan "nilai atau kesalahan":

@bcmils Oh, begitu. Untuk membuka sekaleng gudang sepeda lainnya, saya kira Anda bisa mengatakan

func Atoi(string) ?int

dari pada

func Atoi(string) (int, error)

tetapi WriteString akan tetap tidak berubah:

func WriteString(Writer, String) (int, error)

Saya suka proposal =? / =! / :=? / :=! oleh @bcmills / @jba lebih baik daripada proposal serupa. Ini memiliki beberapa properti yang bagus:

  • dapat dikomposisi (Anda dapat menggunakan =? dalam blok =? )
  • umum (hanya peduli dengan nilai nol, tidak spesifik untuk jenis kesalahan)
  • peningkatan cakupan
  • bisa bekerja dengan defer (dalam satu variasi di atas)

Ini juga memiliki beberapa properti yang menurut saya tidak bagus.

Sarang komposisi. Penggunaan berulang akan terus membuat indentasi semakin jauh ke kanan. Itu tidak selalu merupakan hal yang buruk, tetapi saya akan membayangkan bahwa dalam situasi dengan penanganan kesalahan yang sangat rumit yang memerlukan penanganan kesalahan yang menyebabkan kesalahan yang menyebabkan kesalahan bahwa kode untuk mengatasinya akan menjadi jauh lebih tidak jelas daripada status quo saat ini. Dalam situasi seperti itu, seseorang dapat menggunakan =? untuk kesalahan terluar dan if err != nil dalam kesalahan dalam tetapi apakah ini benar-benar meningkatkan penanganan kesalahan secara umum atau hanya dalam kasus umum? Mungkin meningkatkan kasus umum adalah semua yang diperlukan, tetapi saya tidak menganggapnya menarik, secara pribadi.

Ini memperkenalkan kepalsuan ke dalam bahasa untuk mendapatkan keumumannya. Kepalsuan yang didefinisikan sebagai "adalah (bukan) nilai nol" sangat masuk akal, tetapi if err != nil { lebih baik daripada if err { karena ini eksplisit, menurut saya. Saya berharap melihat liuk di alam liar yang mencoba menggunakan =? /etc. atas aliran kontrol yang lebih alami untuk mencoba mendapatkan akses ke kepalsuannya. Itu pasti akan unidiomatic dan disukai, tapi itu akan terjadi. Sementara potensi penyalahgunaan fitur itu sendiri bukan merupakan argumen terhadap fitur, itu adalah sesuatu yang perlu dipertimbangkan.

Pelingkupan yang ditingkatkan (untuk varian yang menyatakan parameternya) bagus dalam beberapa kasus, tetapi jika pelingkupan perlu diperbaiki, perbaiki pelingkupan secara umum.

Semantik "satu-satunya hasil paling kanan" masuk akal tetapi tampaknya agak aneh bagi saya. Itu lebih merupakan perasaan daripada argumen.

Proposal ini menambah singkatnya bahasa tetapi tidak ada kekuatan tambahan. Itu bisa diimplementasikan sepenuhnya sebagai preprocessor yang melakukan ekspansi makro. Itu tentu saja tidak diinginkan: itu akan memperumit pembangunan dan pengembangan fragmen dan praprosesor semacam itu akan sangat rumit karena harus peka terhadap tipe dan higienis. Saya tidak mencoba untuk mengabaikan dengan mengatakan "buat saja preprocessor". Saya mengangkat ini semata-mata untuk menunjukkan bahwa proposal ini sepenuhnya gula. Itu tidak membiarkan Anda melakukan apa pun yang tidak dapat Anda lakukan di Go sekarang; itu hanya memungkinkan Anda menulisnya dengan lebih ringkas. Saya tidak secara dogmatis menentang gula. Ada kekuatan dalam abstraksi linguistik yang dipilih dengan cermat, tetapi fakta bahwa itu adalah gula berarti bahwa itu harus dianggap sampai terbukti tidak bersalah, sehingga untuk berbicara.

lhs dari operator adalah pernyataan tetapi subset pernyataan yang sangat terbatas. Elemen mana yang termasuk dalam subset itu cukup jelas, tetapi, jika tidak ada yang lain, itu akan membutuhkan pemfaktoran ulang tata bahasa dalam spesifikasi bahasa untuk mengakomodasi perubahan.

Apakah sesuatu seperti?

func F() (S, T, error)

func MustF() (S, T) {
  return F() =? err { panic(err) }
}

diperkenankan?

Jika

defer f.Close() :=? err {
    return err
}

diperbolehkan yang harus (entah bagaimana) setara dengan

func theOuterFunc() (err error) {
  //...
  defer func() {
    if err2 := f.Close(); err2 != nil {
      err = err2
    }
  }()
  //...
}

yang tampaknya sangat bermasalah dan cenderung menyebabkan situasi yang sangat membingungkan, bahkan mengabaikannya dengan cara yang sangat tidak biasa itu menyembunyikan implikasi kinerja dari pengalokasian penutupan secara implisit. Alternatifnya adalah mengembalikan return dari penutupan implisit dan kemudian memiliki pesan kesalahan bahwa Anda tidak dapat mengembalikan nilai tipe error dari func() yang sedikit tumpul.

Sungguh, selain dari perbaikan pelingkupan yang sedikit ditingkatkan, ini tidak memperbaiki masalah apa pun yang saya hadapi saat menangani kesalahan di Go. Paling-paling mengetik if err != nil { return err } merepotkan, modulo sedikit masalah keterbacaan yang saya nyatakan di #21182. Dua masalah terbesar adalah

  1. memikirkan cara menangani kesalahan—dan tidak ada yang bisa dilakukan bahasa tentang itu
  2. mengintrospeksi kesalahan untuk menentukan apa yang harus dilakukan dalam beberapa situasi—beberapa konvensi tambahan dengan dukungan dari paket errors akan sangat membantu di sini, meskipun mereka tidak dapat menyelesaikan semua masalah.

Saya menyadari bahwa itu bukan satu-satunya masalah dan banyak yang menemukan aspek-aspek lain yang lebih mengkhawatirkan, tetapi merekalah yang paling banyak menghabiskan waktu bersama saya dan merasa lebih menjengkelkan dan menyusahkan daripada yang lainnya.

Analisis statis yang lebih baik untuk mendeteksi ketika saya telah mengacaukan sesuatu akan selalu dihargai, tentu saja (dan secara umum, bukan hanya skenario ini). Perubahan dan konvensi bahasa membuatnya lebih mudah untuk menganalisis sumbernya sehingga ini lebih berguna juga akan menarik.

Saya baru saja menulis banyak (BANYAK! maaf!) ​​tentang ini, tetapi saya tidak menolak proposal tersebut. Saya pikir itu memiliki kelebihan, tetapi saya tidak yakin bahwa itu membersihkan bar atau menarik bobotnya.

@jimmyfrasche

Saya ingat ketika perilaku saat ini dari := diperkenalkan — banyak utas yang sedang berjalan menuntut cara untuk secara eksplisit membubuhi keterangan nama mana yang akan digunakan kembali alih-alih "penggunaan kembali yang implisit hanya jika variabel itu ada persis dalam lingkup saat ini " di situlah semua masalah halus yang sulit dilihat terwujud, dalam pengalaman saya.

Saya tidak dapat menemukan utas itu apakah ada yang punya tautan?

Saya pikir Anda pasti mengingat utas yang berbeda, kecuali jika Anda terlibat dalam Go saat dirilis. Spek dari 2009/11/9, tepat sebelum dirilis, memiliki:

Tidak seperti deklarasi variabel biasa, deklarasi variabel pendek dapat mendeklarasikan ulang variabel asalkan mereka awalnya dideklarasikan di blok yang sama dengan tipe yang sama, dan setidaknya satu dari variabel non-kosong baru.

Saya ingat melihat itu ketika membaca spesifikasi untuk pertama kalinya dan berpikir itu adalah aturan yang bagus, karena saya sebelumnya menggunakan bahasa dengan := tetapi tanpa aturan penggunaan kembali itu, dan memikirkan nama baru untuk hal yang sama itu membosankan.

@mpvl
Saya pikir kerumitan contoh asli Anda lebih merupakan hasil dari
API yang Anda gunakan di sana daripada penanganan kesalahan Go itu sendiri.

Ini adalah contoh yang menarik, terutama karena fakta bahwa
Anda tidak ingin menutup file secara normal jika Anda panik, jadi
idiom "defer w.Close()" normal tidak berfungsi.

Jika Anda tidak perlu menghindari menelepon Tutup saat ada
panik, maka Anda bisa melakukan:

func writeToGS(ctx context.Context, bucket, dst string, r io.Reader) (err error) {
    client, err := storage.NewClient(ctx)
    if err != nil {
        return err
    }
    defer client.Close()

    w := client.Bucket(bucket).Object(dst).NewWriter(ctx)
    defer w.Close()
    _, err = io.Copy(w, r)
    if err != nil {
        w.CloseWithError(err)
    }
    return err
}

dengan asumsi bahwa semantik diubah sedemikian rupa sehingga memanggil Tutup
setelah memanggil CloseWithError adalah larangan.

Saya tidak berpikir itu terlihat begitu buruk lagi.

Bahkan dengan persyaratan bahwa file tidak ditulis tanpa kesalahan saat terjadi kepanikan seharusnya tidak terlalu sulit untuk diakomodasi; misalnya dengan menambahkan fungsi Finalize yang harus dipanggil secara eksplisit sebelum Close.

    w := client.Bucket(bucket).Object(dst).NewWriter(ctx)
    defer w.Close()
    _, err = io.Copy(w, r)
    return w.Finalize(err)

Itu tidak dapat melampirkan pesan kesalahan panik, tetapi pencatatan yang layak dapat membuatnya lebih jelas.
(metode Tutup bahkan dapat memiliki panggilan pemulihan di dalamnya, meskipun saya tidak yakin apakah itu
sebenarnya ide yang sangat buruk ...)

Namun, saya pikir aspek pemulihan panik dari contoh ini agak membingungkan dalam konteks ini, karena 99+% kasus penanganan kesalahan tidak melakukan pemulihan panik.

@rogpeppe :

Itu tidak dapat melampirkan pesan kesalahan panik, tetapi pencatatan yang layak dapat membuatnya lebih jelas.

Saya tidak berpikir itu adalah masalah.

Perubahan API yang Anda usulkan berkurang tetapi belum sepenuhnya menyelesaikan masalah. Semantik yang diperlukan membutuhkan kode lain untuk berperilaku dengan benar juga. Perhatikan contoh di bawah ini:

r, w := io.Pipe()
go func() {
    var err error                // used to intercept downstream errors
    defer func() {
        w.CloseWithError(err)
    }()

    r, err := newReader()
    if err != nil {
        return
    }
    defer func() {
        if errC := r.Close(); errC != nil && err == nil {
            err = errC
        }
    }
    _, err = io.Copy(w, r)
}()
return r

Dengan sendirinya, kode ini menunjukkan bahwa penanganan kesalahan bisa rumit atau setidaknya berantakan (dan saya ingin tahu bagaimana ini dapat ditingkatkan dengan proposal lain): ini secara diam-diam meneruskan kesalahan hilir ke atas melalui variabel dan memiliki sedikit terlalu kikuk jika pernyataan untuk memastikan bahwa kesalahan yang tepat dilewatkan. Keduanya terlalu banyak mengalihkan perhatian dari "logika bisnis". Penanganan kesalahan mendominasi kode. Dan contoh ini bahkan belum menangani kepanikan.

Untuk kelengkapan, dalam errd ini _would_ menangani kepanikan dengan benar dan akan terlihat seperti:

r, w := io.Pipe()
go errd.Run(func(e *errd.E) {
    e.Defer(w.CloseWithError)

    r, err := newReader()
    e.Must(err)
    e.Defer(r.Close)

    _, err = io.Copy(w, r)
    e.Must(err)
})
return r

Jika pembaca di atas (tidak menggunakan errd ) diteruskan sebagai pembaca untuk writeToGS, dan io.Reader dikembalikan oleh newReader panik, itu masih akan menghasilkan semantik yang salah dengan perbaikan API yang Anda usulkan (mungkin berpacu dengan penutupan yang berhasil file GS setelah pipa ditutup panik dengan kesalahan nihil.)

Ini juga membuktikan maksudnya. Tidaklah sepele untuk beralasan tentang penanganan kesalahan yang tepat di Go. Ketika saya melihat bagaimana kode akan terlihat seperti menulis ulang dengan errd , saya menemukan banyak kode kereta. Saya benar-benar hanya belajar betapa sulit dan halusnya menulis penanganan kesalahan Go idiomatik yang tepat, ketika menulis tes unit untuk paket errd . :)

Alternatif untuk perubahan API yang Anda usulkan adalah tidak menangani penangguhan sama sekali karena panik. Ini memiliki masalah sendiri dan tidak akan sepenuhnya menyelesaikan masalah dan mungkin tidak dapat dilakukan, tetapi akan memiliki beberapa kualitas yang bagus.

Either way, yang terbaik adalah beberapa perubahan bahasa yang mengurangi seluk-beluk penanganan kesalahan, daripada yang berfokus pada singkatnya.

@mpvl
Saya sering menemukan dengan kode penanganan kesalahan di Go bahwa membuat fungsi lain dapat membersihkan semuanya. Saya akan menulis kode Anda di atas sesuatu seperti ini:

func something() {
    r, w := io.Pipe()
    go func() {
        err := copyFromNewReader(w)
        w.CloseWithError(err)
    }()
    ...
}

func copyFromNewReader(w io.Writer) error {
    r, err := newReader()
    if err != nil {
        return err
    }
    defer r.Close()
    _, err = io.Copy(w, r)
    return err
}()

Saya berasumsi bahwa r.Close tidak mengembalikan kesalahan yang berguna - jika Anda telah membaca seluruh pembaca dan hanya menemukan io.EOF, maka hampir pasti tidak masalah jika mengembalikan kesalahan saat ditutup.

Saya tidak tertarik pada errd API - terlalu sensitif untuk memulai goroutine. Misalnya: https://play.golang.org/p/iT441gO5us Apakah doSomething melakukan atau tidak memulai goroutine untuk menjalankan
function in seharusnya tidak mempengaruhi kebenaran program, tetapi ketika menggunakan errd, hal itu terjadi. Anda mengharapkan kepanikan untuk melintasi batas abstraksi dengan aman, dan itu tidak terjadi di Go.

@mpvl

tunda w.CloseWithError(err)

BTW, baris ini selalu memanggil CloseWithError dengan nilai kesalahan nihil. Saya pikir Anda bermaksud
menulis:

defer func() { 
   w.CloseWithError(err)
}()

@mpvl

Perhatikan bahwa kesalahan yang dikembalikan oleh metode Close pada io.Reader hampir tidak pernah berguna (lihat daftar di https://github.com/golang/go/issues/20803#issuecomment-312318808 ).

Itu menunjukkan bahwa kita harus menulis contoh Anda hari ini sebagai:

r, w := io.Pipe()
go func() (err error) {
    defer func() { w.CloseWithError(err) }()

    r, err := newReader()
    if err != nil {
        return err
    }
    defer r.Close()

    _, err = io.Copy(w, r)
    return err
}()
return r

...yang tampaknya baik-baik saja bagi saya, selain sedikit bertele-tele.

Memang benar bahwa ia meneruskan kesalahan nihil ke w.CloseWithError jika terjadi kepanikan, tetapi seluruh program tetap berakhir pada titik itu. Jika penting untuk tidak pernah menutup dengan kesalahan nihil, ini adalah penggantian nama sederhana ditambah satu baris tambahan:

-go func() (err error) {
-   defer func() { w.CloseWithError(err) }()
+go func() (rerr error) {
+   rerr = errors.New("goroutine exited by panic")
+   defer func() { w.CloseWithError(rerr) }()

@rogpeppe : memang, terima kasih. :)

Ya, saya tahu masalah goroutine. Itu jahat, tapi mungkin sesuatu yang tidak sulit ditangkap dengan pemeriksaan dokter hewan. Bagaimanapun, saya tidak melihat errd sebagai solusi akhir tetapi lebih sebagai cara untuk mendapatkan pengalaman tentang cara terbaik mengatasi penanganan kesalahan. Idealnya akan ada perubahan bahasa yang memecahkan masalah yang sama, tetapi dengan pembatasan yang tepat.

Anda mengharapkan kepanikan untuk melintasi batas abstraksi dengan aman, dan itu tidak terjadi di Go.

Bukan itu yang saya harapkan. Dalam hal ini, saya mengharapkan API untuk tidak melaporkan keberhasilan ketika tidak ada. Potongan kode terakhir Anda menanganinya dengan benar, karena tidak menggunakan penangguhan untuk penulis. Tapi ini sangat halus. Banyak pengguna akan menggunakan penangguhan dalam hal ini karena dianggap idiomatik.

Mungkin serangkaian pemeriksaan dokter hewan dapat mendeteksi penggunaan penangguhan yang bermasalah. Namun, baik dalam kode "idiomatik" asli dan bagian refactored terakhir Anda, ada banyak mengutak-atik untuk mengatasi seluk-beluk penanganan kesalahan untuk sesuatu yang sebaliknya merupakan bagian kode yang cukup sederhana. Kode solusi bukan untuk mencari tahu bagaimana menangani kasus kesalahan tertentu, ini murni pemborosan siklus otak yang dapat digunakan untuk penggunaan produktif.

Secara khusus apa yang saya coba pelajari dari errd adalah apakah itu membuat penanganan kesalahan lebih mudah saat digunakan secara langsung. Dari apa yang saya lihat banyak komplikasi dan kehalusan menghilang. Akan lebih baik untuk melihat apakah kita dapat mengkodifikasi aspek semantiknya ke dalam fitur bahasa baru.

@jimmyfrasche

Ini memperkenalkan kepalsuan ke dalam bahasa untuk mendapatkan keumumannya.

Itu poin yang sangat bagus. Masalah biasa dengan falsiness datang dari lupa memanggil fungsi boolean atau lupa mendereferensi pointer ke nil.

Kita bisa mengatasi yang terakhir dengan mendefinisikan operator hanya bekerja dengan tipe nillable (dan mungkin menjatuhkan =! sebagai hasilnya, karena sebagian besar tidak berguna).

Kita dapat mengatasi yang pertama dengan lebih membatasinya agar tidak berfungsi dengan tipe fungsi, atau hanya bekerja dengan pointer atau tipe antarmuka: maka akan jelas bahwa variabel tersebut bukan boolean, dan upaya untuk menggunakannya untuk perbandingan boolean akan lebih jelas salah.

Apakah sesuatu seperti [ MustF ] diizinkan?

Ya.

Jika [ defer f.Close() :=? err { ] diizinkan, itu harus (entah bagaimana) setara dengan
[ defer func() { … }() ].

Belum tentu, tidak. Itu bisa memiliki semantiknya sendiri (lebih seperti call/cc daripada fungsi anonim). Saya belum mengusulkan perubahan spesifikasi untuk menggunakan =? di defer (setidaknya akan memerlukan perubahan tata bahasa), jadi saya tidak yakin persis seberapa rumit definisi seperti itu .

Dua masalah terbesar adalah […] 2. mengintrospeksi kesalahan untuk menentukan apa yang harus dilakukan dalam beberapa situasi

Saya setuju bahwa itu adalah masalah yang lebih besar dalam praktiknya, tetapi tampaknya kurang lebih ortogonal untuk masalah ini (yang lebih tentang mengurangi boilerplate dan potensi kesalahan yang terkait).

( @rogpeppe , @davecheney , @dsnet , @crawshaw , I, dan beberapa lainnya Saya pasti lupa memiliki diskusi yang bagus di GopherCon tentang API untuk memeriksa kesalahan, dan saya harap kita akan melihat beberapa proposal bagus di depan itu juga , tapi saya benar-benar berpikir itu masalah untuk masalah lain.)

@bcmills : kode ini memiliki dua masalah 1) sama seperti yang disebutkan @rogpeppe : err yang diteruskan ke CloseWithError selalu nihil, dan 2) masih tidak menangani kepanikan sehingga berarti API akan melaporkan keberhasilan secara eksplisit ketika ada kepanikan ( dikembalikan r mungkin memancarkan io.EOF bahkan ketika tidak semua byte telah ditulis), bahkan jika 1 diperbaiki.

Kalau tidak, saya setuju bahwa kesalahan yang dikembalikan oleh Tutup sering kali dapat diabaikan. Tidak selalu, meskipun (lihat contoh pertama).

Saya merasa agak mengejutkan bahwa ada 4 atau 5 saran yang salah dibuat pada contoh saya yang agak langsung (termasuk satu dari saya sendiri) dan saya masih merasa harus berpendapat bahwa penanganan kesalahan di Go bukanlah hal yang sepele. :)

@bcmils :

Memang benar bahwa ia meneruskan kesalahan nil ke w.CloseWithError jika terjadi kepanikan, tetapi seluruh program tetap berakhir pada titik itu.

Melakukannya? Penunda dari goroutine itu masih dipanggil. Sejauh yang saya mengerti mereka akan berjalan sampai selesai. Dalam hal ini, Tutup akan memberi sinyal io.EOF.

Lihat, misalnya, https://play.golang.org/p/5CFbsAe8zF. Setelah goroutine panik, ia masih dengan senang hati meneruskan "foo" ke goroutine lain yang kemudian masih membuatnya untuk menulisnya ke Stdout.

Demikian pula, kode lain mungkin menerima io.EOF yang salah dari goroutine yang panik (seperti yang ada di contoh Anda), menyimpulkan sukses, dan dengan senang hati melakukan file ke GS sebelum goroutine yang panik melanjutkan kepanikannya.

Argumen Anda berikutnya mungkin: jangan menulis kode kereta, tetapi:

  • kemudian buat lebih mudah untuk mencegah bug ini, dan
  • panik dapat disebabkan oleh faktor eksternal, seperti OOM.

Jika penting untuk tidak pernah menutup dengan kesalahan nihil, ini adalah penggantian nama sederhana ditambah satu baris tambahan:

Itu masih harus ditutup dengan nil untuk memberi sinyal io.EOF ketika selesai, sehingga tidak akan berfungsi.

Jika penting untuk tidak pernah menutup dengan kesalahan nihil, ini adalah penggantian nama sederhana ditambah satu baris tambahan:

Itu masih harus ditutup dengan nil untuk memberi sinyal io.EOF ketika selesai, sehingga tidak akan berfungsi.

Kenapa tidak? return err di akhir akan menetapkan rerr menjadi nil .

@bcmils : ah saya mengerti apa yang Anda maksud sekarang. Ya, itu harus berhasil. Saya tidak khawatir tentang jumlah baris, melainkan tentang kehalusan kode.

Saya menemukan ini berada dalam kategori masalah yang sama dengan variabel shadowing, hanya lebih kecil kemungkinannya untuk terjadi (mungkin membuatnya lebih buruk.) Sebagian besar bug shadowing variabel yang dapat Anda perdebatkan dengan unit test yang baik. Kepanikan sewenang-wenang lebih sulit untuk diuji.

Saat beroperasi dalam skala besar, dijamin Anda akan melihat bug seperti ini bermanifestasi. Saya mungkin paranoid, tetapi saya telah melihat skenario yang jauh lebih kecil kemungkinannya menyebabkan hilangnya data dan korupsi. Biasanya ini baik-baik saja, tetapi tidak untuk pemrosesan transaksi (seperti menulis file gs.)

Saya harap Anda tidak keberatan saya membajak proposal Anda dengan sintaks alternatif - bagaimana perasaan orang-orang tentang sesuatu seperti ini:

return err if f, err := os.Open("..."); err != nil

@SirCmpwn Itu mengubur lede. Hal termudah untuk dibaca dalam suatu fungsi adalah aliran kontrol normal, bukan penanganan kesalahan.

Itu wajar, tetapi proposal Anda juga membuat saya tidak nyaman - proposal ini memperkenalkan sintaksis buram (||) yang berperilaku berbeda dari yang diharapkan oleh pengguna yang telah dilatih || berperilaku. Tidak yakin apa solusi yang tepat, akan memikirkannya lagi.

@SirCmpwn Ya, seperti yang saya katakan di posting asli "Saya menulis proposal ini terutama untuk mendorong orang-orang yang ingin menyederhanakan penanganan kesalahan Go untuk memikirkan cara-cara untuk memudahkan membungkus konteks di sekitar kesalahan, bukan hanya mengembalikan kesalahan yang tidak dimodifikasi ." Saya menulis proposal saya sebaik mungkin, tetapi saya tidak berharap itu akan diadopsi.

Dipahami.

Ini sedikit lebih radikal, tetapi mungkin pendekatan berbasis makro akan bekerja lebih baik.

f = try!(os.Open("..."))

try! akan memakan nilai terakhir dalam tupel dan mengembalikannya jika tidak nihil, dan sebaliknya mengembalikan sisa tupel.

Saya ingin menyarankan bahwa pernyataan masalah kami adalah,

Penanganan kesalahan di Go bertele-tele dan berulang. Format idiomatis penanganan kesalahan Go membuat lebih sulit untuk melihat aliran kontrol non-kesalahan dan verbositas tidak menarik, terutama bagi pendatang baru. Sampai saat ini, solusi yang diusulkan untuk masalah ini biasanya memerlukan fungsi penanganan kesalahan satu kali artisanal, mengurangi lokalitas penanganan kesalahan, dan meningkatkan kompleksitas. Karena salah satu tujuan Go adalah memaksa penulis untuk mempertimbangkan penanganan dan pemulihan kesalahan, setiap perbaikan pada penanganan kesalahan juga harus dibangun di atas tujuan itu.

Untuk mengatasi pernyataan masalah ini, saya mengusulkan tujuan ini untuk perbaikan penanganan kesalahan di Go 2.x:

  1. Mengurangi boilerplate penanganan kesalahan berulang dan memaksimalkan fokus pada maksud utama jalur kode.
  2. Mendorong penanganan kesalahan yang tepat, termasuk membungkus kesalahan saat menyebarkannya ke depan.
  3. Mematuhi prinsip-prinsip desain Go tentang kejelasan dan kesederhanaan.
  4. Dapat diterapkan dalam berbagai situasi penanganan kesalahan yang seluas mungkin.

Mengevaluasi proposal ini:

f.Close() =? err { return fmt.Errorf(…, err) }

sesuai dengan tujuan tersebut, saya akan menyimpulkan bahwa itu berhasil dengan baik pada tujuan #1. Saya tidak yakin bagaimana ini membantu dengan # 2, tetapi itu juga tidak membuat penambahan konteks menjadi lebih kecil (proposal saya sendiri membagikan kelemahan ini pada # 2). Itu tidak benar-benar berhasil di # 3 dan # 4:
1) Seperti yang dikatakan orang lain, pemeriksaan dan penetapan nilai kesalahan tidak jelas dan tidak biasa; dan
2) Sintaks =? juga tidak biasa. Ini sangat membingungkan jika digabungkan dengan sintaks =! serupa tetapi berbeda. Butuh beberapa saat bagi orang untuk terbiasa dengan maknanya; dan
3) Mengembalikan nilai yang valid bersama dengan kesalahannya cukup umum sehingga solusi baru apa pun juga harus menangani kasus itu.

Membuat kesalahan yang menangani blok mungkin merupakan ide yang bagus, meskipun, jika seperti yang disarankan orang lain, ini dikombinasikan dengan perubahan pada gofmt . Sehubungan dengan proposal saya, ini meningkatkan generalitas, yang seharusnya membantu dengan tujuan # 4 dan keakraban yang membantu tujuan # 3 dengan mengorbankan pengorbanan dalam singkatnya untuk kasus umum hanya mengembalikan kesalahan dengan konteks tambahan.

Jika Anda bertanya kepada saya secara abstrak, saya mungkin setuju bahwa solusi yang lebih umum akan lebih disukai daripada solusi khusus penanganan kesalahan selama itu memenuhi tujuan perbaikan penanganan kesalahan di atas. Namun sekarang, setelah membaca diskusi ini dan memikirkannya lebih lanjut, saya cenderung percaya bahwa solusi spesifik penanganan kesalahan akan menghasilkan kejelasan dan kesederhanaan yang lebih besar. Sementara kesalahan di Go hanyalah nilai, penanganan kesalahan merupakan bagian yang signifikan dari setiap pemrograman yang memiliki beberapa sintaks khusus untuk membuat kode penanganan kesalahan yang jelas dan ringkas tampaknya tepat. Saya khawatir kita akan membuat masalah yang sudah sulit (mendapatkan solusi bersih untuk penanganan kesalahan) bahkan lebih sulit dan lebih rumit jika kita menggabungkannya dengan tujuan lain seperti pelingkupan dan komposisi.

Terlepas dari itu, seperti yang ditunjukkan @rsc dalam artikelnya, Toward Go 2 , baik pernyataan masalah, tujuan, maupun proposal sintaksis apa pun kemungkinan akan maju tanpa laporan pengalaman yang menunjukkan bahwa masalahnya signifikan. Mungkin daripada memperdebatkan berbagai usulan sintaks, sebaiknya kita mulai menggali data pendukung?

Terlepas dari itu, seperti yang ditunjukkan @rsc dalam artikelnya, Toward Go 2, baik pernyataan masalah, tujuan, maupun proposal sintaksis apa pun kemungkinan akan maju tanpa laporan pengalaman yang menunjukkan bahwa masalahnya signifikan. Mungkin daripada memperdebatkan berbagai usulan sintaks, sebaiknya kita mulai menggali data pendukung?

Saya pikir ini terbukti dengan sendirinya jika kita menganggap bahwa ergonomi itu penting. Buka basis kode Go mana pun dan cari tempat di mana ada peluang untuk MENGERINGKAN dan/atau meningkatkan ergonomi yang dapat diatasi oleh bahasa tersebut - saat ini penanganan kesalahan jelas merupakan hal yang berbeda. Saya pikir pendekatan Toward Go 2 mungkin keliru menganjurkan untuk mengabaikan masalah yang memiliki solusi - dalam hal ini orang hanya tersenyum dan menanggungnya.

if $val, err := $operation($args); err != nil {
  return err
}

Ketika ada lebih banyak boilerplate daripada kode, masalahnya sudah jelas.

@billyh

Saya merasa bahwa format: f.Close() =? err { return fmt.Errorf(…, err) } terlalu bertele-tele dan membingungkan. Saya pribadi tidak merasa bahwa bagian kesalahan harus di blok. Tak pelak lagi, itu akan menyebabkannya menyebar dalam 3 baris, bukan 1. Selanjutnya, dalam perubahan off yang perlu Anda lakukan lebih dari sekadar memodifikasi kesalahan sebelum mengembalikannya, seseorang dapat menggunakan if err != nil { ... } sintaks.

Operator =? juga agak membingungkan. Tidak segera jelas apa yang terjadi di sana.

Dengan sesuatu seperti ini:
file := os.Open("/some/file") or raise(err) errors.Wrap(err, "extra context")
atau singkatan:
file := os.Open("/some/file") or raise
dan yang ditangguhkan:
defer f.Close() or raise(err2) errors.ReplaceIfNil(err, err2)
sedikit lebih bertele-tele dan pilihan kata dapat mengurangi kebingungan awal (yaitu orang mungkin langsung mengaitkan raise dengan kata kunci serupa dari bahasa lain seperti python, atau hanya menyimpulkan bahwa kenaikan itu menimbulkan kesalahan/terakhir-non- nilai default ke atas tumpukan ke pemanggil).

Ini juga merupakan solusi imperatif yang baik, yang tidak mencoba menyelesaikan setiap kemungkinan penanganan kesalahan yang tidak jelas di bawah matahari. Sejauh ini, penanganan kesalahan terbesar di alam liar adalah yang disebutkan di atas. Untuk nanti, sintaks saat ini juga ada untuk membantu.

Sunting:
Jika kita ingin sedikit mengurangi "keajaiban", contoh sebelumnya mungkin juga terlihat seperti:
file, err := os.Open("/some/file") or raise errors.Wrap(err, "extra context")
file, err := os.Open("/some/file") or raise err
defer err2 := f.Close() or errors.ReplaceIfNil(err, err2)
Saya pribadi berpikir contoh sebelumnya lebih baik, karena mereka memindahkan penanganan kesalahan penuh ke kanan, alih-alih membaginya seperti yang terjadi di sini. Ini mungkin lebih jelas.

Saya ingin menyarankan bahwa pernyataan masalah kita adalah, ...

Saya tidak setuju dengan pernyataan masalah. Saya ingin menyarankan alternatif:


Penanganan kesalahan tidak ada dari sudut pandang bahasa. Satu-satunya hal yang disediakan Go adalah jenis kesalahan yang telah dideklarasikan sebelumnya dan bahkan itu hanya untuk kenyamanan karena tidak mengaktifkan sesuatu yang benar-benar baru. Kesalahan hanyalah nilai . Penanganan kesalahan hanyalah kode pengguna biasa. Tidak ada yang istimewa dari bahasa POV dan seharusnya tidak ada yang istimewa darinya. Satu-satunya masalah dengan penanganan kesalahan adalah bahwa beberapa orang percaya kesederhanaan yang berharga dan indah ini harus dihilangkan dengan cara apa pun.

Sejalan dengan apa yang dikatakan Cznic, alangkah baiknya memiliki solusi yang berguna untuk lebih dari sekadar penanganan kesalahan.

Salah satu cara untuk membuat penanganan kesalahan lebih umum adalah dengan memikirkannya dalam hal tipe-serikat/tipe-jumlah dan membuka bungkusnya. Swift dan Rust keduanya memiliki solusi dengan ? ! sintaks, meskipun saya pikir Rust agak tidak stabil.

Jika kita tidak ingin menjadikan sum-types sebagai konsep tingkat tinggi, kita dapat menjadikannya hanya bagian dari pengembalian berganda, karena tupel bukanlah benar-benar bagian dari Go, tetapi Anda masih dapat melakukan pengembalian berganda.

Penusukan pada sintaks yang terinspirasi oleh Swift:

func Failable() (*Thingie | error) {
    ...
}

guard thingie, err := Failable() else { 
    return wrap(err, "Could not make thingie)
}
// err is not in scope here

Anda dapat menggunakan ini untuk hal-hal lain juga, seperti:

guard val := myMap[key] else { val = "default" }

Solusi =? diusulkan oleh @bcmills dan @jba tidak hanya untuk kesalahan, konsepnya adalah untuk bukan nol. contoh ini akan bekerja secara normal.

func Foo()(Bar, Recover){}
bar := Foo() =? recover { log.Println("[Info] Recovered:", recover)}

Gagasan utama dari proposal ini adalah catatan samping, memisahkan tujuan utama kode dan mengesampingkan kasus sekunder, untuk memudahkan membaca.
Bagi saya pembacaan kode Go, dalam beberapa kasus, tidak berkelanjutan, sering kali Anda menghentikan ide dengan if err!= nil {return err} , jadi ide catatan samping tampaknya menarik bagi saya, seperti dalam buku yang kita baca gagasan utama terus-menerus dan kemudian membaca catatan samping. ( @jba bicara )
Dalam situasi yang sangat jarang, kesalahan adalah tujuan utama suatu fungsi, mungkin dalam pemulihan. Biasanya ketika kami memiliki kesalahan, kami menambahkan beberapa konteks, log dan kembali, dalam kasus ini, catatan samping dapat membuat kode Anda lebih mudah dibaca.
Saya tidak tahu apakah itu sintaks terbaik, terutama saya tidak suka blok di bagian kedua, catatan samping harus kecil, satu baris sudah cukup

bar := Foo() =? recover: log.Println("[Info] Recovered:", recover)

@billyh

  1. Seperti yang dikatakan orang lain, pemeriksaan dan penetapan nilai kesalahan tidak jelas dan tidak biasa; dan

Harap lebih konkret: "buram dan tidak biasa" sangat subjektif. Bisakah Anda memberikan beberapa contoh kode yang menurut Anda akan membingungkan proposal?

  1. =? sintaks juga tidak biasa. […]

IMO itu fitur. Jika seseorang melihat operator yang tidak biasa, saya menduga mereka lebih cenderung untuk mencari tahu apa yang dilakukannya daripada hanya mengasumsikan sesuatu yang mungkin akurat atau tidak.

  1. Mengembalikan nilai yang valid bersama dengan kesalahan cukup umum sehingga solusi baru apa pun juga harus menangani kasus itu.

itu tidak?

Baca proposal dengan cermat: =? melakukan tugas sebelum mengevaluasi Block , sehingga dapat digunakan untuk kasus itu juga:

n := r.Read(buf) =? err {
  if err == io.EOF {
    […]
  }
  return err
}

Dan seperti yang dicatat @nigeltao , Anda selalu dapat menggunakan pola 'n, err := r.Read(buf)` yang ada. Menambahkan fitur untuk membantu pelingkupan dan boilerplate untuk kasus umum tidak berarti bahwa kita juga harus menggunakannya untuk kasus yang tidak biasa.

Mungkin daripada memperdebatkan berbagai usulan sintaks, sebaiknya kita mulai menggali data pendukung?

Lihat banyak masalah (dan contohnya) yang ditautkan Ian di pos asli.
Lihat juga https://github.com/golang/go/wiki/ExperienceReports#error -handling.

Jika Anda memiliki wawasan khusus dari laporan tersebut, silakan bagikan.

@urandom

Saya pribadi tidak merasa bahwa bagian kesalahan harus di blok. Tak pelak lagi, itu akan membuatnya tersebar dalam 3 baris, bukan 1.

Tujuan blok ada dua:

  1. untuk memberikan jeda visual dan tata bahasa yang jelas antara ekspresi penghasil kesalahan dan penangannya, dan
  2. untuk memungkinkan penanganan kesalahan yang lebih luas (per tujuan yang dinyatakan @ianlancetaylor di pos asli).

3 baris vs. 1 bahkan bukan perubahan bahasa: jika jumlah baris adalah masalah terbesar Anda, kami dapat mengatasinya dengan perubahan sederhana ke gofmt .

file, err := os.Open("/some/file") or raise errors.Wrap(err, "extra context")
file, err := os.Open("/some/file") or raise err

Kita sudah memiliki return dan panic ; menambahkan raise di atas itu sepertinya menambahkan terlalu banyak cara untuk keluar dari fungsi dengan keuntungan yang terlalu sedikit.

defer err2 := f.Close() or errors.ReplaceIfNil(err, err2)

errors.ReplaceIfNil(err, err2) akan membutuhkan beberapa semantik pass-by-reference yang sangat tidak biasa.
Anda bisa melewatkan err dengan pointer sebagai gantinya, saya kira:

defer err2 := f.Close() or errors.ReplaceIfNil(&err, err2)

tapi menurut saya masih sangat aneh. Apakah token or membuat ekspresi, pernyataan, atau yang lainnya? (Proposal yang lebih konkret akan membantu.)

@carlmjohnson

Apa sintaks dan semantik konkret dari pernyataan guard … else ? Bagi saya, ini sangat mirip =? atau :: dengan token dan posisi variabel yang ditukar. (Sekali lagi, proposal yang lebih konkret akan membantu: apa sintaks dan semantik aktual yang Anda pikirkan?)

@bcmils
ReplaceIfNil hipotetis akan menjadi sederhana:

func ReplaceIfNil(original, replacement error) error {
   if original == nil {
       return replacement
   }
   return original
}

Tidak ada yang tidak biasa tentang itu. Mungkin namanya...

or akan menjadi operator biner, di mana operan kiri akan menjadi IdentifierList, atau PrimaryExpr. Dalam kasus yang pertama, itu direduksi menjadi pengidentifikasi paling kanan. Ini kemudian memungkinkan operan kanan untuk dieksekusi jika operan kiri bukan nilai default.

Itulah sebabnya saya membutuhkan token lain setelahnya, untuk melakukan keajaiban mengembalikan nilai default, untuk semua kecuali parameter terakhir dalam fungsi Hasil, yang akan mengambil nilai ekspresi setelahnya.
IIRC, ada proposal lain belum lama ini yang akan menambahkan bahasa '...' atau sesuatu, yang akan menggantikan inisialisasi nilai default yang membosankan. Karena itu, semuanya mungkin terlihat seperti ini:

f, err := os.Open("/some/file") or return ..., errors.Wrap(err, "more context")

Adapun blok, saya mengerti bahwa itu memungkinkan penanganan yang lebih luas. Saya pribadi tidak yakin apakah ruang lingkup proposal ini harus mencoba dan memenuhi setiap skenario yang mungkin, sebagai lawan untuk menutupi 80% hipotetis. Dan saya pribadi percaya bahwa itu penting berapa banyak baris yang akan diambil (meskipun saya tidak pernah mengatakan itu adalah kekhawatiran terbesar saya, itu sebenarnya keterbacaan, atau kekurangannya, ketika menggunakan token yang tidak jelas seperti =?). Jika proposal baru ini mencakup beberapa baris dalam kasus umum, saya pribadi tidak melihat manfaatnya dibandingkan sesuatu seperti:

if f, err := os.Open("/some/file"); err != nil {
     return errors.Wrap(err, "more context")
}
  • jika variabel yang ditentukan di atas akan tersedia di luar lingkup if .
    Dan itu masih akan membuat fungsi hanya dengan beberapa pernyataan seperti itu lebih sulit untuk dibaca, karena gangguan visual dari blok penanganan kesalahan ini. Dan itulah salah satu keluhan yang dimiliki orang-orang ketika membahas penanganan error di go.

@urandom

or akan menjadi operator biner, di mana operan kiri akan menjadi IdentifierList, atau PrimaryExpr. […] Kemudian memungkinkan operan kanan untuk dieksekusi jika operan kiri bukan nilai default.

Operator biner Go adalah ekspresi, bukan pernyataan, jadi menjadikan or sebagai operator biner akan menimbulkan banyak pertanyaan. (Apa semantik dari or sebagai bagian dari ekspresi yang lebih besar, dan bagaimana hal itu sesuai dengan contoh yang Anda posting dengan := ?)

Dengan asumsi bahwa itu sebenarnya sebuah pernyataan, apa operan tangan kanan? Jika itu adalah ekspresi, apa tipenya, dan dapatkah raise digunakan sebagai ekspresi dalam konteks lain? Jika itu adalah pernyataan, apa semantiknya jika selain raise ? Atau apakah Anda mengusulkan or raise pada dasarnya menjadi satu pernyataan (misalnya or raise sebagai alternatif sintaks untuk :: atau =? )?

Bisakah saya menulis?

defer f.Close() or raise(err2) errors.ReplaceIfNil(err, err2) or raise(err3) Transform(err3)

?

Bisakah saya menulis?

f(r.Read(buf) or raise err)

?

defer f.Close() or raise(err2) errors.ReplaceIfNil(err, err2) or raise(err3) Transform(err3)

Tidak, ini tidak valid karena raise . Jika itu tidak ada, maka seluruh rantai transformasi harus melalui dan hasil akhir harus dikembalikan ke pemanggil. Meskipun semantik seperti itu secara keseluruhan mungkin tidak diperlukan, karena Anda cukup menulis:

defer f.Close() or raise(err2) Transform(errors.ReplaceIfNil(err, err2)


f(r.Read(buf) or raise err)

Jika kita mengasumsikan komentar asli saya - di mana atau akan mengambil nilai terakhir dari sisi kiri, sehingga jika itu adalah nilai default, ekspresi akhir akan dievaluasi ke daftar hasil lainnya; maka ya, ini harus valid. Dalam hal ini, jika r.Read mengembalikan kesalahan, kesalahan itu dikembalikan ke pemanggil. Jika tidak, n akan diteruskan ke f

Sunting:

Kecuali saya bingung dengan istilahnya, saya menganggap or sebagai operator biner, yang operan-operannya harus dari jenis yang sama (tapi agak ajaib, jika operan kiri adalah daftar hal-hal, dan dalam hal ini dibutuhkan elemen terakhir dari daftar hal-hal tersebut). raise akan menjadi operator unary yang mengambil operandnya, dan kembali dari fungsi, menggunakan nilai operan itu sebagai nilai argumen pengembalian terakhir, dengan yang sebelumnya memiliki nilai default. Anda kemudian dapat secara teknis menggunakan raise dalam pernyataan mandiri, untuk tujuan kembali dari suatu fungsi, alias return ..., err

Ini akan menjadi kasus yang ideal, tetapi saya juga baik-baik saja dengan or raise hanya menjadi alternatif sintaks untuk =? , selama itu juga menerima pernyataan sederhana alih-alih blok, sehingga mencakup sebagian besar kasus penggunaan dengan cara yang tidak terlalu bertele-tele. Atau kita bisa menggunakan tata bahasa seperti penangguhan juga, di mana ia menerima ekspresi. Ini akan mencakup sebagian besar kasus seperti:

f := os.Open("/some/file") or raise(err) errors.Wrap(err, "with context")

dan kasus kompleks:

f := os.Open or raise(err) func() {
     if err == io.EOF {
         […]
     }
  return err
}()

Memikirkan proposal saya sedikit lagi, saya sedikit melupakan tipe serikat/jumlah. Sintaks yang saya usulkan adalah

guard [ ASSIGNMENT || EXPRESSION ] else { [ BLOCK ] }

Dalam kasus ekspresi, ekspresi dievaluasi dan jika hasilnya tidak sama dengan true untuk ekspresi boolean atau nilai kosong untuk ekspresi lain, BLOCK dijalankan. Dalam tugas, nilai yang terakhir ditetapkan dievaluasi untuk != true / != nil . Mengikuti pernyataan penjaga, tugas apa pun yang dibuat akan berada dalam cakupan (itu tidak membuat cakupan blok baru [kecuali mungkin untuk variabel terakhir?]).

Di Swift, pernyataan BLOCK untuk guard harus berisi salah satu dari return , break , continue , atau throw . Saya belum memutuskan apakah saya suka itu atau tidak. Tampaknya menambahkan beberapa nilai karena pembaca tahu dari kata guard apa yang akan mengikuti.

Apakah ada yang mengikuti Swift dengan cukup baik untuk mengatakan jika guard dianggap baik oleh komunitas itu?

Contoh:

guard f, err := os.Open("/some/file") else { return errors.Wrap(err, "could not open:") }

guard data, err := ioutil.ReadAll(f) else { return errors.Wrap(err, "could not read:") }

var obj interface{}

guard err = json.Unmarshal(data, &obj) else { return errors.Wrap(err, "could not unmarshal:") }

guard m, _ := obj.(map[string]interface{}) else { return errors.New("unexpected data format") }

guard val, _ := m["key"] else { return errors.New("missing key") }

Imho semua orang membahas terlalu banyak masalah di sini sekaligus, tetapi pola yang paling umum dalam kenyataan adalah "kesalahan pengembalian apa adanya". Jadi mengapa tidak mendekati sebagian besar masalah dengan hal-hal seperti:

code, err ?= fn()

yang berarti fungsi harus kembali pada err != nil?

untuk := operator kami dapat memperkenalkan ?:=

code, err ?:= fn()

situasi dengan ?:= tampaknya lebih buruk karena bayangan, karena kompiler harus meneruskan variabel "err" ke nilai pengembalian err bernama yang sama.

Saya sebenarnya cukup senang bahwa beberapa orang berfokus untuk mempermudah penulisan kode yang benar daripada hanya mempersingkat kode yang salah.

Beberapa catatan:

Sebuah "laporan pengalaman" yang menarik dari salah satu desainer Midori di Microsoft pada model kesalahan.

Saya pikir beberapa ide dari dokumen ini dan Swift dapat diterapkan dengan indah ke Go2.

Memperkenalkan kata kunci throws reseved baru, fungsi dapat didefinisikan seperti:

func Get() []byte throws {
  if (...) {
    raise errors.New("oops")
  }

  return []byte{...}
}

Mencoba memanggil fungsi ini dari fungsi non-throwing lain akan menghasilkan kesalahan kompilasi, karena kesalahan throwable yang tidak tertangani.
Sebaliknya kita harus dapat menyebarkan kesalahan, yang semua orang setuju adalah kasus umum, atau menanganinya.

func ScrapeDate() time.Time throws {
  body := Get() // compilation error, unhandled throwable
  body := try Get() // we've been explicit about potential throwable

  // ...
}

Untuk kasus ketika kita tahu bahwa suatu metode tidak akan gagal, atau dalam pengujian, kita dapat memperkenalkan try! mirip dengan swift.

func GetWillNotFail() time.Time {
  body := Get() // compilation error, throwable not handled
  body := try Get() // compilation error, throwable can not be propagated, because `GetWillNotFail` is not annotated with throws
  body := try! Get() // works, but will panic on throws != nil

  // ...
}

Tidak yakin tentang ini (mirip dengan Swift):

func main() {
  // 1:
  do {
    fmt.Printf("%v", try ScrapeDate())
  } catch err { // err contains caught throwable
    // ...
  }

  // 2:
  do {
    fmt.Printf("%v", try ScrapeDate())
  } catch err.(type) { // similar to a switch statement
    case error:
      // ...
    case io.EOF
      // ...
  }
}

ps1. beberapa nilai pengembalian func ReadRune() (ch Rune, size int) throws { ... }
ps2. kita dapat kembali dengan return try Get() atau return try! Get()
ps3. sekarang kita dapat melakukan panggilan berantai seperti buffer.NewBuffer(try Get()) atau buffer.NewBuffer(try! Get())
ps4. Tidak yakin tentang anotasi (cara mudah untuk menulis errors.Wrap(err, "context") )
ps5. ini sebenarnya pengecualian
ps6. kemenangan terbesar adalah kompilasi kesalahan waktu untuk pengecualian yang diabaikan

Saran yang Anda tulis persis dijelaskan di tautan Midori dengan semua yang buruk
sisinya... Dan satu konsekuensi yang jelas dari "lemparan" adalah "orang
membencinya". Mengapa seseorang harus menulis "melempar" setiap kali untuk sebagian besar?
fungsi?

BTW, niat Anda untuk memaksa kesalahan diperiksa dan tidak diabaikan bisa
diterapkan pada tipe non-kesalahan juga dan saya lebih baik memiliki lebih banyak
bentuk umum (misalnya gcc __attribute__((warn_unused_result))).

Adapun bentuk operator saya akan menyarankan bentuk pendek atau
bentuk kata kunci seperti ini:

?= fn() ATAU periksa fn() -- menyebarkan kesalahan ke pemanggil
!= fn() ATAU nofail fn() -- panik karena kesalahan

Pada Sabtu, 26 Agustus 2017 pukul 12:15, nvartolomei [email protected]
menulis:

Beberapa catatan:

Laporan pengalaman yang menarik
http://joeduffyblog.com/2016/02/07/the-error-model/ dari salah satu
desainer Midori di Microsoft pada model kesalahan.

Saya pikir beberapa ide dari dokumen ini dan Swift
https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/ErrorHandling.html
dapat diterapkan dengan indah ke Go2.

Memperkenalkan kata kunci lemparan reseved baru, fungsi dapat didefinisikan seperti:

func Get() []byte melempar {
jika (...) {
meningkatkan error.New("oops")
}

kembalikan []byte{...}
}

Mencoba memanggil fungsi ini dari fungsi lain yang tidak melempar akan
menghasilkan kesalahan kompilasi, karena kesalahan yang dapat dibuang yang tidak tertangani.
Alih-alih, kita harus dapat menyebarkan kesalahan, yang semua orang setujui adalah
kasus umum, atau menanganinya.

func ScrapeDate() time.Time melempar {
body := Get() // kesalahan kompilasi, throwable yang tidak tertangani
body := try Get() // kita sudah eksplisit tentang potensi throwable

// ...
}

Untuk kasus ketika kita tahu bahwa suatu metode tidak akan gagal, atau dalam pengujian, kita dapat
perkenalkan coba! mirip dengan cepat.

func GetWillNotFail() waktu.Waktu {
body := Get() // kesalahan kompilasi, dapat dibuang tidak ditangani
body := try Get() // kesalahan kompilasi, throwable tidak dapat disebarkan, karena GetWillNotFail tidak dijelaskan dengan throws
tubuh := coba! Get() // berfungsi, tetapi akan panik saat melempar != nil

// ...
}

Tidak yakin tentang ini (mirip dengan Swift):

fungsi utama() {
// 1:
melakukan {
fmt.Printf("%v", coba ScrapeDate())
} catch err { // err berisi tangkapan yang bisa dibuang
// ...
}

// 2:
melakukan {
fmt.Printf("%v", coba ScrapeDate())
} catch err.(type) { // mirip dengan pernyataan switch
kesalahan kasus:
// ...
kasus io.EOF
// ...
}
}

ps1. beberapa nilai pengembalian func ReadRune() (ch Rune, size int) throws {
... }
ps2. kita dapat kembali dengan mencoba kembali Get() atau kembali mencoba! Mendapatkan()
ps3. kita sekarang dapat melakukan panggilan berantai seperti buffer.NewBuffer(coba Get()) atau buffer.NewBuffer(coba!
Mendapatkan())
ps4. Tidak yakin tentang anotasi


Anda menerima ini karena Anda berkomentar.
Balas email ini secara langsung, lihat di GitHub
https://github.com/golang/go/issues/21161#issuecomment-325106225 , atau bisukan
benang
https://github.com/notifications/unsubscribe-auth/AICzv9CLN77RmPceCqvjXVE_UZ6o7JGvks5sb-IYgaJpZM4Oi1c-
.

Saya pikir operator yang diusulkan oleh @jba dan @bcmils adalah ide yang sangat bagus, meskipun dibaca lebih baik dieja sebagai "??" bukannya "=?" IMO.

Melihat contoh ini:

func doStuff() (int,error) {
    x, err := f() 
    if err != nil {
        return 0, wrapError("f failed", err)
    }

    y, err := g(x)
    if err != nil {
        return 0, wrapError("g failed", err)
    }

    return y, nil
}

func doStuff2() (int,error) {
    x := f()  ?? (err error) { return 0, wrapError("f failed", err) }
    y := g(x) ?? (err error) { return 0, wrapError("g failed", err) }
    return y, nil
}

Saya pikir doStuff2 jauh lebih mudah dan lebih cepat untuk dibaca karena:

  1. membuang lebih sedikit ruang vertikal
  2. mudah untuk dengan cepat membaca jalan bahagia di sisi kiri
  3. mudah untuk dengan cepat membaca kondisi kesalahan di sisi kanan
  4. tidak memiliki variabel err yang mencemari namespace lokal fungsi

Bagi saya proposal ini saja terlihat tidak lengkap dan memiliki terlalu banyak keajaiban. Bagaimana definisi operator ?? ? "Menangkap nilai pengembalian terakhir jika bukan nihil"? "Menangkap nilai kesalahan terakhir jika cocok dengan jenis metode?"

Menambahkan operator baru untuk menangani nilai pengembalian berdasarkan posisi dan jenisnya terlihat seperti peretasan.

Pada 29 Agustus 2017, 13:03 +0300, Mikael Gustavsson [email protected] , menulis:

Saya pikir operator yang diusulkan oleh @jba dan @bcmils adalah ide yang sangat bagus, meskipun dibaca lebih baik dieja sebagai "??" bukannya "=?" IMO.
Melihat contoh ini:
func doStuff() (int,kesalahan) {
x, salah := f()
jika salah != nihil {
kembali 0, wrapError("f gagal", err)
}

   y, err := g(x)
   if err != nil {
           return 0, wrapError("g failed", err)
   }

   return y, nil

}

func doStuff2() (int,kesalahan) {
x := f() ?? (kesalahan err) { kembali 0, wrapError("f gagal", err) }
y := g(x) ?? (kesalahan err) { kembali 0, wrapError("g gagal", err) }
kembali y, nihil
}
Saya pikir doStuff2 jauh lebih mudah dan lebih cepat untuk dibaca karena:

  1. membuang lebih sedikit ruang vertikal
  2. mudah untuk dengan cepat membaca jalan bahagia di sisi kiri
  3. mudah untuk dengan cepat membaca kondisi kesalahan di sisi kanan
  4. tidak memiliki variabel err yang mencemari namespace lokal fungsi


Anda menerima ini karena Anda berkomentar.
Balas email ini secara langsung, lihat di GitHub, atau matikan utasnya.

@nvartolomei

Bagaimana definisi operator ?? ?

Lihat https://github.com/golang/go/issues/21161#issuecomment -319434101 dan https://github.com/golang/go/issues/21161#issuecomment -320758279.

Karena @bcmills merekomendasikan untuk menghidupkan kembali utas yang tidak aktif, jika kita akan mempertimbangkan untuk menyalin dari bahasa lain, sepertinya pengubah pernyataan akan menawarkan solusi yang masuk akal untuk semua ini. Untuk mengambil contoh @slvmnd ,

func doStuff() (int, err) {
        x, err := f()
        return 0, wrapError("f failed", err)     if err != nil

    y, err := g(x)
        return 0, wrapError("g failed", err)     if err != nil

        return y, nil
}

Tidak sesingkat memiliki pernyataan dan pemeriksaan kesalahan dalam satu baris, tetapi terbaca dengan cukup baik. (Saya sarankan untuk tidak mengizinkan := bentuk penugasan dalam ekspresi if, jika tidak, masalah pelingkupan kemungkinan akan membingungkan orang meskipun mereka jelas dalam tata bahasa) Mengizinkan "kecuali" sebagai versi negatif dari "jika" adalah sedikit gula sintaksis, tetapi berfungsi dengan baik untuk dibaca dan patut dipertimbangkan.

Saya tidak akan merekomendasikan cribbing dari Perl di sini. (Basic Plus 2 baik-baik saja) Dengan cara itu terletak pengubah pernyataan perulangan yang, meskipun terkadang berguna, membawa masalah lain yang cukup kompleks.

versi yang lebih pendek:
kembali jika err != nihil
juga harus didukung kemudian.

dengan sintaks seperti itu muncul pertanyaan - haruskah pernyataan tidak kembali juga
didukung dengan pernyataan "jika" seperti ini:
func(args) jika kondisi

mungkin daripada menciptakan after-action-jika itu layak untuk memperkenalkan single
garis jika?

jika err! = nihil kembali
jika err!=nil kembali 0, wrapError("gagal", err)
jika err!=nil do_smth()

tampaknya jauh lebih alami daripada bentuk sintaksis khusus, bukan? Meskipun saya kira
itu memperkenalkan banyak rasa sakit dalam penguraian :/

Tapi... itu semua hanya tweak kecil dan bukan dukungan bahasa khusus untuk kesalahan
penanganan/penyebaran.

Pada Senin, 18 Sep 2017 pukul 16:14, dsugalski [email protected] menulis:

Sejak @bcmills https://github.com/bcmills merekomendasikan kebangkitan a
utas diam, jika kita akan mempertimbangkan cribbing dari bahasa lain,
sepertinya pengubah pernyataan akan menawarkan solusi yang masuk akal untuk semua
ini. Untuk mengambil contoh @slvmnd https://github.com/slvmnd, ulangi dengan
pengubah pernyataan:

func doStuff() (int, err) {
x, salah := f()
kembali 0, wrapError("f gagal", err) if err != nil

  y, err := g(x)
    return 0, wrapError("g failed", err)     if err != nil

    return y, nil

}

Tidak sesingkat memiliki pernyataan dan pemeriksaan kesalahan dalam satu
baris, tapi membaca cukup baik. (Saya sarankan untuk melarang bentuk := dari
penugasan dalam ekspresi if, jika tidak, masalah pelingkupan kemungkinan akan terjadi
membingungkan orang bahkan jika mereka mengerti tata bahasanya) Mengizinkan "kecuali" sebagai
versi negatif dari "jika" adalah sedikit gula sintaksis, tetapi berfungsi dengan baik untuk
membaca dan akan layak dipertimbangkan.

Saya tidak akan merekomendasikan cribbing dari Perl di sini. (Basic Plus 2 adalah
baik) Dengan cara itu terletak pengubah pernyataan perulangan yang, sementara kadang-kadang
berguna, membawa satu set masalah yang cukup kompleks.


Anda menerima ini karena Anda berkomentar.
Balas email ini secara langsung, lihat di GitHub
https://github.com/golang/go/issues/21161#issuecomment-330215402 , atau bisukan
benang
https://github.com/notifications/unsubscribe-auth/AICzv1rfnXeGVRRwaigCyyVK_STj-i83ks5sjmylgaJpZM4Oi1c-
.

Memikirkan lebih banyak tentang saran @dsugalski , itu tidak memiliki properti yang diminta @jba dan yang lainnya, yaitu bahwa kode non-kesalahan terlihat berbeda dari kode kesalahan. Masih bisa menjadi ide yang menarik jika memiliki manfaat signifikan untuk jalur kode non-kesalahan juga, tetapi semakin saya memikirkannya, tampaknya semakin tidak menarik dibandingkan dengan alternatif yang diusulkan.

Saya tidak yakin berapa banyak perbedaan visual yang masuk akal untuk diharapkan dari teks murni. Pada titik tertentu tampaknya lebih tepat untuk memasukkannya ke IDE atau lapisan pewarnaan kode editor teks Anda.

Tetapi untuk perbedaan terlihat berbasis teks, standar pemformatan yang kami miliki ketika saya pertama kali mulai menggunakan ini sejak lama adalah bahwa pengubah pernyataan IF/UNLESS harus dibenarkan dengan benar, yang membuatnya cukup menonjol. (Meskipun diberikan standar yang lebih mudah diterapkan dan mungkin lebih berbeda secara visual pada terminal VT-220 daripada di editor dengan ukuran jendela yang lebih fleksibel)

Bagi saya, setidaknya, saya menemukan bahwa kasus pengubah pernyataan mudah dibedakan dan dibaca lebih baik daripada skema if-block saat ini. Ini mungkin tidak berlaku untuk orang lain, tentu saja -- saya membaca kode sumber dengan cara yang sama seperti saya membaca teks bahasa Inggris sehingga memetakan ke dalam pola nyaman yang ada, dan tidak semua orang melakukan ini.

return 0, wrapError("f failed", err) if err != nil dapat ditulis if err != nil { return 0, wrapError("f failed", err) }

if err != nil return 0, wrapError("f failed", err) dapat ditulis sama.

Mungkin yang diperlukan di sini hanyalah gofmt untuk membiarkan if 's ditulis pada satu baris pada satu baris alih-alih memperluasnya menjadi tiga baris?

Ada kemungkinan lain yang mengejutkan saya. Banyak gesekan yang saya alami ketika mencoba menulis kode Go sekali pakai dengan cepat adalah karena saya harus memeriksa kesalahan pada setiap panggilan, jadi saya tidak dapat membuat panggilan dengan baik.

Misalnya, saya tidak dapat memanggil http.Client.Do pada objek permintaan baru tanpa terlebih dahulu menetapkan hasil http.NewRequest ke variabel sementara, lalu memanggil Do pada itu.

Saya ingin tahu apakah kami dapat mengizinkan:

f(y())

untuk bekerja bahkan jika y mengembalikan (T, error) Tuple. Ketika y mengembalikan kesalahan, kompiler dapat membatalkan evaluasi ekspresi dan menyebabkan kesalahan itu dikembalikan dari f. Jika f tidak mengembalikan kesalahan, itu bisa diberikan satu.

Lalu saya bisa melakukan:

n, err := http.DefaultClient.Do(http.NewRequest("DELETE", "/foo", nil))

dan hasil kesalahan tidak akan nol jika NewRequest atau Do gagal.

Ini memiliki satu masalah yang signifikan, namun - ekspresi di atas sudah valid jika f menerima dua argumen, atau argumen variadik. Juga, aturan yang tepat untuk melakukan ini mungkin cukup terlibat.

Jadi secara umum, saya rasa saya tidak menyukainya (saya juga tidak tertarik pada proposal lain di utas ini), tetapi saya pikir saya akan membuang ide itu untuk dipertimbangkan.

@rogpeppe atau Anda bisa menggunakan json.NewEncoder

@gbbr Ha ya, contoh buruk.

Contoh yang lebih baik mungkin http.Request. Saya telah mengubah komentar untuk menggunakannya.

Wow. Banyak ide membuat keterbacaan kode menjadi lebih buruk.
Saya baik-baik saja dengan pendekatan

if val, err := DoMethod(); err != nil {
   // val is accessible only here
   // some code
}

Hanya satu hal yang benar-benar mengganggu adalah pelingkupan variabel yang dikembalikan.
Dalam hal ini Anda harus menggunakan val tetapi dalam lingkup if .
Jadi Anda harus menggunakan else tetapi linter akan menentangnya (dan saya juga), dan satu-satunya cara adalah

val, err := DoMethod()
if err != nil {
   // some code
}
// some code with val

Akan menyenangkan memiliki akses ke variabel dari blok if :

if val, err := DoMethod(); err != nil {
   // some code
}
// some code with val

@dmbreaker Pada dasarnya itulah komentar saya sebelumnya .

Saya setuju untuk menyederhanakan penanganan kesalahan di Go (walaupun saya pribadi tidak terlalu mempermasalahkannya), tetapi saya pikir ini menambahkan sedikit keajaiban ke bahasa yang sederhana dan sangat mudah dibaca.

@gbbr
Apa 'ini' yang Anda maksud di sini? Ada beberapa saran berbeda tentang cara melakukan sesuatu.

Mungkin solusi dua bagian?

Definisikan try sebagai "kelupas nilai paling kanan di tupel kembali; jika bukan nilai nol untuk jenisnya, kembalikan sebagai nilai paling kanan dari fungsi ini dengan yang lain disetel ke nol". Ini membuat kasus umum

 a := try ErrorableFunction(b)

dan memungkinkan chaining

 a := try ErrorableFunction(try SomeOther(b, c))

(Opsional, buat bukan nol daripada bukan nol, untuk efisiensi.) Jika fungsi yang salah mengembalikan bukan nol/bukan nol, fungsi "dibatalkan dengan nilai". Nilai paling kanan dari fungsi try ' harus dapat ditetapkan ke nilai paling kanan dari fungsi panggilan atau ini adalah kesalahan pemeriksaan tipe waktu kompilasi. (Jadi ini tidak sulit untuk menangani hanya error , meskipun mungkin komunitas harus mencegah penggunaannya untuk kode "pintar" lainnya.)

Kemudian, izinkan pengembalian percobaan ditangkap dengan kata kunci seperti penangguhan, baik:

catch func(e error) {
    // whatever this function returns will be returned instead
}

atau, mungkin lebih bertele-tele tetapi lebih sesuai dengan cara kerja Go:

defer func() {
    if err := catch(); err != nil {
        set_catch(ErrorWrapper{a, "while posting request to server"})
    }
}()

Dalam kasus catch , parameter fungsi harus sama persis dengan nilai yang dikembalikan. Jika beberapa fungsi disediakan, nilainya akan melewati semuanya dalam urutan terbalik. Anda tentu saja dapat memberikan nilai yang memutuskan ke fungsi dari tipe yang benar. Dalam kasus contoh berbasis defer , jika satu fungsi defer memanggil set_catch fungsi penangguhan berikutnya akan mendapatkannya sebagai nilainya catch() . (Jika Anda cukup bodoh untuk menyetelnya kembali ke nil dalam proses, Anda akan mendapatkan nilai pengembalian yang membingungkan. Jangan lakukan itu.) Nilai yang diteruskan ke set_catch harus dapat ditetapkan ke jenis yang dikembalikan. Dalam kedua kasus, saya berharap ini berfungsi seperti defer karena ini adalah pernyataan, bukan deklarasi, dan hanya akan berlaku untuk kode setelah pernyataan dijalankan.

Saya cenderung lebih suka solusi berbasis penangguhan dari perspektif kesederhanaan (pada dasarnya tidak ada konsep baru yang diperkenalkan di sana, ini adalah tipe kedua dari recover() daripada hal baru), tetapi akui itu mungkin memiliki beberapa masalah kinerja. Memiliki kata kunci catch yang terpisah dapat memungkinkan lebih banyak efisiensi dengan menjadi lebih mudah untuk dilewati sepenuhnya ketika pengembalian normal terjadi, dan jika seseorang ingin menggunakan efisiensi maksimum, mungkin mengikatnya ke cakupan sehingga hanya satu yang diizinkan untuk aktif per cakupan atau fungsi , yang menurut saya, hampir tanpa biaya. (Mungkin nama file kode sumber dan nomor baris harus dikembalikan dari fungsi tangkap juga? Itu murah pada waktu kompilasi untuk melakukan itu dan akan menghindari beberapa alasan orang meminta pelacakan tumpukan penuh sekarang.)

Entah juga akan memungkinkan penanganan kesalahan berulang untuk ditangani secara efektif di satu tempat dalam suatu fungsi, dan memungkinkan penanganan kesalahan ditawarkan sebagai fungsi perpustakaan dengan mudah, yang merupakan salah satu aspek terburuk dari kasus saat ini, menurut komentar rsc di atas; kerumitan penanganan kesalahan cenderung mendorong "kembali err" daripada penanganan yang benar. Saya tahu saya sendiri banyak berjuang dengan itu.

@thejerf Bagian dari poin Ian dengan proposal ini adalah untuk mencari cara untuk mengatasi kesalahan boilerplate tanpa mengecilkan fungsi dari menambahkan konteks atau memanipulasi kesalahan yang mereka kembalikan.

Memisahkan penanganan kesalahan menjadi try dan catch sepertinya akan bertentangan dengan tujuan itu, meskipun saya kira itu tergantung pada jenis detail yang biasanya ingin ditambahkan oleh program.

Paling tidak, saya ingin melihat cara kerjanya dengan contoh yang lebih realistis.

Inti dari proposal saya adalah untuk memungkinkan menambahkan konteks atau memanipulasi kesalahan, dengan cara yang saya anggap lebih benar secara terprogram daripada sebagian besar proposal di sini yang melibatkan pengulangan konteks itu berulang-ulang, yang dengan sendirinya menghambat keinginan untuk memasukkan konteks tambahan ke dalamnya. .

Untuk menulis ulang contoh aslinya,

func Chdir(dir string) error {
    if e := syscall.Chdir(dir); e != nil {
        return &PathError{"chdir", dir, e}
    }
    return nil
}

keluar sebagai

func Chdir(dir string) error {
    catch func(e error) {
        return &PathError{"chdir", dir, e}
    }

    try syscall.Chdir(dir)
    return nil
}

kecuali bahwa contoh ini terlalu sepele untuk, yah, salah satu dari proposal ini, sebenarnya, dan saya akan mengatakan dalam kasus ini kita biarkan fungsi aslinya saja.

Secara pribadi saya tidak menganggap fungsi Chdir asli sebagai masalah. Saya menyetel ini secara khusus untuk mengatasi kasus di mana suatu fungsi terganggu oleh penanganan kesalahan berulang yang panjang, bukan untuk fungsi satu kesalahan. Saya juga akan mengatakan bahwa jika Anda memiliki fungsi di mana Anda benar-benar melakukan sesuatu yang berbeda untuk setiap kasus penggunaan yang mungkin, jawaban yang tepat mungkin adalah terus menulis apa yang sudah kita dapatkan. Namun, saya menduga itu adalah kasus yang jarang terjadi bagi kebanyakan orang, dengan alasan bahwa jika itu _adalah_ kasus umum, tidak akan ada keluhan di tempat pertama. Kebisingan memeriksa kesalahan hanya signifikan justru karena orang ingin melakukan "kebanyakan hal yang sama" berulang-ulang dalam suatu fungsi.

Saya juga curiga sebagian besar keinginan orang akan terpenuhi

func SomethingBigger(dir string) (interface{}, error) {
     catch func (e error, filename string, lineno int) {
         return PackageSpecificError{e, filename, lineno, dir}
     }

     x := try Something()

     if x == true {
         try SomethingElse()
     } else {
         a, b = try AThirdThing()
     }

     return whatever, nil
}

Jika kita menghilangkan masalah mencoba membuat pernyataan if tunggal terlihat bagus dengan alasan terlalu kecil untuk dipedulikan, dan menghilangkan masalah fungsi yang benar-benar melakukan sesuatu yang unik untuk setiap pengembalian kesalahan dengan alasan bahwa A : itu sebenarnya kasus yang cukup langka dan B: dalam kasus itu, overhead boilerplate sebenarnya tidak begitu signifikan vs kompleksitas kode penanganan yang unik, mungkin masalahnya dapat dikurangi menjadi sesuatu yang memiliki solusi.

Saya juga sangat ingin melihat

func packageSpecificHandler(f string) func (err error, filename string, lineno int) {
    return func (err error, filename string, lineno int) {
        return &PackageSpecificError{"In function " + f, err, filename, lineno}
    }
}

 func SomethingBigger(dir string) (interface{}, error) {
     catch packageSpecificHandler("SomethingBigger")

     ...
 }

atau beberapa yang setara dimungkinkan, karena saat itu berhasil.

Dan, dari semua proposal di halaman... bukankah ini masih terlihat seperti Go? Ini lebih mirip Go daripada Go saat ini.

Sejujurnya, sebagian besar pengalaman teknik profesional saya adalah dengan PHP (saya tahu) tetapi daya tarik utama untuk Go selalu mudah dibaca. Sementara saya menikmati beberapa aspek PHP, bagian yang paling saya benci adalah omong kosong "final" "abstrak" "statis" dan menerapkan konsep yang terlalu rumit ke sepotong kode yang melakukan satu hal.

Melihat proposal ini memberi saya kilas balik langsung pada perasaan melihat sepotong dan harus melakukan pengambilan ganda dan benar-benar "berpikir" tentang apa yang dikatakan/dilakukan potongan kode itu. Saya tidak berpikir bahwa kode ini dapat dibaca dan tidak benar-benar menambah bahasa. Naluri pertama saya adalah melihat ke kiri dan saya pikir ini selalu mengembalikan nil . Namun, dengan perubahan ini saya sekarang harus melihat ke kiri, dan ke kanan, untuk menentukan perilaku kode, yang berarti lebih banyak waktu membaca dan lebih banyak model mental.

Namun, ini tidak berarti tidak ada ruang untuk perbaikan penanganan kesalahan di Go.

Maaf saya belum (belum) membaca seluruh utas ini (sangat panjang) tetapi saya melihat orang-orang membuang sintaks alternatif, jadi saya ingin membagikan ide saya:

a, err := helloWorld(); err? {
  return fmt.Errorf("helloWorld failed with %s", err)
}

Semoga saya tidak melewatkan sesuatu di atas yang membatalkan ini. Saya berjanji akan menyelesaikan semua komentar suatu hari nanti :)

Operator harus diizinkan hanya pada tipe error , saya percaya, untuk menghindari kekacauan semantik konversi tipe.

Menarik, @buchanae , tetapi apakah itu membuat kita lebih dari:

if a, err := helloWorld(); err != nil {
  return fmt.Errorf("helloWorld failed with %s", err)
}

Saya melihat bahwa itu akan memungkinkan a untuk melarikan diri, sedangkan dalam keadaan saat ini, itu dicakup ke blok then dan else.

@object88 Anda benar, perubahannya halus, estetis, dan subjektif. Secara pribadi, yang saya inginkan dari Go 2 tentang topik ini adalah perubahan keterbacaan yang halus.

Secara pribadi, saya merasa lebih mudah dibaca karena baris tidak dimulai dengan if dan tidak memerlukan !=nil . Variabel berada di tepi kiri di mana mereka berada di (kebanyakan?) baris lain.

Poin bagus tentang ruang lingkup a , saya tidak mempertimbangkannya.

Mempertimbangkan kemungkinan lain dari tata bahasa ini, tampaknya ini mungkin.

err := helloWorld(); err? {
  return fmt.Errorf("error: %s", err)
}

dan mungkin

helloWorld()? {
  return fmt.Errorf("hello world failed")
}

yang mungkin di mana ia berantakan.

Mungkin mengembalikan kesalahan harus menjadi bagian dari setiap panggilan fungsi di Go, sehingga Anda dapat membayangkan:
```
a := helloWorld(); berbuat salah? {
return fmt.Errorf("helloWorld gagal: %s", err)
}

Bagaimana dengan penanganan pengecualian yang nyata? Maksud saya Coba, tangkap, akhirnya seperti banyak bahasa modern?

Tidak, itu membuat kode implisit dan tidak jelas (meskipun sedikit lebih pendek)

Pada Thu, 23 Nov 2017 di 7:27, Kamyar Miremadi [email protected]
menulis:

Bagaimana dengan penanganan pengecualian yang nyata? Maksudku Coba, tangkap, akhirnya
bukannya seperti banyak bahasa modern?


Anda menerima ini karena Anda berkomentar.
Balas email ini secara langsung, lihat di GitHub
https://github.com/golang/go/issues/21161#issuecomment-346529787 , atau bisukan
benang
https://github.com/notifications/unsubscribe-auth/AICzvyy_kGAlcs6RmL8AKKS5deNRU4_5ks5s5PQVgaJpZM4Oi1c-
.

Kembali ke @mpvl 's WriteToGCS contoh up-thread , saya ingin menyarankan (sekali lagi) bahwa pola komit/kembalikan tidak cukup umum untuk menjamin perubahan besar dalam penanganan kesalahan Go. Tidak sulit untuk menangkap pola dalam suatu fungsi ( tautan taman bermain ):

func runWithCommit(f, commit func() error, rollback func(error)) (err error) {
    defer func() {
        if r := recover(); r != nil {
            rollback(fmt.Errorf("panic: %v", r))
            panic(r)
        }
    }()
    if err := f(); err != nil {
        rollback(err)
        return err
    }
    return commit()
}

Kemudian kita dapat menulis contohnya sebagai

func writeToGCS(ctx context.Context, bucket, dst string, r io.Reader) error {
    client, err := storage.NewClient(ctx)
    if err != nil {
        return err
    }
    defer client.Close()

    w := client.Bucket(bucket).Object(dst).NewWriter(ctx)
    return runWithCommit(
        func() error { _, err := io.Copy(w, r); return err },
        func() error { return w.Close() },
        func(err error) { _ = w.CloseWithError(err) })
}

Saya akan menyarankan solusi yang lebih sederhana:

func someFunc() error {
    ^err := someAction()
    ....
}

Untuk beberapa pengembalian beberapa fungsi:

func someFunc() error {
    result, ^err := someAction()
    ....
}

Dan untuk beberapa argumen pengembalian:

func someFunc() (result Result, err error) {
    var result Result
    params, ^err := someAction()
    ....
}

^ tanda berarti kembali jika parameter tidak nihil.
Pada dasarnya "pindahkan kesalahan ke atas tumpukan jika itu terjadi"

Adakah kekurangan dari metode ini?

@gladkikhartem
Bagaimana cara mengubah kesalahan sebelum dikembalikan?

@urandom
Wrapping error merupakan tindakan penting yang menurut saya harus dilakukan secara eksplisit.
Kode Go adalah tentang keterbacaan, bukan sihir.
Saya ingin menjaga agar kesalahan membungkus lebih jelas

Tetapi pada saat yang sama saya ingin menyingkirkan kode yang tidak membawa banyak informasi dan hanya memakan tempat.

if err != nil {
    return err
}

Ini seperti klise Go - Anda tidak ingin membacanya, Anda ingin melewatkannya saja.

Apa yang saya lihat sejauh ini dalam diskusi ini adalah kombinasi dari:

  1. mengurangi verbositas sintaksis
  2. memperbaiki kesalahan dengan menambahkan konteks

Ini sejalan dengan deskripsi masalah asli oleh @ianlancetaylor yang menyebutkan kedua aspek tersebut, namun menurut saya keduanya harus didiskusikan/didefinisikan/diperiksa secara terpisah dan mungkin dalam iterasi yang berbeda untuk membatasi ruang lingkup perubahan dan hanya untuk alasan efektivitas (a perubahan yang lebih besar pada bahasa lebih sulit dilakukan daripada yang bertahap).

1. Pengurangan verbositas sintaksis

Saya suka ide @gladkikhartem , bahkan dalam bentuk aslinya yang saya laporkan di sini sejak diedit/diperpanjang:

 result, ^ := someAction()

Dalam konteks fungsi:

func getOddResult() (int, error) {
    result, ^ := someResult()
    if result % 2 == 0 {
          return result + 1, nil
    }
    return result, nil
}

Sintaks pendek ini - atau dalam bentuk yang diusulkan oleh @gladkikhartem dengan err^ - akan membahas bagian verbositas sintaks dari masalah (1).

2. Konteks kesalahan

Untuk bagian ke-2, menambahkan lebih banyak konteks, kita bahkan bisa sepenuhnya melupakannya untuk saat ini dan nanti mengusulkan untuk secara otomatis menambahkan stacktrace ke setiap kesalahan jika tipe contextError digunakan. Jenis kesalahan asli baru seperti itu dapat menggunakan jejak tumpukan penuh atau pendek (bayangkan GO_CONTEXT_ERROR=full ) dan kompatibel dengan antarmuka error sambil menawarkan kemungkinan untuk mengekstrak setidaknya fungsi dan nama file dari tumpukan panggilan teratas pintu masuk.

Saat menggunakan contextError , entah bagaimana Go harus melampirkan panggilan stacktrace tepat pada titik di mana kesalahan dibuat.

Sekali lagi dengan contoh func:

func getOddResult() (int, contextError) {
    result, ^ := someResult() // here a 'contextError' is created; if the error received from 'someResult()' is also a `contextError`, the two are nested
    if result % 2 == 0 {
          return result + 1, nil
    }
    return result, nil
}

Hanya tipe yang diubah dari error menjadi contextError , yang dapat didefinisikan sebagai:

type contextError interface {
    error
    Stack() []StackEntry
    Cause() contextError
}

(perhatikan bagaimana Stack() ini berbeda dari https://golang.org/pkg/runtime/debug/#Stack, karena kami berharap memiliki versi non-byte dari tumpukan panggilan goroutine di sini)

Metode Cause() akan mengembalikan nil atau contextError sebagai hasil dari nesting.

Saya sangat menyadari implikasi memori potensial membawa tumpukan seperti ini, oleh karena itu saya mengisyaratkan kemungkinan memiliki tumpukan pendek default yang hanya berisi 1 atau beberapa entri lagi. Pengembang biasanya akan mengaktifkan stracktrace penuh dalam versi pengembangan/debug dan membiarkan default (stacktraces pendek) sebaliknya.

Seni sebelumnya:

Hanya makanan untuk dipikirkan.

@gladkikhartem @gdm85

Saya pikir Anda telah melewatkan inti dari proposal ini. Per posting asli Ian:

Sudah mudah (mungkin terlalu mudah) untuk mengabaikan kesalahan (lihat #20803). Banyak proposal yang ada untuk penanganan kesalahan membuatnya lebih mudah untuk mengembalikan kesalahan yang tidak dimodifikasi (misalnya, #16225, #18721, #21146, #21155). Beberapa membuatnya lebih mudah untuk mengembalikan kesalahan dengan informasi tambahan.

Mengembalikan kesalahan yang tidak dimodifikasi seringkali salah, dan biasanya paling tidak tidak membantu. Kami ingin mendorong penanganan kesalahan yang hati-hati: menangani hanya kasus penggunaan "kembalikan yang tidak dimodifikasi" akan membuat insentif menjadi bias ke arah yang salah.

@bcmils jika konteks (dalam bentuk jejak tumpukan) sedang ditambahkan maka kesalahan dikembalikan dengan informasi tambahan. Apakah melampirkan pesan yang dapat dibaca manusia misalnya "kesalahan saat memasukkan catatan" dianggap sebagai "penanganan kesalahan yang hati-hati"? Bagaimana cara memutuskan pada titik mana dalam tumpukan panggilan pesan tersebut harus ditambahkan (di setiap fungsi, atas/bawah dll)? Ini semua adalah pertanyaan umum saat mengkode perbaikan penanganan kesalahan.

"Return unmodified" dapat dilawan seperti yang dijelaskan di atas dengan "return unmodified with stacktrace" secara default, dan (dalam gaya reaktif) tambahkan pesan yang dapat dibaca manusia sesuai kebutuhan. Saya belum menentukan bagaimana pesan yang dapat dibaca manusia seperti itu dapat ditambahkan, tetapi orang dapat melihat bagaimana pembungkus bekerja di pkg/errors untuk beberapa ide.

"Mengembalikan kesalahan yang tidak dimodifikasi seringkali salah": oleh karena itu saya mengusulkan jalur pemutakhiran untuk kasus penggunaan yang malas, yang merupakan kasus penggunaan yang sama yang saat ini ditunjukkan sebagai merugikan.

@bcmils
Saya 100% setuju dengan # 20803 bahwa kesalahan harus selalu ditangani atau diabaikan secara eksplisit (dan saya tidak tahu mengapa ini tidak dilakukan sebelumnya ...)
ya, saya tidak membahas poin proposal dan saya tidak harus melakukannya. Saya peduli dengan solusi aktual yang diusulkan, bukan niat di baliknya, karena niat tidak sesuai dengan hasil. Dan saat aku melihat || seperti || hal-hal yang diusulkan - itu membuat saya sangat sedih.

Jika menyematkan info, seperti kode kesalahan dan pesan kesalahan ke dalam kesalahan akan mudah dan transparan - Anda tidak perlu mendorong penanganan kesalahan yang hati-hati - orang akan melakukannya sendiri.
Misalnya hanya membuat kesalahan alias. Kami dapat mengembalikan barang apa pun dan menggunakannya di luar fungsi tanpa casting. Akan membuat hidup jauh lebih mudah.

Saya suka bahwa Go mengingatkan saya untuk menangani kesalahan, tetapi saya benci ketika desain mendorong saya untuk melakukan sesuatu yang dipertanyakan.

@gdm85
Menambahkan jejak tumpukan ke kesalahan secara otomatis adalah ide yang buruk, lihat saja dan jejak tumpukan Java.
Saat Anda membungkus kesalahan sendiri - jauh lebih mudah untuk menavigasi dan memahami apa yang salah. Itulah inti dari membungkusnya.

@gladkikhartem Saya tidak setuju bahwa bentuk "pembungkusan otomatis" akan jauh lebih buruk untuk dinavigasi dan membantu memahami apa yang salah. Saya juga tidak mendapatkan persis apa yang Anda rujuk dalam jejak tumpukan Java (saya kira pengecualian? jelek secara estetika? masalah spesifik apa?), Tetapi untuk membahas dalam arah yang konstruktif: apa yang bisa menjadi definisi yang baik dari "kesalahan yang ditangani dengan hati-hati"?

Saya meminta keduanya untuk meningkatkan pemahaman saya tentang praktik terbaik Go (kurang lebih kanoniknya) dan karena saya merasa definisi seperti itu mungkin menjadi kunci untuk membuat beberapa proposal menuju perbaikan dari situasi saat ini.

@gladkikhartem Saya tahu proposal ini sudah ada di mana-mana, tapi mari kita lakukan apa yang kita bisa untuk tetap fokus pada tujuan yang saya tetapkan pada awalnya. Seperti yang saya katakan ketika memposting masalah ini, sudah ada beberapa proposal berbeda tentang penyederhanaan if err != nil { return err } , dan itu adalah tempat untuk membahas sintaks yang hanya meningkatkan kasus tertentu. Terima kasih.

@ianlancetaylor
Maaf jika saya memindahkan diskusi keluar dari jalan.

Jika Anda ingin menambahkan informasi konteks ke kesalahan, saya sarankan menggunakan sintaks ini:
(dan memaksa orang untuk menggunakan hanya satu jenis kesalahan untuk satu fungsi untuk ekstraksi konteks yang mudah)

type MyError struct {
    Type int
    Message string
    Context string
    Err error
}

func VeryLongFunc() error {
    var err MyError
    err.Context = "general function context"


   result, ^err.Err := someAction() {
       err.Type = PermissionError
       err.Message = fmt.SPrintf("some action has no right to access file %v: ", file)
   }

    // in case we need to make a cleanup after error

   result, ^err.Err := someAction() {
       err.Type = PermissionError
       err.Message = fmt.SPrintf("some action has no right to access file %v: ", file)
       file.Close()
   }

   // another variant with different symbol and return statement

   result, ?err.Err := someAction() {
       err.Type = PermissionError
       err.Message = fmt.SPrintf("some action has no right to access file %v: ", file)
       return err
   }

   // using original approach

   result, err.Err := someAction()
   if err != nil {
       err.Type = PermissionError
       err.Message = fmt.SPrintf("some action has no right to access file %v: ", file)
       return err
   }
}

func main() {
    err := VeryLongFunc()
    if err != nil {
        e := err.(MyError)
        log.Print(e.Error(), " in ", e.Dir)
    }
}

^ simbol digunakan untuk menunjukkan parameter kesalahan, serta membedakan definisi fungsi dari penanganan kesalahan untuk "someAction() {}"
{} dapat dihilangkan jika kesalahan dikembalikan tanpa dimodifikasi

Menambahkan lebih banyak sumber daya untuk membalas undangan saya sendiri untuk mendefinisikan "penanganan kesalahan yang hati-hati" dengan lebih baik:

Meskipun pendekatan saat ini membosankan, saya pikir itu kurang membingungkan daripada alternatifnya, meskipun satu baris jika pernyataan mungkin berhasil? Mungkin?

blah, err := doSomething()
if err != nil: return err

...atau bahkan...

blah, err := doSomething()
if err != nil: return &BlahError{"Something",err}

Seseorang mungkin sudah membicarakan ini, tetapi ada banyak, banyak posting dan saya telah membaca banyak dari mereka tetapi tidak semua. Yang mengatakan, saya pribadi berpikir akan lebih baik untuk menjadi eksplisit daripada implisit.

Saya telah menjadi penggemar pemrograman berorientasi kereta api, idenya berasal dari pernyataan with Elixir.
Blok else akan dieksekusi setelah e == nil dihubung singkat.

Ini proposal saya dengan kode semu di depan:

func Chdir(dir string) (e error) {
    with e == nil {
            e = syscall.Chdir(dir)
            e, val := foo()
            val = val + 1
            // something else
       } else {
           printf("e is not nil")
           return
       }
       return nil
}

@ardhitama Bukankah ini seperti Coba tangkap kecuali "Dengan" seperti pernyataan "Coba" dan bahwa "Lain" seperti "Tangkap"?
Mengapa tidak menerapkan penanganan pengecualian seperti Java atau C# ?
sekarang dalam perjalanan jika seorang programmer tidak ingin menangani pengecualian dalam fungsi itu, ia mengembalikannya sebagai hasil dari fungsi itu. Masih tidak ada cara untuk memaksa pemrogram untuk menangani pengecualian jika mereka tidak mau dan sering kali Anda benar-benar tidak perlu melakukannya, tetapi apa yang kami dapatkan di sini adalah banyak pernyataan if err!=nil yang membuat kode menjadi jelek dan tidak dapat dibaca (banyak noise). Bukankah itu alasan mengapa pernyataan Try Catch Akhirnya ditemukan pertama kali dalam bahasa pemrograman lain?

Jadi, saya pikir lebih baik jika Go Authors "Cobalah" untuk tidak keras kepala!! dan cukup perkenalkan pernyataan "Coba Tangkap Akhirnya" di versi berikutnya. Terima kasih.

@KamyarM
Anda tidak dapat memperkenalkan penanganan pengecualian di go, karena tidak ada pengecualian di Go.
Memperkenalkan try{} catch{} di Go seperti memperkenalkan try{} catch{} di C - itu sama sekali salah .

@ianlancetaylor
Bagaimana dengan tidak mengubah penanganan kesalahan Go sama sekali, melainkan mengubah alat gofmt seperti ini untuk penanganan kesalahan satu baris?

err := syscall.Chdir(dir)
    if err != nil {return &PathError{"chdir", dir, err}}
err = syscall.Chdir(dir2)
    if err != nil {return err}

Ini kompatibel ke belakang dan Anda dapat menerapkannya ke proyek Anda saat ini

Pengecualian adalah pernyataan goto yang dihias, mereka mengubah tumpukan panggilan Anda menjadi grafik panggilan dan ada alasan bagus untuk sebagian besar proyek non-akademik yang serius melarang atau membatasi penggunaannya. Objek stateful memanggil metode yang mentransfer kontrol secara sewenang-wenang ke atas tumpukan dan kemudian melanjutkan mengeksekusi instruksi... terdengar seperti ide yang buruk karena memang demikian.

@KamyarM Pada dasarnya memang demikian, tetapi dalam praktiknya tidak. Menurut pendapat saya karena kami sedang eksplisit di sini dan tidak melanggar idiom Go.

Mengapa?

  1. Ekspresi di dalam pernyataan with tidak dapat mendeklarasikan var baru, oleh karena itu secara eksplisit menyatakan tujuannya adalah untuk mengevaluasi keluar dari blok vars.
  2. Pernyataan di dalam with akan berperilaku seperti di dalam blok try dan catch . Memang itu akan lebih lambat karena pada setiap instruksi berikutnya perlu mengevaluasi kondisi with dalam skenario terburuk.
  3. Secara desain, tujuannya adalah untuk menghapus if s yang berlebihan dan tidak membuat penangan pengecualian karena penangan akan selalu lokal (ekspresi with 's dan blok else ).
  4. Tidak perlu melepas tumpukan karena throw

hal. tolong koreksi saya jika saya salah.

@ardditama
KamyarM benar dalam arti bahwa dengan pernyataan terlihat jelek seperti try catch dan juga memperkenalkan level indentasi untuk aliran kode normal.
Bahkan tidak menyebutkan ide proposal asli untuk memodifikasi setiap kesalahan satu per satu. Itu tidak akan bekerja secara elegan dengan try catch , dengan atau metode lain apa pun yang mengelompokkan pernyataan bersama.

@gladkikhartem
Ya, maka saya mengusulkan untuk mengadopsi "pemrograman berorientasi kereta api" sebagai gantinya dan mencoba untuk tidak menghapus eksplisitnya. Itu hanya sudut lain untuk menyerang masalah, solusi lain ingin menyelesaikannya dengan tidak membiarkan kompiler secara otomatis menulis if err != nil untuk Anda.

with juga tidak hanya untuk penanganan kesalahan, ini dapat berguna untuk aliran kontrol lainnya.

@gladkikhartem
Biarkan saya menjelaskan bahwa saya pikir blok Try Catch Finally itu indah. If err!=nil ... sebenarnya adalah kode jelek.

Go hanyalah bahasa pemrograman. Ada begitu banyak bahasa lain. Saya menemukan bahwa banyak di Komunitas Go melihatnya seperti agama mereka dan tidak terbuka untuk mengubah atau mengakui kesalahan. Ini salah.

@gladkikhartem

Saya baik-baik saja jika Penulis Go menyebutnya Go++ atau Go# atau GoJava dan memperkenalkan The Try Catch Finally sana;)

@KamyarM

Menghindari perubahan yang tidak perlu diperlukan--penting--untuk setiap upaya rekayasa. Ketika orang mengatakan perubahan dalam konteks ini, mereka benar-benar berarti _berubah menjadi lebih baik_, yang mereka sampaikan secara efektif dengan _argumen_ memandu premis ke kesimpulan yang dimaksudkan.

Daya tarik _buka pikiran Anda!_ tidak meyakinkan. Ironisnya, ia mencoba untuk mengatakan sesuatu yang sebagian besar programmer lihat sebagai kuno dan kikuk adalah _baru dan lebih baik_.

Ada juga banyak proposal dan diskusi di mana komunitas Go membahas kesalahan sebelumnya. Tapi saya setuju dengan Anda ketika Anda mengatakan Go hanyalah bahasa pemrograman. Dikatakan demikian di situs web Go dan tempat lain, dan saya telah berbicara dengan beberapa orang yang mengonfirmasinya juga.

Saya menemukan bahwa banyak di Komunitas Go melihatnya seperti agama mereka dan tidak terbuka untuk mengubah atau mengakui kesalahan.

Go didasarkan pada penelitian akademis; pendapat pribadi tidak masalah.

Bahkan pengembang utama kompiler C# Microsoft mengakui secara terbuka bahwa _exceptions_ adalah cara yang buruk untuk mengelola kesalahan, sambil menghargai model Go/Rust sebagai alternatif yang lebih baik: http://joeduffyblog.com/2016/02/07/the-error-model/

Tentunya, ada ruang untuk meningkatkan model kesalahan Go, tetapi tidak dengan mengadopsi solusi seperti pengecualian karena mereka hanya menambah kompleksitas yang sangat besar dengan imbalan beberapa manfaat yang dipertanyakan.

@Dr-Mengerikan Terima kasih atas artikelnya.

Tapi saya tidak menemukan tempat yang menyebut GoLang sebagai bahasa akademis.

Btw, untuk memperjelas maksud saya, dalam contoh ini

func Execute() error {
    err := Operation1()
    if err!=nil{
        return err
    }

    err = Operation2()
    if err!=nil{
        return err
    }

    err = Operation3()
    if err!=nil{
        return err
    }

    err = Operation4()
    return err
}

Mirip dengan menerapkan penanganan pengecualian di C # seperti ini:

         public void Execute()
        {

            try
            {
                Operation1();
            }
            catch (Exception)
            {
                throw;
            }
            try
            {
                Operation2();
            }
            catch (Exception)
            {
                throw;
            }
            try
            {
                Operation3();
            }
            catch (Exception)
            {
                throw;
            }
            try
            {
                Operation4();
            }
            catch (Exception)
            {
                throw;
            }
        }

Bukankah itu cara penanganan pengecualian yang buruk di C#? Jawaban saya adalah ya, saya tidak tahu tentang Anda! Di Go saya tidak punya pilihan lain. Ini adalah pilihan atau jalan raya yang mengerikan. Beginilah di GO dan saya tidak punya pilihan.

Omong-omong seperti yang juga disebutkan dalam artikel yang Anda bagikan, bahasa apa pun dapat menerapkan penanganan kesalahan seperti Go tanpa memerlukan sintaks tambahan apa pun sehingga Go sebenarnya tidak menerapkan cara penanganan kesalahan yang revolusioner. Itu tidak memiliki cara penanganan kesalahan sehingga Anda dibatasi untuk menggunakan pernyataan If untuk penanganan kesalahan.

Btw, saya tahu GO memiliki Panic, Recover , Defer yang tidak direkomendasikan yang agak mirip dengan Try Catch Finally tetapi menurut pendapat pribadi saya sintaks Try Catch Finally jauh lebih bersih dan cara penanganan pengecualian yang lebih terorganisir.

@Dr-Mengerikan

Juga silakan periksa ini:
https://github.com/manucorporat/try

@KamyarM , dia tidak mengatakan bahwa Go adalah bahasa akademis, dia mengatakan itu berdasarkan penelitian akademis. Juga bukan artikel tentang Go, tetapi menyelidiki paradigma penanganan kesalahan yang digunakan oleh Go.

Jika Anda menemukan bahwa manucorporat/try bekerja untuk Anda, silakan gunakan dalam kode Anda. Tetapi biaya (kinerja, kompleksitas bahasa, dll.) untuk menambahkan try/catch ke bahasa itu sendiri tidak sebanding dengan pengorbanannya.

@KamyarM
Anda contoh tidak akurat. Alternatif untuk

    err := Operation1()
    if err!=nil {
        return err
    }
    err = Operation2()
    if err!=nil{
        return err
    }
    err = Operation3()
    if err!=nil{
        return err
    }
    return Operation4()

akan

            Operation1();
            Operation2();
            Operation3();
            Operation4();

penanganan pengecualian tampaknya pilihan yang jauh lebih baik dalam contoh ini. Secara teori itu harus bagus, tetapi dalam praktiknya
Anda harus membalas dengan pesan kesalahan yang akurat untuk setiap kesalahan yang terjadi di titik akhir Anda.
Seluruh aplikasi di Go biasanya menangani kesalahan 50%.

         err := Operation1()
    if err!=nil {
        log.Print("input error", err)
                fmt.Fprintf(w,"invalid input")
        w.WriteHeader(http.StatusBadRequest)
        return
    }
    err = Operation2()
    if err!=nil{
        log.Print("controller error", err)
                fmt.Fprintf(w,"operation has no meaning in this context")
        w.WriteHeader(http.StatusBadRequest)
        return
    }
    err = Operation3()
    if err!=nil{
        log.Print("database error", err)
                fmt.Fprintf(w,"unable to access database, try again later")
        w.WriteHeader(http.StatusServiceUnavailable)
        return
    }

Dan jika orang akan memiliki alat yang kuat seperti try catch, saya 100% yakin mereka akan menggunakannya secara berlebihan demi penanganan kesalahan yang hati-hati.

Sangat menarik bahwa akademisi disebutkan, tetapi Go adalah kumpulan pelajaran dari pengalaman praktis. Jika tujuannya adalah untuk menulis API buruk yang mengembalikan pesan kesalahan yang salah, penanganan pengecualian adalah cara yang harus dilakukan.

Namun, saya tidak ingin kesalahan invalid HTTP header ketika permintaan saya berisi JSON request body , penanganan pengecualian adalah tombol api-dan-lupakan ajaib yang mencapai itu di C++ dan C# API yang menggunakan mereka.

Untuk cakupan API yang besar, tidak mungkin menyediakan konteks kesalahan yang cukup untuk mencapai penanganan kesalahan yang berarti. Itu karena aplikasi yang bagus adalah 50% penanganan kesalahan di Go, dan harus 90% dalam bahasa yang memerlukan transfer kontrol non-lokal untuk menangani kesalahan.

@gladkikhartem

Cara Alternatif yang Anda sebutkan adalah cara yang tepat untuk menulis kode dalam C#. Itu hanya 4 baris kode dan menunjukkan jalur eksekusi yang bahagia. Itu tidak memiliki suara if err!=nil . Jika pengecualian terjadi, fungsi yang peduli dengan pengecualian tersebut dapat menanganinya menggunakan Try Catch Finally (Ini bisa berupa fungsi yang sama itu sendiri atau pemanggil atau pemanggil dari pemanggil atau pemanggil dari pemanggil dari pemanggil dari pemanggil ... atau hanya event handler yang memproses semua kesalahan yang tidak tertangani dalam aplikasi. Pemrogram memiliki pilihan yang berbeda.)

err := Operation1()
    if err!=nil {
        log.Print("input error", err)
                fmt.Fprintf(w,"invalid input")
        w.WriteHeader(http.StatusBadRequest)
        return
    }
    err = Operation2()
    if err!=nil{
        log.Print("controller error", err)
                fmt.Fprintf(w,"operation has no meaning in this context")
        w.WriteHeader(http.StatusBadRequest)
        return
    }
    err = Operation3()
    if err!=nil{
        log.Print("database error", err)
                fmt.Fprintf(w,"unable to access database, try again later")
        w.WriteHeader(http.StatusServiceUnavailable)
        return
    }

Terlihat sederhana tapi itu rumit. Saya kira, Anda dapat menggabungkan jenis kesalahan khusus yang membawa kesalahan sistem, kesalahan pengguna (tanpa membocorkan status internal kepada pengguna yang mungkin tidak bermaksud baik), dan kode HTTP.

func Chdir(dir string) error {
    if e := syscall.Chdir(dir); e != nil {
        return &PathError{"chdir", dir, e}
    }
    return nil
}

Tapi cobalah

func Chdir(dir string) error {
    return  syscall.Chdir(dir) ? &PathError{"chdir", dir, err}:nil;
}
func Chdir(dir string) error {
    return  syscall.Chdir(dir) ? &PathError{"chdir", dir, err};
}



md5-9bcd2745464e8d9597cba6d80c3dcf40



```go
func Chdir(dir string) error {
    n , _ := syscall.Chdir(dir):
               // something to do
               fmt.Println(n)
}

Semua itu mengandung semacam sihir yang tidak jelas, yang tidak menyederhanakan hal-hal bagi pembaca. Dalam dua contoh sebelumnya, err menjadi semacam kata kunci semu atau variabel yang muncul secara spontan. Dalam dua contoh terakhir, sama sekali tidak jelas apa yang seharusnya dilakukan oleh operator : -- apakah kesalahan akan dikembalikan secara otomatis? Apakah RHS operator merupakan pernyataan tunggal, atau blok?

FWIW, saya akan menulis fungsi pembungkus sehingga Anda dapat melakukan return newPathErr("chdir", dir, syscall.Chdir(dir)) dan itu akan secara otomatis mengembalikan kesalahan nil jika parameter ketiga adalah nil. :-)

IMO, proposal terbaik yang pernah saya lihat untuk mencapai tujuan "menyederhanakan penanganan kesalahan di Go" dan "mengembalikan kesalahan dengan informasi kontekstual tambahan" berasal dari @mrkaspa di #21732:

a, b, err? := f1()

berkembang menjadi ini:

if err != nil {
   return nil, errors.Wrap(err, "failed")
}

dan saya bisa memaksanya untuk panik dengan ini:

a, b, err! := f1()

berkembang menjadi ini:

if err != nil {
   panic(errors.Wrap(err, "failed"))
}

Ini akan menjaga kompatibilitas mundur, dan akan memperbaiki semua masalah penanganan kesalahan di go

Ini tidak menangani kasus seperti fungsi bufio yang mengembalikan nilai bukan nol serta kesalahan, tetapi saya pikir tidak apa-apa untuk melakukan penanganan kesalahan eksplisit dalam kasus di mana Anda peduli dengan nilai pengembalian lainnya. Dan tentu saja nilai pengembalian non-kesalahan harus menjadi nilai nihil yang sesuai untuk jenis itu.

? pengubah akan mengurangi kesalahan boilerplate saat menyerahkan fungsi dan ! modifier akan melakukan hal yang sama untuk tempat di mana assert akan digunakan dalam bahasa lain seperti di beberapa fungsi utama.

Solusi ini memiliki keuntungan karena sangat sederhana dan tidak mencoba melakukan terlalu banyak, tetapi menurut saya memenuhi persyaratan yang tercantum dalam pernyataan proposal ini.

Dalam kasus di mana Anda memiliki ...

func foo() (int, int, error) {
    a, b, err? := f1()
    return a, b, nil
}
func bar() (int, error) {
    a, b, err? := foo()
    return a+b, nil
}

Jika ada yang salah dalam foo , maka di situs panggilan bar , kesalahan tersebut dibungkus dua kali dengan teks yang sama, tanpa menambahkan arti apa pun. Paling tidak, saya akan keberatan dengan bagian errors.Wrap dari saran.

Tetapi berkembang lebih jauh, apa hasil yang diharapkan dari ini?

func baz() (a, b int, err error) {
  a = 1
  b = 2
  a, b, err? = f1()
  return

Apakah a dan b dipindahkan ke nilai nil? Jika demikian, itu sihir, yang saya rasa harus kita hindari. Apakah mereka melaksanakan nilai-nilai yang ditetapkan sebelumnya? (Saya sendiri tidak peduli dengan nilai pengembalian yang disebutkan, tetapi tetap harus dipertimbangkan untuk tujuan proposal ini.)

@dup2X Ya mari kita hapus bahasanya, seharusnya lebih seperti itu

@object88 itu wajar untuk mengharapkan bahwa jika terjadi kesalahan segala sesuatu yang lain dibatalkan. Itu mudah dimengerti dan tidak memiliki keajaiban di dalamnya, cukup banyak yang sudah menjadi konvensi untuk kode Go, hanya perlu sedikit diingat dan tidak memiliki kasus khusus. Jika kita membiarkan nilai-nilai dipertahankan maka itu memperumit banyak hal. Jika Anda lupa memeriksa kesalahan, nilai yang dikembalikan dapat digunakan secara tidak sengaja sehingga apa pun bisa terjadi. Seperti memanggil metode pada struktur yang dialokasikan sebagian alih-alih panik pada nihil. Pemrogram bahkan mungkin mulai mengharapkan nilai tertentu dikembalikan jika terjadi kesalahan. Menurut pendapat saya, itu akan menjadi berantakan dan tidak ada hal baik yang akan diperoleh darinya.

Adapun pembungkus, saya tidak berpikir pesan default memberikan sesuatu yang berguna. Akan baik-baik saja untuk hanya menghubungkan kesalahan bersama-sama. Seperti ketika pengecualian memiliki pengecualian batin di dalamnya. Sangat berguna untuk men-debug kesalahan jauh di dalam perpustakaan.

Dengan hormat, saya tidak setuju, @creker. Kami memiliki contoh skenario itu di Go stdlib dari nilai pengembalian non-nil bahkan dalam kasus kesalahan non-nil, dan sebenarnya berfungsi, seperti beberapa fungsi dalam bufio.Reader struct . Kami sebagai programmer Go secara aktif didorong untuk memeriksa / menangani semua kesalahan; rasanya lebih mengerikan untuk mengabaikan kesalahan daripada mendapatkan nilai pengembalian yang tidak nol & kesalahan. Dalam kasus yang Anda kutip, jika Anda mengembalikan nil dan tidak memeriksa kesalahan, Anda mungkin masih beroperasi pada nilai yang tidak valid.

Tapi mengesampingkan itu, mari kita periksa ini sedikit lebih jauh. Apa yang akan menjadi semantik dari operator ? ? Bisakah itu hanya diterapkan pada tipe yang mengimplementasikan antarmuka error ? Bisakah itu diterapkan ke tipe lain atau mengembalikan argumen? Jika itu dapat diterapkan pada tipe yang tidak mengimplementasikan kesalahan, apakah itu dipicu oleh nilai/pointer non-nihil? Bisakah operator ? diterapkan ke lebih dari satu nilai yang dikembalikan, atau apakah itu kesalahan kompiler?

@erwbgy
Jika Anda hanya ingin mengembalikan kesalahan tanpa sesuatu yang berguna yang melekat padanya - akan lebih mudah untuk hanya memberi tahu kompiler untuk memperlakukan semua kesalahan yang tidak ditangani sebagai "jika err != nil return...", misalnya:

func doStuff() error {
    doAnotherStuff() // returns error
}

func doStuff() error {
    res := doAnotherStuff() // returns string, error
}

Dan tidak perlu gila tambahan? simbol dalam hal ini.

@objek88
Saya telah mencoba menerapkan sebagian besar proposal pembungkus kesalahan yang ditampilkan di sini dalam kode nyata dan menghadapi satu masalah besar - kode menjadi terlalu padat dan tidak dapat dibaca.
Apa yang dilakukannya hanyalah mengorbankan lebar kode demi tinggi kode.
Membungkus kesalahan dengan if err != nil yang biasa sebenarnya memungkinkan untuk menyebarkan kode untuk keterbacaan yang lebih baik, jadi saya tidak berpikir kita bahkan harus mengubah apa pun untuk pembungkusan kesalahan sama sekali.

@objek88

Dalam kasus situs Anda, jika Anda mengembalikan nol dan tidak memeriksa kesalahan, Anda mungkin masih beroperasi pada nilai yang tidak valid.

Tapi itu akan menghasilkan kesalahan yang jelas dan mudah dikenali seperti panic on nil. Jika Anda perlu mengembalikan nilai yang berarti pada kesalahan, Anda harus melakukannya secara eksplisit dan mendokumentasikan dengan tepat nilai mana yang dapat digunakan dalam hal ini. Mengembalikan barang acak yang kebetulan ada di variabel saat kesalahan berbahaya dan akan menyebabkan kesalahan halus. Sekali lagi, tidak ada yang diperoleh dari itu.

@gladkikhartem masalah dengan if err != nil adalah bahwa logika yang sebenarnya benar-benar hilang di dalamnya dan Anda harus secara aktif mencarinya jika Anda ingin memahami apa yang dilakukan kode pada jalur yang berhasil dan tidak peduli dengan semua penanganan kesalahan itu . Itu menjadi seperti membaca banyak kode C di mana Anda memiliki beberapa baris kode aktual dan yang lainnya hanyalah pengecekan kesalahan. Orang-orang bahkan menggunakan makro yang membungkus semua itu dan pergi ke akhir fungsi.

Saya tidak melihat bagaimana logika bisa menjadi terlalu padat dalam kode yang ditulis dengan benar. Ini logika. Setiap baris kode Anda berisi kode aktual yang Anda pedulikan, itulah yang Anda inginkan. Apa yang tidak Anda inginkan adalah melewati garis dan garis boilerplate. Gunakan komentar dan bagi kode Anda menjadi beberapa blok jika itu membantu. Tapi itu terdengar lebih seperti masalah dengan kode aktual dan bukan bahasanya.

Ini berfungsi di taman bermain, jika Anda tidak memformatnya kembali:

a, b, err := Frob("one string"); if err != nil { return a, b, fmt.Errorf("couldn't frob: %v", err) }
// continue doing stuff with a and b

Jadi menurut saya proposal asli, dan banyak proposal lainnya yang disebutkan di atas, mencoba membuat sintaks singkatan yang jelas untuk 100 karakter tersebut, dan menghentikan gofmt dari bersikeras menambahkan jeda baris dan memformat ulang blok di 3 baris.

Jadi, bayangkan kita mengubah gofmt untuk berhenti memaksakan blok multi-baris, mulai dengan baris di atas, dan mencoba menemukan cara untuk membuatnya lebih pendek dan lebih jelas.

Saya tidak berpikir bagian sebelum titik koma (tugas) harus diubah, sehingga menyisakan 69 karakter yang mungkin kita kurangi. Dari jumlah tersebut, 49 adalah pernyataan pengembalian, nilai untuk dikembalikan, dan pembungkusan kesalahan, dan saya tidak melihat banyak nilai dalam mengubah sintaksnya (katakanlah, dengan membuat pernyataan pengembalian opsional, yang membingungkan pengguna).

Sehingga meninggalkan menemukan singkatan untuk ; if err != nil { _ } mana garis bawah mewakili sepotong kode. Saya pikir steno apa pun harus secara eksplisit menyertakan err untuk kejelasan bahkan jika itu membuat perbandingan nil agak tidak terlihat, jadi kita harus membuat steno untuk ; if _ != nil { _ } .

Bayangkan sejenak bahwa kita menggunakan satu karakter. Saya akan memilih sebagai pengganti untuk karakter apa pun itu. Baris kode kemudian akan menjadi:

a, b, err := Frob("one string") § err return a, b, fmt.Errorf("couldn't frob: %v", err)

Saya tidak melihat bagaimana Anda bisa melakukan jauh lebih baik dari itu tanpa mengubah sintaks tugas yang ada atau mengembalikan sintaks, atau keajaiban tak terlihat terjadi. (Masih ada keajaiban, fakta bahwa kita membandingkan err dengan nil tidak mudah terlihat.)

Itu 88 karakter, menyimpan total 12 karakter pada baris sepanjang 100 karakter.

Jadi pertanyaan saya adalah: Apakah itu benar-benar layak dilakukan?

Sunting: Saya kira maksud saya adalah, ketika orang melihat blok if err != nil Go dan berkata "Saya berharap kita bisa menyingkirkan omong kosong itu", 80-90% dari apa yang mereka bicarakan adalah _barang yang secara inheren Anda miliki yang harus dilakukan untuk menangani error_. Overhead aktual yang disebabkan oleh sintaks Go minimal.

@lpar , Anda sebagian besar mengikuti logika yang sama yang saya terapkan di atas , jadi tentu saja saya setuju dengan alasan Anda. Tapi saya pikir Anda mengabaikan daya tarik visual dengan meletakkan semua kesalahan di sebelah kanan:

a, b := Frob("one string")  § err { return ... }

lebih mudah dibaca oleh faktor yang melebihi pengurangan karakter belaka.

@lpar Anda dapat menyimpan lebih banyak karakter jika Anda menghapus fmt.Errorf tidak berguna, ubah kembali ke beberapa sintaks khusus dan perkenalkan tumpukan panggilan ke kesalahan sehingga mereka memiliki konteks aktual dan bukan hanya string yang dimuliakan. Itu akan meninggalkanmu dengan sesuatu seperti ini

a, b, err? := Frob("one string")

Masalah dengan kesalahan Go bagi saya selalu kurangnya konteks. Mengembalikan dan membungkus string tidak berguna sama sekali untuk menentukan di mana kesalahan itu sebenarnya terjadi. Itu sebabnya github.com/pkg/errors misalnya, menjadi suatu keharusan bagi saya. Dengan kesalahan seperti itu saya mendapatkan manfaat dari kesederhanaan penanganan kesalahan Go dan manfaat pengecualian yang menangkap konteks dengan sempurna dan memungkinkan Anda menemukan tempat kegagalan yang tepat.

Dan, bahkan jika kami mengambil contoh Anda apa adanya, fakta bahwa penanganan kesalahan ada di sebelah kanan adalah peningkatan keterbacaan yang signifikan. Anda tidak lagi harus melewati beberapa baris boilerplate untuk mendapatkan arti sebenarnya dari kode tersebut. Anda dapat mengatakan apa yang Anda inginkan tentang pentingnya penanganan kesalahan tetapi ketika saya membaca kode untuk memahaminya, saya tidak peduli dengan kesalahan. Yang saya butuhkan hanyalah jalur sukses. Dan ketika saya membutuhkan kesalahan, saya akan mencarinya secara khusus. Kesalahan, pada dasarnya, adalah kasus luar biasa dan harus mengambil ruang sesedikit mungkin.

Saya pikir pertanyaan apakah fmt.Errorf "tidak berguna" dibandingkan dengan errors.Wrap adalah ortogonal untuk masalah ini, karena keduanya sama-sama bertele-tele. (Dalam aplikasi sebenarnya saya juga tidak menggunakan, saya menggunakan sesuatu yang lain yang juga mencatat kesalahan dan baris kode tempat kesalahan itu terjadi.)

Jadi saya kira itu tergantung pada beberapa orang yang benar-benar menyukai penanganan kesalahan berada di sebelah kanan. Saya hanya tidak begitu yakin, bahkan berasal dari latar belakang Perl dan Ruby.

@lpar Saya menggunakan errors.Wrap karena secara otomatis menangkap tumpukan panggilan - Saya tidak benar-benar membutuhkan semua pesan kesalahan ini. Saya lebih peduli tentang tempat terjadinya dan, mungkin, argumen mana yang diteruskan ke fungsi yang menghasilkan kesalahan. Anda bahkan mengatakan bahwa Anda melakukan hal serupa - mencatat baris kode untuk memberikan beberapa konteks pada pesan kesalahan Anda. Mengingat bahwa kita dapat memikirkan cara untuk mengurangi boilerplate sambil memberikan lebih banyak konteks kesalahan (itulah proposal yang cukup banyak di sini).

Adapun kesalahan berada di sebelah kanan. Bagi saya ini bukan hanya tentang tempat tetapi tentang mengurangi beban kognitif yang diperlukan untuk membaca kode yang penuh dengan penanganan kesalahan. Saya tidak menerima argumen bahwa kesalahan sangat penting sehingga Anda ingin mereka mengambil ruang sebanyak yang mereka lakukan. Saya sebenarnya lebih suka mereka pergi sejauh mungkin. Mereka bukan cerita utama.

@creker

Ini lebih mungkin menggambarkan kesalahan pengembang yang sepele daripada kesalahan dalam sistem produksi yang menghasilkan kesalahan karena input pengguna yang buruk. Jika semua yang Anda butuhkan untuk menentukan kesalahan adalah nomor baris dan jalur file, kemungkinan Anda hanya menulis kode dan sudah tahu apa yang salah.

@ karena mirip dengan pengecualian. Dalam sebagian besar kasus, tumpukan panggilan dan pesan pengecualian sudah cukup untuk menentukan tempat dan menyebabkan kesalahan terjadi. Dalam kasus yang lebih kompleks, Anda setidaknya tahu di mana kesalahan terjadi. Pengecualian memberi Anda manfaat ini secara default. Dengan Go Anda harus menggabungkan kesalahan, pada dasarnya meniru tumpukan panggilan, atau menyertakan tumpukan panggilan yang sebenarnya.

Dalam kode yang ditulis dengan benar sebagian besar waktu Anda akan tahu dari nomor baris dan jalur file penyebab pasti karena kesalahan akan diharapkan. Anda benar-benar menulis beberapa kode sebagai persiapan bahwa itu mungkin terjadi. Jika sesuatu yang tidak diharapkan terjadi maka ya, tumpukan panggilan tidak akan memberi Anda penyebabnya tetapi itu akan secara signifikan mengurangi ruang pencarian.

@sebagai

Dalam pengalaman saya, kesalahan input pengguna ditangani segera. Kesalahan produksi yang benar-benar bermasalah terjadi jauh di dalam kode (misalnya layanan sedang down, menyebabkan layanan lain melempar kesalahan), dan ini cukup berguna untuk mendapatkan jejak tumpukan yang tepat. Untuk apa nilainya, jejak tumpukan java sangat berguna saat men-debug masalah produksi, bukan pesannya.

@creker
Kesalahan hanyalah nilai , dan itu adalah bagian dari input dan output fungsi. Mereka tidak bisa menjadi "tak terduga".
Jika Anda ingin mengetahui mengapa fungsi memberi Anda kesalahan - gunakan pengujian, logging, dan lain-lain ...

@gladkikhartem di dunia nyata tidak sesederhana itu. Ya, Anda mengharapkan kesalahan dalam arti bahwa tanda tangan fungsi menyertakan kesalahan sebagai nilai kembaliannya. Tapi yang saya harapkan adalah mengetahui persis mengapa itu terjadi dan apa yang menyebabkannya, sehingga Anda benar-benar tahu apa yang harus dilakukan untuk memperbaikinya atau tidak memperbaikinya sama sekali. Masukan pengguna yang buruk biasanya sangat mudah untuk diperbaiki hanya dengan melihat pesan kesalahan. Jika Anda menggunakan buffer profocol dan beberapa bidang yang diperlukan tidak disetel, itu diharapkan dan sangat mudah untuk diperbaiki jika Anda memvalidasi dengan benar semua yang Anda terima melalui kabel.

Pada titik ini saya tidak lagi mengerti apa yang kita perdebatkan. Jejak tumpukan atau rantai pesan kesalahan sangat mirip jika diterapkan dengan benar. Mereka mengurangi ruang pencarian dan memberi Anda konteks yang berguna untuk mereproduksi dan memperbaiki kesalahan. Yang kita butuhkan adalah memikirkan cara menyederhanakan penanganan kesalahan sambil menyediakan konteks yang cukup. Saya sama sekali tidak menganjurkan bahwa kesederhanaan lebih penting daripada konteks yang tepat.

Itulah argumen Java — pindahkan semua kode kesalahan ke tempat lain sehingga Anda tidak perlu melihatnya. Saya pikir itu salah arah; bukan hanya karena mekanisme Java untuk melakukannya sebagian besar telah gagal, tetapi karena ketika saya melihat kode, bagaimana perilakunya ketika ada kesalahan sama pentingnya bagi saya seperti bagaimana perilakunya ketika semuanya bekerja.

Tidak ada yang membuat argumen itu. Jangan bingung apa yang sedang dibahas di sini dengan penanganan pengecualian di mana semua penanganan kesalahan ada di satu tempat. Menyebutnya "sebagian besar gagal" hanyalah sebuah pendapat, tetapi saya tidak berpikir Go akan kembali ke sana dalam hal apa pun. Penanganan kesalahan Go hanya berbeda dan dapat ditingkatkan.

@creker Saya mencoba membuat poin yang sama dan meminta untuk mengklarifikasi apa yang dianggap sebagai pesan kesalahan yang berarti/berguna.

Sebenarnya, saya akan memberikan teks pesan kesalahan kualitas variabel kapan saja (yang memiliki bias pengembang yang menulisnya pada saat itu dan dengan pengetahuan itu) sebagai ganti tumpukan panggilan dan argumen fungsi. Dengan teks pesan kesalahan ( fmt.Errorf atau errors.New ) Anda akhirnya mencari teks dalam kode sumber, saat membaca tumpukan panggilan/jejak balik (yang tampaknya dibenci dan saya harap bukan karena alasan estetika) sesuai dengan mencari langsung dengan file/nomor baris ( errors.Wrap dan serupa).

Dua gaya berbeda, tetapi tujuannya sama: mencoba mereproduksi dalam pikiran Anda apa yang terjadi saat runtime dalam kondisi tersebut.

Pada topik ini, edisi #19991 mungkin membuat ringkasan yang valid untuk pendekatan gaya kedua dalam mendefinisikan kesalahan yang berarti.

pindahkan semua kode kesalahan ke tempat lain sehingga Anda tidak perlu melihatnya

@lpar , jika Anda menanggapi poin saya tentang memindahkan penanganan kesalahan ke kanan: ada perbedaan besar antara catatan kaki/catatan akhir (Java) dan catatan samping (proposal saya). Catatan samping hanya membutuhkan sedikit perubahan mata, tanpa kehilangan konteks.

@gdm85

Anda akhirnya mencari teks dalam kode sumber

Persis apa yang saya maksud dengan jejak tumpukan dan pesan kesalahan berantai yang serupa. Mereka berdua merekam jalur yang diperlukan untuk kesalahan. Hanya dalam kasus pesan Anda bisa berakhir dengan pesan yang sama sekali tidak berguna yang bisa dari mana saja dalam program jika Anda tidak cukup hati-hati menulisnya. Satu-satunya manfaat dari kesalahan berantai adalah kemampuan untuk merekam nilai variabel. Dan bahkan itu dapat diotomatisasi dalam kasus argumen fungsi atau bahkan variabel secara umum dan akan, setidaknya bagi saya, mencakup hampir semua yang saya butuhkan dari kesalahan. Mereka akan tetap menjadi nilai, Anda masih dapat mengetikkan untuk menggantinya jika Anda membutuhkannya. Tetapi pada titik tertentu Anda mungkin akan mencatatnya dan dapat melihat jejak tumpukan sangat berguna.

Lihat saja apa yang dilakukan Go dengan panik. Anda mendapatkan jejak tumpukan penuh dari setiap goroutine. Saya tidak ingat berapa kali itu membantu saya menemukan penyebab kesalahan dan memperbaikinya dalam waktu singkat. Itu sering membuat saya takjub betapa mudahnya itu. Ini mengalir sempurna dengan seluruh bahasa yang sangat dapat diprediksi sehingga Anda bahkan tidak memerlukan debugger.

Tampaknya ada stigma tentang segala sesuatu yang berhubungan dengan Jawa dan orang-orang sering tidak membawa argumen. Ini buruk hanya karena. Saya bukan penggemar Java tetapi alasan seperti itu tidak membantu siapa pun.

Sekali lagi, kesalahan bukan untuk pengembang untuk memperbaiki bug. Itulah salah satu manfaat dari penanganan kesalahan. Cara Java telah mengajarkan pengembang bahwa itulah penanganan kesalahan, bukan. Kesalahan dapat terjadi pada lapisan aplikasi dan lebih dari itu pada lapisan aliran. Kesalahan di Go secara rutin digunakan untuk mengontrol strategi pemulihan yang diperlukan sistem--saat runtime, bukan waktu kompilasi.

Ini mungkin tidak dapat dipahami ketika bahasa melumpuhkan kontrol aliran mereka sebagai akibat dari kesalahan dengan mengurai tumpukan dan kehilangan memori dari semua yang mereka lakukan sebelum kesalahan terjadi. Kesalahan sebenarnya berguna saat runtime di Go; Saya tidak mengerti mengapa mereka harus membawa hal-hal seperti nomor baris - kode yang berjalan hampir tidak peduli tentang itu.

@sebagai

Sekali lagi, kesalahan bukan untuk pengembang untuk memperbaiki bug

Itu sepenuhnya dan sepenuhnya salah. Kesalahan persis karena alasan itu. Mereka tidak terbatas pada itu tetapi itu adalah salah satu kegunaan utama. Kesalahan menunjukkan bahwa ada sesuatu yang salah dengan sistem dan Anda harus melakukan sesuatu tentang hal itu. Untuk kesalahan yang diharapkan dan mudah, Anda dapat mencoba memulihkan seperti, misalnya, batas waktu TCP. Untuk sesuatu yang lebih serius, Anda membuangnya ke dalam log dan kemudian men-debug masalahnya.

Itulah salah satu manfaat dari penanganan kesalahan. Cara Java telah mengajarkan pengembang bahwa itulah penanganan kesalahan, bukan.

Saya tidak tahu apa yang diajarkan Java kepada Anda, tetapi saya menggunakan pengecualian untuk alasan yang sama - untuk mengontrol sistem strategi pemulihan yang diperlukan saat runtime. Go tidak ada yang istimewa dalam hal penanganan kesalahan.

Ini mungkin tidak dapat dipahami ketika bahasa melumpuhkan kontrol aliran mereka sebagai akibat dari kesalahan dengan mengurai tumpukan dan kehilangan memori dari semua yang mereka lakukan sebelum kesalahan terjadi

Mungkin untuk seseorang, bukan untukku.

Kesalahan sebenarnya berguna saat runtime di Go; Saya tidak mengerti mengapa mereka harus membawa hal-hal seperti nomor baris - kode yang berjalan hampir tidak peduli tentang itu.

Jika Anda peduli untuk memperbaiki bug dalam kode Anda, maka nomor baris adalah cara untuk melakukannya. Bukan Java yang mengajari kami tentang ini, C memiliki __LINE__ dan __FUNCTION__ persis untuk alasan itu. Anda ingin mencatat kesalahan Anda dan mencatat tempat yang tepat di mana itu terjadi. Dan ketika terjadi kesalahan, Anda setidaknya memiliki sesuatu untuk memulai. Bukan pesan kesalahan acak yang disebabkan oleh kesalahan yang tidak dapat dipulihkan. Jika Anda tidak membutuhkan informasi semacam itu, abaikan saja. Itu tidak menyakitimu. Tapi setidaknya itu ada dan bisa digunakan saat dibutuhkan.

Saya tidak mengerti mengapa orang-orang di sini terus mengalihkan percakapan menjadi pengecualian vs nilai kesalahan. Tidak ada yang membuat perbandingan itu. Satu-satunya hal yang dibahas adalah bahwa jejak tumpukan sangat berguna dan membawa banyak informasi konteks. Jika itu tidak dapat dipahami maka Anda mungkin hidup di alam semesta yang sama sekali berbeda di mana tidak ada penelusuran.

Itu sepenuhnya dan sepenuhnya salah.

Tetapi sistem produksi yang saya maksud masih berjalan, dan menggunakan kesalahan untuk kontrol aliran, ditulis dalam Go, dan menggantikan implementasi yang lebih lambat dalam bahasa yang menggunakan pelacakan tumpukan untuk penyebaran kesalahan.

Jika itu tidak dapat dipahami maka Anda mungkin hidup di alam semesta yang sama sekali berbeda di mana tidak ada penelusuran.

Untuk mengaitkan informasi tumpukan panggilan untuk setiap fungsi yang mengembalikan jenis kesalahan, lakukan itu sesuai kebijaksanaan Anda. Jejak tumpukan lebih lambat, dan tidak cocok untuk digunakan di luar proyek mainan karena alasan keamanan. Merupakan pelanggaran teknis untuk menjadikan mereka warga negara Go kelas satu untuk hanya membantu strategi penyebaran kesalahan yang tidak dipikirkan.

jika Anda tidak membutuhkan informasi semacam itu, abaikan saja. Itu tidak menyakitimu.

Software bloat adalah alasan mengapa server ditulis ulang di Go. Apa yang tidak Anda lihat masih dapat menurunkan throughput saluran pipa Anda.

Saya lebih suka contoh perangkat lunak aktual yang mendapat manfaat dari fitur ini daripada pelajaran yang agak tidak relevan tentang penanganan batas waktu TCP dan pembuangan log.

Jejak tumpukan lebih lambat

Mengingat bahwa jejak tumpukan dihasilkan di jalur kesalahan, tidak ada yang peduli seberapa lambat mereka. Pengoperasian normal perangkat lunak sudah terganggu.

dan tidak cocok untuk digunakan di luar proyek mainan karena alasan keamanan

Sejauh ini saya belum melihat satu sistem produksi mematikan jejak tumpukan karena "alasan keamanan", atau sama sekali dalam hal ini. Di sisi lain, dapat dengan cepat mengidentifikasi jalur yang diambil kode untuk menghasilkan kesalahan sangat berguna. Dan ini untuk proyek besar, dengan banyak tim berbeda yang bekerja pada basis kode, dan tidak ada yang memiliki pengetahuan penuh tentang keseluruhan sistem.

Apa yang tidak Anda lihat masih dapat menurunkan throughput saluran pipa Anda.

Tidak, itu benar-benar tidak. Seperti yang saya katakan sebelumnya, jejak tumpukan dihasilkan pada kesalahan. Kecuali jika perangkat lunak Anda terus-menerus menghadapinya, throughput tidak akan terpengaruh sedikit pun.

Mengingat bahwa jejak tumpukan dihasilkan di jalur kesalahan, tidak ada yang peduli seberapa lambat mereka. Pengoperasian normal perangkat lunak sudah terganggu.

Tidak benar.

  • Kesalahan dapat terjadi sebagai bagian dari operasi normal.
  • Kesalahan dapat dipulihkan dan program dapat dilanjutkan, sehingga kinerja masih dipertanyakan.
  • Memperlambat satu rutinitas menguras sumber daya dari rutinitas lain yang _beroperasi_ di jalur bahagia.

@object88 bayangkan kode produksi nyata. Berapa banyak kesalahan yang Anda harapkan untuk dihasilkan? Saya akan berpikir tidak banyak. Setidaknya dalam aplikasi yang ditulis dengan benar. Jika goroutine berada dalam loop sibuk dan terus-menerus melontarkan kesalahan pada setiap iterasi, ada yang salah dengan kodenya. Tetapi bahkan jika itu masalahnya, mengingat bahwa sebagian besar aplikasi Go terikat IO bahkan itu tidak akan menjadi masalah serius.

@sebagai

Tetapi sistem produksi yang saya maksud masih berjalan, dan menggunakan kesalahan untuk kontrol aliran, ditulis dalam Go, dan menggantikan implementasi yang lebih lambat dalam bahasa yang menggunakan pelacakan tumpukan untuk penyebaran kesalahan.

Maaf, tapi ini adalah kalimat yang tidak masuk akal yang tidak ada hubungannya dengan apa yang saya katakan. Tidak akan menjawabnya.

Jejak tumpukan lebih lambat

Lebih lambat tapi berapa banyak? Apakah itu penting? Saya tidak berpikir begitu. Aplikasi Go terikat IO secara umum. Mengejar siklus CPU konyol dalam kasus ini. Anda memiliki masalah yang jauh lebih besar dalam runtime Go yang memakan CPU. Ini bukan argumen untuk membuang fitur berguna yang membantu memperbaiki bug.

tidak cocok untuk digunakan di luar proyek mainan karena alasan keamanan.

Saya tidak akan repot-repot meliput "alasan keamanan" yang tidak ada. Tetapi saya ingin mengingatkan Anda bahwa biasanya jejak aplikasi disimpan secara internal dan hanya pengembang yang memiliki akses ke sana. Dan mencoba menyembunyikan nama fungsi Anda adalah buang-buang waktu. Ini bukan keamanan. Saya harap saya tidak perlu menjelaskannya lebih lanjut.

Jika Anda bersikeras pada alasan keamanan, saya ingin Anda memikirkan macOS/iOS, misalnya. Mereka tidak memiliki masalah melempar kepanikan dan crash dump yang berisi tumpukan semua utas dan nilai semua register CPU. Jangan lihat mereka terpengaruh oleh "alasan keamanan" ini.

Merupakan pelanggaran teknis untuk menjadikan mereka warga negara Go kelas satu untuk hanya membantu strategi penyebaran kesalahan yang tidak dipikirkan.

Bisakah Anda lebih subjektif? "strategi propagasi kesalahan yang tidak dipikirkan" di mana Anda melihatnya?

Software bloat adalah alasan mengapa server ditulis ulang di Go. Apa yang tidak Anda lihat masih dapat menurunkan throughput saluran pipa Anda.

Sekali lagi, berapa banyak?

Saya lebih suka contoh perangkat lunak aktual yang mendapat manfaat dari fitur ini daripada pelajaran yang agak tidak relevan tentang penanganan batas waktu TCP dan pembuangan log.

Pada titik ini sepertinya saya sedang berbicara dengan siapa pun kecuali seorang programmer. Menelusuri menguntungkan setiap dan semua perangkat lunak. Ini adalah teknik umum dalam semua bahasa dan semua jenis perangkat lunak yang membantu memperbaiki bug. Anda dapat membaca wikipedia jika Anda ingin informasi lebih lanjut tentang itu.

Memiliki begitu banyak diskusi yang tidak produktif tanpa konsensus berarti tidak ada cara yang elegan untuk menyelesaikan masalah ini.

@objek88
Pelacakan tumpukan bisa lambat jika Anda ingin melacak semua goroutine, karena Go harus menunggu goroutine lain dibuka blokirnya.
Jika Anda hanya melacak goroutine yang sedang Anda jalankan - itu tidak terlalu lambat.

@creker
Menelusuri menguntungkan semua perangkat lunak, tetapi itu tergantung pada apa yang Anda lacak. Di sebagian besar proyek Go saya terlibat melacak tumpukan bukanlah ide bagus, karena konkurensi terlibat. Data bergerak maju mundur, banyak hal berkomunikasi satu sama lain dan beberapa goroutine hanyalah beberapa baris kode. Memiliki jejak tumpukan dalam kasus seperti itu tidak membantu.
Itu sebabnya saya menggunakan kesalahan yang dibungkus dengan informasi konteks yang ditulis ke log untuk membuat ulang jejak tumpukan yang sama, tetapi yang tidak terikat pada tumpukan goroutine yang sebenarnya, tetapi logika aplikasi itu sendiri.
Sehingga saya bisa melakukan cat *.log | grep "orderID=xxx" dan dapatkan jejak tumpukan dari urutan tindakan aktual yang menyebabkan kesalahan.
Karena sifat bersamaan dari kesalahan kaya konteks Go lebih berharga daripada jejak tumpukan.

@gladkikhartem terima kasih telah meluangkan waktu untuk menulis argumen yang tepat. Aku mulai kesal dengan percakapan itu.

Saya memahami argumen Anda dan sebagian setuju dengannya. Namun, saya menemukan diri saya harus berurusan dengan tumpukan setidaknya 5 fungsi. Itu sudah cukup besar untuk dapat memahami apa yang terjadi dan di mana Anda harus mulai mencari. Tetapi dalam aplikasi yang sangat bersamaan dengan banyak jejak tumpukan goroutine yang sangat kecil kehilangan manfaatnya. Itu saya setuju.

@creker

bayangkan kode produksi nyata. Berapa banyak kesalahan yang Anda harapkan untuk dihasilkan? [...], mengingat bahwa sebagian besar aplikasi Go terikat IO bahkan itu tidak akan menjadi masalah serius.

Bagus bahwa Anda menyebutkan operasi terikat IO. Metode io.Reader Read mengembalikan kesalahan yang sehat di EOF. Jadi itu akan sering terjadi di jalan bahagia.

@urandom

Pelacakan tumpukan tanpa sadar mengekspos informasi yang berharga untuk membuat profil sistem.

  • Nama pengguna
  • Jalur sistem file
  • Jenis/versi basis data backend
  • Alur transaksi
  • Struktur objek
  • Algoritma enkripsi

Saya tidak tahu apakah aplikasi rata-rata akan memperhatikan overhead pengumpulan bingkai tumpukan dalam jenis kesalahan, tetapi saya dapat memberi tahu Anda bahwa untuk aplikasi kritis kinerja, banyak fungsi Go kecil digariskan secara manual karena overhead panggilan fungsi saat ini. Menelusuri akan membuatnya lebih buruk.

Saya yakin tujuan Go adalah memiliki perangkat lunak yang sederhana dan cepat, dan penelusuran akan menjadi langkah mundur. Kita harus dapat menulis fungsi kecil dan mengembalikan kesalahan dari fungsi tersebut tanpa penurunan kinerja yang mendorong jenis kesalahan yang tidak konvensional dan in-lining manual.

@creker

Saya akan menghindari memberi Anda contoh yang menyebabkan disonansi lebih lanjut. Maaf telah mengecewakanmu.

Saya akan mengusulkan menggunakan kata kunci baru "returnif" yang namanya langsung mengungkapkan fungsinya. Juga cukup fleksibel sehingga dapat digunakan dalam lebih banyak kasus penggunaan daripada penanganan kesalahan.

Contoh 1 (menggunakan pengembalian bernama):

a, err = sesuatu (b)
jika salah != nihil {
kembali
}

Akan menjadi:

a, err = sesuatu (b)
returnif err != nihil

Contoh 2 (Tidak menggunakan pengembalian bernama):

a, err := sesuatu(b)
jika salah != nihil {
kembalikan a, err
}

Akan menjadi:

a, err := sesuatu(b)
returnif err != nil { a, err }

Mengenai contoh pengembalian bernama Anda, maksud Anda ...

a, err = something(b)
returnif err != nil

@ambernardino
Mengapa tidak memperbarui alat fmt saja dan Anda tidak perlu memperbarui sintaks bahasa dan menambahkan kata kunci baru yang tidak berguna

a, err := something(b)
if err != nil { return a, err }

atau

a, err := something(b)
    if err != nil { return a, err }

@gladkikhartem idenya bukan untuk mengetik itu setiap kali Anda ingin menyebarkan kesalahan, saya lebih suka ini dan harus bekerja dengan cara yang sama

a, err? := something(b)

@mrkaspa
Idenya adalah untuk membuat kode lebih mudah dibaca . Mengetik kode tidak masalah, membaca.

@gladkikhartem rust menggunakan pendekatan itu dan menurut saya itu tidak membuatnya kurang mudah dibaca

@gladkikhartem Saya tidak berpikir ? membuatnya kurang dapat dibaca. Saya akan mengatakan itu menghilangkan kebisingan sepenuhnya. Masalahnya bagi saya adalah bahwa dengan kebisingan itu juga menghilangkan kemungkinan untuk memberikan konteks yang bermanfaat. Saya hanya tidak melihat di mana Anda dapat memasukkan pesan kesalahan biasa atau kesalahan bungkus. Tumpukan panggilan adalah solusi yang jelas tetapi, seperti yang telah disebutkan, tidak berfungsi untuk semua orang.

@mrkaspa
Dan saya pikir itu membuatnya kurang mudah dibaca, apa selanjutnya? Kami mencoba mencari solusi terbaik atau hanya berbagi pendapat?

@creker
'?' karakter menambah beban kognitif pada pembaca, karena tidak begitu jelas apa yang akan dikembalikan dan tentu saja orang harus tahu apa yang dilakukannya. Dan tentu saja ? tanda menimbulkan pertanyaan di benak pembaca.

Seperti yang saya katakan sebelumnya, jika Anda ingin menyingkirkan err != nil compiler dapat mendeteksi parameter kesalahan yang tidak digunakan dan meneruskannya sendiri.
Dan

a, err? := doStuff(a,b)
err? := doAnotherStuff(b,z,d,g)
a, b, err? := doVeryComplexStuff(b)

akan menjadi lebih mudah dibaca

a := doStuff(a,b)
doAnotherStuff(b,z,d,g)
a, b := doVeryComplexStuff(b)

keajaiban yang sama, hanya lebih sedikit hal untuk diketik dan lebih sedikit hal untuk dipikirkan

@gladkikhartem yah, saya rasa tidak ada solusi yang tidak mengharuskan pembaca mempelajari sesuatu yang baru. Itulah konsekuensi dari mengubah bahasa. Kita harus melakukan trade off - baik kita hidup dengan verbose ke sintaks wajah Anda yang menunjukkan dengan tepat apa yang sedang dilakukan dalam istilah primitif atau kami memperkenalkan sintaks baru yang bisa menyembunyikan verbositas, menambahkan beberapa sintaks gula dll Tidak ada cara lain. Menolak apa pun yang menambah sesuatu untuk dipelajari pembaca adalah kontra-produktif. Kami mungkin juga menutup semua masalah Go2 dan menyebutnya sehari.

Adapun contoh Anda, ini memperkenalkan lebih banyak hal ajaib dan menyembunyikan titik injeksi apa pun untuk memperkenalkan sintaks yang memungkinkan pengembang memberikan konteks kesalahan. Dan, yang paling penting, itu benar-benar menyembunyikan informasi apa pun tentang panggilan fungsi mana yang mungkin menimbulkan kesalahan. Itu semakin berbau seperti pengecualian. Dan jika kita serius untuk hanya mengulang kesalahan maka pelacakan tumpukan menjadi suatu keharusan karena itulah satu-satunya cara Anda dapat mempertahankan konteks dalam kasus itu.

Proposal asli sebenarnya sudah mencakup semua itu dengan cukup baik. Ini cukup verbose dan memberi Anda tempat yang baik untuk membungkus kesalahan dan memberikan konteks yang berguna. Tapi salah satu masalah utama adalah sihir ini 'err'. Saya pikir itu jelek karena tidak cukup ajaib dan tidak cukup bertele-tele. Ini semacam di tengah. Apa yang mungkin membuatnya lebih baik adalah memperkenalkan lebih banyak sihir.

Bagaimana jika || akan menghasilkan kesalahan baru yang secara otomatis membungkus kesalahan asli. Jadi contohnya menjadi seperti ini

func Chdir(dir string) error {
    syscall.Chdir(dir) || &PathError{"chdir", dir}
    return nil
}

err menghilang begitu saja dan semua pembungkus ditangani secara implisit. Sekarang kita membutuhkan beberapa cara untuk mengakses kesalahan batin tersebut. Menambahkan metode lain seperti Inner() error ke antarmuka error tidak akan berfungsi. Salah satu caranya adalah dengan memperkenalkan fungsi bawaan seperti unwrap(error) []error . Apa yang dilakukannya adalah mengembalikan sepotong semua kesalahan dalam urutannya. Dengan begitu Anda dapat mengakses kesalahan atau jangkauan internal apa pun di atasnya.

Implementasi ini dipertanyakan mengingat error hanyalah sebuah antarmuka dan Anda memerlukan tempat untuk meletakkan kesalahan yang dibungkus itu untuk jenis error kustom apa pun.

Bagi saya ini mencentang semua kotak tetapi mungkin agak terlalu ajaib. Tapi, mengingat antarmuka kesalahan itu cukup istimewa menurut definisi, membawanya lebih dekat ke warga kelas satu mungkin bukan ide yang buruk. Penanganan kesalahan bertele-tele karena itu hanya kode Go biasa, tidak ada yang istimewa tentangnya. Itu mungkin bagus di atas kertas dan untuk membuat berita utama yang mencolok tetapi kesalahan terlalu istimewa untuk menjamin perlakuan seperti itu. Mereka membutuhkan casing khusus.

Apakah proposal asli tentang pengurangan jumlah pemeriksaan kesalahan atau panjangnya setiap pemeriksaan individu?

Jika yang terakhir maka sepele untuk menyangkal kebutuhan proposal dengan alasan bahwa hanya ada satu pernyataan bersyarat dan satu pernyataan repitisi. Beberapa orang tidak menyukai loop, haruskah kita menyediakan konstruksi loop implisit juga?

Perubahan sintaks yang diusulkan sejauh ini telah berfungsi sebagai eksperimen pemikiran yang menarik tetapi tidak ada yang sejelas, dapat diucapkan, atau sesederhana aslinya. Go bukan bash dan tidak ada yang "ajaib" tentang kesalahan.

Semakin banyak saya membaca proposal ini, semakin banyak saya melihat orang-orang yang argumennya tidak lain adalah "itu menambahkan sesuatu yang baru, jadi itu buruk, tidak dapat dibaca, biarkan semuanya apa adanya".

@ sebagai proposal memberikan ringkasan tentang apa yang ingin dicapai. Apa yang sedang dilakukan didefinisikan dengan cukup baik.

sejelas, dapat diucapkan, atau sesederhana aslinya

Setiap proposal akan memperkenalkan sintaks baru dan menjadi baru bagi sebagian orang terdengar sama dengan "tidak dapat dibaca, rumit, dll". Hanya karena itu baru tidak membuatnya kurang jelas, mudah diucapkan, atau sederhana. "||" dan "?" contohnya sejelas dan sesederhana sintaks yang ada begitu Anda tahu fungsinya. Atau haruskah kita mulai mengeluh bahwa "->" dan "<-" terlalu ajaib dan pembaca harus tahu apa artinya? Mari kita ganti dengan pemanggilan metode.

Go bukan bash dan tidak ada yang "ajaib" tentang kesalahan.

Itu sama sekali tidak berdasar dan tidak dihitung sebagai argumen untuk apa pun. Apa hubungannya dengan Bash di luar jangkauan saya.

@creker
Ya, saya sangat setuju dengan Anda yang memperkenalkan lebih banyak keajaiban. Contoh saya hanyalah kelanjutan dari ? ide operator untuk mengetik lebih sedikit barang.

Saya setuju bahwa kita perlu mengorbankan sesuatu dan memperkenalkan beberapa perubahan dan tentu saja keajaiban. Itu hanya keseimbangan pro kegunaan dan kontra dari sihir semacam itu.

Asli || proposal cukup bagus, dan teruji secara praktis, tetapi pemformatannya jelek menurut saya, saya sarankan mengubah pemformatan menjadi

syscal.Chdir(dir)
    || return &PathError{"chdir", dir}

PS apa pendapat Anda tentang varian sintaks ajaib seperti itu?

syscal.Chdir(dir) {
    return &PathError{"chdir", dir}
}

@gladkikhartem keduanya terlihat cukup bagus dari sudut pandang keterbacaan tapi saya punya firasat buruk dari yang terakhir. Ini memperkenalkan lingkup blok aneh yang saya tidak begitu yakin.

Saya mendorong Anda untuk tidak melihat sintaks secara terpisah, melainkan dalam konteks suatu fungsi. Metode ini memiliki beberapa blok penanganan kesalahan yang berbeda.

func (l *Loader) processDirectory(p *Package) (*build.Package, error) {
        absPath, err := p.preparePath()
        if err != nil {
                return nil, err
        }
    fis, err := l.context.ReadDir(absPath)
    if err != nil {
        return nil, err
    } else if len(fis) == 0 {
        return nil, nil
    }

    buildPkg, err := l.context.Import(".", absPath, 0)
    if err != nil {
        if _, ok := err.(*build.NoGoError); ok {
            // There isn't any Go code here.
            return nil, nil
        }
        return nil, err
    }

    return buildPkg, nil
}

Bagaimana perubahan yang diusulkan membersihkan fungsi ini?

func (l *Loader) processDirectory(p *Package) (*build.Package, error) {
    absPath, err? := p.preparePath()
    fis, err? := l.context.ReadDir(absPath)
    if len(fis) == 0 {
        return nil, nil
    }

    buildPkg, err := l.context.Import(".", absPath, 0)
    if err != nil {
         if _, ok := err.(*build.NoGoError); ok {
             // There isn't any Go code here.
             return nil, nil
         }
         return nil, err
    }

    return buildPkg, nil
}

Proposal @bcmills lebih cocok.

func (l *Loader) processDirectory(p *Package) (*build.Package, error) {
    absPath := p.preparePath()        =? err { return nil, err }
    fis := l.context.ReadDir(absPath) =? err { return nil, err }
    if len(fis) == 0 {
        return nil, nil
    }
    buildPkg := l.context.Import(".", absPath, 0) =? err {
        if _, ok := err.(*build.NoGoError); ok {
            // There isn't any Go code here.
            return nil, nil
        }
        return nil, err
    }
    return buildPkg, nil
}

@objek88

func (l *Loader) processDirectory(p *Package) (p *build.Package, err error) {
        absPath, err := p.preparePath() {
        return nil, fmt.Errorf("prepare path: %v", err)
    }
    fis, err := l.context.ReadDir(absPath) {
        return nil, fmt.Errorf("read dir: %v", err)
    }
    if len(fis) == 0 {
        return nil, nil
    }

    buildPkg, err := l.context.Import(".", absPath, 0) {
        err, ok := err.(*build.NoGoError)
                if !ok {
            return nil, fmt.Errorf("buildpkg: %v",err)
        }
        return nil, nil
    }
    return buildPkg, nil
}

Terus menyodok ini. Mungkin kita bisa menemukan contoh penggunaan yang lebih lengkap.

@erwbgy , yang ini terlihat terbaik bagi saya, tetapi saya tidak yakin bahwa pembayarannya sebesar itu.

  • Apa nilai pengembalian non-kesalahan? Apakah mereka selalu bernilai nol? Jika ada _was_ nilai yang ditetapkan sebelumnya untuk pengembalian bernama, apakah itu diganti? Jika nilai bernama digunakan untuk menyimpan hasil dari fungsi yang salah, apakah itu dikembalikan?
  • Bisakah operator ? diterapkan ke nilai non-kesalahan? Bisakah saya melakukan sesuatu seperti (!ok)? atau ok!? (yang agak aneh, karena Anda menggabungkan tugas dan operasi)? Atau apakah sintaks ini hanya bagus untuk error ?

@rodcorsi , yang ini mengganggu saya karena bagaimana jika fungsinya bukan ReadDir tetapi ReadBuildTargetDirectoryForFileInfo atau sesuatu yang konyol panjang seperti itu. Atau mungkin Anda memiliki banyak argumen. Penanganan kesalahan untuk preparePath juga akan terdorong keluar dari layar. Pada perangkat dengan ukuran layar horizontal terbatas (atau area pandang yang tidak terlalu lebar, seperti Github), Anda kemungkinan besar akan kehilangan bagian =? . Kami sangat baik dalam pengguliran vertikal; tidak begitu banyak di horizontal.

@gladkikhartem , sepertinya itu terkait dengan beberapa (hanya yang terakhir?) argumen yang mengimplementasikan antarmuka error . Ini sangat mirip dengan deklarasi fungsi, dan itu hanya... _feels_ aneh. Apakah ada cara yang dapat dikaitkan dengan nilai pengembalian gaya ok ? Secara keseluruhan, Anda hanya membeli 1 baris.

@objek88
pembungkusan kata memecahkan masalah kode yang sangat luas. apakah itu tidak digunakan secara luas?

@object88 tentang panggilan fungsi yang sangat panjang. Mari kita berurusan dengan masalah utama di sini. Masalahnya bukan penanganan kesalahan yang didorong dari layar. Masalahnya adalah nama fungsi yang panjang dan/atau daftar besar argumen. Itu perlu diperbaiki sebelum argumen apa pun dapat dibuat tentang penanganan kesalahan yang berada di luar layar.

Saya belum melihat IDE atau editor teks ramah kode yang diatur ke bungkus kata secara default. Dan saya belum menemukan cara untuk melakukannya sama sekali dengan Github selain meretas CSS secara manual setelah halaman dimuat.

Dan menurut saya lebar kode merupakan faktor penting -- ini berbicara tentang _readability_, yang merupakan dorongan untuk proposal ini. Klaimnya adalah bahwa ada "terlalu banyak kode" di sekitar kesalahan. Bukannya fungsinya tidak ada, atau kesalahan perlu diterapkan dengan cara lain, tetapi kodenya tidak terbaca dengan baik.

@objek88
ya, kode ini akan berfungsi untuk fungsi apa pun yang mengembalikan antarmuka kesalahan sebagai parameter terakhir.

Mengenai penghematan saluran, Anda tidak dapat memasukkan lebih banyak informasi dalam jumlah baris yang lebih sedikit. Kode harus didistribusikan secara merata, tidak terlalu padat dan tidak memiliki spasi setelah setiap pernyataan.
Saya setuju itu terlihat seperti deklarasi fungsi, tetapi pada saat yang sama sangat mirip dengan if ...; err != nil { pernyataan, agar orang tidak terlalu bingung.

Lebar kode merupakan faktor penting. Bagaimana jika saya memiliki editor 80 baris dan kode 80 baris adalah panggilan fungsi dan setelah itu saya punya || err ? Saya tidak akan dapat mengidentifikasi bahwa fungsi itu mengembalikan sesuatu, karena apa yang akan saya baca akan menjadi kode go yang valid tanpa pengembalian apa pun.

Hanya untuk kelengkapan, saya akan memberikan contoh dengan sintaks || , pembungkus kesalahan otomatis saya dan auto-zeroing nilai pengembalian non-kesalahan

func (l *Loader) processDirectory(p *Package) (*build.Package, error) {
        absPath := p.preparePath() || errors.New("prepare path")
    fis := l.context.ReadDir(absPath) || errors.New("ReadDir")
    if len(fis) == 0 {
        return nil, nil
    }

    buildPkg, err := l.context.Import(".", absPath, 0)
    if err != nil {
        if _, ok := err.(*build.NoGoError); ok {
            // There isn't any Go code here.
            return nil, nil
        }
        return nil, err
    }

    return buildPkg, nil
}

Mengenai pertanyaan Anda tentang nilai pengembalian lainnya. Jika terjadi kesalahan, mereka akan memiliki nilai nol dalam semua kasus. Saya sudah membahas mengapa saya percaya itu penting.

Masalahnya adalah contoh Anda tidak begitu terlibat sejak awal. Tapi itu masih menunjukkan apa yang diwakili oleh proposal ini, setidaknya bagi saya. Apa yang saya ingin selesaikan adalah idiom yang paling umum dan digunakan secara liar

err := func()
if err != nil {
    return err
}

Kita semua setuju bahwa jenis kode adalah bagian besar (jika bukan yang terbesar) dari penanganan kesalahan secara umum. Jadi hanya logis untuk menyelesaikan kasus itu. Dan jika Anda ingin melakukan sesuatu yang lebih terlibat dengan kesalahan, terapkan beberapa logika - lakukan saja. Di situlah verbositas harus berada di mana ada logika yang sebenarnya untuk programmer untuk membaca dan memahami. Apa yang tidak kita butuhkan adalah membuang-buang ruang dan waktu untuk membaca boilerplate yang tidak ada artinya. Ini tidak berarti tetapi tetap merupakan bagian penting dari kode Go.

Mengenai pembicaraan sebelumnya tentang mengembalikan nilai nol secara implisit. Jika Anda perlu mengembalikan nilai yang berarti pada kesalahan - lakukan saja. Sekali lagi, verbositas bagus di sini dan membantu memahami kode. Tidak ada salahnya membuang gula sintaksis jika Anda perlu melakukan sesuatu yang lebih rumit. Dan || cukup fleksibel untuk menyelesaikan kedua kasus. Anda dapat mengabaikan nilai non-kesalahan dan nilai tersebut akan dinolkan secara implisit. Atau Anda dapat menentukannya secara eksplisit jika perlu. Saya ingat bahkan ada proposal terpisah untuk ini yang juga melibatkan kasus di mana Anda ingin mengembalikan kesalahan dan menghilangkan yang lainnya.

@objek88

Klaimnya adalah bahwa ada "terlalu banyak kode" di sekitar kesalahan.

Ini bukan sembarang kode. Masalah utama adalah bahwa ada terlalu banyak boilerplate yang tidak berarti di sekitar kesalahan dan kasus penanganan kesalahan yang sangat umum. Verbositas penting ketika ada sesuatu yang bernilai untuk dibaca. Tidak ada nilai dalam if err == nil then return err kecuali Anda ingin mengulang kesalahan. Untuk logika primitif seperti itu dibutuhkan banyak ruang. Dan semakin Anda memiliki logika, panggilan perpustakaan, pembungkus dll yang semuanya bisa mengembalikan kesalahan, semakin boilerplate ini mulai mendominasi hal-hal penting - logika sebenarnya dari kode Anda. Dan logika itu sebenarnya dapat berisi beberapa logika penanganan kesalahan yang penting. Tapi itu hilang dalam sifat berulang dari sebagian besar boilerplate di sekitarnya. Dan itu bisa diselesaikan dan bahasa modern lainnya yang bersaing dengan Go mencoba untuk menyelesaikannya. Karena penanganan kesalahan sangat penting, ini bukan hanya kode biasa.

@creker
Saya setuju bahwa jika err != nil return err terlalu banyak boilerplate, hal yang kami takutkan adalah jika kami akan membuat cara mudah untuk meneruskan kesalahan ke atas tumpukan - secara statistik programmer, terutama junior akan menggunakan metode termudah, daripada melakukan apa yang tepat dalam situasi tertentu.
Ini adalah ide yang sama dengan penanganan kesalahan Go - ini memaksa Anda untuk melakukan hal yang layak.
Jadi dalam proposal ini kami ingin mendorong orang lain untuk menangani dan membungkus kesalahan dengan bijaksana.

Saya akan mengatakan bahwa kita harus membuat penanganan kesalahan sederhana terlihat jelek dan lama untuk diterapkan, tetapi penanganan kesalahan yang anggun dengan pembungkusan atau tumpukan jejak terlihat bagus dan mudah dilakukan.

@gladkikhartem Saya selalu menganggap argumen tentang editor kuno ini konyol. Siapa yang peduli tentang itu dan mengapa bahasa harus menderita karena ini? Ini tahun 2018, hampir semua orang memiliki layar besar dan beberapa editor yang layak. Minoritas yang sangat kecil seharusnya tidak mempengaruhi orang lain. Seharusnya sebaliknya - minoritas harus menghadapinya sendiri. Gulir, pembungkusan kata, apa saja.

@gladkikhartem Go sudah memiliki masalah itu dan saya rasa kita tidak bisa berbuat apa-apa. Pengembang akan selalu malas sampai Anda memaksa mereka dengan kompilasi yang gagal atau kepanikan runtime yang, omong-omong, Go tidak melakukannya.

Apa yang sebenarnya dilakukan Go adalah tidak memaksakan apapun. Gagasan bahwa Go memaksa Anda untuk menangani kesalahan adalah menyesatkan dan selalu demikian. Penulis Go memaksa Anda untuk melakukan itu dalam posting blog dan pembicaraan konferensi mereka. Bahasa yang sebenarnya membuat Anda melakukan apa pun yang Anda inginkan. Dan masalah utama di sini adalah apa yang dipilih Go secara default - secara default kesalahan diabaikan secara diam-diam. Bahkan ada usulan untuk mengubahnya. Jika Go ingin memaksa Anda melakukan hal yang layak, maka Go harus melakukan hal berikut. Kembalikan kesalahan pada waktu kompilasi atau panik saat runtime jika kesalahan yang dikembalikan tidak ditangani dengan benar. Dari apa yang saya pahami, Rust melakukan ini - kesalahan panik secara default. Itulah yang saya sebut memaksa untuk melakukan hal yang benar.

Apa yang sebenarnya memaksa saya untuk menangani kesalahan di Go adalah hati nurani pengembang saya, tidak ada yang lain. Tapi selalu menggoda untuk menyerah. Saat ini jika saya tidak secara eksplisit membaca tanda tangan fungsi, tidak ada yang akan memberi tahu saya apa pun bahwa itu mengembalikan kesalahan. Ada contoh nyata. Untuk waktu yang lama saya tidak tahu bahwa fmt.Println mengembalikan kesalahan. Saya tidak menggunakan nilai pengembaliannya, saya hanya ingin mencetak barang. Jadi tidak ada insentif bagi saya untuk melihat apa yang dikembalikannya. Itu masalah yang sama yang dimiliki C. Kesalahan adalah nilai dan Anda dapat mengabaikannya semua yang Anda inginkan sampai kode Anda rusak saat runtime dan Anda tidak akan tahu apa-apa tentangnya karena tidak ada crash dengan kepanikan yang membantu seperti, misalnya, dengan pengecualian yang tidak tertangani.

@gladkikhartem dari apa yang saya pahami tentang proposal ini, bukan untuk mendorong pengembang untuk membungkus kesalahan dengan serius. Ini tentang mendorong mereka yang akan datang dengan proposal untuk tidak lupa untuk menutupi itu. Karena seringkali orang datang dengan solusi yang hanya menampilkan kembali kesalahan dan lupa bahwa Anda sebenarnya ingin memberikan lebih banyak konteks dan baru kemudian mengulanginya.

Saya menulis proposal ini terutama untuk mendorong orang-orang yang ingin menyederhanakan penanganan kesalahan Go untuk memikirkan cara-cara mempermudah membungkus konteks di sekitar kesalahan, bukan hanya mengembalikan kesalahan yang tidak dimodifikasi.

@creker
Editor saya memiliki lebar 100 karakter, karena saya memiliki file explorer, git console dan lain-lain.... , di tim kami tidak ada yang menulis kode lebih dari 100 karakter, itu hanya konyol (dengan beberapa pengecualian)

Go tidak memaksa penanganan kesalahan, linter melakukannya. (Mungkin kita harus menulis linter untuk ini?)

Oke, jika kita tidak dapat menemukan solusi dan semua orang memahami proposal dengan caranya sendiri - mengapa tidak menentukan beberapa persyaratan untuk hal yang kita butuhkan? jenis menyetujui persyaratan terlebih dahulu dan kemudian mengembangkan solusi akan menjadi tugas yang jauh lebih mudah.

Sebagai contoh:

  1. Sintaks proposal baru harus memiliki pernyataan pengembalian dalam teks, jika tidak, tidak jelas bagi pembaca apa yang terjadi. ( setuju tidak setuju )
  2. Proposal baru harus mendukung fungsi yang mengembalikan banyak nilai (setuju/tidak setuju)
  3. Proposal baru harus mengambil lebih sedikit ruang (1 baris, 2 baris, tidak setuju)
  4. Proposal baru harus mampu menangani ekspresi yang sangat panjang (setuju / tidak setuju)
  5. Proposal baru harus memungkinkan beberapa pernyataan jika terjadi kesalahan (setuju / tidak setuju)
  6. .....

@creker , sekitar 75% dari pengembangan saya dilakukan pada laptop 15" di VSCode. Saya memaksimalkan real estat horizontal saya, tetapi masih ada batasnya, terutama jika saya mengedit berdampingan. Saya akan bertaruh itu di antara siswa , ada jauh lebih banyak laptop daripada desktop. Saya tidak ingin membatasi bahasa yang mudah didekati karena kami mengantisipasi setiap orang memiliki monitor format besar.

Dan sayangnya, tidak peduli seberapa besar layar yang Anda miliki, github masih membatasi viewport.

@gladkikhartem

Kemalasan pemula berlaku di sini, tetapi penggunaan bebas errors.New dalam beberapa contoh ini juga menunjukkan kurangnya pemahaman bahasa. Kesalahan tidak boleh dialokasikan dalam nilai yang dikembalikan kecuali jika bersifat dinamis, dan jika kesalahan tersebut akan dimasukkan ke dalam variabel cakupan paket yang sebanding, sintaksnya akan lebih pendek pada halaman dan sebenarnya dapat diterima dalam kode produksi juga. Mereka yang paling menderita dari kesalahan penanganan Go "boilerplate" mengambil jalan pintas paling banyak dan tidak memiliki cukup pengalaman untuk menangani kesalahan dengan benar.

Tidak jelas apa yang dimaksud dengan simplifying error handling , tetapi presedennya adalah less runes != simple . Saya pikir ada beberapa kualifikasi untuk kesederhanaan yang dapat mengukur konstruksi dengan cara yang dapat diukur:

  • Banyaknya cara konstruk diekspresikan
  • Kesamaan konstruk tersebut dengan konstruk lainnya, dan kohesi antara konstruk tersebut
  • Jumlah operasi logis yang diringkas oleh konstruk
  • Kesamaan konstruk dengan bahasa alami (yaitu, tidak adanya negasi, dll)

Misalnya, proposal asli meningkatkan jumlah cara untuk menyebarkan kesalahan dari 2 menjadi 3. Ini mirip dengan logika OR, tetapi memiliki semantik yang berbeda. Ini merangkum pengembalian bersyarat dari kompleksitas rendah (dibandingkan dengan mengatakan copy atau append , atau >> ). Metode baru kurang alami daripada yang lama, dan jika diucapkan dengan lantang mungkin akan menjadi abs, err := path(foo) || return err -> if theres an error, it's returning err dalam hal ini akan menjadi misteri mengapa dimungkinkan untuk menggunakan bilah vertikal jika Anda dapat menulisnya dengan cara yang sama dengan yang diucapkan dalam ulasan kode.

@sebagai
Sangat setuju bahwa less runes != simple .
Secara sederhana maksud saya dapat dibaca dan dimengerti.
Sehingga siapa pun yang tidak terbiasa dengan go harus membacanya dan memahami fungsinya.
Itu harus seperti lelucon - Anda tidak perlu menjelaskannya.

Penanganan kesalahan saat ini sebenarnya dapat dimengerti, tetapi tidak sepenuhnya dapat dibaca jika Anda memiliki terlalu banyak if err != nil return.

@object88 tidak apa-apa. Saya katakan lebih umum karena argumen ini cukup sering muncul. Seperti, mari kita bayangkan beberapa layar terminal kuno yang konyol yang dapat digunakan untuk menulis Go. Argumen macam apa itu? Di mana batas kekonyolannya? Jika kita serius tentang hal itu maka kita harus mengamati fakta-fakta keras - apa ukuran dan resolusi layar yang paling populer. Dan hanya dari situ kita bisa menggambar sesuatu. Tetapi argumen biasanya hanya membayangkan beberapa ukuran layar yang tidak digunakan siapa pun tetapi ada kemungkinan kecil seseorang dapat melakukannya.

@gladkikhartem tidak, linter tidak memaksa Anda, saran mereka. Ada perbedaan besar di sini. Mereka opsional, bukan bagian dari toolchain Go dan hanya memberikan saran. Memaksa hanya bisa berarti dua hal - kompilasi atau kesalahan runtime. Segala sesuatu yang lain adalah saran dan pilihan untuk dipilih.

Saya setuju, sebaiknya kita merumuskan apa yang kita inginkan karena proposal itu tidak sepenuhnya mencakup semua aspek.

@sebagai

Metode baru ini kurang alami daripada yang lama, dan jika diucapkan dengan lantang mungkin akan menjadi abs, err := path(foo) || return err -> jika ada kesalahan, itu kembali err dalam hal ini akan menjadi misteri mengapa dimungkinkan untuk menggunakan bilah vertikal jika Anda dapat menulisnya dengan cara yang sama seperti yang dikatakan dengan keras dalam tinjauan kode.

Metode baru ini kurang alami hanya karena satu alasan - ini bukan bagian dari bahasa saat ini. Tidak ada alasan lain. Bayangkan Go sudah dengan sintaks itu - itu wajar karena Anda sudah terbiasa dengannya. Sama seperti Anda terbiasa dengan -> , select , go dan hal lain yang tidak ada dalam bahasa lain. Mengapa mungkin menggunakan bilah vertikal alih-alih kembali? Saya menjawab dengan pertanyaan. Mengapa ada cara untuk menambahkan irisan dalam satu panggilan ketika Anda dapat melakukan hal yang sama dengan loop? Mengapa ada cara untuk menyalin hal-hal dari antarmuka pembaca ke penulis dalam satu panggilan ketika Anda dapat melakukan hal yang sama dengan loop? etc etc etc Karena Anda ingin kode Anda lebih ringkas dan lebih mudah dibaca. Anda membuat argumen ini ketika Go sudah mengkontradiksikannya dengan banyak contoh. Sekali lagi, mari kita lebih terbuka dan tidak menjatuhkan apa pun hanya karena itu baru dan belum ada dalam bahasa. Kami tidak akan mencapai apa pun dengan itu. Ada masalah, banyak orang yang meminta solusi, mari kita atasi. Go bukanlah bahasa ideal yang suci yang akan dinodai oleh apa pun yang ditambahkan padanya.

Mengapa ada cara untuk menambahkan irisan dalam satu panggilan ketika Anda dapat melakukan hal yang sama dengan loop?

Menulis pemeriksaan kesalahan pernyataan if itu sepele, saya akan tertarik melihat implementasi Anda dari append .

Mengapa ada cara untuk menyalin hal-hal dari antarmuka pembaca ke penulis dalam satu panggilan ketika Anda dapat melakukan hal yang sama dengan loop?

Seorang pembaca dan penulis mengabstraksikan sumber dan tujuan dari operasi penyalinan, strategi buffering, dan kadang-kadang bahkan nilai sentinel dalam loop. Anda tidak dapat mengekspresikan abstraksi itu dengan loop dan slice.

Anda membuat argumen ini ketika Go sudah mengkontradiksikannya dengan banyak contoh.

Saya tidak percaya itu masalahnya, setidaknya tidak dengan contoh-contoh itu.

Sekali lagi, mari kita lebih terbuka dan tidak menjatuhkan apa pun hanya karena itu baru dan belum ada dalam bahasa.

Mengingat bahwa Go memiliki jaminan kompatibilitas, Anda harus memeriksa fitur-fitur baru paling banyak karena Anda harus berurusan dengan mereka selamanya jika itu buruk. Apa yang belum pernah dilakukan siapa pun di sini sejauh ini adalah menciptakan bukti konsep yang sebenarnya dan menggunakannya dengan tim pengembangan kecil.

Jika Anda melihat sejarah beberapa proposal (misalnya, obat generik), Anda akan melihat bahwa setelah melakukan hal itu realisasinya sering: "wow, ini sebenarnya bukan solusi yang baik, mari kita tidak membuat perubahan apa pun". Alternatifnya adalah bahasa yang penuh dengan saran dan tidak ada cara mudah untuk menghapusnya secara surut.

Soal layar lebar vs tipis, hal lain yang perlu diperhatikan adalah multi-tasking .

Anda mungkin memiliki beberapa jendela berdampingan untuk sesekali melacak sesuatu yang lain saat Anda mengumpulkan sedikit kode, daripada hanya menatap editor, sepenuhnya mengalihkan konteks ke jendela lain untuk mencari fungsi, mungkin StackOverflow, dan melompat kembali ke editor.

@sebagai
Sangat setuju bahwa sebagian besar fitur yang diusulkan tidak praktis, dan saya mulai berpikir bahwa || dan ? barang bisa jadi.

@creker
copy() dan append() bukan tugas sepele untuk diterapkan

Saya memiliki linter di CI/CD dan mereka benar-benar memaksa saya untuk menangani semua kesalahan. Mereka bukan bagian dari bahasa, tetapi tidak peduli - saya hanya butuh hasil.
(dan omong-omong, saya memiliki pendapat yang kuat - jika seseorang tidak menggunakan linter di Go - dia hanya ........)

Tentang ukuran layar - itu bahkan tidak lucu, serius. Tolong hentikan diskusi yang tidak relevan ini. Layar Anda bisa selebar yang Anda inginkan - Anda akan selalu memiliki kemungkinan || return &PathError{Err:err} bagian dari kode tidak akan terlihat. Cukup google kata "ide" dan lihat ruang seperti apa yang tersedia untuk kode.

Dan tolong baca teks orang lain dengan serius, saya tidak mengatakan bahwa Go memaksa Anda untuk menangani semua kesalahan

Ini adalah ide yang sama dengan penanganan kesalahan Go - ini memaksa Anda untuk melakukan hal yang layak.

@gladkikhartem Go tidak memaksakan apa pun dalam hal penanganan kesalahan, itulah masalahnya. Hal yang layak atau tidak, tidak masalah, itu hanya memilih nit. Meskipun bagi saya itu berarti menangani semua kesalahan dalam semua kasus selain mungkin hal-hal seperti fmt.Println .

jika seseorang tidak menggunakan linter di Go - dia hanya

Mungkin. Tetapi jika sesuatu tidak benar-benar dipaksakan maka itu tidak akan terbang. Beberapa akan menggunakannya, yang lain tidak.

Tentang ukuran layar - itu bahkan tidak lucu, serius. Tolong hentikan diskusi yang tidak relevan ini.

Saya bukan orang yang mulai melempar angka acak yang entah bagaimana seharusnya memengaruhi pengambilan keputusan. Saya dengan jelas menyatakan bahwa saya memahami masalahnya tetapi harus objektif. Bukan "Saya memiliki 80 simbol IDE lebar, Go harus memperhitungkan itu dan mengabaikan orang lain".

Jika kita berbicara tentang ukuran layar saya. kode studio visual memberi saya 270 simbol ruang horizontal. Saya tidak akan menganjurkan bahwa itu normal untuk mengambil banyak ruang. Tetapi kode saya dapat dengan mudah melebihi 120 simbol ketika Anda memperhitungkan struct akun dengan komentar dan khususnya jenis bidang bernama panjang. Jika saya menggunakan sintaks || maka itu akan dengan mudah masuk ke 100-120 jika terjadi pemanggilan fungsi argumen 3-5 dan kesalahan yang dibungkus dengan pesan khusus.

Cara lain jika sesuatu seperti || akan diimplementasikan maka gofmt mungkin tidak akan memaksa Anda untuk menulisnya dalam satu baris. Dalam beberapa kasus, ini mungkin memakan terlalu banyak ruang.

@erwbgy , yang ini terlihat terbaik bagi saya, tetapi saya tidak yakin bahwa pembayarannya sebesar itu.

@object88 Pembayaran bagi saya adalah menghapus boilerplate umum untuk penanganan kesalahan sederhana dan tidak mencoba melakukan terlalu banyak. Itu hanya membuat:

val, err := func()
if err != nil {
    return nil, errors.WithStack(err)
}

lebih sederhana:

val, err? := func()

Tidak ada yang mencegah penanganan kesalahan yang lebih kompleks dilakukan dengan cara saat ini.

Apa nilai pengembalian non-kesalahan? Apakah mereka selalu bernilai nol? Jika ada nilai yang ditetapkan sebelumnya untuk pengembalian bernama, apakah itu diganti? Jika nilai bernama digunakan untuk menyimpan hasil dari fungsi yang salah, apakah itu dikembalikan?

Semua parameter pengembalian lainnya adalah nilai nil yang sesuai. Untuk parameter bernama, saya berharap mereka akan mempertahankan nilai yang ditetapkan sebelumnya karena mereka dijamin sudah diberi beberapa nilai.

Bisakah ? operator diterapkan ke nilai non-kesalahan? Bisakah saya melakukan sesuatu seperti (!ok)? atau oke!? (yang agak aneh, karena Anda menggabungkan tugas dan operasi)? Atau apakah sintaks ini hanya bagus untuk kesalahan?

Tidak, saya rasa tidak masuk akal untuk menggunakan sintaks ini untuk apa pun selain nilai kesalahan.

Saya pikir fungsi "harus" akan berkembang biak karena putus asa untuk kode yang lebih mudah dibaca.

sqlx

db.MustExec(schema)

template html

var t = template.Must(template.New("name").Parse("html"))

Saya mengusulkan operator panik (tidak yakin apakah saya harus menyebutnya 'operator')

a,  😱 := someFunc(b)

sama seperti, tapi mungkin lebih cepat dari

a, err := someFunc(b)
if err != nil {
  panic(err)
}

mungkin terlalu sulit untuk diketik, kita bisa menggunakan sesuatu seperti !, atau !!, atau

a,  !! := someFunc(b)
!! = maybeReturnsError()

Mungkin !! panik dan ! kembali

Waktu untuk 2 sen saya. Mengapa kita tidak bisa menggunakan debug.PrintStack() perpustakaan standar untuk pelacakan tumpukan? Idenya adalah untuk mencetak jejak tumpukan hanya di tingkat terdalam, di mana kesalahan terjadi.

Mengapa kita tidak bisa menggunakan debug.PrintStack() perpustakaan standar untuk pelacakan tumpukan?

Kesalahan mungkin transit di banyak tumpukan. Mereka dapat dikirim melintasi saluran, disimpan dalam variabel, dll. Seringkali lebih membantu untuk mengetahui titik transisi tersebut daripada mengetahui fragmen tempat kesalahan pertama kali dibuat.

Selain itu, pelacakan tumpukan itu sendiri sering kali menyertakan fungsi pembantu internal (tidak diekspor). Itu berguna ketika Anda mencoba untuk men-debug crash yang tidak terduga, tetapi tidak membantu untuk kesalahan yang terjadi selama operasi normal.

Apa pendekatan yang paling ramah pengguna untuk pemula pemrograman yang lengkap?

Saya menemukan versi yang lebih sederhana. Hanya membutuhkan satu if !err
Tidak ada yang istimewa, intuitif, tidak ada tanda baca tambahan, kode yang jauh lebih kecil

``` pergi
absPath, err := p.preparePath()
kembali nihil, err jika err

err := doSomethingWith(absPath) jika !err
doSomethingElse() jika !err

melakukanSesuatuTerlepasDariErr()

// Menangani err di satu tempat; jika diperlukan; menangkap seperti tanpa indentasi
jika salah {
kembalikan "kesalahan tanpa polusi kode", err
}
```

err := doSomethingWith(absPath) if !err
doSomethingElse() if !err

Selamat datang kembali, kondisi postingan MUMPS lama yang bagus ;-)

Terima kasih tapi tidak, terima kasih.

@dmajkic Ini tidak melakukan apa pun untuk membantu "mengembalikan kesalahan dengan informasi kontekstual tambahan".

@erwbgy judul masalah ini adalah _proposal: Go 2: sederhanakan penanganan kesalahan dengan || err suffix_ komentar saya dalam konteks itu. Maaf jika saya melangkah dalam diskusi sebelumnya.

@cznic Yup. Kondisi pasca tidak Go-way, tetapi pra kondisi juga terlihat tercemar:

if !err; err := doSomethingWith(absPath)
if !err; doSomethingElse()

@dmajkic Ada lebih banyak proposal daripada hanya judul - ianlancetaylor menjelaskan tiga cara menangani kesalahan dan secara khusus menunjukkan bahwa beberapa proposal mempermudah pengembalian kesalahan dengan informasi tambahan.

@erwbgy Saya memang melewati semua masalah yang ditentukan oleh @ianlancetaylor Mereka semua menyampaikan menambahkan kata kunci baru (seperti try() ) atau menggunakan karakter non-alfanumerik khusus. Secara pribadi - saya tidak suka itu, karena kode dipenuhi dengan !"#$%& cenderung terlihat menyinggung, seperti mengumpat.

Saya setuju, dan merasa, apa yang dinyatakan oleh beberapa baris pertama dari masalah ini: terlalu banyak kode Go yang menyebabkan penanganan kesalahan. Saran yang saya buat sejalan dengan sentimen tersebut, dengan saran yang sangat mendekati apa yang dirasakan Go sekarang, tanpa perlu tambahan kata kunci atau key-char.

Bagaimana dengan penangguhan bersyarat?

func something() (int, error) {
    var error err
    var oth err

    defer err != nil {
        return 0, mycustomerror("More Info", err)
    }
    defer oth != nil {
        return 1, mycustomerror("Some other case", oth)
    }

    _, err = a()
    _, err = b()
    _, err = c()
    _, oth = d()
    _, err = e()

    return 2, nil
}


func something() (int, error) {
    var error err
    var oth err

    _, err = a()
    if err != nil {
        return 0, mycustomerror("More Info", err)
    }
    _, err = b()
    if err != nil {
        return 0, mycustomerror("More Info", err)
    }
    _, err = c()
    if err != nil {
        return 0, mycustomerror("More Info", err)
    }
    _, oth = d()
    if oth != nil {
        return 1, mycustomerror("Some other case", oth)
    }
    _, err = e()
    if err != nil {
        return 0, mycustomerror("More Info", err)
    }

    return 2, nil
}

Itu akan secara signifikan mengubah arti defer -- itu hanya sesuatu yang dijalankan di akhir lingkup, bukan sesuatu yang menyebabkan lingkup keluar lebih awal.

Jika mereka memperkenalkan Try Catch dalam bahasa ini, semua masalah ini akan diselesaikan dengan cara yang sangat mudah.

Mereka harus memperkenalkan sesuatu seperti ini. Jika nilai kesalahan diatur ke sesuatu selain nil, ini dapat merusak alur kerja saat ini dan secara otomatis memicu bagian tangkap dan kemudian bagian terakhir dan pustaka saat ini dapat bekerja juga tanpa perubahan. Masalah terpecahkan!

try (var err error){
     i, err:=DoSomething1()
     i, err=DoSomething2()
     i, err=DoSomething3()
} catch (err error){
   HandleError(err)
   // return err  // similar to throw err
} finally{
  // Do something
}

image

@sbinet Ini lebih baik daripada tidak sama sekali, tetapi jika mereka hanya menggunakan paradigma coba-tangkap yang sama yang semua orang kenal, itu jauh lebih baik.

@KamyarM Anda tampaknya menyarankan menambahkan mekanisme untuk melempar pengecualian setiap kali variabel disetel ke nilai bukan nol. Itu bukan "paradigma yang semua orang kenal". Saya tidak mengetahui bahasa apa pun yang berfungsi seperti itu.

Terlihat mirip dengan Swift yang juga memiliki "pengecualian" yang tidak berfungsi seperti pengecualian.

Bahasa yang berbeda telah menunjukkan bahwa try catch benar-benar merupakan solusi kelas dua, sementara saya kira Go tidak akan dapat menyelesaikannya seperti dengan Maybe monad dan seterusnya.

@ianlancetaylor Saya baru saja merujuk ke Try-Catch dalam bahasa pemrograman lain seperti C++, Java , C# ,... dan bukan solusi yang saya miliki di sini. Lebih baik jika GoLang memiliki Try-Catch dari hari 1 sehingga kita tidak perlu berurusan dengan cara penanganan kesalahan ini (yang sebenarnya tidak baru. Anda dapat menulis penanganan kesalahan GoLang yang sama dengan bahasa pemrograman lain jika Anda mau untuk kode seperti itu) tetapi yang saya sarankan adalah cara untuk memiliki kompatibilitas mundur dengan perpustakaan saat ini yang dapat mengembalikan objek kesalahan.

Pengecualian Jawa adalah kecelakaan kereta api, jadi saya harus sangat tidak setuju dengan Anda di sini @KamyarM. Hanya karena sesuatu yang akrab, tidak berarti bahwa itu adalah pilihan yang baik.

Apa yang saya maksud.

@KamyarM Terima kasih atas klarifikasinya. Kami secara eksplisit mempertimbangkan dan menolak pengecualian. Kesalahan tidak luar biasa; mereka terjadi karena segala macam alasan yang sepenuhnya normal. https://blog.golang.org/errors-are-values

Luar biasa atau tidak, tetapi mereka memecahkan masalah kode mengasapi karena kesalahan penanganan boilerplate. Masalah yang sama melumpuhkan Objective-C yang bekerja hampir persis seperti Go. Kesalahan hanyalah nilai tipe NSError, tidak ada yang istimewa tentangnya. Dan itu memiliki masalah yang sama dengan banyak ifs dan pembungkusan kesalahan. Itulah sebabnya Swift mengubah keadaan. Mereka berakhir dengan campuran dua - ini berfungsi seperti pengecualian yang berarti bahwa itu menghentikan eksekusi dan Anda harus menangkap pengecualian. Tapi itu tidak melepaskan tumpukan dan bekerja seperti pengembalian biasa. Jadi argumen teknis yang menentang penggunaan pengecualian untuk aliran kontrol tidak berlaku di sana - "pengecualian" ini secepat pengembalian reguler. Ini lebih merupakan gula sintaksis. Tapi Swift memiliki masalah unik dengan mereka. Banyak API Kakao tidak sinkron (panggilan balik dan GCD) dan tidak kompatibel dengan penanganan kesalahan semacam itu - pengecualian tidak berguna tanpa sesuatu seperti menunggu. Tetapi hampir semua kode Go sinkron dan "pengecualian" ini benar-benar dapat berfungsi.

@urandom
Pengecualian di Jawa tidak buruk. Masalahnya adalah dengan programmer yang buruk yang tidak tahu bagaimana menggunakannya.

Jika bahasa Anda memiliki fitur yang buruk, seseorang pada akhirnya akan menggunakan fitur itu. Jika bahasa Anda tidak memiliki fitur seperti itu, ada kemungkinan 0%. Ini adalah matematika sederhana.

@ karena saya tidak setuju dengan Anda bahwa try-catch adalah fitur yang mengerikan. Ini adalah fitur yang sangat berguna dan membuat hidup kita lebih mudah dan itulah alasan kami berkomentar di sini, jadi mungkin tim Google GoLang menambahkan fungsi serupa. Saya pribadi membenci kode penanganan kesalahan if-elses di GoLang dan saya tidak terlalu menyukai konsep defer-panic-recover (Ini mirip dengan try-catch tetapi tidak terorganisir seperti dengan blok Try-Catch-Finally) . Itu menambahkan begitu banyak kebisingan ke dalam kode yang membuat kode tidak dapat dibaca dalam banyak kasus.

Fungsionalitas untuk menangani kesalahan tanpa boilerplate sudah ada dalam bahasa tersebut. Menambahkan lebih banyak fitur untuk memuaskan pemula yang berasal dari bahasa berbasis pengecualian sepertinya bukan ide yang bagus.

Dan bagaimana dengan siapa yang berasal dari C/C++, Objective-C di mana kita memiliki masalah yang sama persis dengan boilerplate? Dan membuat frustrasi melihat bahasa modern seperti Go mengalami masalah yang persis sama. Itulah mengapa seluruh hype seputar kesalahan sebagai nilai terasa sangat palsu dan konyol - itu sudah dilakukan selama bertahun-tahun, puluhan tahun. Rasanya seperti Go tidak belajar apa-apa dari pengalaman itu. Terutama melihat Swift/Rust yang sebenarnya berusaha menemukan cara yang lebih baik. Selesaikan dengan solusi yang ada seperti Java/C# diselesaikan dengan pengecualian tetapi setidaknya itu adalah bahasa yang jauh lebih tua.

@KamyarM Pernahkah Anda menggunakan pemrograman berorientasi kereta api? BALOK?

Anda tidak akan terlalu memuji pengecualian, jika Anda akan menggunakan ini, imho.

@ShalokShalom Tidak banyak. Tapi bukankah itu hanya mesin negara? Jika gagal lakukan ini dan jika berhasil lakukan itu? Yah saya pikir tidak semua jenis kesalahan harus ditangani seperti pengecualian. Ketika hanya validasi input pengguna yang diperlukan, seseorang cukup mengembalikan nilai boolean dengan detail tentang kesalahan validasi. Pengecualian harus dibatasi pada IO atau akses Jaringan atau input fungsi yang buruk dan jika kesalahan sangat penting dan Anda ingin menghentikan jalur eksekusi yang menyenangkan dengan cara apa pun.

Salah satu alasan beberapa orang mengatakan Try-Catch tidak bagus adalah karena kinerjanya. Mungkin itu disebabkan oleh penggunaan tabel peta handler untuk setiap tempat sehingga pengecualian dapat terjadi. Saya membaca di suatu tempat bahwa bahkan pengecualian lebih cepat (Zero-Cost ketika tidak ada pengecualian tetapi memiliki biaya yang jauh lebih banyak ketika itu benar-benar terjadi) membandingkannya dengan If Error check (Itu selalu diperiksa terlepas dari memiliki kesalahan atau tidak). Selain itu saya tidak berpikir ada masalah dengan sintaks Try-Catch. Hanya cara itu diimplementasikan oleh kompiler yang membuatnya berbeda, bukan sintaksnya.

Orang-orang yang berasal dari C/C++ benar-benar memuji Go karena TIDAK memiliki pengecualian dan
untuk membuat pilihan yang bijaksana, menolak mereka yang mengklaim itu "modern" dan
berterima kasih kepada Tuhan tentang alur kerja yang dapat dibaca (terutama setelah C++).

Pada Tue, 17 Apr 2018 at 03:46, Antonenko Artem [email protected]
menulis:

Dan bagaimana dengan siapa yang berasal dari C/C++, Objective-C di mana kita memiliki hal yang sama?
masalah yang tepat dengan boilerplate? Dan frustasi melihat bahasa modern
seperti Go menderita masalah yang persis sama. Itu sebabnya seluruh hype ini
seputar kesalahan karena nilai terasa sangat palsu dan konyol - itu sudah dilakukan
selama bertahun-tahun, puluhan tahun. Rasanya seperti Go tidak belajar apa-apa dari itu
pengalaman. Terutama melihat Swift/Rust yang sebenarnya berusaha menemukan
cara yang lebih baik. Selesaikan dengan solusi yang ada seperti Java/C# diselesaikan dengan
pengecualian tetapi setidaknya itu adalah bahasa yang jauh lebih tua.


Anda menerima ini karena Anda berkomentar.
Balas email ini secara langsung, lihat di GitHub
https://github.com/golang/go/issues/21161#issuecomment-381793840 , atau bisukan
benang
https://github.com/notifications/unsubscribe-auth/AICzv9w608ea2fwPq_wNpTDBnKMAdAKTks5tpTtsgaJpZM4Oi1c-
.

@kirillx Saya tidak pernah mengatakan saya ingin pengecualian seperti di C++. Silakan baca lagi komentar saya. Dan apa hubungannya dengan C di mana penanganan kesalahan bahkan lebih mengerikan? Anda tidak hanya memiliki banyak boilerplate tetapi Anda juga kekurangan penangguhan dan beberapa nilai pengembalian yang memaksa Anda untuk mengembalikan nilai menggunakan argumen penunjuk dan menggunakan goto untuk mengatur logika pembersihan Anda. Go menggunakan konsep kesalahan yang sama tetapi memecahkan beberapa masalah dengan penangguhan dan beberapa nilai pengembalian. Tapi boilerplate masih ada. Bahasa modern lainnya juga tidak menginginkan pengecualian tetapi juga tidak ingin puas dengan gaya C karena verbositasnya. Itulah sebabnya kami memiliki proposal ini dan sangat tertarik dengan masalah ini.

Orang yang menganjurkan pengecualian harus membaca artikel ini: https://ckwop.me.uk/Why-Exceptions-Suck.html

Alasan mengapa pengecualian gaya Java/C++ pada dasarnya buruk tidak ada hubungannya dengan kinerja implementasi tertentu. Pengecualian buruk karena itu adalah "on error goto" BASIC, dengan goto tidak terlihat dalam konteks di mana mereka mungkin berlaku. Pengecualian menyembunyikan penanganan kesalahan di mana Anda dapat dengan mudah melupakannya. Pengecualian yang diperiksa Java seharusnya menyelesaikan masalah itu, tetapi dalam praktiknya tidak karena orang hanya menangkap dan memakan pengecualian atau membuang jejak tumpukan di mana-mana.

Saya menulis Java hampir setiap minggu, dan saya secara tegas tidak ingin melihat pengecualian gaya Java di Go, tidak peduli seberapa tinggi kinerjanya.

@lpar Bukankah semua for loop, while loop, if elses , switch cases , break dan continue semacam GoTo things. Apa yang tersisa dari bahasa pemrograman?

Sementara, untuk dan jika/lain tidak melibatkan aliran eksekusi melompat tanpa terlihat ke tempat lain tanpa penanda untuk menunjukkan bahwa itu akan terjadi.

Apa bedanya jika seseorang hanya meneruskan kesalahan ke penelepon sebelumnya di GoLang dan penelepon itu hanya mengembalikannya ke penelepon sebelumnya dan seterusnya (selain banyak kode noise)? Berapa banyak kode yang perlu kita lihat dan lintasi untuk melihat siapa yang akan menangani kesalahan? Hal yang sama berlaku dengan try-catch.

Apa yang bisa menghentikan programmer? Terkadang fungsi benar-benar tidak perlu menangani kesalahan. kami hanya ingin meneruskan kesalahan ke UI sehingga pengguna atau admin sistem dapat menyelesaikannya atau menemukan solusi.

Jika suatu fungsi tidak ingin menangani pengecualian, itu tidak menggunakan blok try-catch sehingga pemanggil sebelumnya dapat menanganinya. Saya tidak berpikir sintaks memiliki masalah. Ini juga jauh lebih bersih. Performa dan cara penerapannya dalam bahasa berbeda.

Seperti yang Anda lihat di bawah, kita perlu menambahkan 4 baris kode agar tidak menangani kesalahan:

func myFunc1() error{
  // ...
  if (err){
      return err
  }
  return nil
}

Jika Anda ingin meneruskan kesalahan kembali ke pemanggil untuk ditangani, tidak apa-apa. Intinya adalah terlihat bahwa Anda melakukannya, pada titik di mana kesalahan dikembalikan kepada Anda.

Mempertimbangkan:

x, err := lib.SomeFunc(100, 4)
if err != nil {
  // A
}
// B

Dari melihat kodenya, Anda tahu bahwa kesalahan mungkin terjadi saat memanggil fungsi. Anda tahu bahwa jika kesalahan terjadi, aliran kode akan berakhir di titik A. Anda tahu bahwa satu-satunya aliran kode tempat lain yang akan berakhir adalah titik B. Ada juga kontrak implisit bahwa jika err adalah nihil, x adalah beberapa nilai yang valid, nol atau sebaliknya.

Kontras dengan Jawa:

x = SomeFunc(100, 4)

Dari melihat kode, Anda tidak tahu apakah kesalahan mungkin terjadi saat fungsi dipanggil. Jika kesalahan terjadi dan dinyatakan sebagai pengecualian, maka goto terjadi, dan Anda bisa berakhir di suatu tempat di bagian bawah kode sekitarnya... atau jika pengecualian tidak tertangkap, Anda bisa berakhir di suatu tempat di bagian bawah kode Anda yang sama sekali berbeda. Atau Anda bisa berakhir dengan kode orang lain. Faktanya, karena penangan pengecualian default dapat diganti, Anda berpotensi berakhir di mana saja, berdasarkan sesuatu yang dilakukan oleh kode orang lain.

Selain itu, tidak ada kontrak implisit bahwa x valid -- biasanya fungsi mengembalikan nol untuk menunjukkan kesalahan atau nilai yang hilang.

Dengan Java, masalah ini dapat terjadi pada setiap panggilan -- tidak hanya dengan kode yang buruk, ini adalah sesuatu yang harus Anda khawatirkan dengan _semua_ kode Java. Itulah mengapa lingkungan pengembangan Java memiliki bantuan pop-up untuk menunjukkan kepada Anda apakah fungsi yang Anda tunjuk dapat menyebabkan pengecualian atau tidak, dan pengecualian apa yang mungkin ditimbulkannya. Itu sebabnya Java menambahkan pengecualian yang diperiksa, sehingga untuk kesalahan umum Anda setidaknya harus memiliki beberapa peringatan bahwa panggilan fungsi mungkin menimbulkan pengecualian dan mengalihkan aliran program. Sementara itu, null yang dikembalikan dan sifat NullPointerException yang tidak dicentang adalah masalah sehingga mereka menambahkan kelas Optional ke Java 8 untuk mencoba dan memperbaikinya, meskipun biayanya harus secara eksplisit membungkus mengembalikan nilai pada setiap fungsi tunggal yang mengembalikan objek.

Pengalaman saya adalah bahwa NullPointerException dari nilai nol tak terduga yang saya terima adalah satu-satunya cara paling umum kode Java saya berakhir mogok, dan saya biasanya berakhir dengan backtrace besar yang hampir seluruhnya tidak berguna, dan pesan kesalahan yang tidak menunjukkan penyebabnya karena dihasilkan jauh dari kode yang salah. Di Go, sejujurnya saya tidak menemukan kepanikan dereferensi nihil sebagai masalah yang signifikan, meskipun saya kurang berpengalaman dengan Go. Itu, bagi saya, menunjukkan bahwa Java harus belajar dari Go, bukan sebaliknya.

Saya tidak berpikir sintaks memiliki masalah.

Saya tidak berpikir ada yang mengatakan bahwa sintaks adalah masalah dengan pengecualian gaya Java.

@lpar , Mengapa kepanikan dereferensi nihil di Go lebih baik daripada NullPointerException di Jawa? Apa perbedaan dari "Panik" dan "Lempar"? Apa perbedaan semantik mereka?

Panik hanya dapat dipulihkan dan lemparan dapat ditangkap? Benar?

Saya baru saja ingat satu perbedaan, dengan panik Anda dapat membuat panik objek kesalahan atau objek string atau mungkin jenis objek lainnya (koreksi saya jika saya salah) tetapi dengan melempar Anda dapat melempar objek bertipe Pengecualian atau subkelas pengecualian saja.

Mengapa panik dereferensi nihil di Go lebih baik daripada NullPointerException di Jawa?

Karena yang pertama hampir tidak pernah terjadi dalam pengalaman saya, sedangkan yang terakhir terjadi sepanjang waktu, untuk alasan yang saya jelaskan.

@lpar Yah saya belum memprogram dengan Java baru-baru ini dan saya kira itu hal baru (5 tahun terakhir) tetapi C # memiliki operator navigasi yang aman untuk menghindari referensi nol untuk membuat pengecualian tetapi Apa yang dimiliki Go? Saya tidak yakin tetapi saya kira itu tidak memiliki apa pun untuk menangani situasi itu. Jadi, jika Anda ingin menghindari kepanikan, Anda masih perlu menambahkan pernyataan if-not-nil-else yang jelek itu ke dalam kode.

Biasanya Anda tidak perlu memeriksa nilai pengembalian untuk melihat apakah nilai tersebut nol di Go, selama Anda memeriksa nilai pengembalian kesalahan. Jadi tidak ada pernyataan if bersarang yang jelek.

Dereference nol adalah contoh yang buruk. Jika Anda tidak menangkapnya, Go dan Java bekerja persis sama - Anda mendapatkan crash dengan stack trace. Bagaimana tumpukan jejak menjadi tidak berguna, saya tidak melakukannya sekarang. Anda tahu persis tempat kejadiannya. Baik di C# dan Go bagi saya, biasanya sepele untuk memperbaiki kerusakan semacam itu karena dereferensi nol dalam pengalaman saya disebabkan oleh kesalahan pemrogram sederhana. Dalam kasus khusus ini tidak ada yang bisa dipelajari dari siapa pun.

@lpar

Karena yang pertama hampir tidak pernah terjadi dalam pengalaman saya, sedangkan yang terakhir terjadi sepanjang waktu, untuk alasan yang saya jelaskan.

Itu tidak disengaja dan saya tidak melihat alasan apa pun dalam komentar Anda bahwa Java entah bagaimana lebih buruk di nil/null daripada Go. Saya mengamati banyak gangguan dereferensi nihil dalam kode Go. Mereka persis sama dengan dereference nol di C#/Java. Anda mungkin menggunakan lebih banyak tipe nilai di Go yang membantu (C# juga memilikinya) tetapi tidak mengubah apa pun.

Adapun pengecualian, mari kita lihat Swift. Anda memiliki kata kunci throws untuk fungsi yang dapat menimbulkan kesalahan. Fungsi tanpa itu tidak bisa melempar. Dari segi implementasi, ini berfungsi seperti pengembalian - mungkin beberapa register dicadangkan untuk mengembalikan kesalahan dan setiap kali Anda melempar fungsi kembali secara normal tetapi disertai dengan nilai kesalahan. Jadi masalah kesalahan tak terduga terpecahkan. Anda tahu persis fungsi mana yang mungkin terjadi, Anda tahu persis di mana itu bisa terjadi. Kesalahan adalah nilai dan tidak memerlukan pelepasan tumpukan. Mereka baru saja dikembalikan sampai Anda menangkapnya.

Atau sesuatu yang mirip dengan Rust di mana Anda memiliki tipe Hasil khusus yang membawa hasil dan kesalahan. Kesalahan dapat disebarkan tanpa pernyataan bersyarat eksplisit. Ditambah satu ton kebaikan pencocokan pola tapi itu mungkin bukan untuk Go.

Kedua bahasa ini mengambil kedua solusi (C dan Java) dan menggabungkannya menjadi sesuatu yang lebih baik. Propagasi kesalahan dari pengecualian + nilai kesalahan dan aliran kode yang jelas dari C + tidak ada kode boilerplate jelek yang tidak berguna. Jadi saya pikir bijaksana untuk melihat implementasi khusus ini dan tidak mengabaikannya sepenuhnya hanya karena mereka menyerupai pengecualian dalam beberapa hal. Ada alasan mengapa pengecualian digunakan dalam banyak bahasa karena mereka memiliki sisi positifnya. Jika tidak, bahasa akan mengabaikannya. Terutama setelah C++.

Bagaimana tumpukan jejak menjadi tidak berguna, saya tidak melakukannya sekarang.

Saya berkata "hampir sama sekali tidak berguna". Seperti, saya hanya membutuhkan satu baris informasi darinya tetapi panjangnya puluhan baris.

Itu tidak disengaja dan saya tidak melihat alasan apa pun dalam komentar Anda bahwa Java entah bagaimana lebih buruk di nil/null daripada Go.

Maka Anda tidak mendengarkan. Kembali dan baca bagian tentang kontrak implisit.

Kesalahan dapat disebarkan tanpa pernyataan bersyarat eksplisit.

Dan itulah masalahnya -- kesalahan menyebar dan aliran kontrol berubah tanpa ada tanda yang jelas bahwa itu akan terjadi. Tampaknya Anda tidak berpikir itu masalah, tetapi yang lain tidak setuju.

Apakah pengecualian yang diterapkan oleh Rust atau Swift mengalami masalah yang sama dengan Java, saya tidak tahu, saya akan menyerahkannya kepada seseorang yang berpengalaman dengan bahasa yang dimaksud.

@KamyarM Anda pada dasarnya membuat nil berlebihan dan mendapatkan keamanan tipe penuh untuk itu:

https://fsharpforfundandprofit.com/posts/the-option-type/

Dan itulah masalahnya -- kesalahan menyebar dan aliran kontrol berubah tanpa ada tanda yang jelas bahwa itu akan terjadi.

Ini benar bagi saya. Jika saya mengembangkan beberapa paket yang menggunakan paket lain, dan paket itu mengeluarkan pengecualian, sekarang _I_ juga harus menyadarinya, terlepas dari apakah saya ingin menggunakan fitur itu. Ini adalah aspek yang tidak umum di antara fitur bahasa yang diusulkan; sebagian besar adalah hal-hal yang dapat dipilih oleh seorang programmer, atau tidak digunakan sesuai kebijaksanaan mereka. Pengecualian, dengan niat mereka sendiri, melintasi segala macam batas, diharapkan atau sebaliknya.

Saya berkata "hampir sama sekali tidak berguna". Seperti, saya hanya membutuhkan satu baris informasi darinya tetapi panjangnya puluhan baris.

Dan jejak Go yang besar dengan ratusan goroutine entah bagaimana lebih berguna? Saya tidak mengerti ke mana Anda akan pergi dengan ini. Java dan Go persis sama di sini. Dan terkadang Anda merasa berguna untuk mengamati tumpukan penuh untuk memahami bagaimana kode Anda berakhir di tempat yang macet. Jejak C# dan Go membantu saya berkali-kali dengan itu.

Maka Anda tidak mendengarkan. Kembali dan baca bagian tentang kontrak implisit.

Saya membacanya, tidak ada yang berubah. Dalam pengalaman saya itu tidak masalah. Untuk itulah dokumentasi dalam kedua bahasa ( net.ParseIP misalnya). Jika Anda lupa memeriksa apakah nilai Anda nil/null atau tidak, Anda memiliki masalah yang sama persis di kedua bahasa. Dalam kebanyakan kasus, Go akan mengembalikan kesalahan dan C# akan mengeluarkan pengecualian sehingga Anda bahkan tidak perlu khawatir tentang nihil. API yang baik tidak hanya mengembalikan Anda nol tanpa memberikan pengecualian atau sesuatu untuk memberi tahu apa yang salah. Dalam kasus lain Anda memeriksanya secara eksplisit. Jenis kesalahan yang paling umum dengan nol dalam pengalaman saya adalah ketika Anda memiliki buffer protokol di mana setiap bidang adalah pointer/objek atau Anda memiliki logika internal di mana bidang kelas/struktur bisa nol tergantung pada keadaan internal dan Anda lupa memeriksanya sebelumnya mengakses. Itu pola yang paling umum bagi saya dan tidak ada di Go yang secara signifikan mengurangi masalah ini. Saya dapat menyebutkan dua hal yang sedikit membantu - nilai kosong yang berguna dan tipe nilai. Tapi ini lebih tentang kemudahan pemrograman karena Anda tidak diharuskan untuk membangun setiap variabel sebelum digunakan.

Dan itulah masalahnya -- kesalahan menyebar dan aliran kontrol berubah tanpa ada tanda yang jelas bahwa itu akan terjadi. Tampaknya Anda tidak berpikir itu masalah, tetapi yang lain tidak setuju.

Itu masalah, saya tidak pernah mengatakan sebaliknya tetapi orang-orang di sini begitu terpaku pada pengecualian Java/C#/C++ sehingga mereka mengabaikan apa pun yang sedikit mirip dengan mereka. Persis mengapa Swift mengharuskan Anda untuk menandai fungsi dengan throws sehingga Anda dapat melihat dengan tepat apa yang Anda harapkan dari suatu fungsi dan di mana aliran kontrol mungkin rusak dan di Rust yang Anda gunakan ? untuk secara eksplisit menyebarkan kesalahan dengan berbagai metode pembantu untuk memberikan lebih banyak konteks. Mereka berdua menggunakan konsep kesalahan yang sama sebagai nilai tetapi membungkusnya dengan gula sintaksis untuk mengurangi boilerplate.

Dan jejak Go yang besar dengan ratusan goroutine entah bagaimana lebih berguna?

Dengan Go, Anda menangani kesalahan dengan mencatatnya bersama dengan lokasi saat kesalahan itu terdeteksi. Tidak ada backtrace kecuali Anda memilih untuk menambahkannya. Saya hanya perlu sekali melakukan itu.

Dalam pengalaman saya itu tidak masalah.

Yah, pengalaman saya berbeda, dan saya pikir pengalaman kebanyakan orang berbeda, dan sebagai buktinya saya mengutip fakta bahwa Java 8 menambahkan tipe Opsional.

Utas ini di sini banyak membahas kelebihan dan kekurangan Go dan sistem penanganan kesalahannya, termasuk diskusi tentang pengecualian atau tidak, saya sangat menyarankan untuk membacanya:

https://elixirforum.com/t/discussing-go-split-thread/13006/2

2 sen saya untuk penanganan kesalahan (maaf jika ide seperti itu disebutkan di atas).

Kami ingin menampilkan kembali kesalahan dalam banyak kasus. Ini mengarah ke cuplikan seperti itu:

a, err := fn()
if err != nil {
    return err
}
use(a)
return nil

Mari kita rethrow non-nil error secara otomatis jika tidak ditetapkan ke variabel (tanpa sintaks tambahan). Kode di atas akan menjadi:

a := fn()
use(a)

// or just

use(fn())

Kompilator akan menyimpan err ke variabel implisit (tidak terlihat), periksa nil dan lanjutkan (jika err == nil) atau kembalikan (jika err != nil) dan kembalikan nil di akhir fungsi jika tidak ada kesalahan yang terjadi selama eksekusi fungsi seperti biasanya tetapi secara otomatis dan implisit.

Jika err harus ditangani, ia harus ditetapkan ke variabel eksplisit dan digunakan:

a, err := fn()
if err != nil {
    doSomething(err)
} else {
    use(a)
}
return nil

Kesalahan dapat ditekan sedemikian rupa:

a, _ := fn()
use(a)

Dalam kasus langka (fantastis) dengan lebih dari satu kesalahan yang dikembalikan, penanganan kesalahan eksplisit akan wajib (seperti sekarang):

err1, err2 := fn2()
if err1 != nil || err2 != nil {
    return err1, err2
}
return nil, nil

Itu argumen saya juga - kami ingin memunculkan kembali kesalahan dalam banyak kasus, itu biasanya kasus default. Dan mungkin memberikan beberapa konteks. Dengan pengecualian konteks ditambahkan secara otomatis oleh pelacakan tumpukan. Dengan kesalahan seperti di Go kami melakukannya dengan tangan dengan menambahkan pesan kesalahan. Mengapa tidak membuatnya lebih sederhana. Dan itulah yang coba dilakukan oleh bahasa lain sambil menyeimbangkannya dengan masalah kejelasan.

Jadi saya setuju dengan "Let's rethrow non-nil error secara otomatis jika tidak ditetapkan ke variabel (tanpa sintaks tambahan)" tetapi bagian terakhir mengganggu saya. Di situlah akar masalah dengan pengecualian dan mengapa, saya pikir, orang-orang sangat menentang berbicara tentang sesuatu yang sedikit terkait dengan mereka. Mereka mengubah aliran kontrol tanpa sintaks tambahan. Itu hal yang buruk.

Jika Anda melihat Swift, misalnya, kode ini tidak akan dikompilasi

func a() throws {}
func b() throws {
  a()
}

a dapat menimbulkan kesalahan sehingga Anda harus menulis try a() bahkan untuk menyebarkan kesalahan. Jika Anda menghapus throws dari b maka itu tidak akan dikompilasi bahkan dengan try a() . Anda harus menangani kesalahan di dalam b . Itu cara yang jauh lebih baik untuk menangani kesalahan yang memecahkan masalah aliran kontrol yang tidak jelas dari pengecualian dan verbositas kesalahan Objective-C. Yang terakhir hampir persis seperti kesalahan di Go dan apa yang dimaksudkan untuk diganti oleh Swift. Yang saya tidak suka adalah barang try, catch yang juga digunakan Swift. Saya lebih suka meninggalkan kesalahan sebagai bagian dari nilai pengembalian.

Jadi yang akan saya usulkan adalah benar-benar memiliki sintaks tambahan. Sehingga situs panggilan mengatakan dengan sendirinya bahwa itu adalah tempat potensial di mana aliran kontrol mungkin berubah. Apa yang saya juga usulkan adalah bahwa tidak menulis sintaks tambahan ini akan menghasilkan kesalahan kompilasi. Itu, tidak seperti cara kerja Go sekarang, akan memaksa Anda untuk menangani kesalahan. Anda dapat menggunakan kemampuan untuk membungkam kesalahan dengan sesuatu seperti _ karena dalam beberapa kasus akan sangat frustasi untuk menangani setiap kesalahan kecil. Seperti, printf . Saya tidak peduli jika gagal mencatat sesuatu. Go sudah memiliki impor yang mengganggu ini. Tapi itu diselesaikan dengan perkakas setidaknya.

Ada dua alternatif untuk mengkompilasi kesalahan waktu yang dapat saya pikirkan saat ini. Seperti Go sekarang, biarkan kesalahan diabaikan secara diam-diam. Saya tidak suka itu dan itu selalu menjadi masalah saya dengan penanganan kesalahan Go. Itu tidak memaksa apa pun, perilaku default adalah mengabaikan kesalahan secara diam-diam. Itu buruk, bukan itu cara Anda menulis program yang kuat dan mudah di-debug. Saya memiliki terlalu banyak kasus di Objective-C ketika saya malas atau kehabisan waktu dan mengabaikan kesalahan hanya untuk terkena bug dalam kode yang sama tetapi tanpa info diagnostik mengapa itu terjadi. Setidaknya logging itu akan memungkinkan saya untuk memecahkan masalah di sana dalam banyak kasus.

Kelemahannya adalah bahwa orang mungkin mulai mengabaikan kesalahan, menempatkan try, catch(...) mana-mana sehingga untuk berbicara. Itu kemungkinan tetapi, pada saat yang sama, dengan kesalahan yang diabaikan secara default, lebih mudah untuk melakukannya. Saya pikir argumen tentang pengecualian tidak berlaku di sini. Dengan pengecualian, apa yang beberapa orang coba capai adalah ilusi bahwa program mereka lebih stabil. Fakta bahwa pengecualian yang tidak tertangani membuat crash program adalah masalahnya di sini.

Alternatif lain adalah panik. Tapi itu hanya membuat frustrasi dan membawa ingatan akan pengecualian. Itu pasti akan mengarahkan orang untuk melakukan pengkodean "defensif" sehingga program mereka tidak macet. Bagi saya bahasa modern harus melakukan sebanyak mungkin hal pada waktu kompilasi dan meninggalkan sesedikit mungkin keputusan untuk runtime. Di mana kepanikan mungkin tepat berada di atas tumpukan panggilan. Misalnya, tidak menangani kesalahan pada fungsi utama secara otomatis akan menghasilkan kepanikan. Apakah ini juga berlaku untuk goroutine? Mungkin tidak.

Mengapa mempertimbangkan kompromi?

@nick-korsakov proposal asli (masalah ini) ingin menambahkan lebih banyak konteks ke kesalahan:

Sudah mudah (mungkin terlalu mudah) untuk mengabaikan kesalahan (lihat #20803). Banyak proposal yang ada untuk penanganan kesalahan membuatnya lebih mudah untuk mengembalikan kesalahan yang tidak dimodifikasi (misalnya, #16225, #18721, #21146, #21155). Beberapa membuatnya lebih mudah untuk mengembalikan kesalahan dengan informasi tambahan.

Lihat juga komentar ini .

Dalam komentar ini saya menyarankan bahwa untuk membuat kemajuan dalam diskusi ini (daripada berjalan berulang-ulang) kita harus mendefinisikan tujuan dengan lebih baik, misalnya apa yang dimaksud dengan pesan kesalahan yang ditangani dengan hati-hati. Semuanya cukup menarik untuk dibaca tetapi tampaknya dipengaruhi oleh masalah memori ikan mas tiga detik (tidak terlalu fokus/bergerak maju, mengulangi perubahan sintaksis kreatif yang bagus dan argumen tentang pengecualian/kepanikan dll).

Gudang sepeda lainnya:

func makeFile(url string) (size int, err error){
    rsp, err := http.Get(url)
    try err
    defer rsp.Body.Close()

    var data dataStruct
    dec := json.NewDecoder(rsp.Body)
    err := dec.Decode(&data)
    try errors.Errorf("could not decode %s: %v", url, err)

    f, err := os.Create(data.Path)
    try errors.Errorf("could not open file %s: %v", data.Path, err)
    defer f.Close()

    return f.Write([]byte(data.Rows))
}

try berarti "kembalikan jika bukan nilai kosong". Dalam hal ini saya berasumsi errors.Errorf akan mengembalikan nil ketika err adalah nihil. Saya pikir ini adalah penghematan sebanyak yang bisa kita harapkan sambil tetap berpegang pada tujuan pembungkusan yang mudah.

Jenis pemindai di perpustakaan standar menyimpan status kesalahan di dalam struktur yang metodenya dapat secara bertanggung jawab memeriksa keberadaan kesalahan sebelum melanjutkan.

type Scanner struct{
    err error
}
func (s *Scanner) Scan() bool{
   if s.err != nil{
       return false
   }
   // scanning logic
}
func (s *Scanner) Err() error{ return s.err }

Dengan menggunakan tipe untuk menyimpan status kesalahan, dimungkinkan untuk menjaga kode yang menggunakan tipe tersebut bebas dari pemeriksaan kesalahan yang berlebihan.

Ini juga tidak memerlukan perubahan sintaks yang kreatif dan aneh atau transfer kontrol yang tidak terduga dalam bahasa.

Saya juga harus menyarankan sesuatu seperti try/catch, di mana err didefinisikan di dalam try{}, dan jika err disetel ke nilai bukan nil - aliran terputus dari blok try{} ke err handler block (jika ada).

Secara internal tidak ada pengecualian, tetapi semuanya harus lebih dekat
ke sintaks yang melakukan pemeriksaan if err != nil break setelah setiap baris di mana err dapat ditetapkan.
Misalnya:

...
try(err) {
   err = doSomethig()
   err, value := doSomethingElse()
   doSomethingObliviousToErr()
   err = thirdErrorProneThing()
} 
catch(err SomeErrorType) {
   handleSomeSpecificErr(err)
}
catch(err Error) {
  panic(err)
}

Saya tahu ini terlihat seperti C++, tetapi juga terkenal dan lebih bersih daripada manual if err != nil {...} setelah setiap baris.

@sebagai

Jenis pemindai berfungsi karena melakukan semua pekerjaan, oleh karena itu mampu melacak kesalahannya sendiri di sepanjang jalan. Jangan menipu diri sendiri bahwa ini adalah solusi universal, tolong.

@carlmjohnson

Jika kita menginginkan penanganan satu liner untuk kesalahan sederhana, kita dapat mengubah sintaks untuk memungkinkan pernyataan return menjadi awal dari blok satu baris.
Itu akan memungkinkan orang untuk menulis:

func makeFile(url string) (size int, err error){
    rsp, err := http.Get(url)
    if err != nil return err
    defer rsp.Body.Close()

    var data dataStruct
    dec := json.NewDecoder(rsp.Body)
    err := dec.Decode(&data)
    if err != nil return errors.Errorf("could not decode %s: %v", url, err)

    f, err := os.Create(data.Path)
    if err != nil return errors.Errorf("could not open file %s: %v", data.Path, err)
    defer f.Close()

    return f.Write([]byte(data.Rows))
}

Saya pikir spesifikasinya harus diubah menjadi sesuatu seperti (ini bisa sangat naif :))

Block = "{" StatementList "}" | "return" Expression .

Saya tidak berpikir bahwa pengembalian casing khusus benar-benar lebih baik daripada hanya mengubah gofmt menjadi sederhana jika err memeriksa satu baris, bukan tiga.

@urandom

Kesalahan penggabungan di luar satu jenis kotak dan tindakannya tidak boleh didorong. Bagi saya ini menunjukkan kurangnya upaya untuk membungkus atau menambahkan konteks kesalahan antara kesalahan yang berasal dari berbagai tindakan yang tidak terkait.

Pendekatan pemindai adalah salah satu hal terburuk yang saya baca dalam konteks mantra "kesalahan adalah nilai" ini:

  1. Ini tidak berguna di hampir semua kasus penggunaan yang membutuhkan banyak kesalahan penanganan pelat ketel. Fungsi yang memanggil beberapa paket eksternal tidak akan mendapat manfaat darinya.
  2. Ini konsep yang berbelit-belit dan asing. Memperkenalkannya hanya akan membingungkan pembaca di masa mendatang dan membuat kode Anda lebih rumit daripada yang seharusnya agar Anda dapat mengatasi kekurangan desain bahasa.
  3. Ini menyembunyikan logika dan mencoba menjadi serupa dengan pengecualian dengan mengambil yang terburuk darinya (aliran kontrol kompleks) tanpa mengambil manfaat apa pun.
  4. Dalam beberapa kasus itu akan membuang sumber daya komputasi. Setiap panggilan harus membuang waktu untuk pemeriksaan kesalahan tidak berguna yang terjadi berabad-abad yang lalu.
  5. Itu menyembunyikan tempat yang tepat di mana kesalahan terjadi. Bayangkan sebuah kasus di mana Anda mem-parsing atau membuat serial beberapa format file. Anda akan memiliki rantai panggilan baca/tulis. Bayangkan yang pertama gagal. Bagaimana Anda tahu, di mana tepatnya kesalahan itu terjadi? Bidang mana yang diurai atau diserialisasi? "IO error", "timeout" - kesalahan ini tidak akan berguna dalam kasus ini. Anda dapat memberikan konteks untuk setiap baca/tulis (nama bidang, misalnya). Tetapi pada titik ini lebih baik menyerah saja pada seluruh pendekatan karena itu merugikan Anda.

Dalam beberapa kasus itu akan membuang sumber daya komputasi.

Tolak ukur? Apa sebenarnya "sumber daya komputasi" itu?

Itu menyembunyikan tempat yang tepat di mana kesalahan terjadi.

Tidak, tidak, karena kesalahan non-nol tidak ditimpa

Fungsi yang memanggil beberapa paket eksternal tidak akan mendapat manfaat darinya.
Ini konsep yang berbelit-belit dan tidak dikenal
Pendekatan pemindai adalah salah satu hal terburuk yang saya baca dalam konteks keseluruhan "kesalahan adalah nilai"

Kesan saya adalah Anda tidak memahami pendekatannya. Ini secara logis setara dengan pemeriksaan kesalahan biasa dalam tipe mandiri, saya sarankan Anda mempelajari contoh dengan cermat jadi mungkin itu bisa menjadi hal terburuk yang Anda pahami daripada hanya hal terburuk yang Anda _baca_.

Maaf, saya akan menambahkan proposal saya sendiri ke tumpukan. Saya telah membaca sebagian besar dari apa yang ada di sini, dan sementara saya menyukai beberapa proposal, saya merasa mereka mencoba melakukan terlalu banyak. Masalahnya adalah kesalahan boilerplate. Proposal saya hanyalah untuk menghilangkan boilerplate itu di tingkat sintaksis, dan membiarkan cara kesalahan disebarkan sendiri.

Usul

Kurangi kesalahan boilerplate dengan mengaktifkan penggunaan token _! sebagai gula sintaksis untuk menyebabkan kepanikan saat diberi nilai error non-nil

val, err := something.MayError()
if err != nil {
    panic(err)
}

bisa menjadi

val, _! := something.MayError()

dan

if err := something.MayError(); err != nil {
    panic(err)
}

bisa menjadi

_! = something.MayError()

Tentu saja, simbol tertentu untuk diperdebatkan. Saya juga mempertimbangkan _^ , _* , @ , dan lainnya. Saya memilih _! sebagai saran de-facto karena saya pikir itu akan menjadi yang paling akrab secara sekilas.

Secara sintaksis, _! (atau token yang dipilih) akan menjadi simbol tipe error tersedia dalam lingkup penggunaannya. Itu dimulai sebagai nil , dan setiap kali ditugaskan, cek nil dilakukan. Jika disetel ke nilai error non-nil, kepanikan dimulai. Karena _! (atau, sekali lagi, token yang dipilih) bukan merupakan pengidentifikasi yang valid secara sintaksis, tabrakan nama tidak akan menjadi masalah. Variabel ethereal ini hanya akan diperkenalkan dalam lingkup di mana ia digunakan, mirip dengan nilai pengembalian bernama. Jika pengidentifikasi yang valid secara sintaksis diperlukan, mungkin placeholder dapat digunakan yang akan ditulis ulang ke nama unik pada waktu kompilasi.

Pembenaran

Salah satu kritik yang lebih umum saya lihat diratakan saat pergi adalah verbositas penanganan kesalahan. Kesalahan pada batas API bukanlah hal yang buruk. Namun, harus mendapatkan kesalahan ke batas API bisa menjadi masalah, terutama untuk algoritme yang sangat rekursif. Untuk menyiasati propagasi kesalahan verbositas tambahan yang diperkenalkan ke kode rekursif, panik dapat digunakan. Saya merasa bahwa ini adalah teknik yang cukup umum digunakan. Saya telah menggunakannya dalam kode saya sendiri, dan saya telah melihatnya digunakan di alam liar, termasuk di parser go. Terkadang, Anda telah melakukan validasi di tempat lain dalam program Anda dan mengharapkan kesalahan menjadi nihil. Jika kesalahan non-nol diterima, ini akan melanggar invarian Anda. Ketika invarian dilanggar, panik bisa diterima. Dalam kode inisialisasi yang kompleks, terkadang masuk akal untuk mengubah kesalahan menjadi kepanikan dan memulihkannya untuk dikembalikan ke suatu tempat dengan lebih banyak pengetahuan tentang konteksnya. Dalam semua skenario ini, ada peluang untuk mengurangi kesalahan boilerplate.

Saya menyadari bahwa itu adalah filosofi go untuk menghindari kepanikan sebanyak mungkin. Mereka bukan alat untuk penyebaran kesalahan melintasi batas-batas API. Namun, mereka adalah fitur bahasa dan memiliki kasus penggunaan yang sah, seperti yang dijelaskan di atas. Kepanikan adalah cara yang fantastis untuk menyederhanakan penyebaran kesalahan dalam kode pribadi, dan penyederhanaan sintaks akan sangat membantu untuk membuat kode lebih bersih dan, bisa dibilang, lebih jelas. Saya merasa lebih mudah untuk mengenali _! (atau @ , atau `_^, dll...) secara sekilas daripada bentuk "if-error-panic". Token dapat secara dramatis mengurangi jumlah kode yang harus ditulis/dibaca untuk menyampaikan/memahami:

  1. mungkin ada kesalahan
  2. jika ada kesalahan, kami tidak mengharapkannya
  3. jika ada kesalahan, itu mungkin sedang ditangani rantai

Seperti halnya fitur sintaks lainnya, ada potensi penyalahgunaan. Dalam hal ini, komunitas go sudah memiliki seperangkat praktik terbaik untuk mengatasi kepanikan. Karena penambahan sintaksis ini adalah gula sintaksis untuk panik, kumpulan praktik terbaik itu dapat diterapkan untuk penggunaannya.

Selain penyederhanaan kasus penggunaan yang dapat diterima untuk kepanikan, ini juga membuat pembuatan prototipe cepat menjadi lebih mudah. Jika saya memiliki ide yang ingin saya tulis dalam kode, dan hanya ingin kesalahan membuat program crash saat saya bermain-main, saya dapat menggunakan penambahan sintaks ini daripada bentuk "jika-kesalahan-panik". Jika saya dapat mengekspresikan diri saya dalam lebih sedikit baris pada tahap awal pengembangan, memungkinkan saya untuk memasukkan ide-ide saya ke dalam kode lebih cepat. Setelah saya memiliki ide lengkap dalam kode, saya kembali dan memperbaiki kode saya untuk mengembalikan kesalahan pada batas yang sesuai. Saya tidak akan membiarkan kepanikan gratis dalam kode produksi, tetapi mereka bisa menjadi alat pengembangan yang kuat.

Kepanikan hanyalah pengecualian dengan nama lain, dan satu hal yang saya sukai dari Go adalah pengecualian itu luar biasa. Saya tidak ingin mendorong lebih banyak pengecualian dengan memberi mereka gula sintaksis.

@carlmjohnson Salah satu dari dua hal harus benar:

  1. Kepanikan adalah bagian dari bahasa dengan kasus penggunaan yang sah, atau
  2. Panik tidak memiliki kasus penggunaan yang sah, dan oleh karena itu harus dihapus dari bahasa

Saya menduga jawabannya adalah 1.
Saya juga tidak setuju bahwa "panik hanyalah pengecualian dengan nama lain". Saya pikir lambaian tangan semacam itu mencegah diskusi yang sebenarnya. Ada perbedaan utama antara panik dan pengecualian seperti yang terlihat di sebagian besar bahasa lain.

Saya memahami reaksi spontan "panik itu buruk", tetapi perasaan pribadi tentang penggunaan panik tidak mengubah fakta bahwa panik digunakan, dan sebenarnya berguna. Compiler go menggunakan panics untuk menyelamatkan proses rekursif yang mendalam baik di parser maupun dalam fase pengecekan tipe (terakhir saya lihat).
Menggunakannya untuk menyebarkan kesalahan melalui kode yang sangat rekursif tampaknya tidak hanya menjadi penggunaan yang dapat diterima, tetapi juga penggunaan yang didukung oleh pengembang go.

Panik mengomunikasikan sesuatu yang spesifik:

ada yang tidak beres di sini yang di sini tidak siap untuk ditangani

Akan selalu ada tempat dalam kode di mana itu benar. Terutama di awal perkembangan. Go telah dimodifikasi untuk meningkatkan pengalaman refactoring sebelumnya: penambahan alias tipe. Mampu menyebarkan kesalahan yang tidak diinginkan dengan panik sampai Anda dapat menyempurnakan jika dan bagaimana menanganinya pada tingkat yang lebih dekat ke sumbernya dapat membuat penulisan dan pemfaktoran ulang kode secara progresif jauh lebih sedikit bertele-tele.

Saya merasa sebagian besar proposal di sini mengusulkan perubahan besar pada bahasa. Ini adalah pendekatan paling transparan yang bisa saya lakukan. Hal ini memungkinkan seluruh model kognitif penanganan kesalahan saat ini tetap utuh, sementara memungkinkan pengurangan sintaks untuk kasus tertentu, tetapi umum. Praktik terbaik saat ini menentukan bahwa "kode go tidak boleh panik melintasi batas API". Jika saya memiliki metode publik dalam sebuah paket, mereka harus mengembalikan kesalahan jika terjadi kesalahan, kecuali pada kesempatan langka di mana kesalahan tidak dapat dipulihkan (pelanggaran invarian, misalnya). Penambahan bahasa ini tidak akan menggantikan praktik terbaik itu. Ini hanyalah cara untuk mengurangi boilerplate dalam kode internal, dan membuat sketsa ide lebih jelas. Ini tentu membuat kode lebih mudah dibaca secara linier.

var1, _! := trySomeTask1()
var2, _! := trySomeTask2(var1)
var3, _! := trySomeTask3(var2)
var4, _! := trySomeTask4(var3)

jauh lebih mudah dibaca daripada

var1, err := trySomeTask1()
if err != nil {
    panic(err)
}
var2, err := trySomeTask2(var1)
if err != nil {
    panic(err)
}
var3, err := trySomeTask3(var2)
if err != nil {
    panic(err)
}
var4, err := trySomeTask4(var3)
if err != nil {
    panic(err)
}

Sebenarnya tidak ada perbedaan mendasar antara panik di Go dan pengecualian di Java atau Python dll selain dari sintaks dan kurangnya hierarki objek (yang masuk akal karena Go tidak memiliki warisan). Bagaimana mereka bekerja dan bagaimana mereka digunakan adalah sama.

Tentu saja kepanikan memiliki tempat yang sah dalam bahasa tersebut. Kepanikan adalah untuk menangani kesalahan yang seharusnya hanya terjadi karena kesalahan pemrogram yang tidak dapat dipulihkan. Misalnya, jika Anda membagi dengan nol dalam konteks bilangan bulat, tidak ada kemungkinan nilai kembalian dan itu adalah kesalahan Anda sendiri karena tidak memeriksa nol terlebih dahulu, sehingga panik. Demikian pula, jika Anda membaca sepotong di luar batas, coba gunakan nil sebagai nilai, dll. Hal-hal itu disebabkan oleh kesalahan programmer—bukan oleh kondisi yang diantisipasi, seperti jaringan sedang down atau file yang memiliki izin yang buruk—jadi mereka hanya panik dan meledakkan tumpukan. Go menyediakan beberapa fungsi pembantu yang panik seperti template. Harus karena diantisipasi bahwa itu akan digunakan dengan string yang di-hardcode di mana kesalahan apa pun harus disebabkan oleh kesalahan programmer. Kehabisan memori bukanlah kesalahan programmer semata, tetapi juga tidak dapat dipulihkan dan dapat terjadi di mana saja, jadi ini bukan kesalahan tetapi kepanikan.

Orang-orang juga terkadang menggunakan kepanikan sebagai cara untuk mempersingkat tumpukan, tetapi itu umumnya tidak disukai karena alasan keterbacaan dan kinerja, dan saya tidak melihat kemungkinan Go berubah untuk mendorong penggunaannya.

Go panik dan pengecualian Java yang tidak dicentang hampir identik dan mereka ada untuk alasan yang sama dan untuk menangani kasus penggunaan yang sama. Jangan mendorong orang untuk menggunakan kepanikan untuk kasus lain karena kasus tersebut memiliki masalah yang sama dengan pengecualian dalam bahasa lain.

Orang juga terkadang menggunakan kepanikan sebagai cara untuk mempersingkat tumpukan, tetapi itu umumnya tidak disukai karena alasan keterbacaan dan kinerja.

Pertama-tama, masalah keterbacaan adalah sesuatu yang langsung ditangani oleh perubahan sintaks ini:

// clearly, linearly shows that these steps must occur in order,
// and any errors returned cause a panic, because this piece of
// code isn't responsible for reporting or handling possible failures:
// - IO Error: either network or disk read/write failed
// - External service error: some unexpected response from the external service
// - etc...
// It's not this code's responsibility to be aware of or handle those scenarios.
// That's perhaps the parent process's job.
var1, _! := trySomeTask1()
var2, _! := trySomeTask2(var1)
var3, _! := trySomeTask3(var2)
var4, _! := trySomeTask4(var3)

vs

var1, err := trySomeTask1()
if err != nil {
    panic(err)
}
var2, err := trySomeTask2(var1)
if err != nil {
    panic(err)
}
var3, err := trySomeTask3(var2)
if err != nil {
    panic(err)
}
var4, err := trySomeTask4(var3)
if err != nil {
    panic(err)
}

Mengesampingkan keterbacaan untuk saat ini, alasan lain yang diberikan adalah kinerja.
Ya, memang benar bahwa menggunakan pernyataan panik dan menunda menimbulkan penalti kinerja, tetapi dalam banyak kasus perbedaan ini diabaikan untuk operasi yang dilakukan. Disk & IO jaringan akan, rata-rata, membutuhkan waktu lebih lama daripada sihir tumpukan potensial apa pun untuk mengelola penangguhan/kepanikan.

Saya mendengar poin ini banyak disalahartikan ketika membahas kepanikan, dan saya pikir tidak jujur ​​untuk mengatakan bahwa kepanikan adalah penurunan kinerja. Mereka pasti BISA, tetapi tidak harus demikian. Sama seperti banyak hal lain dalam suatu bahasa. Jika Anda panik di dalam lingkaran ketat di mana pukulan kinerja benar-benar penting, Anda juga tidak boleh menunda dalam lingkaran itu. Faktanya, fungsi apa pun yang memilih untuk panik pada umumnya tidak akan menangkap kepanikannya sendiri. Demikian pula, fungsi go yang ditulis hari ini tidak akan mengembalikan kesalahan dan kepanikan. Itu tidak jelas, konyol, dan bukan praktik terbaik. Mungkin begitulah cara kita terbiasa melihat pengecualian yang digunakan di Java, Python, Javascript, dll, tetapi itu bukan cara panik umumnya digunakan dalam kode go, dan saya tidak percaya bahwa menambahkan operator khusus untuk kasus menyebarkan kesalahan naik tumpukan panggilan melalui panik akan mengubah cara orang menggunakan panik. Mereka tetap menggunakan kepanikan. Inti dari ekstensi sintaks ini adalah untuk mengakui fakta bahwa pengembang menggunakan kepanikan, dan memiliki penggunaan yang sah, dan mengurangi boilerplate di sekitarnya.

Bisakah Anda memberi saya beberapa contoh kode bermasalah yang menurut Anda akan diaktifkan oleh fitur sintaks ini, yang saat ini tidak memungkinkan/melawan praktik terbaik? Jika seseorang mengomunikasikan kesalahan kepada pengguna kode mereka melalui panik/pemulihan, yang saat ini disukai dan jelas akan terus berlanjut, bahkan jika sintaks seperti ini ditambahkan. Jika Anda bisa, tolong jawab yang berikut ini:

  1. Pelanggaran apa yang Anda bayangkan akan muncul dari ekstensi sintaksis seperti ini?
  2. Apa yang var1, err := trySomeTask1(); if err != nil { panic(err) } sampaikan bahwa var1, _! := trySomeTask1() tidak? Mengapa?

Sepertinya saya bahwa inti dari argumen Anda adalah bahwa "panik itu buruk dan kita tidak boleh menggunakannya".
Saya tidak dapat membongkar dan mendiskusikan alasan di balik itu jika tidak dibagikan.

Hal-hal itu disebabkan oleh kesalahan programmer—bukan oleh kondisi yang diantisipasi, seperti jaringan yang sedang down atau file yang memiliki izin yang buruk—jadi mereka hanya panik dan meledakkan tumpukan.

Saya, seperti kebanyakan penjual, menyukai gagasan kesalahan-sebagai-nilai. Saya percaya ini membantu mengomunikasikan dengan jelas bagian mana dari API yang menjamin hasil versus yang bisa gagal, tanpa harus melihat dokumentasi.

Ini memungkinkan hal-hal seperti mengumpulkan kesalahan dan menambah kesalahan dengan lebih banyak informasi. Itu semua sangat penting pada batasan API, di mana kode Anda bersinggungan dengan kode pengguna. Namun, dalam batasan API tersebut, seringkali tidak perlu melakukan semua itu dengan kesalahan Anda. Terutama jika Anda mengharapkan jalur bahagia, dan memiliki kode lain yang bertanggung jawab untuk menangani kesalahan jika jalur itu gagal.

Ada kalanya bukan tugas kode Anda untuk menangani kesalahan.
Jika saya sedang menulis perpustakaan, saya tidak peduli jika tumpukan jaringan sedang down - itu di luar kendali saya sebagai pengembang perpustakaan. Saya akan mengembalikan kesalahan itu kembali ke kode pengguna.

Bahkan dalam kode saya sendiri, ada kalanya saya menulis sepotong kode yang tugasnya hanya mengembalikan kesalahan ke fungsi induk.

Misalnya, Anda memiliki http.HandlerFunc membaca file dari disk sebagai respons - ini hampir selalu berhasil, dan jika gagal, kemungkinan program tidak ditulis dengan benar (kesalahan programmer) atau ada masalah dengan sistem file di luar lingkup tanggung jawab program. Setelah http.HandlerFunc panik, ia keluar dan beberapa pengendali dasar akan menangkap kepanikan itu dan menulis 500 ke klien. Jika suatu saat nanti saya ingin menangani kesalahan itu secara berbeda, saya dapat mengganti _! dengan err dan melakukan apa pun yang saya inginkan dengan nilai kesalahan. Masalahnya, selama program berlangsung, saya mungkin tidak perlu melakukan itu. Jika saya mengalami masalah seperti itu, handler bukanlah bagian dari kode yang bertanggung jawab untuk menangani kesalahan itu.

Saya dapat, dan biasanya, menulis if err != nil { panic(err) } atau if err != nil { return ..., err } di handler saya untuk hal-hal seperti kegagalan IO, kegagalan jaringan, dll. Ketika saya perlu memeriksa kesalahan, saya masih dapat melakukannya. Namun, sebagian besar waktu, saya hanya menulis if err != nil { panic(err) } .

Atau, contoh lain, Jika saya mencari trie secara rekursif (misalnya dalam implementasi router http), saya akan mendeklarasikan fungsi func (root *Node) Find(path string) (found Value, err error) . Fungsi itu akan menunda fungsi untuk memulihkan kepanikan yang dihasilkan saat turun dari pohon. Bagaimana jika program membuat percobaan yang salah? Bagaimana jika beberapa IO gagal karena program tidak berjalan sebagai pengguna dengan izin yang benar? Masalah-masalah ini bukan masalah algoritme pencarian trie saya - kecuali saya secara eksplisit membuatnya nanti - tetapi itu kemungkinan kesalahan yang mungkin saya temui. Mengembalikan semuanya ke atas tumpukan mengarah ke banyak verbositas ekstra, termasuk memegang apa yang idealnya akan menjadi beberapa nilai kesalahan nihil pada tumpukan. Sebagai gantinya, saya dapat memilih untuk membuat panik kesalahan hingga fungsi API publik itu dan mengembalikannya ke pengguna. Saat ini, ini masih menimbulkan verbositas ekstra, tetapi tidak harus.

Proposal lain membahas bagaimana memperlakukan satu nilai pengembalian sebagai istimewa. Ini pada dasarnya adalah pemikiran yang sama, tetapi alih-alih menggunakan fitur yang sudah ada di dalam bahasa, mereka melihat untuk memodifikasi perilaku bahasa untuk kasus-kasus tertentu. Dalam hal kemudahan implementasi, jenis proposal ini (gula sintaksis untuk sesuatu yang sudah didukung) akan menjadi yang paling mudah.

Sunting untuk menambahkan:
Saya tidak menikah dengan proposal yang saya buat seperti yang tertulis, tetapi saya pikir melihat masalah penanganan kesalahan dari sudut pandang baru itu penting. Tidak ada yang menyarankan apa pun tentang novel itu, dan saya ingin melihat apakah kita dapat membingkai ulang pemahaman kita tentang masalah ini. Masalahnya adalah ada terlalu banyak tempat di mana kesalahan ditangani secara eksplisit ketika tidak perlu, dan pengembang ingin cara untuk menyebarkannya ke atas tumpukan tanpa kode boilerplate tambahan. Ternyata Go sudah memiliki fitur itu, tetapi tidak ada sintaks yang bagus untuk itu. Ini adalah diskusi tentang membungkus fungsionalitas yang ada dalam sintaksis yang tidak terlalu bertele-tele untuk membuat bahasa lebih ergonomis tanpa mengubah perilaku. Bukankah itu sebuah kemenangan, jika kita bisa mencapainya?

@mccolljr Terima kasih, tetapi salah satu tujuan proposal ini adalah untuk mendorong orang mengembangkan cara baru untuk menangani ketiga kasus penanganan kesalahan: abaikan kesalahan, kembalikan kesalahan tanpa dimodifikasi, kembalikan kesalahan dengan informasi kontekstual tambahan. Proposal panik Anda tidak membahas kasus ketiga. Ini penting.

@mccolljr Saya pikir batas API jauh lebih umum daripada yang tampaknya Anda asumsikan. Saya tidak melihat panggilan dalam-API sebagai kasus umum. Jika ada, mungkin sebaliknya (beberapa data akan menarik di sini). Jadi saya tidak yakin mengembangkan sintaks khusus untuk panggilan dalam-API adalah arah yang benar. Selain itu, menggunakan kesalahan return ed, daripada kesalahan panic ed, dalam API biasanya merupakan cara yang baik untuk dilakukan (terutama jika kami membuat rencana untuk masalah ini). panic ed kesalahan memiliki kegunaannya, tetapi situasi di mana mereka terbukti lebih baik tampaknya jarang terjadi.

Saya tidak percaya bahwa menambahkan operator khusus untuk kasus menyebarkan kesalahan ke tumpukan panggilan melalui kepanikan akan mengubah cara orang menggunakan kepanikan.

Saya pikir Anda salah. Orang-orang akan mencari operator steno Anda karena ini sangat nyaman, dan kemudian mereka akan menggunakan lebih banyak kepanikan daripada sebelumnya.

Apakah kepanikan kadang-kadang berguna atau jarang, dan apakah kepanikan itu berguna di seluruh atau di dalam batas-batas API, adalah masalah besar. Ada banyak tindakan yang mungkin dilakukan jika terjadi kesalahan. Kami sedang mencari cara untuk mempersingkat kode penanganan kesalahan tanpa mengutamakan satu tindakan di atas yang lain.

tetapi dalam banyak kasus perbedaan ini diabaikan untuk operasi yang dilakukan

Meskipun benar, saya pikir itu adalah jalan yang berbahaya untuk diambil. Terlihat sepele pada awalnya, itu akan menumpuk dan akhirnya menyebabkan kemacetan di kemudian hari ketika sudah terlambat. Saya pikir kami harus mengingat kinerja sejak awal dan mencoba menemukan solusi yang lebih baik. Telah disebutkan Swift dan Rust memang memiliki propagasi kesalahan tetapi mengimplementasikannya sebagai, pada dasarnya, pengembalian sederhana yang dibungkus dengan gula sintaksis. Ya, mudah untuk menggunakan kembali solusi yang ada, tetapi saya lebih suka membiarkan semuanya apa adanya daripada menyederhanakan dan mendorong orang untuk menggunakan kepanikan yang tersembunyi di balik gula sintaksis yang tidak dikenal yang mencoba menyembunyikan fakta bahwa itu, pada dasarnya, pengecualian.

Terlihat sepele pada awalnya, itu akan menumpuk dan akhirnya menyebabkan kemacetan di kemudian hari ketika sudah terlambat.

Tidak, terima kasih. Kemacetan kinerja imajiner adalah hambatan kinerja yang dapat diabaikan secara geometris.

Tidak, terima kasih. Kemacetan kinerja imajiner adalah hambatan kinerja yang dapat diabaikan secara geometris.

Silakan tinggalkan perasaan pribadi Anda dari topik ini. Anda jelas memiliki beberapa masalah dengan saya dan tidak ingin membawa sesuatu yang berguna, jadi abaikan saja komentar saya dan tinggalkan suara seperti yang Anda lakukan dengan hampir semua komentar sebelumnya. Tidak perlu terus memposting jawaban yang tidak masuk akal ini.

Saya tidak punya masalah dengan Anda, Anda hanya membuat klaim tentang kemacetan kinerja tanpa data apa pun untuk mendukungnya dan saya menunjukkannya dengan kata-kata dan jempol.

Teman-teman, tolong jaga percakapan tetap sopan dan sesuai topik. Masalah ini tentang menangani kesalahan Go.

https://golang.org/conduct

Saya ingin mengunjungi kembali bagian "kembalikan kesalahan/dengan konteks tambahan" lagi, karena saya menganggap mengabaikan kesalahan sudah dicakup oleh _ .

Saya mengusulkan kata kunci dua kata yang dapat diikuti oleh string (opsional). Alasan itu adalah kata kunci dua kata ada dua. Pertama, tidak seperti operator yang secara inheren samar, lebih mudah untuk memahami apa yang dilakukannya tanpa terlalu banyak pengetahuan sebelumnya. Saya telah memilih "atau gelembung", karena saya berharap kata or dengan tidak adanya kesalahan yang ditetapkan akan menandakan kepada pengguna bahwa kesalahan sedang ditangani di sini, jika tidak nihil. Beberapa pengguna sudah akan mengaitkan or dengan menangani nilai palsu dari bahasa lain (Perl, python), dan membaca data := Foo() or ... mungkin secara tidak sadar memberi tahu mereka bahwa data tidak dapat digunakan jika or bagian dari pernyataan tercapai. Kedua, kata kunci bubble meskipun relatif pendek, mungkin menandakan kepada pengguna bahwa ada sesuatu yang naik (tumpukan). Kata up mungkin juga cocok, meskipun saya tidak yakin apakah keseluruhan or up cukup dapat dimengerti. Akhirnya, semuanya adalah kata kunci, pertama dan terutama karena lebih mudah dibaca, dan kedua karena perilaku itu tidak dapat ditulis oleh fungsi itu sendiri (Anda mungkin bisa memanggil panic untuk keluar dari fungsi tempat Anda berada, tetapi kemudian Anda bisa' t berhenti sendiri, orang lain harus pulih).

Berikut ini hanya untuk propagasi kesalahan, oleh karena itu hanya dapat digunakan dalam fungsi yang mengembalikan kesalahan, dan nilai nol dari argumen pengembalian lainnya:

Untuk mengembalikan kesalahan tanpa mengubahnya dengan cara apa pun:

func Worker(path string) ([]byte, error) {
    data := ioutil.ReadFile(path) or bubble

    return data;
}

Untuk mengembalikan kesalahan dengan pesan tambahan:

func Worker(path string) ([]byte, error) {
    data := ioutil.ReadFile(path) or bubble fmt.Sprintf("reading file %s", path)

    modified := modifyData(data) or bubble "modifying the data"

    return data;
}

Dan akhirnya, perkenalkan mekanisme adaptor global untuk modifikasi kesalahan yang disesuaikan:

// Default Bubble Processor
errors.BubbleProcessor(func(msg string, err error) error {
    return fmt.Errorf("%s: %v", msg, err)
})

// Some program might register the following:
errors.BubbleProcessor(func(msg string, err error) error {
    return errors.WithMessage(err, msg)
})

Akhirnya, untuk beberapa tempat di mana penanganan yang sangat kompleks diperlukan, cara verbose yang sudah ada sudah merupakan cara terbaik.

Menarik. Memiliki penangan gelembung global memberi orang yang ingin jejak tumpukan tempat untuk melakukan panggilan untuk jejak, yang merupakan manfaat bagus dari metode itu. OTOH, jika memiliki tanda tangan func(string, error) error , maka itu berarti penggelembungan harus dilakukan dengan tipe kesalahan bawaan dan bukan tipe lain, seperti tipe konkret yang mengimplementasikan error .

Juga, keberadaan or bubble menunjukkan kemungkinan or die atau or panic . Saya tidak yakin apakah itu fitur atau bug.

tidak seperti operator yang secara inheren samar, lebih mudah untuk memahami apa yang dilakukannya tanpa terlalu banyak pengetahuan sebelumnya

Itu mungkin bagus ketika Anda pertama kali menemukannya. Tetapi membaca dan menulisnya lagi dan lagi - tampaknya terlalu bertele-tele dan membutuhkan terlalu banyak ruang untuk menyampaikan hal yang cukup sederhana - kesalahan yang tidak tertangani dengan menggelembungkan tumpukan. Operator samar pada awalnya tetapi mereka ringkas dan memiliki kontras yang baik dengan semua kode lainnya. Mereka dengan jelas memisahkan logika utama dari penanganan kesalahan karena sebenarnya merupakan pemisah. Memiliki begitu banyak kata dalam satu baris akan merusak keterbacaan menurut saya. Setidaknya gabungkan mereka menjadi orbubble atau jatuhkan salah satunya. Saya tidak melihat ada gunanya memiliki dua kata kunci di sana. Ternyata Go menjadi bahasa lisan dan kita tahu bagaimana kelanjutannya (VB, misalnya)

Saya bukan penggemar adaptor global. Jika paket saya mengatur prosesor khusus, dan paket Anda juga mengatur prosesor khusus, siapa yang menang?

@objek88
Saya pikir ini mirip dengan logger default. Anda hanya mengatur output sekali (dalam program Anda), dan itu mempengaruhi semua paket yang Anda gunakan.

Penanganan kesalahan sangat berbeda dengan pencatatan; satu menggambarkan keluaran informatif program, yang lain mengatur aliran program. Jika saya mengatur adaptor untuk melakukan satu hal dalam paket saya, bahwa saya perlu mengelola aliran logis dengan benar, dan paket atau program lain mengubahnya, Anda berada di Tempat yang Buruk.

Tolong bawa kembali Try Catch Akhirnya dan kita tidak perlu lagi berkelahi. Itu membuat semua orang bahagia. Tidak ada salahnya meminjam fitur dan sintaks dari bahasa pemrograman lain. Java melakukannya dan C# juga melakukannya dan keduanya adalah bahasa pemrograman yang sangat sukses. Komunitas GO (atau penulis) harap terbuka untuk perubahan bila diperlukan.

@KamyarM , dengan hormat saya tidak setuju; coba/tangkap tidak _tidak_ membuat semua orang senang. Bahkan jika Anda ingin menerapkannya dalam kode Anda, pengecualian yang dilemparkan berarti bahwa setiap orang yang menggunakan kode Anda perlu menangani pengecualian. Itu bukan perubahan bahasa yang dapat dilokalkan ke kode Anda.

@objek88
Sebenarnya, menurut saya prosesor gelembung menggambarkan keluaran kesalahan informatif program, membuatnya tidak jauh berbeda dari pencatat. Dan saya membayangkan Anda menginginkan representasi kesalahan tunggal di seluruh aplikasi Anda, dan tidak berbeda dari satu paket ke paket lainnya.

Meskipun mungkin Anda dapat memberikan contoh singkat, mungkin ada sesuatu yang saya lewatkan.

Terima kasih banyak atas jempol Anda. Itulah tepatnya masalah yang saya bicarakan. Komunitas GO tidak terbuka untuk perubahan dan saya merasakannya dan saya sangat tidak menyukainya.

Ini mungkin tidak terkait dengan kasus ini, tetapi saya mencari hari lain untuk Go yang setara dengan operator ternary C++ dan saya menemukan pendekatan alternatif ini:

v := map[bool]int{true: first_expression, false: second_expression} [kondisi]
bukannya sederhana
v = kondisi ? ekspresi_pertama : ekspresi_kedua;

Manakah dari 2 bentuk yang kalian sukai? Kode yang tidak dapat dibaca di atas (Go My Way) dengan mungkin banyak masalah kinerja atau sintaks sederhana kedua di C++ (Highway)? Saya lebih suka orang jalan raya. Saya tidak tahu tentang Anda.

Jadi untuk meringkas tolong bawa sintaks baru, pinjam dari bahasa pemrograman lain. Tidak ada yang salah dengan itu.

Salam Hormat,

Komunitas GO tidak terbuka untuk perubahan dan saya merasakannya dan saya sangat tidak menyukainya.

Saya pikir ini salah mencirikan sikap yang mendasari apa yang Anda alami. Ya, komunitas menghasilkan banyak dorongan balik ketika seseorang mengusulkan try/catch atau ?:. Tapi alasannya bukan karena kami menolak ide-ide baru. Kami hampir semua memiliki pengalaman menggunakan bahasa dengan fitur-fitur ini. Kami cukup akrab dengan mereka, dan seseorang dari kami telah menggunakannya setiap hari selama bertahun-tahun. Penolakan kami didasarkan pada fakta bahwa ini adalah _ide lama_, bukan yang baru. Kami telah menerima perubahan: perubahan dari try/catch dan perubahan dari penggunaan ?:. Yang kami tahan adalah mengubah _kembali_ menggunakan hal-hal yang sudah kami gunakan dan tidak kami sukai.

Sebenarnya, menurut saya prosesor gelembung menggambarkan keluaran kesalahan informatif program, membuatnya tidak jauh berbeda dari pencatat. Dan saya membayangkan Anda menginginkan representasi kesalahan tunggal di seluruh aplikasi Anda, dan tidak berbeda dari satu paket ke paket lainnya.

Bagaimana jika seseorang ingin menggunakan gelembung untuk melewatkan jejak tumpukan dan kemudian menggunakannya untuk membuat keputusan. Misalnya, jika kesalahan berasal dari operasi file, maka gagal tetapi jika berasal dari jaringan, tunggu dan coba lagi. Saya bisa melihat membangun beberapa logika untuk ini menjadi penangan kesalahan, tetapi jika hanya ada satu penangan kesalahan per runtime, itu akan menjadi resep untuk konflik.

@urandom , mungkin ini adalah contoh sepele, tetapi katakanlah adaptor saya mengembalikan struct lain yang mengimplementasikan error , yang saya harapkan untuk dikonsumsi di tempat lain dalam kode saya. Jika adaptor lain datang dan menggantikan adaptor saya, maka kode saya berhenti berfungsi dengan benar.

@KamyarM Bahasa dan

Coba-tangkap-akhirnya akan menjadi perubahan yang sangat invasif: itu akan secara mendasar mengubah cara program Go terstruktur. Sebaliknya, sebagian besar proposal lain yang Anda lihat di sini bersifat lokal untuk setiap fungsi: kesalahan masih berupa nilai yang dikembalikan secara eksplisit, aliran kontrol menghindari lompatan non-lokal, dll.

Untuk menggunakan contoh operator ternary Anda: ya, Anda dapat memalsukannya hari ini menggunakan peta, tetapi saya harap Anda tidak akan benar-benar menemukannya dalam kode produksi. Itu tidak mengikuti idiom. Sebagai gantinya, Anda biasanya akan melihat sesuatu yang lebih seperti:

    var v int
    if condition {
        v = first_expression
    } else {
        v = second_expression
    }

Bukannya kami tidak ingin meminjam sintaks, tetapi kami harus mempertimbangkan bagaimana sintaks tersebut cocok dengan bahasa lainnya dan kode lainnya yang sudah ada saat ini.

@KamyarM Saya menggunakan Go dan Java, dan saya dengan tegas _tidak_ ingin Go menyalin penanganan pengecualian dari Java. Jika Anda ingin Java, gunakan Java. Dan silakan diskusikan operator ternary ke masalah yang sesuai, misalnya #23248.

@lpar Jadi Jika saya bekerja untuk sebuah perusahaan dan untuk beberapa alasan yang tidak diketahui mereka memilih GoLang sebagai bahasa pemrograman mereka, saya hanya perlu keluar dari pekerjaan saya dan melamar Java!? Ayolah!

@bcmils Anda dapat menghitung kode yang Anda sarankan di sana. Saya pikir itu 6 baris kode, bukan satu dan mungkin Anda mendapatkan beberapa poin kompleksitas siklomatik kode untuk itu (Kalian menggunakan Linter. kan?).

@carlmjohnson dan @bcmils Setiap sintaks yang lama dan matang tidak berarti itu buruk. Sebenarnya saya pikir sintaks if else jauh lebih tua dari sintaks operator ternary.

Baik bahwa Anda membawa hal idiom GO ini. Saya pikir itu hanya salah satu masalah bahasa ini. Setiap kali ada permintaan untuk perubahan, seseorang mengatakan oh tidak, itu bertentangan dengan idiom Go. Saya melihatnya hanya sebagai alasan untuk menolak perubahan dan memblokir ide-ide baru.

@KamyarM harap sopan. Jika Anda ingin membaca lebih lanjut tentang beberapa pemikiran di balik menjaga bahasa tetap kecil, saya sarankan https://commandcenter.blogspot.com/2012/06/less-is-exponentially-more.html.

Juga, komentar umum, tidak terkait dengan diskusi baru-baru ini tentang try/catch.

Ada banyak proposal di utas ini. Berbicara sendiri, saya masih tidak merasa bahwa saya memiliki pemahaman yang kuat tentang masalah yang harus dipecahkan. Saya akan senang mendengar lebih banyak tentang mereka.

Saya juga akan senang jika seseorang ingin mengambil tugas yang tidak menyenangkan tetapi penting untuk mempertahankan daftar masalah yang terorganisir dan diringkas yang telah dibahas.

@josharian saya hanya berbicara terus terang di sana. Saya ingin menunjukkan masalah yang tepat dalam bahasa atau komunitas. Anggap itu sebagai lebih banyak kritik. GoLang terbuka untuk kritik, kan?

@KamyarM Jika Anda bekerja untuk perusahaan yang telah memilih Rust untuk bahasa pemrogramannya, apakah Anda akan pergi ke Rust Github dan mulai menuntut manajemen memori yang dikumpulkan sampah dan pointer gaya C++ sehingga Anda tidak perlu berurusan dengan pemeriksa pinjaman?

Alasan pemrogram Go tidak menginginkan pengecualian gaya Java tidak ada hubungannya dengan kurangnya keakraban dengan mereka. Saya pertama kali menemukan pengecualian pada tahun 1988 melalui Lisp, dan saya yakin ada orang lain di utas ini yang menemukannya lebih awal -- idenya kembali ke awal 1970-an.

Hal yang sama bahkan lebih benar dari ekspresi ternary. Baca sejarah Go -- Ken Thompson, salah satu pencipta Go, mengimplementasikan operator ternary dalam bahasa B (pendahulu C) di Bell Labs pada tahun 1969. Saya rasa aman untuk mengatakan bahwa dia menyadari manfaat dan perangkapnya saat mempertimbangkan apakah akan memasukkannya ke dalam Go.

Go terbuka untuk kritik tetapi kami mengharuskan diskusi di forum Go harus sopan. Bersikap jujur ​​tidak sama dengan tidak sopan. Lihat bagian "Nilai Gopher" di https://golang.org/conduct. Terima kasih.

@lpar Ya, Jika Rust memiliki forum seperti itu, saya akan melakukannya ;-) Serius saya akan melakukannya. Karena saya ingin suara saya didengar.

@ianlancetaylor Apakah saya menggunakan kata-kata atau bahasa vulgar? Apakah saya menggunakan bahasa diskriminasi atau intimidasi terhadap seseorang atau rayuan seksual yang tidak diinginkan? Saya tidak berpikir begitu.
Ayo bung, Kami hanya berbicara tentang bahasa pemrograman Go di sini. Ini bukan tentang agama atau politik atau semacamnya.
saya jujur. Aku ingin suaraku didengar. Saya pikir itu sebabnya ada forum ini. Agar suara dapat didengar. Anda mungkin tidak menyukai saran atau kritik saya. Tidak apa-apa. Tapi saya kira Anda perlu membiarkan saya berbicara dan berdiskusi jika tidak, kita semua dapat menyimpulkan bahwa semuanya sempurna dan tidak ada masalah sehingga tidak perlu diskusi lebih lanjut.

@josharian Terima kasih atas artikelnya saya akan lihat itu.

Yah, saya melihat kembali komentar saya untuk melihat apakah ada sesuatu yang buruk di sana. Satu-satunya hal yang mungkin saya hina (saya masih menyebut kritik itu btw) adalah Idiom bahasa pemrograman GoLang! Hahaha!

Untuk kembali ke topik kami, Jika Anda mendengar suara saya, silakan Penulis mempertimbangkan untuk membawa kembali blok tangkapan Coba. Serahkan pada programmer untuk memutuskan menggunakannya di tempat yang tepat atau tidak (Anda sudah memiliki yang serupa, maksud saya panik menunda pulih lalu mengapa tidak Coba Catch yang lebih akrab bagi programmer?).
Saya menyarankan solusi untuk penanganan Go Error saat ini untuk kompatibilitas mundur. Saya tidak mengatakan itu pilihan terbaik tetapi saya pikir itu layak.

Saya akan mengundurkan diri dari membahas lebih lanjut tentang topik ini.

Terima kasih atas kesempatannya.

@KamyarM Anda membingungkan permintaan kami agar Anda tetap sopan dengan ketidaksetujuan kami dengan argumen Anda. Ketika orang tidak setuju dengan Anda, Anda menanggapi secara pribadi dengan komentar seperti "Terima kasih banyak atas jempol Anda. Itulah masalah yang saya bicarakan. Komunitas GO tidak terbuka untuk perubahan dan saya merasakannya dan saya benar-benar tidak' tidak seperti itu."

Sekali lagi: tolong bersikap sopan. Tetap berpegang pada argumen teknis. Hindari argumen ad hominem yang menyerang orang daripada ide. Jika Anda benar-benar tidak mengerti apa yang saya maksud, saya bersedia membahasnya secara offline; email aku. Terima kasih.

Saya akan memasukkan 2c saya, dan berharap itu tidak secara harfiah mengulangi sesuatu di N ratus komentar lainnya (atau menginjak diskusi proposal urandom).

Saya suka ide asli yang diposting, tetapi dengan dua penyesuaian utama:

  • Penumpahan sepeda sintaksis: Saya sangat percaya bahwa apa pun yang memiliki aliran kontrol implisit harus menjadi operatornya sendiri, daripada kelebihan operator yang ada. Saya akan membuang ?! luar sana, tetapi saya senang dengan apa pun yang tidak mudah dikacaukan dengan operator yang ada di Go.

  • RHS operator ini harus mengambil fungsi, bukan ekspresi dengan nilai yang disuntikkan secara sewenang-wenang. Ini akan memungkinkan para pengembang menulis kode penanganan kesalahan yang cukup singkat, sambil tetap menjelaskan maksud mereka, dan fleksibel dengan apa yang dapat mereka lakukan, mis.

func returnErrorf(s string, args ...interface{}) func(error) error {
  return func(err error) error {
    return errors.New(fmt.Sprintf(s, args...) + ": " + err.Error())
  }
}

func foo(r io.ReadCloser, callAfterClosing func() error, bs []byte) ([]byte, error) {
  // If r.Read fails, returns `nil, errors.New("reading from r: " + err.Error())`
  n := r.Read(bs) ?! returnErrorf("reading from r")
  bs = bs[:n]
  // If r.Close() fails, returns `nil, errors.New("closing r after reading [[bs's contents]]: " + err.Error())`
  r.Close() ?! returnErrorf("closing r after reading %q", string(bs))
  // Not that I advocate this inline-func approach, but...
  callAfterClosing() ?! func(err error) error { return errors.New("oh no!") }
  return bs, nil
}

RHS tidak boleh dievaluasi jika kesalahan tidak terjadi, jadi kode ini tidak akan mengalokasikan penutupan apa pun atau apa pun di jalur bahagia.

Ini juga cukup mudah untuk "membebani" pola ini agar berfungsi dalam kasus yang lebih menarik. Saya memiliki tiga contoh dalam pikiran.

Pertama, kita dapat membuat return bersyarat jika RHS adalah func(error) (error, bool) , seperti itu (jika kita mengizinkan ini, saya pikir kita harus menggunakan operator yang berbeda dari pengembalian tanpa syarat. Saya akan gunakan ?? , tetapi pernyataan "Saya tidak peduli selama itu berbeda" saya masih berlaku):

func maybeReturnError(err error) (error, bool) {
  if err == io.EOF {
    return nil, false
  }
  return err, true
}

func id(err error) error { return err }

func ignoreError(err error) (error, bool) { return nil, false }

func foo(n int) error {
  // Does nothing
  id(io.EOF) ?? ignoreError
  // Still does nothing
  id(io.EOF) ?? maybeReturnError
  // Returns the given error
  id(errors.New("oh no")) ?? maybeReturnError
  return nil
}

Atau, kami dapat menerima fungsi RHS yang memiliki tipe pengembalian yang cocok dengan fungsi luar, seperti:

func foo(r io.Reader) ([]int, error) {
  returnError := func(err error) ([]int, error) { return []int{0}, err }
  // returns `[]int{0}, err` on a Read failure
  n := r.Read(make([]byte, 4)) ?! returnError
  return []int{n}, nil
}

Dan terakhir, jika kita benar -

func returnOpFailed(name string) func(bool) error {
  return func(_ bool) error {
    return errors.New(name + " failed")
  }
}

func returnErrOpFailed(name string) func(error) error {
  return func(err error) error {
    return errors.New(name + " failed: " + err.Error())
  }
}

func foo(c chan int, readInt func() (int, error), d map[int]string) (string, error) {
  n := <-c ?! returnOpFailed("receiving from channel")
  m := readInt() ?! returnErrOpFailed("reading an int")
  result := d[n + m] ?! returnOpFailed("looking up the number")
  return result, nil
}

...Yang menurut saya pribadi sangat berguna ketika saya harus melakukan sesuatu yang buruk, seperti decoding tangan map[string]interface{} .

Untuk lebih jelasnya, saya terutama menunjukkan ekstensi sebagai contoh. Saya tidak yakin mana dari mereka (jika ada) yang memiliki keseimbangan yang baik antara kesederhanaan, kejelasan, dan kegunaan umum.

Saya ingin mengunjungi kembali bagian "kembalikan kesalahan/dengan konteks tambahan" lagi, karena saya menganggap mengabaikan kesalahan sudah dicakup oleh _.

Saya mengusulkan kata kunci dua kata yang dapat diikuti oleh string (opsional).

@urandom bagian pertama dari proposal Anda dapat diterima, orang selalu dapat memulai dengan itu dan meninggalkan BubbleProcessor untuk revisi kedua. Kekhawatiran yang diangkat oleh @object88 adalah IMO yang valid; Saya baru-baru ini melihat saran seperti "Anda tidak boleh menimpa klien/transportasi default http ", ini akan menjadi salah satunya.

Ada banyak proposal di utas ini. Berbicara sendiri, saya masih tidak merasa bahwa saya memiliki pemahaman yang kuat tentang masalah yang harus dipecahkan. Saya akan senang mendengar lebih banyak tentang mereka.

Saya juga akan senang jika seseorang ingin mengambil tugas yang tidak menyenangkan tetapi penting untuk mempertahankan daftar masalah yang terorganisir dan diringkas yang telah dibahas.

Bisa jadi Anda @josharian jika @ianlancetaylor menunjuk Anda? :blush: Saya tidak tahu bagaimana masalah lain sedang direncanakan/dibahas tapi mungkin diskusi ini hanya digunakan sebagai "kotak saran"?

@KamyarM

@bcmils Anda dapat menghitung kode yang Anda sarankan di sana. Saya pikir itu 6 baris kode, bukan satu dan mungkin Anda mendapatkan beberapa poin kompleksitas siklomatik kode untuk itu (Kalian menggunakan Linter. kan?).

Menyembunyikan kompleksitas siklomatik membuatnya lebih sulit untuk dilihat tetapi tidak menghapusnya (ingat strlen ?). Sama seperti membuat penanganan kesalahan "dipendekkan" membuat semantik penanganan kesalahan lebih mudah diabaikan--tetapi lebih sulit untuk dilihat.

Pernyataan atau ekspresi apa pun dalam sumber yang mengubah rute kontrol aliran harus jelas dan singkat, tetapi jika itu adalah keputusan antara jelas atau singkat, yang jelas harus lebih disukai dalam kasus ini.

Baik bahwa Anda membawa hal idiom GO ini. Saya pikir itu hanya salah satu masalah bahasa ini. Setiap kali ada permintaan untuk perubahan, seseorang mengatakan oh tidak, itu bertentangan dengan idiom Go. Saya melihatnya hanya sebagai alasan untuk menolak perubahan dan memblokir ide-ide baru.

Ada perbedaan antara baru dan bermanfaat. Apakah Anda percaya bahwa karena Anda memiliki sebuah ide, keberadaan ide itu perlu mendapat persetujuan? Sebagai latihan, silakan lihat pelacak masalah dan coba bayangkan Go hari ini jika setiap ide disetujui terlepas dari apa yang dipikirkan komunitas.

Mungkin Anda percaya bahwa ide Anda lebih baik dari yang lain. Di situlah diskusi masuk. Alih-alih merendahkan percakapan menjadi berbicara tentang bagaimana seluruh sistem rusak karena idiom, sampaikan kritik secara langsung, poin demi poin, atau temukan jalan tengah antara Anda dan rekan-rekan Anda.

@gdm85
Saya menambahkan prosesor untuk semacam penyesuaian pada bagian dari kesalahan yang dikembalikan. Dan meskipun saya percaya bahwa ini seperti menggunakan pencatat default, karena Anda dapat menggunakannya hampir sepanjang waktu, saya memang mengatakan bahwa saya terbuka untuk saran. Dan sebagai catatan, saya tidak percaya bahwa logger default dan klien http default bahkan berada dalam kategori yang sama dari jarak jauh.

Saya juga menyukai proposal @gburgessiv , meskipun saya bukan penggemar berat operator samar itu sendiri (mungkin setidaknya memilih ? seperti di Rust, meskipun saya masih berpikir itu samar). Apakah ini akan terlihat lebih mudah dibaca:

func foo(r io.ReadCloser, callAfterClosing func() error, bs []byte) ([]byte, error) {
  // If r.Read fails, returns `nil, errors.New("reading from r: " + err.Error())`
  n := r.Read(bs) or returnErrorf("reading from r")
  bs = bs[:n]
  // If r.Close() fails, returns `nil, errors.New("closing r after reading [[bs's contents]]: " + err.Error())`
  r.Close() or returnErrorf("closing r after reading %q", string(bs))
  // Not that I advocate this inline-func approach, but...
  callAfterClosing() or func(err error) error { return errors.New("oh no!") }
  return bs, nil
}

Dan semoga proposalnya juga akan menyertakan implementasi default dari fungsi yang mirip dengan returnErrorf-nya di suatu tempat di paket errors . Mungkin errors.Returnf() .

@KamyarM
Anda sudah menyatakan pendapat Anda di sini, dan tidak mendapatkan komentar atau reaksi apa pun yang bersimpati pada penyebab pengecualian. Saya tidak melihat apa yang akan dilakukan dengan mengulangi hal yang sama, tbh, selain mengganggu diskusi lainnya. Dan jika itu adalah tujuan Anda, itu tidak keren.

@josharian , saya akan mencoba merangkum pembahasannya secara singkat. Ini akan menjadi bias, karena saya memiliki proposal dalam campuran, dan tidak lengkap, karena saya tidak ingin membaca ulang seluruh utas.

Masalah yang kami coba atasi adalah kekacauan visual yang disebabkan oleh penanganan kesalahan Go. Berikut ini contoh yang bagus ( sumber ):

func (ds *GitDataSource) Fetch(from, to string) ([]string, error) {
    if err := createFolderIfNotExist(to); err != nil {
        return nil, err
    }
    if err := clearFolder(to); err != nil {
        return nil, err
    }
    if err := cloneRepo(to, from); err != nil {
        return nil, err
    }
    dirs, err := getContentFolders(to)
    if err != nil {
        return nil, err
    }
    return dirs, nil
}

Beberapa komentator di utas ini tidak berpikir ini perlu diperbaiki; mereka senang bahwa penanganan kesalahan mengganggu, karena menangani kesalahan sama pentingnya dengan menangani kasus non-kesalahan. Bagi mereka, tidak ada proposal di sini yang sepadan.

Proposal yang mencoba menyederhanakan kode seperti ini dibagi menjadi beberapa kelompok.

Beberapa mengusulkan bentuk penanganan pengecualian. Mengingat bahwa Go dapat memilih penanganan pengecualian di awal dan memilih untuk tidak melakukannya, ini tampaknya tidak mungkin diterima.

Banyak proposal di sini memilih tindakan default, seperti kembali dari fungsi (proposal asli) atau panik, dan menyarankan sedikit sintaks yang membuat tindakan itu mudah diungkapkan. Menurut pendapat saya, semua proposal itu gagal, karena mereka mengutamakan satu tindakan dengan mengorbankan yang lain. Saya secara teratur menggunakan pengembalian, t.Fatal dan log.Fatal untuk menangani kesalahan, terkadang semuanya di hari yang sama.

Proposal lain tidak memberikan cara untuk menambah atau membungkus kesalahan asli, atau membuatnya lebih sulit untuk dibungkus daripada tidak. Ini juga tidak memadai, karena membungkus adalah satu-satunya cara untuk menambahkan konteks ke kesalahan, dan jika kita membuatnya terlalu mudah untuk dilewati, itu akan dilakukan lebih jarang daripada sekarang.

Sebagian besar proposal yang tersisa menambahkan sedikit gula dan terkadang sedikit keajaiban untuk menyederhanakan berbagai hal tanpa membatasi tindakan yang mungkin dilakukan atau kemampuan untuk membungkus. Proposal saya dan @bcmills menambahkan jumlah gula minimal dan nol sihir untuk sedikit meningkatkan keterbacaan, dan juga untuk mencegah jenis bug yang tidak menyenangkan .

Beberapa proposal lain menambahkan semacam aliran kontrol non-lokal yang dibatasi, seperti bagian penanganan kesalahan di awal atau akhir fungsi.

Last but not least, @mpvl menyadari bahwa penanganan kesalahan bisa menjadi sangat rumit dengan adanya kepanikan. Dia menyarankan perubahan yang lebih radikal pada penanganan kesalahan Go untuk meningkatkan kebenaran serta keterbacaan. Ia memiliki argumentasi yang meyakinkan, namun pada akhirnya saya rasa kasusnya tidak memerlukan perubahan drastis dan dapat ditangani dengan mekanisme yang ada .

Permintaan maaf kepada siapa pun yang idenya tidak terwakili di sini.

Saya merasa seseorang akan bertanya kepada saya tentang perbedaan antara gula dan sihir. (Saya sendiri yang menanyakannya.)

Sugar adalah sedikit sintaks yang mempersingkat kode tanpa mengubah aturan bahasa secara mendasar. Operator penugasan singkat := adalah gula. Begitu juga operator ternary C ?: .

Sihir adalah gangguan yang lebih keras pada bahasa, seperti memasukkan variabel ke dalam ruang lingkup tanpa mendeklarasikannya, atau melakukan transfer kontrol non-lokal.

Garisnya pasti kabur.

Terima kasih telah melakukan itu, @jba. Sangat membantu. Hanya untuk menarik sorotan, masalah yang diidentifikasi sejauh ini adalah:

kekacauan visual yang disebabkan oleh penanganan kesalahan Go

dan

penanganan kesalahan bisa menjadi sangat rumit dengan adanya kepanikan

Jika ada masalah lain yang berbeda secara mendasar (bukan solusi) yang terlewatkan oleh

@josharian Apakah Anda ingin mempertimbangkan masalah pelingkupan (https://github.com/golang/go/issues/21161#issuecomment-319277657) sebagai varian dari masalah "kekacauan visual", atau masalah terpisah?

@bcmills tampaknya berbeda bagi saya, karena ini tentang masalah kebenaran yang halus, yang bertentangan dengan estetika/ergonomis (atau paling banyak masalah kebenaran yang melibatkan kode massal). Terima kasih! Ingin mengedit komentar saya dan menambahkan sinopsis satu baris?

Saya merasa seseorang akan bertanya kepada saya tentang perbedaan antara gula dan sihir. (Saya sendiri yang menanyakannya.)

Saya menggunakan definisi sihir ini: Melihat sedikit kode sumber, jika Anda dapat mengetahui apa yang seharusnya dilakukan oleh beberapa varian dari algoritma berikut:

  1. Cari semua pengidentifikasi, kata kunci, dan konstruksi tata bahasa yang ada di baris atau di fungsi.
  2. Untuk konstruksi tata bahasa dan kata kunci, lihat dokumentasi bahasa resmi.
  3. Untuk pengidentifikasi, harus ada mekanisme yang jelas untuk menemukannya menggunakan informasi dalam kode yang Anda lihat, menggunakan cakupan tempat kode saat ini berada, seperti yang ditentukan oleh bahasa, dari mana Anda bisa mendapatkan definisi pengidentifikasi, yang akan akurat pada saat run-time.

Jika algoritme ini _reliably_ menghasilkan pemahaman yang benar tentang apa yang akan dilakukan kode, itu tidak ajaib. Jika tidak, maka ada sejumlah keajaiban di dalamnya. Seberapa rekursif Anda harus menerapkannya saat Anda mencoba mengikuti referensi dokumentasi dan definisi pengenal hingga definisi pengenal lain memengaruhi _kompleksitas_, tetapi bukan _keajaiban_, dari konstruksi/kode yang dimaksud.

Contoh keajaiban meliputi: Pengidentifikasi tanpa jalur yang jelas kembali ke asalnya karena Anda mengimpornya tanpa namespace (titik impor di Go, terutama jika Anda memiliki lebih dari satu). Kemampuan apa pun yang mungkin dimiliki suatu bahasa untuk secara non-lokal menentukan apa yang akan diselesaikan oleh beberapa operator, seperti dalam bahasa dinamis di mana kode dapat sepenuhnya mendefinisikan ulang referensi fungsi secara non-lokal, atau mendefinisikan kembali apa yang dilakukan bahasa untuk pengidentifikasi yang tidak ada. Objek yang dibangun oleh skema yang dimuat dari database saat runtime, jadi pada waktu kode seseorang kurang lebih berharap mereka akan ada di sana.

Hal yang menyenangkan tentang ini adalah hampir semua subjektivitasnya hilang dari pertanyaan.

Kembali ke topik yang dibahas, sepertinya ada banyak sekali proposal yang sudah dibuat, dan kemungkinan orang lain menyelesaikan ini dengan proposal lain yang membuat semua orang berkata, "Ya! Itu dia!" mendekati nol.

Tampaknya bagi saya mungkin percakapan harus bergerak ke arah pengkategorian berbagai dimensi proposal yang dibuat di sini, dan memahami prioritas. Saya terutama ingin melihat ini dengan tujuan mengungkapkan persyaratan kontradiktif yang diterapkan secara samar oleh orang-orang di sini.

Misalnya, saya telah melihat beberapa keluhan tentang lompatan tambahan yang ditambahkan dalam aliran kontrol. Tetapi untuk saya sendiri, dalam bahasa proposal yang sangat asli, saya menilai tidak perlu menambahkan || &PathError{"chdir", dir, err} delapan kali dalam suatu fungsi jika itu umum. (Saya tahu Go tidak alergi terhadap kode berulang seperti beberapa bahasa lain, tapi tetap saja, kode berulang berisiko sangat tinggi untuk bug divergensi.) Tapi cukup banyak menurut definisi, jika ada mekanisme untuk memfaktorkan penanganan kesalahan seperti itu, kode tidak dapat mengalir dari atas ke bawah, kiri ke kanan, tanpa lompatan. Mana yang umumnya dianggap lebih penting? Saya menduga pemeriksaan yang cermat terhadap persyaratan yang secara implisit ditempatkan pada kode akan mengungkapkan persyaratan lain yang saling bertentangan.

Tetapi secara umum, saya merasa jika komunitas dapat menyetujui persyaratan setelah semua analisis ini, solusi yang benar mungkin akan keluar dari mereka dengan jelas, atau setidaknya, rangkaian solusi yang benar akan sangat dibatasi sehingga masalah menjadi dapat diatasi.

(Saya juga akan menunjukkan bahwa karena ini adalah proposal, perilaku saat ini secara umum harus tunduk pada analisis yang sama dengan proposal baru. Tujuannya adalah peningkatan yang signifikan, bukan kesempurnaan; menolak dua atau tiga peningkatan signifikan karena tidak satupun dari mereka sempurna adalah jalan menuju kelumpuhan. Bagaimanapun, semua proposal kompatibel ke belakang, jadi dalam kasus-kasus di mana pendekatan saat ini sudah yang terbaik (imho, kasus di mana setiap kesalahan ditangani secara sah secara berbeda, yang menurut pengalaman saya jarang terjadi tetapi terjadi) , pendekatan saat ini akan tetap tersedia.)

Saya telah memikirkan hal ini sejak kedua kalinya saya menulis if err !=nil dalam suatu fungsi, menurut saya solusi yang cukup sederhana adalah dengan mengizinkan pengembalian bersyarat yang terlihat seperti bagian pertama dari ternary dengan pemahaman adalah jika kondisi gagal kami tidak kembali.

Saya tidak yakin seberapa baik ini akan bekerja dalam hal penguraian/kompilasi tetapi tampaknya itu harus cukup mudah untuk ditafsirkan sebagai pernyataan if di mana '?' terlihat tanpa merusak kompatibilitas di tempat yang tidak terlihat, jadi saya pikir saya akan membuangnya sebagai opsi.

Selain itu akan ada kegunaan lain untuk ini di luar penanganan kesalahan.

jadi Anda mungkin melakukan sesuatu seperti ini:

func example1() error {
    err := doSomething()
    return err != nil ? err
    //more code
}

func example2() (*Mything, error) {
    err := doSomething()
    return err != nil ? nil, err
    //more code
}

Kami juga dapat melakukan hal-hal seperti ini ketika kami memiliki beberapa kode pembersihan, dengan asumsi bahwa handleErr mengembalikan kesalahan:

func example3() error {
    err := doSomething()
    return err !=nil ? handleErr(err)
    //more code
}

func example4() (*Mything, error) {
    err := doSomething()
    return err != nil ? nil, handleErr(err)
    //more code
}

Mungkin kemudian Anda juga dapat mengurangi ini menjadi satu liner jika Anda mau:

func example5() error {
    return err := doSomething(); err !=nil ? handleErr(err)
    //more code
}

func example6() (*Mything, error) {
    return err := doSomething(); err !=nil ? nil, handleErr(err)
    //more code
}

contoh pengambilan sebelumnya dari @jba berpotensi terlihat seperti ini:

func (ds *GitDataSource) Fetch(from, to string) ([]string, error) {

    return err := createFolderIfNotExist(to); err != nil ? nil, err
    return err := clearFolder(to); err != nil ? nil, err
    return err := cloneRepo(to, from); err != nil ? nil, err
    dirs, err := getContentFolders(to)

    return dirs, err
}

Akan tertarik dengan reaksi terhadap saran ini, mungkin bukan kemenangan besar dalam menghemat boilerplate tetapi tetap cukup eksplisit dan mudah-mudahan hanya membutuhkan perubahan kecil yang kompatibel ke belakang (mungkin beberapa asumsi yang sangat tidak akurat di bagian depan itu).

Anda mungkin dapat memisahkan ini dengan pengembalian yang terpisah? kata kunci yang dapat menambah kejelasan dan membuat hidup lebih sederhana dalam hal tidak perlu khawatir tentang kompatibilitas dengan pengembalian (memikirkan semua perkakas), ini kemudian dapat ditulis ulang secara internal seolah-olah/kembalikan pernyataan, memberi kami ini:

func (ds *GitDataSource) Fetch(from, to string) ([]string, error) {

    return? err := createFolderIfNotExist(to); err != nil ? nil, err
    return? err := clearFolder(to); err != nil ? nil, err
    return? err := cloneRepo(to, from); err != nil ? nil, err
    dirs, err := getContentFolders(to)

    return dirs, err
}

Sepertinya tidak ada banyak perbedaan antara

return err != nil ? err

dan

 if err != nil { return err }

Juga, terkadang Anda mungkin ingin melakukan sesuatu selain kembali, seperti call panic atau log.Fatal .

Saya sudah memikirkan ini sejak saya mengajukan proposal minggu lalu, dan saya sampai pada kesimpulan bahwa saya setuju dengan @thejerf : kami telah mendiskusikan proposal demi proposal tanpa benar-benar mundur selangkah dan memeriksa apa yang kami suka tentang masing-masing, ketidaksukaan tentang masing-masing, dan apa prioritas untuk solusi yang "tepat".

Persyaratan yang paling umum dinyatakan adalah bahwa pada akhirnya, Go harus mampu menangani 4 kasus penanganan kesalahan:

  1. Mengabaikan kesalahan
  2. Mengembalikan kesalahan tanpa dimodifikasi
  3. Mengembalikan kesalahan dengan konteks ditambahkan
  4. Panik (atau mematikan program)

Proposal tampaknya termasuk dalam salah satu dari tiga kategori:

  1. Kembali ke try-catch-finally gaya penanganan kesalahan.
  2. Tambahkan sintaks/bawaan baru untuk menangani semua 4 kasus yang tercantum di atas
  3. Menegaskan bahwa pergi menangani beberapa kasus dengan cukup baik, dan mengusulkan sintaks/bawaan untuk membantu kasus lain.

Kritik terhadap proposal yang diberikan tampaknya terbagi antara kekhawatiran atas keterbacaan kode, lompatan yang tidak jelas, penambahan variabel secara implisit ke cakupan, dan keringkasan. Secara pribadi, saya pikir ada banyak pendapat pribadi dalam kritik proposal. Saya tidak mengatakan bahwa itu adalah hal yang buruk, tetapi menurut saya tidak ada kriteria objektif untuk menilai proposal.

Saya mungkin bukan orang yang mencoba membuat daftar kriteria itu, tetapi saya pikir akan sangat membantu jika seseorang menggabungkannya. Saya telah mencoba menguraikan pemahaman saya tentang debat sejauh ini sebagai titik awal untuk memecah 1. apa yang telah kita lihat, 2. apa yang salah dengannya, 3. mengapa hal-hal itu salah, dan 4. apa yang akan kita lakukan suka melihat sebagai gantinya. Saya pikir saya menangkap jumlah yang layak dari 3 item pertama, tetapi saya mengalami kesulitan menemukan jawaban untuk item 4 tanpa menggunakan "apa yang dimiliki Go saat ini".

@jba memiliki komentar ringkasan bagus lainnya di atas, untuk lebih banyak konteks. Dia mengatakan banyak dari apa yang saya katakan di sini, dengan kata-kata yang berbeda.

@ianlancetaylor , atau siapa pun yang lebih dekat terlibat dengan proyek daripada saya, apakah Anda merasa nyaman menambahkan serangkaian kriteria "formal" (semua dalam satu komentar, terorganisir, dan agak komprehensif tetapi tidak mengikat) yang perlu dipenuhi? Mungkin jika kita membahas kriteria tersebut dan menetapkan 4-6 poin-poin yang perlu dipenuhi oleh proposal, kita dapat memulai kembali diskusi dengan lebih banyak konteks?

Saya tidak berpikir saya bisa menulis seperangkat kriteria formal yang komprehensif. Yang terbaik yang dapat saya lakukan adalah membuat daftar lengkap hal-hal penting yang hanya boleh diabaikan jika ada manfaat yang signifikan untuk melakukannya.

  • Dukungan yang baik untuk 1) mengabaikan kesalahan; 2) mengembalikan kesalahan yang tidak dimodifikasi; 3) membungkus kesalahan dengan konteks tambahan.
  • Meskipun kode penanganan kesalahan harus jelas, kode tersebut tidak boleh mendominasi fungsi. Seharusnya mudah untuk membaca kode penanganan non-kesalahan.
  • Kode Go 1 yang ada harus terus berfungsi, atau setidaknya harus memungkinkan untuk menerjemahkan Go 1 secara mekanis ke pendekatan baru dengan keandalan lengkap.
  • Pendekatan baru harus mendorong pemrogram untuk menangani kesalahan dengan benar. Idealnya harus mudah untuk melakukan hal yang benar, apa pun hal yang benar dalam situasi apa pun.
  • Setiap pendekatan baru harus lebih pendek dan/atau kurang berulang daripada pendekatan saat ini, sambil tetap jelas.
  • Bahasa ini berfungsi hari ini, dan setiap perubahan memerlukan biaya. Manfaat dari perubahan harus jelas sepadan dengan biayanya. Seharusnya tidak hanya mencuci, itu harus jelas lebih baik.

Saya berharap untuk mengumpulkan catatan di beberapa titik di sini sendiri, tetapi saya ingin membahas apa yang IMHO merupakan batu sandungan utama lainnya dalam diskusi ini, contoh-contoh yang sepele.

Saya telah mengekstrak ini dari proyek nyata saya dan menggosoknya untuk rilis eksternal. (Saya pikir stdlib bukan sumber terbaik, karena tidak ada masalah pencatatan, antara lain..)

func NewClient(...) (*Client, error) {
    listener, err := net.Listen("tcp4", listenAddr)
    if err != nil {
        return nil, err
    }
    defer func() {
        if err != nil {
            listener.Close()
        }
    }()

    conn, err := ConnectionManager{}.connect(server, tlsConfig)
    if err != nil {
        return nil, err
    }
    defer func() {
        if err != nil {
            conn.Close()
        }
    }()

    if forwardPort == 0 {
        env, err := environment.GetRuntimeEnvironment()
        if err != nil {
            log.Printf("not forwarding because: %v", err)
        } else {
            forwardPort, err = env.PortToForward()
            if err != nil {
                log.Printf("env couldn't provide forward port: %v", err)
            }
        }
    }
    var forwardOut *forwarding.ForwardOut
    if forwardPort != 0 {
        u, _ := url.Parse(fmt.Sprintf("http://127.0.0.1:%d", forwardPort))
        forwardOut = forwarding.NewOut(u)
    }

    client := &Client{...}

    toServer := communicationProtocol.Wrap(conn)
    err = toServer.Send(&client.serverConfig)
    if err != nil {
        return nil, err
    }

    err = toServer.Send(&stprotocol.ClientProtocolAck{ClientVersion: Version})
    if err != nil {
        return nil, err
    }

    session, err := communicationProtocol.FinalProtocol(conn)
    if err != nil {
        return nil, err
    }
    client.session = session

    return client, nil
}

(Jangan terlalu banyak mengacak-acak kodenya. Tentu saja saya tidak bisa menghentikan Anda, tapi ingat ini bukan kode saya yang sebenarnya, dan telah sedikit rusak. Dan saya tidak bisa menghentikan Anda untuk memposting kode sampel Anda sendiri.)

Pengamatan:

  1. Ini bukan kode yang sempurna; Saya sering mengembalikan kesalahan karena sangat mudah, persis seperti kode yang bermasalah. Proposal harus dinilai baik dengan keringkasan dan dengan seberapa mudah mereka menunjukkan memperbaiki kode ini.
  2. Klausa if forwardPort == 0 _dengan sengaja_ berlanjut melalui kesalahan, dan ya, ini adalah perilaku sebenarnya, bukan sesuatu yang saya tambahkan untuk contoh ini.
  3. Kode ini BAIK mengembalikan klien yang valid dan terhubung ATAU mengembalikan kesalahan dan tidak ada kebocoran sumber daya, jadi penanganan di sekitar .Close() (hanya jika fungsi error) disengaja. Perhatikan juga kesalahan dari Tutup menghilang, seperti yang cukup khas di Go yang sebenarnya.
  4. Nomor port dibatasi di tempat lain, jadi url.Parse tidak dapat gagal (dengan pemeriksaan).

Saya tidak akan mengklaim ini menunjukkan setiap kemungkinan perilaku kesalahan, tetapi itu mencakup cukup banyak. (Saya sering membela Go di HN dan semacamnya dengan menunjukkan bahwa pada saat kode saya selesai dimasak, sering terjadi di server jaringan saya bahwa saya memiliki _semua jenis_ perilaku kesalahan; memeriksa kode produksi saya sendiri, dari 1/3 untuk sepenuhnya 1/2 dari kesalahan melakukan sesuatu selain hanya dikembalikan.)

Saya juga akan (-) memposting ulang proposal saya sendiri (diperbarui) sebagaimana diterapkan pada kode ini (kecuali seseorang meyakinkan saya bahwa mereka memiliki sesuatu yang lebih baik sebelum itu), tetapi untuk kepentingan tidak memonopoli percakapan, saya akan menunggu setidaknya selama akhir pekan. (Ini lebih sedikit teks daripada yang terlihat karena ini adalah sebagian besar sumber, tapi tetap saja ....)

Menggunakan try mana try hanyalah jalan pintas untuk if != nil return mengurangi kode sebanyak 6 baris dari 59, yaitu sekitar 10%.

func NewClient(...) (*Client, error) {
    listener, err := net.Listen("tcp4", listenAddr)
    try err

    defer func() {
        if err != nil {
            listener.Close()
        }
    }()

    conn, err := ConnectionManager{}.connect(server, tlsConfig)
    try err

    defer func() {
        if err != nil {
            conn.Close()
        }
    }()

    if forwardPort == 0 {
        env, err := environment.GetRuntimeEnvironment()
        if err != nil {
            log.Printf("not forwarding because: %v", err)
        } else {
            forwardPort, err = env.PortToForward()
            if err != nil {
                log.Printf("env couldn't provide forward port: %v", err)
            }
        }
    }
    var forwardOut *forwarding.ForwardOut
    if forwardPort != 0 {
        u, _ := url.Parse(fmt.Sprintf("http://127.0.0.1:%d", forwardPort))
        forwardOut = forwarding.NewOut(u)
    }

    client := &Client{...}

    toServer := communicationProtocol.Wrap(conn)
    err = toServer.Send(&client.serverConfig)
    try err

    err = toServer.Send(&stprotocol.ClientProtocolAck{ClientVersion: Version})
    try err

    session, err := communicationProtocol.FinalProtocol(conn)
    try err

    client.session = session

    return client, nil
}

Khususnya, di beberapa tempat saya ingin menulis try x() tetapi saya tidak bisa karena saya perlu err diatur agar penangguhan berfungsi dengan baik.

Satu lagi. Jika kita memiliki try menjadi sesuatu yang dapat terjadi pada baris penugasan, itu turun menjadi 47 baris.

func NewClient(...) (*Client, error) {
    try listener, err := net.Listen("tcp4", listenAddr)

    defer func() {
        if err != nil {
            listener.Close()
        }
    }()

    try conn, err := ConnectionManager{}.connect(server, tlsConfig)

    defer func() {
        if err != nil {
            conn.Close()
        }
    }()

    if forwardPort == 0 {
        env, err := environment.GetRuntimeEnvironment()
        if err != nil {
            log.Printf("not forwarding because: %v", err)
        } else {
            forwardPort, err = env.PortToForward()
            if err != nil {
                log.Printf("env couldn't provide forward port: %v", err)
            }
        }
    }
    var forwardOut *forwarding.ForwardOut
    if forwardPort != 0 {
        u, _ := url.Parse(fmt.Sprintf("http://127.0.0.1:%d", forwardPort))
        forwardOut = forwarding.NewOut(u)
    }

    client := &Client{...}

    toServer := communicationProtocol.Wrap(conn)
    try err = toServer.Send(&client.serverConfig)

    try err = toServer.Send(&stprotocol.ClientProtocolAck{ClientVersion: Version})

    try session, err := communicationProtocol.FinalProtocol(conn)

    client.session = session

    return client, nil
}
import "github.com/pkg/errors"

func Func3() (T1, T2, error) {...}

type PathError {
    err Error
    x   T3
    y   T4
}

type MiscError {
    x   T5
    y   T6
    err Error
}


func Foo() (T1, T2, error) {
    // Old school
    a, b, err := Func(3)
    if err != nil {
        return nil
    }

    // Simplest form.
    // If last unhandled arg's type is same 
    // as last param of func,
    // then use anon variable,
    // check and return
    a, b := Func3()
    /*    
    a, b, err := Func3()
    if err != nil {
         return T1{}, T2{}, err
    }
    */

    // Simple wrapper
    // If wrappers 1st param TypeOf Error - then pass last and only unhandled arg from Func3() there
    a, b, errors.WithStack() := Func3() 
    /*
    a, b, err := Func3()
    if err != nil {
        return T1{}, T2{}, errors.WithStack(err)
    }
    */

    // Bit more complex wrapper
    a, b, errors.WithMessage("unable to get a and b") := Func3()
    /*
    a, b, err := Func3()
    if err != nil {
        return T1{}, T2{}, errors.WithMessage(err, "unable to get a and b")
    }
    */

    // More complex wrapper
    // If wrappers 1nd param TypeOf is not Error - then pass last and only unhandled arg from Func3() as last
    a, b, fmt.Errorf("at %v Func3() return error %v", time.Now()) := Func3()
    /*
    a, b, err := Func3()
    if err != nil {
        return T1{}, T2{}, fmt.Errorf("at %v Func3() return error %v", time.Now(), err)
    }
    */

    // Wrapping with error types
    a, b, &PathError{x,y} := Func3()
    /*
    a, b, err := Func3()
    if err != nil {
        return T1{}, T2{}, &PathError{err, x, y}
    }
    */
    a, b, &MiscError{x,y} := Func3()
    /*
    a, b, err := Func3()
    if err != nil {
        return T1{}, T2{}, &MiscError{x, y, err}
    }
    */

    return a, b, nil
}

Sedikit ajaib (jangan ragu untuk -1) tetapi mendukung terjemahan mekanis

Seperti inilah (agak diperbarui) proposal saya:

func NewClient(...) (*Client, error) {
    defer annotateError("client couldn't be created")

    listener := pop net.Listen("tcp4", listenAddr)
    defer closeOnErr(listener)
    conn := pop ConnectionManager{}.connect(server, tlsConfig)
    defer closeOnErr(conn)

    if forwardPort == 0 {
        env, err := environment.GetRuntimeEnvironment()
        if err != nil {
            log.Printf("not forwarding because: %v", err)
        } else {
            forwardPort, err = env.PortToForward()
            if err != nil {
                log.Printf("env couldn't provide forward port: %v", err)
            }
        }
    }
    var forwardOut *forwarding.ForwardOut
    if forwardPort != 0 {
         forwardOut = forwarding.NewOut(pop url.Parse(fmt.Sprintf("http://127.0.0.1:%d", forwardPort)))
    }

    client := &Client{listener: listener, conn: conn, forward: forwardOut}

    toServer := communicationProtocol.Wrap(conn)
    pop toServer.Send(&client.serverConfig)
    pop toServer.Send(&stprotocol.ClientProtocolAck{ClientVersion: Version})
    session := pop communicationProtocol.FinalProtocol(conn)
    client.session = session

    return client, nil
}

func closeOnErr(c io.Closer) {
    if err := erroring(); err != nil {
        closeErr := c.Close()
        if err != nil {
            seterr(multierror.Append(err, closeErr))
        }
    }
}

func annotateError(annotation string) {
    if err := erroring(); err != nil {
        log.Printf("%s: %v", annotation, err)
        seterr(errwrap.Wrapf(annotation +": {{err}}", err))
    }
}

Definisi:

pop legal untuk ekspresi fungsi di mana nilai paling kanan adalah kesalahan. Ini didefinisikan sebagai "jika kesalahannya tidak nol, keluar dari fungsi dengan nilai nol untuk semua nilai lain dalam hasil dan kesalahan itu, jika tidak, hasilkan satu set nilai tanpa kesalahan". pop tidak memiliki interaksi istimewa dengan erroring() ; pengembalian kesalahan yang normal masih akan terlihat oleh erroring() . Ini juga berarti Anda dapat mengembalikan nilai bukan nol untuk nilai pengembalian lainnya, dan masih menggunakan penanganan kesalahan yang ditangguhkan. Metafora mengeluarkan elemen paling kanan dari "daftar" nilai kembalian.

erroring() didefinisikan sebagai menaikkan tumpukan ke fungsi yang ditangguhkan yang sedang dijalankan, dan kemudian elemen tumpukan sebelumnya (fungsi tempat penangguhan dijalankan, NewClient dalam kasus ini), untuk mengakses nilai kesalahan yang dikembalikan saat ini sedang berlangsung. Jika fungsi tidak memiliki parameter seperti itu, panic atau return nil (mana yang lebih masuk akal). Nilai kesalahan ini tidak harus berasal dari pop ; itu adalah segala sesuatu yang mengembalikan kesalahan dari fungsi target.

seterr(error) memungkinkan Anda untuk mengubah nilai kesalahan yang dikembalikan. Ini kemudian akan menjadi kesalahan yang terlihat oleh panggilan erroring() masa mendatang, yang seperti yang ditunjukkan di sini memungkinkan rantai berbasis penangguhan yang sama yang dapat dilakukan sekarang.

Saya menggunakan pembungkus hashicorp dan multierror di sini; masukkan paket pintar Anda sendiri seperti yang diinginkan.

Bahkan dengan fungsi tambahan yang ditentukan, jumlahnya lebih pendek. Saya berharap untuk mengamortisasi dua fungsi di penggunaan tambahan, jadi itu seharusnya hanya dihitung sebagian.

Amati Saya hanya membiarkan penanganan forwardPort saja, daripada mencoba macet beberapa sintaks di sekitarnya. Sebagai kasus luar biasa, tidak apa-apa untuk lebih bertele-tele.

Hal yang paling menarik tentang IMHO proposal ini hanya dapat dilihat jika Anda membayangkan mencoba menulis ini dengan pengecualian konvensional. Itu akhirnya menjadi cukup bersarang, dan menangani _collection_ dari kesalahan yang dapat terjadi cukup membosankan dengan penanganan pengecualian. (Sama seperti dalam kode Go nyata, kesalahan .Tutup cenderung diabaikan, kesalahan yang terjadi pada penangan pengecualian itu sendiri cenderung diabaikan dalam kode berbasis pengecualian.)

Ini memperluas pola Go yang ada seperti defer dan penggunaan error-as-values ​​untuk mempermudah memperbaiki pola penanganan kesalahan yang dalam beberapa kasus sulit diungkapkan baik dengan Go saat ini atau pengecualian, tidak memerlukan operasi radikal ke runtime (saya tidak berpikir), dan juga, sebenarnya tidak _memerlukan_ Go 2.0.

Kerugiannya termasuk mengklaim erroring , pop , dan seterr sebagai kata kunci, menimbulkan defer overhead untuk fungsi ini, fakta bahwa penanganan kesalahan terfaktor memang melompati beberapa ke fungsi penanganan, dan itu tidak melakukan apa pun untuk "memaksa" penanganan yang benar. Meskipun saya tidak yakin yang terakhir mungkin, karena dengan persyaratan (benar) untuk kompatibel ke belakang, Anda selalu dapat melakukan hal saat ini.

Diskusi yang sangat menarik di sini.

Saya ingin menyimpan variabel kesalahan di sisi kiri sehingga tidak ada variabel yang muncul secara ajaib yang diperkenalkan. Seperti proposal asli, saya ingin menangani hal-hal kesalahan di baris yang sama. Saya tidak akan menggunakan || -operator karena terlihat "terlalu boolean" untuk saya dan entah bagaimana menyembunyikan "pengembalian".

Jadi saya akan membuatnya lebih mudah dibaca dengan menggunakan kata kunci tambahan "kembali?". Dalam C# tanda tanya digunakan di beberapa tempat untuk membuat jalan pintas. E.g. bukannya menulis:

if(foo != null)
{ foo.Bar(); }

Anda hanya dapat menulis:
foo?.Bar();

Jadi untuk Go 2 saya ingin mengusulkan solusi ini:

func foobar() error {
    return fmt.Errorf("Some error happened")
}

// Implicitly return err (there must be exactly one error variable on the left side)
err := foobar() return?
// Explicitly return err
err := foobar() return? err
// Return extended error info
err := foobar() return? &PathError{"chdir", dir, err}
// Return tuple
err := foobar() return? -1, err
// Return result of function (e. g. for logging the error)
err := foobar() return? handleError(err)
// This doesn't compile as you ignore the error intentionally
foobar() return?

Hanya pemikiran saja:

foo, err := myFunc()
salah != nihil ? bungkus kembali (err)

Atau

jika salah != nihil ? bungkus kembali (err)

Jika Anda ingin memasang kurung kurawal, kami tidak perlu mengubah apa pun!

if err != nil { return wrap(err) }

Anda dapat memiliki _semua_ penanganan khusus yang Anda inginkan (atau tidak sama sekali), Anda telah menyimpan dua baris kode dari kasus biasa, 100% kompatibel ke belakang (karena tidak ada perubahan pada bahasa), lebih ringkas, dan mudah. Ini menyentuh banyak poin mengemudi Ian . Satu-satunya perubahan mungkin adalah perkakas gofmt ?

Saya menulis ini sebelum membaca saran carlmjohnson, yang mirip ...

Hanya # sebelum kesalahan.

Tetapi dalam aplikasi dunia nyata Anda masih harus menulis if err != nil { ... } normal sehingga Anda dapat mencatat kesalahan, ini membuat penanganan kesalahan minimalis tidak berguna, Kecuali Anda dapat menambahkan middleware kembali melalui anotasi yang disebut after , yang berjalan setelah fungsi kembali... (seperti defer tetapi dengan args).

@after(func (data string, err error) {
  if err != nil {
    log.Error("error", data, " - ", err)
  }
})
func alo() (string, error) {
  // this is the equivalent of
  // data, err := db.Find()
  // if err != nil { 
  //   return "", err 
  // }
  str, #err := db.Find()

  // ...

  #err = db.Create()

  // ...

  return str, nil
}

func alo2() ([]byte, []byte, error, error) {
  // this is the equivalent of
  // data, data2, err, errNotFound := db.Find()
  // if err != nil { 
  //   return nil, nil, err, nil
  // } else if errNotFound != nil {
  //   return nil, nil, nil, errNotFound
  // }
  data, data2, #err, #errNotFound := db.Find()

  // ...

  return data, data2, nil, nil
}

lebih bersih dari:

func alo() (string, error) {
  str, err := db.Find()
  if err != nil {
    log.Error("error on find in database", err)
    return "", err
  }

  // ...

  if err := db.Create(); err != nil {
    log.Error("error on create", err)
    return "", err
  }

  // ...

  return str, nil
}

func alo2() ([]byte, []byte, error, error) {
  data, data2, err, errNotFound := db.Find()
  if err != nil { 
    return nil, nil, err, nil
  } else if errNotFound != nil {
    return nil, nil, nil, errNotFound
  }

  // ...

  return data, data2, nil, nil
}

Bagaimana dengan pernyataan guard Swift seperti, kecuali alih-alih guard...else itu guard...return :

file, err := os.Open("fails.txt")
guard err return &FooError{"Couldn't foo fails.txt", err}

guard os.Remove("fails.txt") return &FooError{"Couldn't remove fails.txt", err}

Saya suka kejelasan penanganan kesalahan go. Satu-satunya masalah, imho, adalah berapa banyak ruang yang dibutuhkan. Saya akan menyarankan 2 tweak:

  1. izinkan item yang tidak dapat digunakan dalam konteks boolean, di mana nil setara dengan false , non- nil hingga true
  2. mendukung operator pernyataan bersyarat satu baris, seperti && dan ||

Jadi

file, err := os.Open("fails.txt")
if err != nil {
    return &FooError{"Couldn't foo fails.txt", err}
}

bisa menjadi

file, err := os.Open("fails.txt")
if err {
    return &FooError{"Couldn't foo fails.txt", err}
}

atau bahkan lebih pendek

file, err := os.Open("fails.txt")
err && return &FooError{"Couldn't foo fails.txt", err}

dan, kita bisa melakukannya

i,ok := v.(int)
ok || return fmt.Errorf("not a number")

atau mungkin

i,ok := v.(int)
ok && s *= i

Jika kelebihan && dan || menimbulkan terlalu banyak ambiguitas, mungkin beberapa karakter lain (tidak lebih dari 2) dapat dipilih, misalnya ? dan # atau ?? dan ## , atau ?? dan !! , terserah. Intinya adalah untuk mendukung pernyataan bersyarat satu baris dengan karakter "berisik" minimal (tidak diperlukan tanda kurung, kurung kurawal, dll.). Operator && dan || baik karena penggunaan ini sudah ada dalam bahasa lain.

Ini bukan proposal untuk mendukung ekspresi kondisional satu baris yang kompleks, hanya pernyataan kondisional satu baris.

Juga, ini bukan proposal untuk mendukung berbagai "kebenaran" ala beberapa bahasa lain. Persyaratan ini hanya akan mendukung nil/non-nil, atau boolean.

Untuk operator bersyarat ini, mungkin cocok untuk membatasi variabel tunggal dan tidak mendukung ekspresi. Apa pun yang lebih kompleks, atau dengan klausa else akan ditangani dengan konstruksi standar if ... .

Mengapa tidak menemukan roda dan menggunakan formulir try..catch dikenal seperti yang dikatakan

try {
    a := foo() // func foo(string, error)
    b := bar() // func bar(string, error)
} catch (err) {
    // handle error
}

Sepertinya tidak ada alasan untuk membedakan sumber kesalahan yang ditangkap, karena jika Anda benar-benar membutuhkan ini, Anda selalu dapat menggunakan bentuk lama if err != nil tanpa try..catch .

Juga, saya tidak begitu yakin tentang itu, tetapi mungkinkah menambahkan kemampuan untuk "melempar" kesalahan jika tidak ditangani?

func foo() (string, error) {
    f := bar() // similar to if err != nil { return "", err }
}

func baz() string {
    // Compilation error.
    // bar's error must be handled because baz() does not return error.
    return bar()
}

@gobwas dari sudut pandang keterbacaan, sangat penting untuk sepenuhnya memahami aliran kontrol. Melihat contoh Anda, tidak ada yang tahu, baris mana yang mungkin menyebabkan lompatan ke blok tangkap. Ini seperti pernyataan goto tersembunyi. Tidak heran bahasa modern mencoba secara eksplisit tentangnya dan mengharuskan programmer untuk secara eksplisit menandai tempat-tempat di mana aliran kontrol mungkin berbeda karena kesalahan yang dilemparkan. Hampir seperti return atau goto tetapi dengan sintaks yang jauh lebih baik.

@creker ya, saya sangat setuju dengan Anda. Saya sedang berpikir tentang kontrol aliran pada contoh di atas, tetapi tidak menyadari bagaimana melakukan ini dalam bentuk yang sederhana.

Mungkin sesuatu seperti:

try {
    a ::= foo() // func foo(string, error)
    b ::= bar() // func bar(string, error)
} catch (err) {
    // handle error
}

Atau saran lain sebelumnya seperti try a := foo() ..?

@gobwas

Ketika saya mendarat di blok tangkap, bagaimana saya tahu fungsi mana di blok coba yang menyebabkan kesalahan?

@urandom jika Anda perlu mengetahuinya, Anda mungkin ingin melakukan if err != nil tanpa try..catch .

@robert-wallis: Saya menyebutkan pernyataan penjaga Swift sebelumnya di utas tetapi halamannya sangat besar, Github tidak lagi memuatnya secara default. :-PI masih berpikir itu ide yang bagus, dan secara umum, saya mendukung melihat bahasa lain untuk contoh positif/negatif.

@pdk

memungkinkan item nil-able untuk digunakan dalam konteks boolean, di mana nil ekuivalen dengan false, non-nil to true

Saya melihat ini menyebabkan banyak bug menggunakan paket flag di mana orang akan menulis if myflag { ... } tetapi bermaksud menulis if *myflag { ... } dan itu tidak akan ditangkap oleh kompiler.

try/catch hanya lebih pendek daripada if/else ketika Anda mencoba banyak hal secara berurutan, yang kurang lebih disetujui semua orang adalah buruk karena masalah aliran kontrol, dll.

FWIW, coba/tangkap Swift setidaknya menyelesaikan masalah visual karena tidak mengetahui pernyataan mana yang mungkin muncul:

do {
    let dragon = try summonDefaultDragon() 
    try dragon.breathFire()
} catch DragonError.dragonIsMissing {
    // ...
} catch DragonError.halatosis {
    // ...
}

@robert-wallis , Anda punya contoh:

file, err := os.Open("fails.txt")
guard err return &FooError{"Couldn't foo fails.txt", err}

guard os.Remove("fails.txt") return &FooError{"Couldn't remove fails.txt", err}

Pada penggunaan pertama guard , kelihatannya sangat mirip if err != nil { return &FooError{"Couldn't foo fails.txt", err}} , jadi saya tidak yakin apakah itu kemenangan besar.

Pada penggunaan kedua, tidak jelas dari mana err berasal. Sepertinya itu yang dikembalikan dari os.Open , yang menurut saya bukan maksud Anda? Apakah ini akan lebih akurat?

guard err = os.Remove("fails.txt") return &FooError{"Couldn't remove fails.txt", err}

Dalam hal ini, sepertinya ...

if err = os.Remove("fails.txt"); err != nil { return &FooError{"Couldn't remove fails.txt", err}}

Tetapi masih memiliki sedikit kekacauan visual. if err = , ; err != nil { - meskipun hanya satu kalimat, masih ada banyak hal yang terjadi untuk hal yang begitu sederhana

Setuju bahwa ada lebih sedikit kekacauan. Tapi lumayan kurang untuk menjamin penambahan bahasa? Saya tidak yakin saya setuju di sana.

Saya pikir ada keterbacaan blok try-catch di Java/C#/... sangat baik karena Anda dapat mengikuti urutan "jalur bahagia" tanpa gangguan oleh penanganan kesalahan. Kelemahannya adalah Anda pada dasarnya memiliki mekanisme goto tersembunyi.

Di Go saya mulai memasukkan baris kosong setelah penangan kesalahan untuk membuat kelanjutan dari logika "jalur bahagia" lebih terlihat. Jadi dari contoh ini dari golang.org (9 baris)

record := new(Record)
err := datastore.Get(c, key, record) 
if err != nil {
    return &appError{err, "Record not found", 404}
}
err := viewTemplate.Execute(w, record)
if err != nil {
    return &appError{err, "Can't display record", 500}
}

saya sering melakukan itu (11 baris)

record := new(Record)

err := datastore.Get(c, key, record) 
if err != nil {
    return &appError{err, "Record not found", 404}
}

err := viewTemplate.Execute(w, record)
if err != nil {
    return &appError{err, "Can't display record", 500}
}

Sekarang kembali ke proposal, karena saya sudah memposting sesuatu seperti ini akan menyenangkan (3 baris)

record := new(Record)
err := datastore.Get(c, key, record) return? &appError{err, "Record not found", 404}
err := viewTemplate.Execute(w, record) return? &appError{err, "Can't display record", 500}

Sekarang saya melihat jalan bahagia dengan jelas. Mata saya masih sadar bahwa di sisi kanan ada kode untuk penanganan kesalahan tetapi saya hanya perlu "mengurai mata" ketika benar-benar diperlukan.

Sebuah pertanyaan untuk semua pihak: haruskah kode ini dikompilasi?

func foobar() error {
    return fmt.Errorf("Some error")
}
func main() {
    foobar()
}

IMHO pengguna harus dipaksa untuk mengatakan bahwa dia sengaja mengabaikan kesalahan dengan:

func main() {
    _ := foobar()
}

Menambahkan laporan pengalaman mini terkait dengan poin 3 dari posting asli @ianlancetaylor , kembalikan kesalahan dengan informasi kontekstual tambahan .

Saat mengembangkan perpustakaan flac untuk Go, kami ingin menambahkan informasi kontekstual ke kesalahan menggunakan paket @davecheney pkg/errors (https://github.com/mewkiz/flac/issues/22). Lebih khusus lagi, kami membungkus kesalahan yang dikembalikan menggunakan error.WithStack yang menjelaskan kesalahan dengan informasi pelacakan tumpukan.

Karena kesalahan dijelaskan, perlu membuat tipe dasar baru untuk menyimpan informasi tambahan ini, jika terjadi error.WithStack, tipenya adalah error.withStack .

type withStack struct {
    error
    *stack
}

Sekarang, untuk mengambil kesalahan asli, konvensinya adalah menggunakan error. Cause . Ini memungkinkan Anda membandingkan kesalahan asli dengan misalnya io.EOF .

Pengguna perpustakaan kemudian dapat menulis sesuatu di sepanjang baris https://github.com/mewkiz/flac/blob/0884ed715ef801ce2ce0c262d1e674fdda6c3d94/cmd/flac2wav/flac2wav.go#L78 menggunakan errors.Cause untuk memeriksa kesalahan asli nilai:

frame, err := stream.ParseNext()
if err != nil {
    if errors.Cause(err) == io.EOF {
        break
    }
    return errors.WithStack(err)
}

Ini bekerja dengan baik di hampir semua kasus.

Saat memfaktorkan ulang penanganan kesalahan kami untuk menggunakan pkg/kesalahan secara konsisten untuk informasi konteks tambahan, kami mengalami masalah yang agak serius. Untuk memvalidasi zero-padding, kami telah menerapkan io.Reader yang hanya memeriksa apakah byte yang dibaca adalah nol, dan melaporkan kesalahan sebaliknya. Masalahnya adalah setelah melakukan pemfaktoran ulang otomatis untuk menambahkan informasi kontekstual ke kesalahan kami, tiba-tiba kasus pengujian kami mulai gagal .

Masalahnya adalah jenis kesalahan mendasar yang dikembalikan oleh zeros.Read sekarang adalah error.withStack, bukan io.EOF. Dengan demikian selanjutnya menyebabkan masalah ketika kami menggunakan pembaca itu dalam kombinasi dengan io.Copy , yang memeriksa io.EOF secara khusus, dan tidak tahu untuk menggunakan errors.Cause untuk "membuka" kesalahan yang dijelaskan dengan informasi kontekstual. Karena kami tidak dapat memperbarui pustaka standar, solusinya adalah mengembalikan kesalahan tanpa informasi beranotasi (https://github.com/mewkiz/flac/commit/6805a34d854d57b12f72fd74304ac296fd0c07be).

Meskipun kehilangan informasi beranotasi untuk antarmuka yang mengembalikan nilai konkret adalah kerugian, itu mungkin untuk dijalani.

Yang diambil dari pengalaman kami adalah bahwa kami beruntung, karena kasus uji kami menangkap ini. Kompilator tidak menghasilkan kesalahan apa pun, karena tipe zeros masih mengimplementasikan antarmuka io.Reader . Kami juga tidak berpikir bahwa kami akan mengalami masalah, karena anotasi kesalahan yang ditambahkan adalah penulisan ulang yang dihasilkan mesin, cukup tambahkan informasi kontekstual ke kesalahan tidak akan memengaruhi perilaku program dalam keadaan normal.

Tapi memang, dan untuk alasan ini, kami ingin menyumbangkan laporan pengalaman kami untuk dipertimbangkan; ketika memikirkan bagaimana mengintegrasikan penambahan informasi kontekstual ke dalam penanganan kesalahan untuk Go 2, sehingga perbandingan kesalahan (seperti yang digunakan dalam kontrak antarmuka) masih berjalan mulus.

Baik,
Robin

@mewmew , mari kita simpan masalah ini tentang aspek aliran kontrol dari penanganan kesalahan. Cara terbaik untuk membungkus dan membuka kesalahan harus didiskusikan di tempat lain, karena sebagian besar ortogonal untuk mengontrol aliran.

Saya tidak akrab dengan basis kode Anda, dan saya menyadari Anda mengatakan bahwa itu adalah refactoring otomatis, tetapi mengapa Anda perlu memasukkan informasi kontekstual dengan EOF? Meskipun diperlakukan sebagai kesalahan oleh sistem tipe, EOF sebenarnya lebih merupakan nilai sinyal, bukan kesalahan yang sebenarnya. Dalam implementasi io.Reader khususnya, ini adalah nilai yang diharapkan sebagian besar waktu. Bukankah perbaikan yang lebih baik adalah dengan hanya membungkus kesalahan jika bukan io.EOF ?

Ya, saya mengusulkan agar kita membiarkan hal-hal seperti apa adanya. Saya mendapat kesan bahwa sistem kesalahan Go sengaja dirancang dengan cara ini untuk mencegah pengembang memasukkan kesalahan ke tumpukan panggilan. Kesalahan itu dimaksudkan untuk diselesaikan di mana mereka terjadi, dan untuk mengetahui kapan lebih tepat menggunakan panik ketika Anda tidak bisa.

Maksud saya, bukankah try-catch-throw pada dasarnya sama dengan perilaku panic() dan restore()?

Sigh, jika kita benar-benar akan mulai mencoba untuk menempuh jalan ini. Mengapa kita tidak bisa melakukan sesuatu seperti

_, ? := foo()
x?, err? := bar()

atau mungkin bahkan sesuatu seperti

_, err := foo(); return err?
x, err := bar(); return x? || err?
x, y, err := baz(); return (x? && y?) || err?

Dimana ? menjadi alias singkatan untuk if var != nil{ return var }.

Kami bahkan dapat mendefinisikan antarmuka bawaan khusus lainnya yang dipenuhi oleh metode ini

func ?() bool //looks funky but avoids breakage.

yang pada dasarnya dapat kita gunakan untuk mengesampingkan perilaku default dari operator bersyarat yang baru dan yang ditingkatkan.

@mortdeus

Saya pikir saya setuju.
Jika masalahnya adalah memiliki cara yang bagus untuk menyajikan jalur bahagia, plugin untuk IDE dapat melipat/membuka setiap instance if err != nil { return [...] } dengan pintasan?

Saya merasa setiap bagian sekarang penting. err != nil penting. return ... penting.
Agak repot untuk menulis, tetapi harus ditulis. Dan apakah itu benar-benar memperlambat orang? Yang membutuhkan waktu adalah memikirkan kesalahan dan apa yang harus dikembalikan, bukan menulisnya.

Saya akan jauh lebih tertarik pada proposal yang memungkinkan untuk membatasi ruang lingkup variabel err .

Saya pikir ide kondisional saya adalah cara paling rapi untuk menyelesaikan masalah ini. Saya baru saja memikirkan beberapa hal lain yang akan membuat fitur ini cukup layak untuk disertakan dalam Go. Saya akan menulis ide saya dalam proposal terpisah.

Saya tidak melihat bagaimana ini bisa bekerja:

x, y, err := baz(); kembali ( x? && y? ) || err?

Dimana ? menjadi alias singkatan untuk if var == nil{ return var }.

x, y, err := baz(); kembali ( if x == nil{ return x} && if y== nil{ return y} ) || if err == nil{ return err}

x, y, err := baz(); kembali (x? && y?) || berbuat salah?

menjadi

x, y, err := baz();
jika ((x != nil && y !=nil) || err !=nil)){
kembali x,y, err
}

ketika Anda melihat x? && kamu? || berbuat salah? Anda harus berpikir "apakah x dan y valid? Bagaimana dengan err?"

jika tidak maka fungsi pengembalian tidak dijalankan. Saya baru saja menulis proposal baru tentang ide ini yang membawa ide sedikit lebih jauh dengan tipe antarmuka bawaan khusus yang baru

Saya sarankan Go tambahkan penanganan err default di Go versi 2.

Jika pengguna tidak menangani kesalahan, kompiler mengembalikan kesalahan jika tidak nihil, jadi jika pengguna menulis:

func Func() error {
    func1()
    func2()
    return nil
}

func func1() error {
    ...
}

func func2() error {
    ...
}

kompilasi mengubahnya menjadi:

func Func() error {
    err := func1()
    if err != nil {
        return err
    }

    err = func2()
    if err != nil {
        return err
    }

    return nil
}

func func1() error {
    ...
}

func func2() error {
    ...
}

Jika pengguna menangani kesalahan atau mengabaikannya menggunakan _, kompiler tidak akan melakukan apa pun:

_ = func1()

atau

err := func1()

untuk beberapa nilai pengembalian, ini serupa:

func Func() (*YYY, error) {
    ch, x := func1()
    return yyy, nil
}

func func1() (chan int, *XXX, error) {
    ...
}

compiler akan berubah menjadi:

func Func() (*YYY, error) {
    ch, x, err := func1()
    if err != nil {
        return nil, err
    }

    return yyy, nil
}

func func1() (chan int, *XXX, error) {
    ...
}

Jika tanda tangan Func() tidak mengembalikan kesalahan tetapi memanggil fungsi yang mengembalikan kesalahan, kompiler akan melaporkan Kesalahan: "Harap tangani kesalahan Anda di Func()"
Kemudian pengguna bisa mencatat kesalahan di Func()

Dan jika pengguna ingin membungkus beberapa info ke err:

func Func() (*YYY, error) {
    ch, x := func1() ? Wrap(err, "xxxxx", ch, "abc", ...)
    return yyy, nil
}

atau

func Func() (*YYY, error) {
    ch, x := func1() ? errors.New("another error")
    return yyy, nil
}

Manfaatnya adalah,

  1. program hanya gagal pada titik di mana kesalahan terjadi, pengguna tidak dapat mengabaikan kesalahan secara implisit.
  2. dapat mengurangi baris kode terutama.

itu tidak mudah karena Go dapat memiliki beberapa nilai pengembalian dan bahasa tidak boleh menetapkan apa yang pada dasarnya sama dengan nilai default untuk argumen pengembalian tanpa pengembang secara eksplisit mengetahui apa yang sedang terjadi.

Saya percaya bahwa menempatkan penanganan kesalahan dalam sintaks penugasan tidak menyelesaikan akar masalah, yaitu "penanganan kesalahan berulang".

Menggunakan if (err != nil) { return nil } (atau serupa) setelah banyak baris kode (di mana masuk akal untuk melakukannya) bertentangan dengan prinsip KERING (jangan ulangi sendiri). Saya percaya itu sebabnya kami tidak menyukai ini.

Ada juga masalah dengan try ... catch . Anda tidak perlu secara eksplisit menangani kesalahan dalam fungsi yang sama di mana itu terjadi. Saya percaya itu adalah alasan penting mengapa kami tidak menyukai try...catch .

Saya tidak percaya ini saling eksklusif; kita dapat memiliki semacam try...catch tanpa throws .

Hal lain yang saya pribadi tidak suka tentang try...catch adalah kebutuhan sewenang-wenang dari kata kunci try . Tidak ada alasan Anda tidak dapat catch setelah batasan ruang lingkup apa pun, sejauh menyangkut tata bahasa yang berfungsi. (seseorang menyebutnya jika saya salah tentang ini)

Inilah yang saya usulkan:

  • menggunakan ? sebagai pengganti untuk kesalahan yang dikembalikan, di mana _ akan digunakan untuk mengabaikannya
  • alih-alih catch seperti pada contoh saya di bawah ini, error? dapat digunakan sebagai gantinya untuk kompatibilitas mundur penuh

^ Jika asumsi saya bahwa ini kompatibel ke belakang tidak benar, harap panggil.

func example() {
    {
        // The following line will invoke the catch block
        data, ? := foopkg.buyIt()
        // The next two lines handle an error differently
        otherData, err := foopkg.useIt()
        if err != nil {
            // Here we eliminated deeper indentation
            otherData, ? = foopkg.breakIt()
        }
        if data == "" || otherData == "" {
        }
    } catch (err) {
        return errors.Label("fix it", err)
        // Aside: why not add Label() to the error package?
    }
}

Saya memikirkan argumen yang menentang ini: jika Anda menulisnya dengan cara ini, mengubah blok catch mungkin memiliki pengaruh yang tidak diinginkan pada kode dalam cakupan yang lebih dalam. Ini adalah masalah yang sama yang kita miliki dengan try...catch .

Saya pikir jika Anda hanya dapat melakukan ini dalam lingkup satu fungsi, risikonya dapat dikelola - mungkin sama dengan risiko saat ini karena lupa mengubah baris kode penanganan kesalahan ketika Anda bermaksud mengubah banyak dari mereka. Saya melihat ini sebagai perbedaan yang sama antara konsekuensi penggunaan kembali kode, dan konsekuensi tidak mengikuti KERING (yaitu tidak ada makan siang gratis, seperti yang mereka katakan)

Sunting: Saya lupa menentukan perilaku penting untuk contoh saya. Dalam hal ? digunakan dalam lingkup tanpa catch , saya pikir ini harus menjadi kesalahan kompiler (daripada membuat panik, yang memang merupakan hal pertama yang saya pikirkan)

Sunting 2: Ide gila: mungkin blok catch tidak akan memengaruhi aliran kontrol ... itu benar-benar seperti menyalin dan menempelkan kode di dalam catch { ... } ke baris setelah kesalahan ? ed (tidak cukup - itu masih memiliki ruang lingkupnya sendiri). Tampaknya aneh karena tidak ada dari kita yang terbiasa, jadi catch seharusnya tidak menjadi kata kunci jika dilakukan dengan cara ini, tetapi sebaliknya... mengapa tidak?

@mewmew , mari kita simpan masalah ini tentang aspek aliran kontrol dari penanganan kesalahan. Cara terbaik untuk membungkus dan membuka kesalahan harus didiskusikan di tempat lain, karena sebagian besar ortogonal untuk mengontrol aliran.

Ok, mari kita simpan utas ini untuk mengontrol aliran. Saya menambahkannya hanya karena itu adalah masalah yang terkait dengan penggunaan konkret poin 3 mengembalikan kesalahan dengan informasi kontekstual tambahan .

@jba Apakah Anda mengetahui masalah apa pun yang secara khusus didedikasikan untuk membungkus/membuka bungkus informasi kontekstual untuk kesalahan?

Saya tidak akrab dengan basis kode Anda, dan saya menyadari Anda mengatakan bahwa itu adalah refactoring otomatis, tetapi mengapa Anda perlu memasukkan informasi kontekstual dengan EOF? Meskipun diperlakukan sebagai kesalahan oleh sistem tipe, EOF sebenarnya lebih merupakan nilai sinyal, bukan kesalahan yang sebenarnya. Dalam implementasi io.Reader khususnya, ini adalah nilai yang diharapkan sebagian besar waktu. Bukankah perbaikan yang lebih baik adalah dengan hanya membungkus kesalahan jika bukan io.EOF?

@DeedleFake Saya dapat menguraikan sedikit, tetapi untuk tetap pada topik akan melakukannya dalam masalah yang disebutkan di atas yang didedikasikan untuk membungkus/membuka bungkus informasi kontekstual untuk kesalahan.

Semakin saya membaca semua proposal (termasuk milik saya) semakin sedikit saya pikir kami benar-benar memiliki masalah dengan penanganan kesalahan saat berjalan.

Yang saya inginkan adalah beberapa penegakan untuk tidak secara tidak sengaja mengabaikan nilai pengembalian kesalahan tetapi menegakkan setidaknya
_ := returnsError()

Saya tahu ada alat untuk menemukan masalah ini, tetapi dukungan tingkat pertama dari bahasa tersebut dapat menangkap beberapa bug. Tidak menangani kesalahan sama sekali seperti memiliki variabel yang tidak digunakan untuk saya - yang sudah merupakan kesalahan. Ini juga akan membantu dengan refactoring, ketika Anda memperkenalkan jenis pengembalian kesalahan ke suatu fungsi, karena Anda dipaksa untuk menanganinya di semua tempat.

Masalah utama yang kebanyakan orang coba selesaikan di sini tampaknya adalah "jumlah pengetikan" atau "jumlah baris". Saya akan setuju dengan sintaks apa pun yang mengurangi jumlah baris, tetapi itu sebagian besar merupakan masalah gofmt. Cukup izinkan "cakupan baris tunggal" sebaris dan kami baik-baik saja.

Saran lain untuk menghemat pengetikan adalah pemeriksaan implisit nil seperti dengan boolean:

err := returnsError()
if err { return err }

atau bahkan

if err := returnsError(); err { return err }

Itu akan bekerja dengan semua jenis pointer penyebab.

Perasaan saya adalah bahwa segala sesuatu yang mengurangi panggilan fungsi + penanganan kesalahan menjadi satu baris akan menyebabkan kode yang kurang dapat dibaca dan sintaks yang lebih kompleks.

kode yang kurang dapat dibaca dan sintaks yang lebih kompleks.

Kami sudah memiliki kode yang kurang dapat dibaca karena penanganan kesalahan verbose. Menambahkan trik Scanner API yang telah disebutkan, yang seharusnya menyembunyikan verbositas itu, membuatnya semakin buruk. Menambahkan sintaks yang lebih kompleks mungkin membantu keterbacaan, itulah gunanya gula sintaksis pada akhirnya. Kalau tidak, tidak ada gunanya dalam diskusi ini. Pola menggelegak kesalahan dan mengembalikan nilai nol untuk yang lainnya cukup umum untuk menjamin perubahan bahasa, menurut pendapat saya.

Pola memunculkan kesalahan dan mengembalikan nilai nol untuk semuanya

19642 akan membuat ini lebih mudah.


Juga, terima kasih @mewmew atas laporan pengalamannya. Ini pasti terkait dengan utas ini, sejauh terkait dengan bahaya dalam jenis desain penanganan kesalahan tertentu. Saya ingin melihat lebih banyak dari ini.

Saya merasa tidak menjelaskan ide saya dengan baik, jadi saya membuat intisari (dan merevisi banyak kekurangan yang baru saya perhatikan)

https://Gist.github.com/KernelDeimos/384aabd36e1789efe8cbce3c17ffa390

Ada lebih dari satu ide dalam inti ini, jadi saya harap mereka dapat didiskusikan secara terpisah satu sama lain

Mengesampingkan sejenak gagasan bahwa proposal di sini harus secara eksplisit tentang penanganan kesalahan, bagaimana jika Go memperkenalkan sesuatu seperti pernyataan collect ?

Pernyataan collect akan berbentuk collect [IDENT] [BLOCK STMT] , di mana ident harus saya variabel dalam lingkup dari tipe nil -able. Dalam pernyataan collect , variabel khusus _! tersedia sebagai alias untuk variabel yang dikumpulkan. _! tidak dapat digunakan di mana pun kecuali sebagai tugas, sama seperti _ . Setiap kali _! ditetapkan, pemeriksaan implisit nil dilakukan, dan jika _! tidak nihil, blok akan menghentikan eksekusi dan melanjutkan dengan sisa kode.

Secara teoritis, ini akan terlihat seperti ini:

func TryComplexOperation() (*Result, error) {
    var result *Result
    var err error

    collect err {
        intermediate1, _! := Step1()
        intermediate2, _! := Step2(intermediate1, "something")
        // assign to result from the outer scope
        result, _! = Step3(intermediate2, 12)
    }
    return result, err
}

yang setara dengan

func TryComplexOperation() (*Result, error) {
    var result *Result
    var err error

    {
        var intermediate1 SomeType
        intermediate1, err = Step1()
        if err != nil { goto collectEnd }

        var intermediate2 SomeOtherType
        intermediate2, err = Step2(intermediate1, "something")
        if err != nil { goto collectEnd }

        result, err = Step3(intermediate2, 12)
        // if err != nil { goto collectEnd }, but since we're at the end already we can omit this
    }

collectEnd:
    return result, err
}

Beberapa hal bagus lainnya yang akan diaktifkan oleh fitur sintaks seperti ini:

// try several approaches for acquiring a value
func GetSomething() (s *Something) {
    collect s {
        _! = fetchOrNil1()
        _! = fetchOrNil2()
        _! = new(Something)
    }
    return s
}

Fitur sintaks baru yang diperlukan:

  1. kata kunci collect
  2. special ident _! (Saya sudah memainkan ini di parser, tidak sulit untuk membuat match ini sebagai ident tanpa merusak yang lain)

Alasan saya menyarankan sesuatu seperti ini adalah karena argumen "penanganan kesalahan terlalu berulang" dapat diringkas menjadi "pemeriksaan nihil terlalu berulang". Go sudah memiliki banyak fitur penanganan kesalahan yang berfungsi apa adanya. Anda dapat mengabaikan kesalahan dengan _ (atau hanya tidak menangkap nilai pengembalian), Anda dapat mengembalikan kesalahan yang tidak dimodifikasi dengan if err != nil { return err } , atau menambahkan konteks dan kembali dengan if err != nil { return wrap(err) } . Tak satu pun dari metode itu sendiri yang terlalu berulang. Pengulangan ( jelas ) berasal dari keharusan mengulangi pernyataan sintaksis ini atau yang serupa di seluruh kode. Saya pikir memperkenalkan cara untuk mengeksekusi pernyataan hingga nilai non-nol ditemukan adalah cara yang baik untuk menjaga penanganan kesalahan tetap sama, tetapi kurangi jumlah boilerplate yang diperlukan untuk melakukannya.

  • Dukungan yang baik untuk 1) mengabaikan kesalahan; 3) membungkus kesalahan dengan konteks tambahan.

periksa, karena tetap sama (kebanyakan)

  • Meskipun kode penanganan kesalahan harus jelas, kode tersebut tidak boleh mendominasi fungsi.

periksa, karena kode penanganan kesalahan sekarang dapat masuk ke satu tempat jika diperlukan, sedangkan inti dari fungsi dapat terjadi dengan cara yang dapat dibaca secara linier

  • Kode Go 1 yang ada harus terus berfungsi, atau setidaknya harus memungkinkan untuk menerjemahkan Go 1 secara mekanis ke pendekatan baru dengan keandalan lengkap.

cek, ini adalah tambahan dan bukan perubahan

  • Pendekatan baru harus mendorong pemrogram untuk menangani kesalahan dengan benar.

periksa, saya pikir, karena mekanisme penanganan kesalahan tidak berbeda - kami hanya memiliki sintaks untuk "mengumpulkan" nilai non-nol pertama dari serangkaian eksekusi dan penugasan, yang dapat digunakan untuk membatasi jumlah tempat kita harus menulis kode penanganan kesalahan kita dalam suatu fungsi

  • Pendekatan baru apa pun harus lebih pendek dan/atau kurang berulang daripada pendekatan saat ini, sambil tetap jelas.

Saya tidak yakin ini berlaku di sini, karena fitur yang disarankan tidak hanya berlaku untuk penanganan kesalahan. Saya pikir itu dapat mempersingkat dan memperjelas kode yang dapat menghasilkan kesalahan, tanpa mengacaukan pemeriksaan nol dan pengembalian awal

  • Bahasa ini berfungsi hari ini, dan setiap perubahan memerlukan biaya. Seharusnya tidak hanya mencuci, itu harus jelas lebih baik.

Setuju, dan sepertinya perubahan yang cakupannya lebih dari sekadar penanganan kesalahan mungkin tepat. Saya percaya masalah mendasarnya adalah nil check in go menjadi berulang dan bertele-tele, dan kebetulan error adalah tipe nil -able.

@KernelDeimos Kami pada dasarnya baru saja menemukan hal yang sama. Namun, saya mengambil satu langkah lebih jauh dan menjelaskan mengapa cara x, ? := doSomething() tidak terlalu berhasil dalam praktiknya. Meskipun rapi untuk melihat bahwa saya bukan satu-satunya orang yang berpikir untuk menambahkan ? operator ke dalam bahasa dengan cara yang menarik.

https://github.com/golang/go/issues/25582

Bukankah ini pada dasarnya hanya jebakan ?

Berikut adalah spitball:

func NewClient(...) (*Client, error) {
    trap(err error) {
        return nil, err
    }

    listener, err? := net.Listen("tcp4", listenAddr)
    trap(_ error) {
        listener.Close()
    }

    conn, err? := ConnectionManager{}.connect(server, tlsConfig)
    trap(_ error) {
        conn.Close()
    }

    if forwardPort == 0 {
        env, err := environment.GetRuntimeEnvironment()
        if err != nil {
            log.Printf("not forwarding because: %v", err)
        } else {
            forwardPort, err = env.PortToForward()
            if err != nil {
                log.Printf("env couldn't provide forward port: %v", err)
            }
        }
    }
    var forwardOut *forwarding.ForwardOut
    if forwardPort != 0 {
        u, _ := url.Parse(fmt.Sprintf("http://127.0.0.1:%d", forwardPort))
        forwardOut = forwarding.NewOut(u)
    }

    client := &Client{...}

    toServer := communicationProtocol.Wrap(conn)
    toServer.Send(&client.serverConfig)?

    toServer.Send(&stprotocol.ClientProtocolAck{ClientVersion: Version})?

    session, err? := communicationProtocol.FinalProtocol(conn)
    client.session = session

    return client, nil
}

59 baris → 44

trap berarti "jalankan kode ini dalam urutan tumpukan jika variabel yang ditandai dengan ? dari jenis yang ditentukan bukan nilai nol." Ini seperti defer tetapi dapat mempengaruhi aliran kontrol.

Saya agak menyukai ide trap , tetapi sintaksnya sedikit mengganggu saya. Bagaimana jika itu adalah jenis deklarasi? Misalnya, trap err error {} mendeklarasikan trap disebut err dari tipe error yang, ketika ditetapkan, menjalankan kode yang diberikan. Kode bahkan tidak harus kembali; itu hanya diperbolehkan untuk melakukannya. Ini juga mematahkan ketergantungan pada nil menjadi spesial.

Sunting: Memperluas dan memberi contoh sekarang karena saya tidak menggunakan telepon.

func Example(r io.Reader) error {
  trap err error {
    if err != nil {
      return err
    }
  }

  n, err? := io.Copy(ioutil.Discard, r)
  fmt.Printf("Read %v bytes.\n", n)
}

Pada dasarnya, trap berfungsi seperti var , kecuali bahwa setiap kali ditugaskan dengan operator ? melekat padanya, blok kode dieksekusi. Operator ? juga mencegahnya dibayangi saat digunakan dengan := . Mendeklarasikan ulang trap dalam lingkup yang sama, tidak seperti var , diperbolehkan, tetapi harus dari jenis yang sama dengan yang sudah ada; ini memungkinkan Anda untuk mengubah blok kode terkait. Karena blok yang berjalan belum tentu kembali, ini juga memungkinkan Anda untuk memiliki jalur terpisah untuk hal-hal tertentu, seperti memeriksa apakah err == io.EOF .

Apa yang saya sukai dari pendekatan ini adalah tampaknya mirip dengan contoh errWriter dari Errors are Values , tetapi dalam pengaturan yang agak lebih umum yang tidak memerlukan deklarasi tipe baru.

@carlmjohnson yang Anda balas?
Bagaimanapun, konsep trap tampaknya hanya cara yang berbeda untuk menulis pernyataan defer , bukan? Kode, seperti yang tertulis, pada dasarnya akan sama jika Anda panic 'd pada kesalahan non-nihil dan kemudian menggunakan penutupan yang ditangguhkan untuk menetapkan nilai pengembalian bernama dan melakukan pembersihan. Saya pikir ini mengalami masalah yang sama seperti proposal saya sebelumnya menggunakan _! untuk secara otomatis panik, karena menempatkan satu metode penanganan kesalahan di atas yang lain. FWIW Saya juga merasa bahwa kodenya, seperti yang tertulis, jauh lebih sulit untuk dipikirkan daripada yang asli. Bisakah konsep trap ditiru dengan go hari ini, meskipun kurang jelas daripada memiliki sintaks untuk itu? Saya merasa seperti itu bisa dan itu akan menjadi if err != nil { panic (err) } dan defer untuk menangkap dan menanganinya.

Tampaknya mirip dengan konsep blok collect saya sarankan di atas, yang menurut saya pribadi memberikan cara yang lebih bersih untuk mengekspresikan ide yang sama ("jika nilai ini tidak nihil, saya ingin menangkapnya dan melakukan sesuatu dengan itu"). Go suka menjadi linier, dan eksplisit. trap terasa seperti sintaks baru untuk panic / defer tetapi dengan aliran kontrol yang kurang jelas.

@mccolljr , tampaknya itu adalah balasan untuk saya . Saya menyimpulkan dari posting Anda bahwa Anda tidak melihat proposal saya (sekarang di "item tersembunyi", meskipun tidak sejauh itu), karena saya sebenarnya menggunakan pernyataan penangguhan dalam proposal saya, diperpanjang dengan recover - seperti fungsi untuk penanganan kesalahan.

Saya juga mengamati penulisan ulang "perangkap" menjatuhkan banyak fungsi yang dimiliki proposal saya (_sangat_ kesalahan yang berbeda keluar), dan selain itu, tidak jelas bagi saya bagaimana memfaktorkan penanganan kesalahan dengan pernyataan jebakan. Banyak pengurangan dari proposal saya datang dalam bentuk menghilangkan kesalahan penanganan kebenaran dan, saya percaya, kembali membuatnya lebih mudah untuk hanya mengembalikan kesalahan secara langsung daripada melakukan hal lain.

Kemampuan untuk melanjutkan aliran diizinkan oleh contoh trap yang dimodifikasi yang saya berikan di atas . Saya mengeditnya nanti, jadi saya tidak tahu apakah Anda melihatnya atau tidak. Ini sangat mirip dengan collect , tapi saya pikir ini memberi sedikit lebih banyak kendali padanya. Tergantung pada bagaimana aturan pelingkupan untuk itu bekerja, itu bisa menjadi sedikit spaghetti-ish, tapi saya pikir itu mungkin untuk menemukan keseimbangan yang baik.

@thejerf Ah, itu lebih masuk akal. Saya tidak menyadari itu adalah balasan atas proposal Anda. Namun, tidak jelas bagi saya apa perbedaan antara erroring() dan recover() , selain dari kenyataan bahwa recover merespons panic . Tampaknya kita secara implisit melakukan semacam kepanikan ketika kesalahan perlu dikembalikan. Menunda juga merupakan operasi yang agak mahal jadi saya tidak yakin bagaimana perasaan saya tentang menggunakannya di semua fungsi yang mungkin menghasilkan kesalahan.

@DeedleFake Sama berlaku untuk trap , karena cara saya melihatnya trap pada dasarnya adalah makro yang menyisipkan kode ketika operator ? digunakan yang menyajikan serangkaian kekhawatirannya sendiri dan pertimbangan, atau diimplementasikan sebagai goto ... yang, bagaimana jika pengguna tidak kembali di blok trap , atau hanya berbeda secara sintaksis defer . Juga, bagaimana jika saya mendeklarasikan beberapa blok perangkap dalam suatu fungsi? Apakah itu diperbolehkan? Jika demikian, yang mana yang dieksekusi? Itu menambah kompleksitas implementasi. Go suka dikritik, dan saya suka itu. Saya pikir collect atau konstruksi linier serupa lebih selaras dengan ideologi Go daripada trap , yang, seperti yang ditunjukkan kepada saya setelah proposal pertama saya, tampaknya adalah try-catch membangun dalam kostum.

bagaimana jika pengguna tidak kembali di blok perangkap

Jika trap tidak mengembalikan atau mengubah aliran kontrol ( goto , continue , break , dll.), aliran kontrol kembali ke tempat kode blok 'dipanggil' dari. Blok itu sendiri akan bekerja mirip dengan memanggil penutupan, dengan pengecualian bahwa ia memiliki akses ke mekanisme aliran kontrol. Mekanismenya akan bekerja di tempat blok itu dideklarasikan, bukan di tempat asalnya, jadi

for {
  trap err error {
    break
  }

  err? = errors.New("Example")
}

akan bekerja.

Juga, bagaimana jika saya mendeklarasikan beberapa blok perangkap dalam suatu fungsi? Apakah itu diperbolehkan? Jika demikian, yang mana yang dieksekusi?

Ya, itu diperbolehkan. Blok diberi nama oleh jebakan, jadi cukup mudah untuk menentukan mana yang harus dipanggil. Misalnya, di

trap err error {
  // Block 1.
}

trap n int {
  // Block 2.
}

n? = 3

blok 2 dipanggil. Pertanyaan besar dalam kasus itu mungkin adalah apa yang terjadi dalam kasus n?, err? = 3, errors.New("Example") , yang mungkin memerlukan urutan penugasan yang harus ditentukan, seperti yang muncul di #25609.

Saya pikir kumpulkan atau konstruksi linier serupa lebih selaras dengan ideologi Go daripada perangkap, yang, seperti yang ditunjukkan kepada saya setelah proposal pertama saya, tampaknya merupakan konstruksi coba-tangkap dalam kostum.

Saya pikir collect dan trap pada dasarnya adalah try-catch s secara terbalik. Standar try-catch adalah kebijakan gagal-dengan-default yang mengharuskan Anda untuk memeriksa atau meledak. Ini adalah sistem sukses-oleh-default yang memungkinkan Anda untuk menentukan jalur kegagalan, pada dasarnya.

Satu hal yang memperumit semuanya adalah fakta bahwa kesalahan tidak secara inheren diperlakukan sebagai kegagalan, dan beberapa kesalahan, seperti io.EOF , tidak benar-benar menentukan kegagalan sama sekali. Saya pikir itu sebabnya sistem yang tidak terkait dengan kesalahan secara khusus, seperti collect atau trap , adalah cara yang harus dilakukan.

"Ah, itu lebih masuk akal. Saya tidak menyadari itu adalah balasan atas proposal Anda. Namun, tidak jelas bagi saya apa perbedaan antara erroring() dan recovery(), selain dari fakta bahwa pemulihan merespons panik."

Tidak memiliki banyak perbedaan adalah intinya. Saya mencoba meminimalkan jumlah konsep baru yang dibuat sambil mendapatkan kekuatan sebanyak mungkin dari mereka. Saya menganggap membangun fungsionalitas yang ada sebagai fitur, bukan bug.

Salah satu poin dari proposal saya adalah untuk mengeksplorasi lebih dari sekedar "bagaimana jika kita memperbaiki potongan berulang dari tiga baris di mana kita return err dan menggantinya dengan ? " menjadi "bagaimana pengaruhnya terhadap bahasa lainnya? Pola baru apa yang diaktifkannya? 'Praktik terbaik' baru apa yang diciptakannya? 'Praktik terbaik' lama apa yang tidak lagi menjadi praktik terbaik?" Saya tidak mengatakan saya menyelesaikan pekerjaan itu. Dan bahkan jika dinilai, ide tersebut sebenarnya memiliki terlalu banyak kekuatan untuk selera Go (karena Go bukanlah bahasa yang memaksimalkan kekuatan, dan bahkan dengan pilihan desain untuk membatasinya untuk mengetik error itu mungkin masih yang paling kuat proposal yang dibuat di utas ini, yang saya maksudkan sepenuhnya dalam arti baik dan buruk dari "kuat"), saya pikir kita dapat berdiri untuk mengeksplorasi pertanyaan tentang apa yang akan dilakukan konstruksi baru terhadap program secara keseluruhan, daripada apa yang akan dilakukan akan lakukan untuk tujuh fungsi contoh baris, itulah sebabnya saya mencoba untuk setidaknya membawa contoh hingga ~50-100 baris rentang "kode nyata". Semuanya terlihat sama dalam 5 baris, yang mencakup penanganan kesalahan Go 1.0, yang mungkin merupakan bagian dari alasan kita semua tahu dari pengalaman kita sendiri ada masalah nyata di sini, tetapi percakapan hanya berputar-putar jika kita berbicara tentang dalam skala yang terlalu kecil sampai beberapa orang mulai meyakinkan diri mereka sendiri mungkin tidak ada masalah sama sekali. (Percayai pengalaman pengkodean Anda yang sebenarnya, bukan sampel 5 baris!)

"Sepertinya kita secara implisit melakukan semacam kepanikan ketika kesalahan perlu dikembalikan."

Hal ini tidak implisit. Hal ini eksplisit. Anda menggunakan operator pop ketika melakukan apa yang Anda inginkan. Ketika itu tidak melakukan apa yang Anda inginkan, Anda tidak menggunakannya. Apa yang dilakukannya cukup sederhana untuk ditangkap dalam satu kalimat sederhana, meskipun spesifikasinya mungkin akan membutuhkan satu paragraf penuh karena begitulah cara kerjanya. Tidak ada implisit. Selanjutnya tidak panik karena hanya melepas satu tingkat tumpukan, persis seperti pengembalian; itu sama paniknya dengan pengembaliannya, yang tidak sama sekali.

Saya juga tidak peduli jika Anda mengeja pop sebagai ? atau terserah. Saya pribadi berpikir sebuah kata terlihat sedikit lebih mirip Go karena Go saat ini bukan bahasa yang kaya simbol, tetapi tidak dapat disangkal bahwa simbol memiliki keuntungan karena tidak bertentangan dengan kode sumber yang ada. Saya tertarik pada semantik dan apa yang dapat kita bangun di atasnya dan perilaku apa yang diberikan semantik baru untuk programmer baik yang baru maupun yang berpengalaman lebih daripada ejaan.

"Menunda juga merupakan operasi yang agak mahal, jadi saya tidak yakin bagaimana perasaan saya tentang menggunakannya di semua fungsi yang mungkin menghasilkan kesalahan."

Saya sudah mengakui itu. Meskipun saya menyarankan bahwa secara umum itu tidak terlalu mahal dan saya tidak merasa terlalu buruk untuk mengatakan bahwa untuk tujuan pengoptimalan jika Anda memiliki fungsi yang panas, tulislah dengan cara saat ini. Secara eksplisit bukan tujuan saya untuk mencoba memodifikasi 100% dari semua fungsi penanganan kesalahan, tetapi untuk membuat 80% dari mereka lebih sederhana dan lebih benar dan membiarkan 20% kasus (mungkin sejujurnya lebih seperti 98/2, jujur) tetap seperti semula. adalah. Sebagian besar kode Go tidak sensitif untuk menggunakan penangguhan, yang, bagaimanapun, mengapa defer ada di tempat pertama.

Faktanya, Anda dapat dengan mudah memodifikasi proposal untuk tidak menggunakan defer dan menggunakan beberapa kata kunci seperti trap sebagai deklarasi yang dijalankan hanya sekali di mana pun kemunculannya, daripada cara defer sebenarnya adalah pernyataan yang mendorong handler ke tumpukan fungsi yang ditangguhkan. Saya sengaja memilih untuk menggunakan kembali defer untuk menghindari penambahan konsep baru ke bahasa... bahkan memahami jebakan yang dapat dihasilkan dari penangguhan loop yang tiba-tiba menggigit orang. Tapi itu masih satu-satunya konsep defer harus dipahami.

Hanya untuk memperjelas bahwa menambahkan kata kunci baru ke bahasa adalah perubahan besar.

package main

import (
    "fmt"
)

func return(i int)int{
    return i
}

func main() {
    return(1)
}

menghasilkan

prog.go:7:6: syntax error: unexpected select, expecting name or (

Artinya jika kita mencoba menambahkan try , trap , assert , kata kunci apa pun ke dalam bahasa tersebut, kita berisiko melanggar banyak kode. Kode yang mungkin lebih lama dipertahankan.

Itulah mengapa saya awalnya mengusulkan untuk menambahkan operator ? go khusus yang dapat diterapkan ke variabel dalam konteks pernyataan. Karakter ? saat ini ditetapkan sebagai karakter ilegal untuk nama variabel. Artinya, kode tersebut saat ini tidak digunakan dalam kode Go mana pun saat ini dan oleh karena itu kami dapat memperkenalkannya tanpa menimbulkan perubahan yang merusak.

Sekarang masalah menggunakannya di sisi kiri tugas adalah tidak mempertimbangkan bahwa Go mengizinkan beberapa argumen pengembalian.

Misalnya pertimbangkan fungsi ini

func getCoord() x int, y int, z int, err error{
    x, err = getX()
    if err != nil{
        return 
    }

    y, err = getY()
    if err != nil{
        return 
    }

    z, err = getZ()
        if err != nil{
        return 
    }
    return
}

jika kita menggunakan? atau coba di lhs tugas untuk menyingkirkan blok if err != nil, apakah kita secara otomatis menganggap bahwa kesalahan berarti semua nilai lain sekarang menjadi sampah? Bagaimana jika kita melakukannya seperti ini

func GetCoord() (x, y, z int, err error) {
    err = try GetX(&x) // or err? = GetX(&x) 
    err = try GetY(&y) // or err? = GetY(&x) 
    err = try GetZ(&z) // or err? = GetZ(&x) 
}

asumsi apa yang kita buat di sini? Bahwa seharusnya tidak berbahaya untuk hanya menganggap tidak apa-apa membuang nilainya? bagaimana jika kesalahan itu dimaksudkan untuk lebih merupakan peringatan dan nilai x baik-baik saja? Bagaimana jika satu-satunya fungsi yang memunculkan err adalah panggilan ke GetZ() dan nilai x,y sebenarnya bagus? Apakah kita berani mengembalikannya. Bagaimana jika kita tidak menggunakan argumen pengembalian bernama? Bagaimana jika argumen pengembalian adalah tipe referensi seperti peta atau saluran, Haruskah kita menganggap aman untuk mengembalikan nil ke pemanggil?

TLDR; menambahkan? atau try untuk tugas dalam upaya untuk menghilangkan

if err != nil{
    return err
}

memperkenalkan terlalu banyak kebingungan daripada tunjangan.

Dan menambahkan sesuatu seperti saran trap memperkenalkan kemungkinan kerusakan.

Itulah sebabnya dalam proposal saya yang saya buat dalam edisi terpisah. Saya mengizinkan kemampuan untuk mendeklarasikan func ?() bool pada jenis apa pun sehingga ketika Anda menelepon katakan

x, err := doSomething; return x, err?    

Anda dapat membuat efek samping jebakan itu terjadi dengan cara yang berlaku untuk semua jenis.

Dan menerapkan? untuk bekerja hanya pada pernyataan seperti yang saya tunjukkan memungkinkan untuk programabilitas pernyataan. Dalam proposal saya, saya menyarankan untuk mengizinkan pernyataan sakelar khusus yang memungkinkan seseorang untuk beralih kasus yang merupakan kata kunci + ?

switch {
    case select?:
    //side effect/trap code specific to select
    case return?:
    //side effect/trap code specific to returns
    case for?: 
    //side effect/trap code specific to for? 

    //etc...
}  

Jika kita menggunakan? pada tipe yang tidak memiliki eksplisit ? fungsi dideklarasikan atau tipe bawaan, maka perilaku default memeriksa apakah var == nil || nilai nol {execute the statement} adalah dugaan maksud.

Idk, saya bukan ahli dalam desain bahasa pemrograman, tetapi bukankah ini?

Misalnya, fungsi os.Chdir saat ini

func Chdir(dir string) error {
  if e := syscall.Chdir(dir); e != nil {
      return &PathError{"chdir", dir, e}
  }
  return nil
}

Di bawah proposal ini, dapat ditulis sebagai

func Chdir(dir string) error {
  syscall.Chdir(dir) || &PathError{"chdir", dir, err}
  return nil
}

pada dasarnya sama dengan fungsi panah javascript atau seperti yang didefinisikan Dart sebagai "sintaks panah gemuk"

misalnya

func Chdir(dir string) error {
    syscall.Chdir(dir) => &PathError{"chdir", dir, err}
    return nil
}

dari tur panah .

Untuk fungsi yang hanya berisi satu ekspresi, Anda dapat menggunakan sintaks singkatan:

bool isNoble(int atomicNumber) => _nobleGases[atomicNumber] != null;
Sintaks => expr adalah singkatan untuk { return expr; }. Notasi => kadang-kadang disebut sebagai sintaks panah gemuk.

@mortdeus , sisi kiri panah Dart adalah tanda tangan fungsi, sedangkan syscall.Chdir(dir) adalah ekspresi. Mereka tampak kurang lebih tidak berhubungan.

@mortdeus Saya lupa mengklarifikasi sebelumnya, tetapi ide yang saya komentari di sini hampir tidak mirip dengan proposal yang Anda tandai. Saya menyukai ide ? sebagai pengganti jadi saya menyalinnya, tetapi ide saya menekankan penggunaan kembali satu blok kode untuk menangani kesalahan sambil menghindari beberapa masalah yang diketahui dengan try...catch . Saya sangat berhati-hati untuk menemukan sesuatu yang sebelumnya tidak dibicarakan sehingga saya dapat menyumbangkan ide baru.

Bagaimana dengan pernyataan baru bersyarat return (atau returnIf )?

return(bool expression) ...

yaitu.

err := syscall.Chdir(dir)
return(err != nil) &PathError{"chdir", dir, e}

a, b, err := Foo()    // signature: func Foo() (string, string, error)
return(err != nil) "", "", err

Atau biarkan fmt memformat fungsi hanya-kembali satu liner pada satu baris, bukan tiga:

err := syscall.Chdir(dir)
if err != nil { return &PathError{"chdir", dir, e} }

a, b, err := Foo()    // signature: func Foo() (string, string, error)
if err != nil { return "", "", err }

Terpikir oleh saya bahwa saya bisa mendapatkan semua yang saya inginkan hanya dengan penambahan beberapa operator yang kembali lebih awal jika kesalahan paling kanan tidak nihil, jika saya menggabungkannya dengan parameter bernama:

func NewClient(...) (c *Client, err error) {
    defer annotateError(&err, "client couldn't be created")

    listener := net.Listen("tcp4", listenAddr)?
    defer closeOnErr(&err, listener)
    conn := ConnectionManager{}.connect(server, tlsConfig)?
    defer closeOnErr(&err, conn)

    if forwardPort == 0 {
        env, err := environment.GetRuntimeEnvironment()
        if err != nil {
            log.Printf("not forwarding because: %v", err)
        } else {
            forwardPort, err = env.PortToForward()
            if err != nil {
                log.Printf("env couldn't provide forward port: %v", err)
            }
        }
    }
    var forwardOut *forwarding.ForwardOut
    if forwardPort != 0 {
         forwardOut = forwarding.NewOut(pop url.Parse(fmt.Sprintf("http://127.0.0.1:%d", forwardPort)))
    }

    client := &Client{listener: listener, conn: conn, forward: forwardOut}

    toServer := communicationProtocol.Wrap(conn)
    toServer.Send(&client.serverConfig)?
    toServer.Send(&stprotocol.ClientProtocolAck{ClientVersion: Version})?
    session := communicationProtocol.FinalProtocol(conn)?
    client.session = session

    return client, nil
}

func closeOnErr(err *error, c io.Closer) {
    if *err != nil {
        closeErr := c.Close()
        if err != nil {
            *err = multierror.Append(*err, closeErr)
        }
    }
}

func annotateError(err *error, annotation string) {
    if *err != nil {
        log.Printf("%s: %v", annotation, *err)
        *err = errwrap.Wrapf(annotation +": {{err}}", err)
    }
}

Saat ini pemahaman saya tentang konsensus Go bertentangan dengan parameter bernama, tetapi jika kemampuan parameter nama berubah, begitu juga konsensusnya. Tentu saja jika konsensus cukup kuat menentangnya, ada opsi untuk memasukkan pengakses.

Pendekatan ini memberi saya apa yang saya cari (sehingga lebih mudah untuk menangani kesalahan secara sistematis di bagian atas suatu fungsi, kemampuan untuk memfaktorkan kode tersebut, juga pengurangan jumlah baris) dengan hampir semua proposal lain di sini juga, termasuk yang asli . Dan bahkan jika komunitas Go memutuskan tidak menyukainya, saya tidak perlu peduli, karena itu ada dalam kode saya berdasarkan fungsi demi fungsi dan tidak memiliki ketidakcocokan impedansi di kedua arah.

Meskipun saya akan menyatakan preferensi untuk proposal yang mengizinkan fungsi tanda tangan func GetInt() (x int, err error) untuk digunakan dalam kode dengan OtherFunc(GetInt()?, "...") (atau apa pun hasil akhirnya) ke yang tidak dapat dikomposisikan sebuah ekspresi. Sementara gangguan yang lebih rendah pada klausa penanganan kesalahan sederhana yang berulang-ulang, jumlah kode saya yang membongkar fungsi arity 2 hanya agar dapat memiliki hasil pertama masih sangat mengganggu, dan tidak benar-benar menambahkan apa pun pada kejelasan kode yang dihasilkan.

@thejerf , saya merasa ada banyak perilaku aneh di sini. Anda memanggil net.Listen , yang mengembalikan kesalahan, tetapi tidak ditetapkan. Dan kemudian Anda menunda, meneruskan err . Apakah setiap defer menimpa yang terakhir, sehingga annotateError tidak pernah dipanggil? Atau apakah mereka menumpuk, sehingga jika kesalahan dikembalikan dari, katakanlah, toServer.Send , maka closeOnErr dipanggil dua kali, dan kemudian annotateError dipanggil? Apakah closeOnErr dipanggil hanya jika panggilan sebelumnya memiliki tanda tangan yang cocok? Bagaimana dengan kasus ini?

conn := ConnectionManager{}.connect(server, tlsConfig)?
fmt.Printf("Attempted to connect to server %#v", server)
defer closeOnErr(&err, conn)

Membaca kode, itu juga membingungkan, seperti mengapa saya tidak bisa mengatakannya

client.session = communicationProtocol.FinalProtocol(conn)?

Agaknya, karena FinalProtocol mengembalikan kesalahan? Tapi itu disembunyikan dari pembaca.

Terakhir, apa yang terjadi ketika saya ingin melaporkan kesalahan dan memulihkannya dalam suatu fungsi? Sepertinya contoh Anda akan mencegah kasus itu?

_Tambahan_

Oke, saya pikir ketika Anda ingin memulihkan dari kesalahan, dengan contoh Anda, Anda menetapkannya, seperti pada baris ini:

env, err := environment.GetRuntimeEnvironment()

Tidak apa-apa karena err dibayangi, tetapi kemudian jika saya berubah ...

forwardPort, err = env.PortToForward()
if err != nil {
    log.Printf("env couldn't provide forward port: %v", err)
}

untuk hanya

forwardPort = env.PortToForward()

Maka kesalahan yang ditangguhkan Anda yang ditangani tidak akan menangkapnya, karena Anda menggunakan err dibuat dalam lingkup. Atau apakah saya melewatkan sesuatu?

Saya percaya tambahan sintaks yang menunjukkan suatu fungsi bisa gagal adalah awal yang baik. Saya mengusulkan sesuatu di sepanjang baris ini:

func (r Reader) Read(b []byte) (n int) fails {
    if somethingFailed {
        fail errors.New("something failed")
    }

    return 0
}

Jika suatu fungsi gagal (dengan menggunakan kata kunci fail alih-alih return , ia mengembalikan nilai nol untuk setiap parameter yang dikembalikan.

func (c EmailClient) SendEmail(to, content string) fails {
    if !c.connected() {
        fail errors.New("could not connect")
    }

    // You can handle it and execution will continue if you don't fail or return
    n := r.Read(b) handle (err) {
        fmt.Printf("failed to read: %s", err)
    }

    // This shouldn't compile and should complain about an unhandled error
    n := r.Read(b)
}

Pendekatan ini akan memiliki manfaat memungkinkan kode saat ini untuk bekerja, sementara memungkinkan mekanisme baru untuk penanganan kesalahan yang lebih baik (setidaknya dalam sintaks).

Komentar tentang kata kunci:

  • fails mungkin bukan pilihan terbaik tapi itu yang terbaik yang bisa saya pikirkan saat ini. Saya berpikir untuk menggunakan err (atau errs ), tetapi cara penggunaannya saat ini mungkin menjadikannya pilihan yang buruk karena ekspektasi saat ini ( err kemungkinan besar adalah nama variabel, dan errs mungkin dianggap sebagai irisan atau larik atau kesalahan).
  • handle bisa sedikit menyesatkan. Saya ingin menggunakan recover , tetapi digunakan untuk panic s...

edit: Mengubah permintaan r.Read agar sesuai dengan io.Reader.Read() .

Sebagian dari alasan saran tersebut adalah karena pendekatan saat ini di Go tidak membantu alat untuk memahami jika nilai error dikembalikan menunjukkan fungsi yang gagal atau mengembalikan nilai kesalahan sebagai bagian dari fungsinya (mis github.com/pkg/errors ).

Saya percaya mengaktifkan fungsi untuk mengekspresikan kegagalan secara eksplisit adalah langkah pertama untuk meningkatkan penanganan kesalahan.

@ibrasho , bagaimana contoh Anda berbeda dari ...

func (c EmailClient) SendEmail(to, content string) error {
    // ...

    // You can handle it and execution will continue if you don't fail or return
    _, _, err := r.Read()
        if err != nil {
        fmt.Printf("failed to read: %s", err)
    }

    // This shouldn't compile and should complain about an unhandled error
    _, _, err := r.Read()
}

... jika kita memberikan peringatan kompilator atau lint untuk instance error tidak tertangani? Tidak ada perubahan bahasa yang diperlukan.

Dua hal:

  • Saya pikir sintaks yang diusulkan membaca dan terlihat lebih baik. 😁
  • Versi saya membutuhkan pemberian fungsi kemampuan untuk menyatakan mereka gagal secara eksplisit. Ini adalah sesuatu yang hilang di Go saat ini yang dapat memungkinkan alat untuk berbuat lebih banyak. Kami selalu dapat memperlakukan fungsi yang mengembalikan nilai error sebagai gagal, tapi itu asumsi. Bagaimana jika fungsi mengembalikan 2 nilai error ?

Saran saya memiliki sesuatu yang saya hapus, yaitu propagasi otomatis:

func (c EmailClient) SendEmail(to, content string) fails {
    n := r.Read(b)

    // Would automaticaly propgate the error, so it will be equivlent to this:
    // n := r.Read(b) handle (err) {
    //  fail err
    // }
}

Saya menghapusnya karena saya pikir penanganan kesalahan harus eksplisit.

edit: Mengubah permintaan r.Read agar sesuai dengan io.Reader.Read() .

Jadi, ini akan menjadi tanda tangan atau prototipe yang valid?

func (r *MyFileReader) Read(b []byte) (n int, err error) fails

(Mengingat bahwa implementasi io.Reader menetapkan io.EOF ketika tidak ada lagi yang perlu dibaca dan beberapa kesalahan lain untuk kondisi kegagalan.)

Ya. Tetapi tidak seorang pun harus mengharapkan err untuk menunjukkan kegagalan dalam fungsi yang melakukan tugasnya. Kesalahan kegagalan Baca harus diteruskan ke blok pegangan. Kesalahan adalah nilai dalam Go, dan kesalahan yang dikembalikan oleh fungsi ini seharusnya tidak memiliki arti khusus selain menjadi sesuatu yang dikembalikan oleh fungsi ini (untuk beberapa alasan aneh).

Saya mengusulkan bahwa kegagalan akan menghasilkan nilai yang dikembalikan sebagai nilai nol. Reader.Read saat ini sudah membuat beberapa janji yang mungkin tidak mungkin dilakukan dengan pendekatan baru ini.

Ketika Baca menemukan kesalahan atau kondisi akhir file setelah berhasil membaca n > 0 byte, ia mengembalikan jumlah byte yang dibaca. Ini dapat mengembalikan kesalahan (non-nil) dari panggilan yang sama atau mengembalikan kesalahan (dan n == 0) dari panggilan berikutnya. Contoh dari kasus umum ini adalah bahwa Pembaca yang mengembalikan jumlah byte bukan nol di akhir aliran input dapat mengembalikan err == EOF atau err == nil. Baca berikutnya harus mengembalikan 0, EOF.

Penelepon harus selalu memproses n > 0 byte yang dikembalikan sebelum mempertimbangkan kesalahan err. Melakukannya dengan benar akan menangani kesalahan I/O yang terjadi setelah membaca beberapa byte dan juga kedua perilaku EOF yang diizinkan.

Implementasi Read tidak disarankan untuk mengembalikan hitungan byte nol dengan kesalahan nil, kecuali jika len(p) == 0. Penelepon harus memperlakukan pengembalian 0 dan nil sebagai indikasi bahwa tidak ada yang terjadi; khususnya tidak menunjukkan EOF.

Tidak semua perilaku ini dimungkinkan dalam pendekatan yang diusulkan saat ini. Dari kontrak antarmuka Baca saat ini, saya melihat beberapa kekurangan, seperti cara menangani pembacaan sebagian.

Secara umum, bagaimana seharusnya suatu fungsi berperilaku ketika sebagian selesai pada saat gagal? Sejujurnya saya belum memikirkan hal ini.

Kasus io.EOF sederhana:

func DoSomething(r io.Reader) fails {
    // I'm using rerr so that I don't shadow the err returned from the function
    n, err := r.Read(b) handle (rerr) {
        if rerr != io.EOF {
            fail err
        }
        // Else do nothing?
    }
}

@thejerf , saya merasa ada banyak perilaku aneh di sini. Anda menelepon net.Listen, yang mengembalikan kesalahan, tetapi tidak ditetapkan.

Saya menggunakan operator ? diusulkan oleh banyak orang untuk menunjukkan pengembalian kesalahan dengan nilai nol untuk nilai lainnya, jika kesalahannya tidak nol. Saya sedikit lebih suka kata pendek daripada operator karena menurut saya Go bukan bahasa yang berat bagi operator, tetapi jika ? digunakan untuk bahasa tersebut, saya masih akan menghitung kemenangan saya daripada menginjak kaki saya. gusar.

Apakah setiap penangguhan baru menimpa yang terakhir, sehingga annotateError tidak pernah dipanggil? Atau apakah mereka menumpuk, sehingga jika kesalahan dikembalikan dari, katakanlah, keServer.Send, lalu closeOnErr dipanggil dua kali, dan kemudian annotateError dipanggil?

Ini berfungsi seperti penangguhan sekarang: https://play.golang.org/p/F0xgP4h5Vxf Saya mengharapkan beberapa jempol untuk posting itu, di mana jawaban yang saya rencanakan akan menunjukkan ini adalah _sudah_ bagaimana penangguhan bekerja dan Anda akan melakukannya hanya meremehkan perilaku Go saat ini, tetapi tidak mendapatkan apa pun. Sayang. Seperti yang juga ditunjukkan oleh cuplikan itu, membayangi bukan masalah, atau setidaknya, tidak lagi menjadi masalah daripada yang sudah ada. (Ini tidak akan memperbaikinya, atau membuatnya lebih buruk.)

Saya pikir satu aspek yang mungkin membingungkan adalah bahwa sudah terjadi di Go saat ini bahwa parameter bernama akan berakhir menjadi "apa pun yang sebenarnya dikembalikan", sehingga Anda dapat melakukan apa yang saya lakukan dan mengambil pointer ke sana dan meneruskan itu ke fungsi yang ditangguhkan dan memanipulasinya, terlepas dari apakah Anda secara langsung mengembalikan nilai seperti return errors.New(...) , yang mungkin secara intuitif tampak seperti "variabel baru" yang bukan variabel bernama, tetapi sebenarnya Go akan berakhir dengan itu ditugaskan ke variabel bernama pada saat penangguhan berjalan. Sangat mudah untuk mengabaikan detail khusus dari Go saat ini. Saya menyampaikan bahwa meskipun mungkin membingungkan sekarang, jika Anda bekerja bahkan pada basis kode yang menggunakan idiom ini (yaitu, saya tidak mengatakan bahwa ini bahkan harus menjadi "Lakukan praktik terbaik", hanya beberapa eksposur yang akan dilakukan) Anda' d mengetahuinya dengan cukup cepat. Karena, hanya untuk mengatakannya sekali lagi untuk menjadi sangat jelas, begitulah Go sudah bekerja, bukan perubahan yang diusulkan.

Inilah proposal yang menurut saya belum pernah diusulkan sebelumnya. Menggunakan contoh:

 r, !handleError := something()

Arti dari ini sama dengan ini:

 r, _xyzzy := something()
 if ok, R := handleError(_xyzzy); !ok { return R }

(di mana _xyzzy adalah variabel baru yang cakupannya hanya diperluas ke dua baris kode ini, dan R dapat berupa beberapa nilai).

Keuntungan proposal ini tidak khusus untuk kesalahan, tidak memperlakukan nilai nol secara khusus, dan mudah untuk secara ringkas menentukan cara membungkus kesalahan di dalam blok kode tertentu. Perubahan sintaks kecil. Mengingat terjemahan langsung, mudah untuk memahami cara kerja fitur ini.

Kekurangannya adalah ia memperkenalkan pengembalian implisit, seseorang tidak dapat menulis penangan umum yang hanya mengembalikan kesalahan (karena nilai pengembaliannya harus didasarkan pada fungsi dari mana ia dipanggil), dan bahwa nilai yang diteruskan ke penangan adalah tidak tersedia dalam kode panggilan.

Inilah cara Anda menggunakannya:

func Read(filename string) error {
  herr := func(err error) (bool, error) {
      if err != nil { return true, fmt.Errorf("Read failed: %s", err) }
      return false, nil
  }

  f, !herr := OpenFile(filename)
  b, !herr := ReadBytes(f)
  !herr := ProcessBytes(b)
  return nil
}

Atau Anda mungkin menggunakannya dalam tes untuk memanggil t.Fatal jika kode init Anda gagal:

func TestSomething(t *testing.T) {
  must := func(err error) bool { t.Fatalf("init code failed: %s", err); return true }
  !must := setupTest()
  !must := clearDatabase()
  ...
}

Saya akan menyarankan mengubah tanda tangan fungsi menjadi hanya func(error) error . Ini menyederhanakan kasus mayoritas, dan jika Anda perlu menganalisis kesalahan lebih lanjut, cukup gunakan mekanisme saat ini.

Pertanyaan sintaks: Bisakah Anda mendefinisikan fungsi sebaris?

func Read(filename string) error {
    f, !func(err error) error {
        if err != nil { return true, fmt.Errorf("... %s", err) }
        return false, nil
    } := OpenFile(filename)
    /...

Saya merasa nyaman dengan "jangan lakukan itu", tetapi sintaksnya mungkin harus mengizinkannya untuk mengurangi jumlah kasus khusus. Hal tersebut juga akan memungkinkan:

func failed(s string) func(error) error {
    return func(err error) {
       // returns a decorated error with the given string
   }
}

func Read(filename string) error {
  f, !failed("couldn't open file") := OpenFile(filename)
  b, !failed("couldn't read file") := ReadBytes(f)
  !failed("couldn't process file") := ProcessBytes(b)
  return nil
}

Yang menurut saya salah satu proposal yang lebih baik untuk hal semacam itu, setidaknya dalam hal memasukkan barang-barang itu ke sana secara ringkas. Ini adalah kasus lain di mana IMHO Anda memberikan kesalahan yang lebih baik pada saat ini daripada kode berbasis pengecualian cenderung, yang sering gagal untuk mendapatkan detail ini tentang sifat kesalahan karena sangat mudah untuk membiarkan pengecualian menyebar.

Saya juga menyarankan untuk alasan kinerja bahwa fungsi kesalahan bang didefinisikan sebagai tidak dipanggil pada nilai nol. Itu membuat dampak kinerja mereka cukup minimal; dalam kasus yang baru saja saya tunjukkan, jika Baca berhasil secara normal, itu tidak lebih mahal daripada implementasi baca saat ini yang sudah if ing pada setiap kesalahan dan gagal klausa if . Jika kita memanggil fungsi pada nil sepanjang waktu, itu akan menjadi sangat mahal setiap kali tidak dapat digariskan, yang pada akhirnya akan menjadi jumlah waktu yang tidak sepele. (Jika kesalahan terjadi secara aktif, kami mungkin dapat membenarkan & melakukan panggilan fungsi di hampir semua keadaan (jika Anda tidak dapat kembali ke metode saat ini), tetapi tidak benar-benar menginginkannya untuk non-kesalahan.) Itu juga berarti fungsi bang dapat mengasumsikan nilai bukan nol dalam implementasinya, yang juga menyederhanakannya.

@thejerf jalan yang bagus tapi bahagia sangat diubah.
banyak pesan yang lalu ada saran untuk memiliki Ruby seperti sintax "atau" - f := OpenFile(filename) or failed("couldn't open file") .

Perhatian tambahan - apakah itu untuk semua jenis params atau hanya untuk kesalahan? jika hanya untuk kesalahan - maka kesalahan ketik harus memiliki arti khusus untuk kompiler.

@thejerf jalan yang bagus tapi bahagia sangat diubah.

Saya akan merekomendasikan untuk membedakan antara jalur yang mungkin umum dari proposal asli yang terlihat seperti saran asli paulhankin:

func Read(filename string) error {
  herr := func(err error) (bool, error) {
      if err != nil { return true, fmt.Errorf("Read failed: %s", err) }
      return false, nil
  }

  f, !herr := OpenFile(filename)
  b, !herr := ReadBytes(f)
  !herr := ProcessBytes(b)
  return nil
}

mungkin bahkan dengan herr diperhitungkan di suatu tempat, dan eksplorasi saya tentang apa yang diperlukan untuk menentukannya sepenuhnya, yang merupakan kebutuhan untuk percakapan ini, dan renungan saya sendiri tentang bagaimana saya dapat menggunakannya dalam kode pribadi saya, yang hanya merupakan eksplorasi apa lagi yang dimungkinkan dan diberikan oleh sugesti. Saya sudah mengatakan secara harfiah menggarisbawahi suatu fungsi mungkin adalah ide yang buruk, tetapi tata bahasa mungkin harus mengizinkannya untuk menjaga tata bahasa tetap sederhana. Saya sudah bisa menulis fungsi Go yang mengambil tiga fungsi, dan memasukkan semuanya langsung ke dalam panggilan. Itu tidak berarti Go rusak atau Go perlu melakukan sesuatu untuk mencegahnya; itu berarti saya tidak boleh melakukan itu jika saya menghargai kejelasan kode. Saya suka bahwa Go memberikan kejelasan kode, tetapi masih ada beberapa tingkat tanggung jawab yang tidak dapat direduksi pada pengembang untuk menjaga kode tetap jelas.

Jika Anda akan memberi tahu saya bahwa "jalan bahagia" untuk

func Read(filename string) error {
  f, !failed("couldn't open file") := OpenFile(filename)
  b, !failed("couldn't read file") := ReadBytes(f)
  !failed("couldn't process file") := ProcessBytes(b)
  return nil
}

kasar dan sulit dibaca, tetapi jalan bahagia mudah dibaca

func Read(filename string) error {
  f, err := OpenFile(filename)
  if err != nil {
    return fmt.Errorf("Read failed: %s", err)
  }

  b, err := ReadBytes(f)
  if err != nil {
    return fmt.Errorf("Read failed: %s", err)
  }

  err = ProcessBytes(b)
  if err != nil {
    return fmt.Errorf("Read failed: %s", err)
  }

  return nil
}

lalu saya sampaikan satu-satunya cara yang mungkin agar yang kedua lebih mudah dibaca adalah bahwa Anda sudah terbiasa membacanya dan mata Anda dilatih untuk melompati jalan yang benar untuk melihat jalan yang bahagia. Saya bisa menebak itu, karena milik saya juga. Tapi begitu Anda terbiasa dengan sintaks alternatif, Anda akan dilatih untuk itu juga. Anda harus menganalisis sintaks berdasarkan apa yang akan Anda rasakan ketika Anda sudah terbiasa, bukan bagaimana perasaan Anda sekarang.

Saya juga mencatat bahwa baris baru yang ditambahkan pada contoh kedua mewakili apa yang terjadi pada kode saya yang sebenarnya. Bukan hanya "baris" kode yang cenderung ditambahkan oleh penanganan kesalahan Go saat ini, tetapi juga menambahkan banyak "paragraf" ke fungsi yang seharusnya sangat sederhana. Saya ingin membuka file, membaca beberapa byte, dan memprosesnya. aku tidak mau

Buka file.

Dan kemudian baca beberapa byte, jika tidak apa-apa.

Dan kemudian memprosesnya, jika tidak apa-apa.

Saya merasa ada banyak downvotes yang berjumlah "ini bukan yang biasa saya lakukan", daripada analisis aktual tentang bagaimana ini akan dimainkan dalam kode nyata, setelah Anda terbiasa dan menggunakannya dengan lancar .

Saya tidak suka ide menyembunyikan pernyataan pengembalian, saya lebih suka:

f := OpenFile(filename) or return failed("couldn't open file")
....
func failed(msg string, err error) error { ... } 

Dalam hal ini or adalah operator penerusan bersyarat nol ,
meneruskan pengembalian terakhir jika bukan nol.
Ada proposal serupa di C# menggunakan operator ?>

f := OpenFile(filename) ?> return failed("couldn't open file")

@thejerf "jalur bahagia" dalam kasus Anda diawali dengan panggilan ke

@rodcorsi karakter yang dimasukkan dengan tombol "shift" adalah IMHO yang buruk - (jika Anda memiliki beberapa tata letak keyboard)

Tolong jangan membuat cara ini lebih rumit dari sekarang. Benar-benar memindahkan kode yang sama dalam satu baris (bukan 3 atau lebih) sebenarnya bukan solusi. Saya pribadi tidak melihat proposal ini layak sebanyak itu. Teman-teman, matematikanya sangat sederhana. Baik merangkul ide "Coba-tangkap" atau pertahankan hal-hal seperti sekarang yang berarti banyak "jika kemudian yang lain" dan suara kode dan tidak benar-benar cocok untuk digunakan dalam pola OO seperti Antarmuka Lancar.

Terima kasih banyak untuk semua hand-down Anda dan mungkin beberapa hand-up ;-) (bercanda)

@KamyarM IMO, "gunakan alternatif yang paling dikenal atau tidak membuat perubahan sama sekali" bukanlah pernyataan yang sangat produktif. Ini menghentikan inovasi dan memfasilitasi argumen melingkar.

@KernelDeimos Saya setuju dengan Anda tetapi saya melihat banyak komentar di utas ini yang pada dasarnya menganjurkan yang lama dengan memindahkan tepat 4 5 baris menjadi satu baris yang saya tidak melihatnya sebagai solusi yang benar-benar solusi dan juga banyak di Komunitas Go yang sangat AGAMA menolak penggunaan Coba-Tangkap yang menutup pintu untuk pendapat lain. Saya pribadi berpikir mereka yang menemukan konsep try-catch ini benar-benar memikirkannya dan meskipun mungkin memiliki beberapa kekurangan tetapi kekurangan itu hanya disebabkan oleh kebiasaan pemrograman yang buruk dan tidak ada cara untuk memaksa programmer untuk menulis kode yang baik bahkan jika Anda menghapus atau membatasi semua yang baik atau beberapa mungkin mengatakan fitur buruk yang mungkin dimiliki bahasa pemrograman.
Saya mengusulkan sesuatu seperti ini sebelumnya dan ini bukan Java atau C# try-catch dan saya pikir ini dapat mendukung penanganan kesalahan dan perpustakaan saat ini dan saya menggunakan salah satu contoh dari atas. Jadi pada dasarnya kompiler memeriksa kesalahan setelah setiap baris dan melompat ke blok catch jika nilai err disetel ke tidak nil:

try (var err error){ 
    f, err := OpenFile(filename)
    b, err := ReadBytes(f)
    err = ProcessBytes(b)
    return nil
} catch (err error){ //Required
   return err
} finally{ // Optional
    // Do something else like close the file or connection to DB. Not necessary in this example since we  return earlier.
}

@KamyarM
Dalam contoh Anda, bagaimana saya tahu (pada saat menulis kode) metode mana yang mengembalikan kesalahan? Bagaimana cara memenuhi cara ketiga untuk menangani kesalahan ("kembalikan kesalahan dengan informasi kontekstual tambahan")?

@urandom
salah satu caranya adalah dengan menggunakan sakelar Go dan temukan jenis pengecualian di tangkapan. Katakanlah Anda memiliki pengecualian pathError yang Anda tahu itu disebabkan oleh OpenFile() seperti itu. Cara lain yang tidak jauh berbeda dengan penanganan error if err!=nil saat ini di GoLang adalah sebagai berikut:

try (var err error){ 
    f, err := OpenFile(filename)
} catch (err error){ //Required
   return err
}
try (var err error){ 
    b, err := ReadBytes(f)
catch (err error){ //Required
   return err
}
try (var err error){ 
    err = ProcessBytes(b)
catch (err error){ //Required
   return err
}
return nil

Jadi Anda memiliki pilihan dengan cara ini tetapi Anda tidak dibatasi. Jika Anda benar-benar ingin tahu persis baris mana yang menyebabkan masalah, Anda mencoba setiap baris dengan cara yang sama seperti saat Anda menulis banyak if-then-elses. Jika kesalahan tidak penting bagi Anda dan Anda ingin meneruskannya ke metode pemanggil yang dalam contoh yang dibahas di utas ini sebenarnya tentang itu, saya pikir kode yang saya usulkan hanya berfungsi.

@KamyarM Saya melihat dari mana Anda berasal sekarang. Saya akan mengatakan jika ada begitu banyak orang yang menentang try...catch, saya melihat itu sebagai bukti bahwa try...catch tidak sempurna dan memiliki kesalahan. Ini adalah solusi mudah untuk suatu masalah, tetapi jika Go2 dapat membuat penanganan kesalahan lebih baik daripada apa yang telah kita lihat dalam bahasa lain, saya pikir itu akan sangat keren.

Saya pikir mungkin untuk mengambil apa yang baik tentang try...catch tanpa mengambil apa yang buruk tentang try...catch, yang saya usulkan sebelumnya. Saya setuju bahwa mengubah tiga baris menjadi 1 atau 2 tidak menyelesaikan apa pun.

Masalah mendasar, seperti yang saya lihat, adalah kode penanganan kesalahan dalam suatu fungsi akan diulang jika bagian dari logika "kembali ke pemanggil". Jika Anda ingin mengubah logika kapan saja, Anda harus mengubah setiap instance if err != nil { return nil } .

Yang mengatakan, saya sangat menyukai gagasan try...catch selama fungsi tidak dapat throw apa pun secara implisit.

Hal lain yang menurut saya akan berguna adalah jika logika di catch {} memerlukan kata kunci break untuk memutus aliran kontrol. Terkadang Anda ingin menangani kesalahan tanpa memutus aliran kontrol. (mis: "untuk setiap item dari data ini lakukan sesuatu, tambahkan kesalahan non-nol ke daftar dan lanjutkan")

@KernelDeimos Saya setuju sepenuhnya dengan itu. Saya telah melihat situasi yang tepat seperti itu. Anda perlu menangkap kesalahan sebanyak mungkin sebelum memecahkan kode. Jika sesuatu seperti Saluran dapat digunakan pada situasi tersebut di GoLang yang bagus. maka Anda dapat mengirim semua kesalahan ke saluran yang diharapkan tangkapan dan kemudian tangkapan dapat menanganinya satu per satu.

Saya lebih suka memadukan "atau kembali" dengan #19642, #21498 daripada menggunakan try..catch (defer/panic/recover sudah ada; melempar dalam fungsi yang sama seperti memiliki beberapa pernyataan goto dan menjadi berantakan dengan sakelar tipe tambahan di dalam catch; memungkinkan melupakan penanganan kesalahan dengan memiliki try..catch tinggi di stack (atau secara signifikan mempersulit kompiler jika lingkup try..catch di dalam fungsi tunggal)

@gorse
Tampaknya sintaks try-catch yang disarankan @KamyarM adalah beberapa gula sintaks untuk menangani variabel pengembalian kesalahan, bukan pengantar untuk pengecualian. Meskipun saya lebih suka sintaks tipe "atau kembali" karena berbagai alasan, sepertinya itu saran yang sah.

Karena itu, @KamyarM , mengapa try memiliki porsi definisi variabel di dalamnya? Anda mendefinisikan variabel err , tetapi variabel itu dibayangi oleh variabel err di dalam blok itu sendiri. Apa tujuannya?

Saya pikir ini untuk memberi tahu variabel apa yang harus diperhatikan, memungkinkannya dipisahkan dari tipe error . Mungkin akan membutuhkan perubahan pada aturan yang membayangi, kecuali jika Anda hanya membutuhkan orang untuk benar-benar berhati-hati dengannya. Saya tidak yakin tentang deklarasi di blok catch .

@egorse Persis apa yang @DeedleFake sebutkan ada tujuannya. Ini berarti blok try memiliki mata pada objek itu. Juga membatasi ruang lingkupnya. Ini adalah sesuatu yang mirip dengan pernyataan using di C#. Dalam C# objek yang didefinisikan dengan menggunakan kata kunci secara otomatis dibuang setelah blok dieksekusi dan ruang lingkup objek tersebut terbatas pada blok "Menggunakan".
https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/using-statement

Menggunakan tangkapan diperlukan karena kami ingin memaksa pemrogram untuk memutuskan bagaimana menangani kesalahan dengan cara yang benar. Di C# dan Java, tangkap juga wajib. di C# jika Anda tidak ingin menangani pengecualian, Anda tidak menggunakan try-catch dalam fungsi itu sama sekali. Ketika pengecualian terjadi, metode apa pun dalam hierarki panggilan dapat menangani pengecualian atau bahkan melempar kembali (atau membungkusnya dengan pengecualian lain) lagi. Saya tidak berpikir Anda dapat melakukan hal yang sama di Jawa. Di Java, sebuah metode yang mungkin melempar pengecualian perlu mendeklarasikannya dalam tanda tangan fungsi.

Saya ingin menekankan bahwa blok try-catch ini bukanlah blok yang tepat. Saya menggunakan kata kunci ini karena ini mirip dengan apa yang ingin dicapai dan juga ini adalah apa yang banyak programmer kenal dan diajarkan di sebagian besar kursus konsep pemrograman.

Mungkin ada tugas _return on error_, yang hanya berfungsi jika ada _named error return parameter_, seperti pada:

func process(someInput string) (someOutput string, err error) {
    err ?= otherAction()
    return
}

Jika err bukan nil maka kembalilah.

Saya pikir diskusi tentang menambahkan gula try ke Rust akan mencerahkan peserta dalam diskusi ini.

FWIW, pemikiran lama tentang menyederhanakan penanganan kesalahan (maaf jika ini tidak masuk akal):

Pengidentifikasi kenaikan , dilambangkan dengan simbol tanda sisipan ^ , dapat digunakan sebagai salah satu operan di sisi kiri tugas. Untuk tujuan penetapan, pengidentifikasi kenaikan adalah alias untuk nilai kembalian terakhir dari fungsi yang memuat, terlepas dari apakah nilai tersebut memiliki nama atau tidak. Setelah penetapan selesai, fungsi menguji nilai pengembalian terakhir terhadap nilai nol tipenya (nil, 0, false, ""). Jika dianggap nol, fungsi terus dijalankan, jika tidak maka akan kembali.

Tujuan utama dari pengidentifikasi kenaikan adalah untuk secara ringkas menyebarkan kesalahan dari fungsi yang dipanggil kembali ke pemanggil dalam konteks tertentu tanpa menyembunyikan fakta bahwa ini terjadi.

Sebagai contoh, perhatikan kode berikut:

func Alpha() (string, error) {

    b, ^ := beta()
    g, ^ := gamma()
    return b + g, nil
}

Ini kira-kira setara dengan:

func Alpha() (ret1 string, ret2 error) {

    b, ret2 := beta()
    if ret2 != nil {
        return
    }

    g, ret2 := gamma()
    if ret2 != nil {
        return
    }

    return b + g, nil
}

Program salah format jika:

  • pengidentifikasi kenaikan gaji digunakan lebih dari sekali dalam tugas
  • fungsi tidak mengembalikan nilai
  • jenis nilai pengembalian terakhir tidak memiliki tes yang berarti dan efisien untuk nol

Saran ini serupa dengan saran lainnya karena tidak membahas masalah penyediaan informasi kontekstual yang lebih besar, untuk apa nilainya.

@gboyle Itu sebabnya nilai pengembalian terakhir IMO harus dinamai dan bertipe error . Ini memiliki dua implikasi penting:

1 - nilai pengembalian lainnya juga disebutkan, oleh karena itu
2 - mereka sudah memiliki nilai nol yang berarti.

@object88 Seperti yang context , ini memerlukan beberapa tindakan dari tim inti, seperti mendefinisikan tipe error bawaan (hanya Go normal error ) dengan beberapa atribut umum (pesan? tumpukan panggilan? dll dll).

AFAIK tidak banyak konstruksi bahasa kontekstual di Go. Selain go dan defer tidak ada yang lain dan bahkan keduanya sangat eksplisit dan jelas (bot dalam sintaks - dan mata - dan semantik).

Bagaimana dengan sesuatu yang seperti ini?

(menyalin beberapa kode nyata yang sedang saya kerjakan):

func (g *Generator) GenerateDevices(w io.Writer) error {
    var err error
    catch err {
        _, err = io.WriteString(w, "package cc\n\nconst (") // if err != nil { goto Caught }
        for _, bd := range g.zwClasses.BasicDevices {
            _, err = w.Write([]byte{'\t'}) // if err != nil { goto Caught }
            _, err = io.WriteString(w, toGoName(bd.Name)) // if err != nil { goto Caught }
            _, err = io.WriteString(w, " BasicDeviceType = ") // if err != nil { goto Caught }
            _, err = io.WriteString(w, bd.Key) // if err != nil { goto Caught }
            _, err = w.Write([]byte{'\n'}) // if err != nil { goto Caught }
        }
        _, err = io.WriteString(w, ")\n\nvar BasicDeviceTypeNames = map[BasicDeviceType]string{\n") // if err != nil { goto Caught }
       // ...snip
    }
    // Caught:
    return err
}

Ketika err bukan nihil, ia berhenti di akhir pernyataan "catch". Anda dapat menggunakan "catch" untuk mengelompokkan panggilan serupa yang biasanya menghasilkan jenis kesalahan yang sama. Bahkan jika panggilan tidak terkait, Anda dapat memeriksa jenis kesalahan setelahnya dan membungkusnya dengan benar.

@lukescott telah membaca posting blog ini oleh @robpike https://blog.golang.org/errors-are-values

@davecheney Ide menangkap (tanpa mencoba) tetap dalam semangat sentimen itu. Ini memperlakukan kesalahan sebagai nilai. Itu hanya rusak (dalam fungsi yang sama) ketika nilainya tidak lagi nihil. Itu tidak merusak program dengan cara apa pun.

@lukescott Anda dapat menggunakan teknik Rob hari ini, Anda tidak perlu mengubah bahasa.

Ada perbedaan yang agak besar antara pengecualian dan kesalahan:

  • kesalahan diharapkan (kita dapat menulis tes untuk mereka),
  • pengecualian tidak diharapkan (maka "pengecualian"),

Banyak bahasa memperlakukan keduanya sebagai pengecualian.

Antara obat generik dan penanganan kesalahan yang lebih baik, saya akan memilih penanganan kesalahan yang lebih baik karena sebagian besar kekacauan kode di Go berasal dari penanganan kesalahan. Meskipun dapat dikatakan verbositas semacam ini baik dan mendukung kesederhanaan, IMO juga mengaburkan _happy path_ alur kerja ke tingkat ambigu.

Saya ingin sedikit membangun proposal dari @thejerf .

Pertama, alih-alih ! , operator or diperkenalkan, shift itu adalah argumen yang dikembalikan terakhir dari pemanggilan fungsi di sisi kiri, dan memanggil pernyataan return di sebelah kanan, yang ekspresinya adalah fungsi yang dipanggil, jika argumen yang digeser bukan nol (bukan nol untuk tipe kesalahan), meneruskan argumen itu. Tidak apa-apa jika orang berpikir seharusnya hanya untuk jenis kesalahan juga, meskipun saya merasa bahwa konstruksi ini akan berguna untuk fungsi yang mengembalikan boolean sebagai argumen terakhir mereka juga (adalah sesuatu yang ok/tidak ok).

Metode Baca akan terlihat seperti ini:

func Read(filename string) error {
  f := OpenFile(filename) or return errors.Contextf("opening file %s", filename)
  b := ReadBytes(f) or return errors.Contextf("reading file %s", filename)
  ProcessBytes(b) or return errors.Context("processing data")
  return nil
}

Saya berasumsi paket kesalahan menyediakan fungsi kenyamanan seperti berikut:

func Noop() func(error) error {
   return func(err error) {
       return err   
   }
}


func Context(msg string) func(error) error {
    return func(err error) {
        return fmt.Errorf("%s: %v", msg, err)
    }
}
...

Ini terlihat sangat mudah dibaca, sementara mencakup semua poin yang diperlukan, dan tidak terlihat terlalu asing juga, karena familiar dengan pernyataan return.

@urandom Dalam pernyataan ini f := OpenFile(filename) or return errors.Contextf("opening file %s", filename) bagaimana alasannya dapat diketahui? Misalnya tidak ada izin baca atau file tidak ada sama sekali?

@dc0d
Yah, bahkan dalam contoh di atas, kesalahan asli disertakan, karena pesan yang diberikan pengguna hanya menambahkan konteks. Seperti yang dinyatakan, dan diturunkan dari proposal asli, or return mengharapkan fungsi yang menerima parameter tunggal dari tipe yang digeser. Ini adalah kuncinya, dan memungkinkan tidak hanya fungsi utilitas yang akan sesuai dengan kerumunan yang cukup besar, tetapi Anda dapat menulis sendiri jika Anda memerlukan penanganan nilai-nilai tertentu yang benar-benar kustom.

@urandom IMO menyembunyikan terlalu banyak.

2 sen saya di sini, saya ingin mengusulkan aturan sederhana:

"parameter kesalahan hasil implisit untuk fungsi"

Untuk fungsi apa pun, parameter kesalahan tersirat di akhir daftar parameter hasil
jika tidak didefinisikan secara eksplisit.

Asumsikan kita memiliki fungsi yang didefinisikan sebagai berikut untuk kepentingan diskusi:

func f() (int) {}
yang identik dengan: func f() (int, error) {}
menurut aturan kesalahan hasil implisit kami.

untuk tugas, Anda dapat memunculkan, mengabaikan, atau menangkap kesalahan sebagai berikut:

1) menggelembungkan

x := f()

jika f mengembalikan kesalahan, fungsi saat ini akan segera kembali dengan kesalahan
(atau buat tumpukan kesalahan baru?)
jika fungsi saat ini adalah utama, program akan berhenti.

Ini setara dengan cuplikan kode berikut:

x, salah := f()
jika salah != nihil {
kembali..., err
}

2) mengabaikan

x, _ := f()

pengidentifikasi kosong di akhir daftar ekspresi penetapan untuk secara eksplisit menandakan penghapusan kesalahan.

3) tangkap

x, salah := f()

err harus ditangani seperti biasa.

Saya percaya perubahan konvensi kode idiomatik ini seharusnya hanya memerlukan sedikit perubahan pada kompiler
atau preprocessor harus melakukan pekerjaan itu.

@dc0d Bisakah Anda memberikan contoh apa yang disembunyikannya, dan bagaimana caranya?

@urandom Ini adalah alasan yang menyebabkan pertanyaan "di mana kesalahan asli?", Seperti yang saya tanyakan di komentar sebelumnya. Ini melewati kesalahan secara implisit dan tidak jelas (misalnya) di mana kesalahan asli ditempatkan di baris ini: f := OpenFile(filename) or return errors.Contextf("opening file %s", filename) . Kesalahan asli dikembalikan oleh OpenFile() - yang mungkin seperti kurangnya izin baca atau file tidak ada dan bukan hanya "ada yang salah dengan nama file".

@dc0d
saya tidak setuju. Ini sejelas berurusan dengan http.Handlers, di mana dengan nanti Anda meneruskannya ke beberapa mux, dan tiba-tiba Anda mendapatkan permintaan dan penulis tanggapan. Dan orang-orang sudah terbiasa dengan perilaku seperti ini. Bagaimana orang tahu apa yang dilakukan pernyataan go ? Jelas tidak jelas pada pertemuan pertama, namun cukup meresap dan dalam bahasa.

Saya tidak berpikir kita harus menentang proposal apa pun dengan alasan bahwa itu baru dan tidak ada yang tahu cara kerjanya, karena itu berlaku untuk sebagian besar dari mereka.

@urandom Sekarang itu lebih masuk akal (termasuk contoh http.Handler ).

Dan kami sedang mendiskusikan banyak hal. Saya tidak berbicara menentang atau untuk ide tertentu. Tapi saya mendukung kesederhanaan dan menjadi eksplisit dan pada saat yang sama menyampaikan sedikit kewarasan tentang pengalaman pengembang.

@dc0d

yang mungkin seperti kurangnya izin baca atau file tidak ada

Dalam hal ini Anda tidak akan hanya mengulang kesalahan tetapi memeriksa konten yang sebenarnya. Bagi saya masalah ini adalah tentang meliput kasus yang paling populer. Artinya, lemparan ulang kesalahan dengan konteks tambahan. Hanya dalam kasus yang lebih jarang Anda mengubah kesalahan menjadi beberapa jenis konkret dan memeriksa apa yang sebenarnya dikatakannya. Dan untuk itu sintaks penanganan kesalahan saat ini baik-baik saja dan tidak akan kemana-mana bahkan jika salah satu proposal di sini akan diterima.

@creker Kesalahan bukan pengecualian (beberapa komentar saya sebelumnya). Kesalahan adalah nilai sehingga melempar atau melempar kembali tidak mungkin dilakukan. Untuk skenario coba/tangkap, Go memiliki kepanikan/pemulihan.

@dc0d Saya tidak berbicara tentang pengecualian. Dengan melempar kembali maksud saya mengembalikan kesalahan ke pemanggil. or return errors.Contextf("opening file %s", filename) diusulkan pada dasarnya membungkus dan mengembalikan kesalahan.

@creker Terima kasih atas penjelasannya. Itu juga menambahkan beberapa panggilan fungsi tambahan yang memengaruhi penjadwal yang pada gilirannya mungkin tidak menghasilkan perilaku yang diinginkan dalam beberapa situasi.

@dc0d itu detail implementasi dan mungkin berubah di masa mendatang. Dan itu benar-benar bisa berubah, pencegahan non-kooperatif sedang dikerjakan sekarang.

@creker
Saya pikir Anda dapat mencakup lebih banyak kasus daripada hanya mengembalikan kesalahan yang dimodifikasi:

func retryReadErrHandler(filename string, count int) func(error) error {
     return func(err error) error {
          if os.IsTimeout(err) {
               count++
               return Read(filename, count)
          }
          if os.IsPermission(err) {
               log.Fatal("Permission")
          }

          return fmt.Errorf("opening file %s: %v", filename, err)
      }
}

func Read(filename string, count int) error {
  if count > 3 {
    return errors.New("max retries")
  }

  f := OpenFile(filename) or return retryReadErrHandler(filename, count)

  ...
}

@dc0d
Panggilan fungsi tambahan mungkin akan digarisbawahi oleh kompiler

@urandom yang terlihat sangat menarik. Agak ajaib dengan argumen implisit tetapi yang ini sebenarnya bisa umum dan cukup ringkas untuk mencakup semuanya. Hanya dalam kasus yang sangat jarang Anda harus menggunakan if err != nil

@urandom , saya bingung dengan contoh Anda. Mengapa retryReadErrHandler mengembalikan fungsi?

@objek88
Itulah ide di balik operator or return . Ia mengharapkan fungsi yang akan dipanggilnya jika argumen terakhir yang dikembalikan bukan nol dari sisi kiri. Dalam hal ini ia bertindak persis sama dengan http.Handler, meninggalkan logika sebenarnya tentang cara menangani argumen dan pengembaliannya (atau permintaan dan responsnya, dalam kasus penangan), ke panggilan balik. Dan untuk menggunakan data kustom Anda sendiri dalam panggilan balik, Anda membuat fungsi pembungkus yang menerima data tersebut sebagai parameter dan mengembalikan apa yang diharapkan.

Atau dalam istilah yang lebih akrab, ini mirip dengan apa yang biasanya kita lakukan dengan penangan:
``` pergi
func nodeHandler(repo Repo) http.Handler {
kembalikan http.HandlerFunc(func(dengan http.ResponseWriter, r *http.Request) {
data, _ := json.Marshal(repo.GetNodes())
w.Tulis(data)
})
}

@urandom , Anda mungkin menghindari keajaiban dengan membiarkan LHS sama seperti hari ini dan mengubah or ... return menjadi returnif (cond) :

func Read(filename string) error {
   f, err := OpenFile(filename) returnif(err != nil) errors.Contextf(err, "opening file %s", filename)
   b, err := ReadBytes(f) returnif(err != nil) errors.Contextf(err, "reading file %s", filename)
   err = ProcessBytes(b) returnif(err != nil) errors.Context(err, "processing data")
   return nil
}

Ini meningkatkan keumuman dan transparansi nilai kesalahan di sebelah kiri dan kondisi pemicu di sebelah kanan.

Semakin saya melihat proposal yang berbeda ini, semakin saya cenderung menginginkan perubahan hanya-go. Bahasa sudah memiliki kekuatan, mari kita membuatnya lebih dapat dipindai. @billyh , bukan untuk memilih saran Anda secara khusus tetapi returnif(cond) ... hanyalah cara menulis ulang if cond { return ...} . Mengapa kita tidak bisa menulis yang terakhir saja? Kita sudah tahu artinya.

x, err := foo()
if err != nil { return fmt.Errorf(..., err) }

atau bahkan

if x, err := foo(); err != nil { return fmt.Errorf(..., err) }

atau

x, err := foo(); if err != nil { return fmt.Errorf(..., err) }

Tidak ada kata kunci ajaib atau sintaks atau operator baru.

(Mungkin membantu jika kami juga memperbaiki #377 untuk menambahkan beberapa fleksibilitas pada penggunaan := .)

@randall77 Saya juga semakin cenderung seperti itu.

@randall77 Pada titik mana blok itu akan dibungkus?

Solusi di atas lebih dapat diterima jika dibandingkan dengan alternatif yang diusulkan di sini, tetapi saya tidak yakin bahwa itu lebih baik daripada tidak mengambil tindakan. Gofmt harus sedeterministik mungkin.

@as , saya belum benar-benar memikirkannya, tetapi mungkin "jika isi pernyataan if berisi satu pernyataan return , maka pernyataan if diformat sebagai satu baris."

Mungkin perlu ada batasan tambahan pada kondisi if , seperti harus berupa variabel boolean atau operator biner dari dua variabel atau konstanta.

@billyh
Saya tidak melihat kebutuhan untuk membuatnya lebih bertele-tele, karena saya tidak melihat sesuatu yang membingungkan dari sedikit keajaiban di or . Saya berasumsi bahwa tidak seperti @as , banyak orang tidak menemukan sesuatu yang membingungkan dalam cara kami bekerja dengan penangan http.

@randall77
Apa yang Anda sarankan lebih sesuai sebagai saran gaya kode, dan di situlah go sangat berpendirian. Ini mungkin tidak berfungsi dengan baik di komunitas secara keseluruhan karena tiba-tiba ada 2 gaya pemformatan pernyataan if.

Belum lagi satu kalimat seperti itu jauh lebih sulit untuk dibaca. if != ; { } terlalu banyak bahkan di beberapa baris, maka proposal ini. Polanya tetap untuk hampir semua kasus dan dapat diubah menjadi gula sintaksis yang mudah dibaca dan dipahami.

Masalah yang saya miliki dengan sebagian besar saran ini adalah tidak jelas apa yang terjadi. Di posting pembuka disarankan untuk menggunakan kembali || untuk mengembalikan kesalahan. Tidak jelas bagi saya bahwa pengembalian terjadi di sana. Saya pikir jika sintaks baru akan ditemukan, itu perlu diselaraskan dengan harapan kebanyakan orang. Ketika saya melihat || saya tidak mengharapkan pengembalian, atau bahkan jeda dalam eksekusi. Ini menggelegar bagi saya.

Saya menyukai sentimen "kesalahan adalah nilai" Go, tetapi saya juga setuju bahwa if err := expression; err != nil { return err } terlalu bertele-tele, terutama karena hampir setiap panggilan diharapkan menghasilkan kesalahan. Ini berarti Anda akan memiliki banyak dari ini, dan mudah untuk mengacaukannya, tergantung di mana err dinyatakan (atau dibayangi). Itu telah terjadi dengan kode kita.

Karena Go tidak menggunakan try/catch dan menggunakan panic/defer untuk keadaan "luar biasa", kami mungkin memiliki kesempatan untuk menggunakan kembali kata kunci try dan/atau catch untuk mempersingkat penanganan kesalahan tanpa merusak program.

Inilah pemikiran yang saya miliki:

func WriteFooBar(w io.Writer) (err error) {
    _, try err = io.WriteString(w, "foo")
    _, try err = w.Write([]byte{','})
    _, try err = io.WriteString(w, "bar")
    return
}

Pikirannya adalah Anda awalan err pada LHS dengan kata kunci try . Jika err bukan nihil, pengembalian akan segera terjadi. Anda tidak harus menggunakan tangkapan di sini, kecuali pengembaliannya tidak sepenuhnya memuaskan. Ini lebih sesuai dengan harapan orang-orang tentang "coba hentikan eksekusi", tetapi alih-alih merusak program, program itu hanya kembali.

Jika pengembalian tidak sepenuhnya terpenuhi (pemeriksaan waktu kompilasi) atau kami ingin membungkus kesalahan, kami dapat menggunakan catch sebagai label khusus forward-only seperti ini:

func WriteFooBar(w io.Writer) (err error) {
    _, try err = io.WriteString(w, "foo")
    _, try err = w.Write([]byte{','})
    _, try err = io.WriteString(w, "bar")
    return
catch:
    return &CustomError{"some detail", err}
}

Ini juga memungkinkan Anda untuk memeriksa dan mengabaikan kesalahan tertentu:

func WriteFooBar(w io.Writer) (err error) {
    _, try err = io.WriteString(w, "foo")
    _, try err = w.Write([]byte{','})
    _, err = io.WriteString(w, "bar")
        if err == io.EOF {
            err = nil
        } else {
            goto catch
        }
    return
catch:
    return &CustomError{"some detail", err}
}

Mungkin Anda bahkan dapat meminta try menentukan label:

func WriteFooBar(w io.Writer) (err error) {
    _, try(handle1) err = io.WriteString(w, "foo")
    _, try(handle2) err = w.Write([]byte{','})
    _, try(handle3) err = io.WriteString(w, "bar")
    return
handle1:
    return &CustomError1{"...", err}
handle2:
    return &CustomError2{"...", err}
handle3:
    return &CustomError3{"...", err}
}

Saya menyadari contoh kode saya agak payah (foo/bar, ack). Tapi mudah-mudahan saya telah mengilustrasikan apa yang saya maksud dengan berjalan dengan/melawan harapan yang ada. Saya juga akan baik-baik saja dengan menjaga kesalahan seperti yang ada di Go 1. Tetapi jika sintaks baru ditemukan, perlu dipikirkan dengan cermat tentang bagaimana sintaks itu sudah dirasakan, tidak hanya di Go. Sulit untuk menemukan sintaks baru tanpa itu sudah berarti sesuatu, jadi seringkali lebih baik untuk mengikuti harapan yang ada daripada melawannya.

Mungkin semacam rantai seperti bagaimana Anda bisa merantai metode tetapi untuk kesalahan? Saya tidak begitu yakin seperti apa bentuknya atau apakah itu akan berhasil, hanya ide liar.
Anda dapat mengurutkan rantai sekarang untuk mengurangi jumlah pemeriksaan kesalahan dengan mempertahankan semacam nilai kesalahan dalam sebuah struct dan mengekstraknya di akhir rantai.

Ini adalah situasi yang sangat aneh karena meskipun ada sedikit boilerplate, saya tidak yakin bagaimana menyederhanakannya lebih jauh sambil tetap masuk akal.

Kode sampel @thejerf terlihat seperti ini dengan proposal @lukescott :

func NewClient(...) (*Client, error) {
    listener, try err := net.Listen("tcp4", listenAddr)
    defer func() {
        if err != nil {
            listener.Close()
        }
    }()

    conn, try err := ConnectionManager{}.connect(server, tlsConfig)
    defer func() {
        if err != nil {
            conn.Close()
        }
    }()

    if forwardPort == 0 {
        env, err := environment.GetRuntimeEnvironment()
        if err != nil {
            log.Printf("not forwarding because: %v", err)
        } else {
            forwardPort, err = env.PortToForward()
            if err != nil {
                log.Printf("env couldn't provide forward port: %v", err)
            }
        }
    }
    var forwardOut *forwarding.ForwardOut
    if forwardPort != 0 {
        u, _ := url.Parse(fmt.Sprintf("http://127.0.0.1:%d", forwardPort))
        forwardOut = forwarding.NewOut(u)
    }

    client := &Client{...}

    toServer := communicationProtocol.Wrap(conn)
    try err = toServer.Send(&client.serverConfig)

    try err = toServer.Send(&stprotocol.ClientProtocolAck{ClientVersion: Version})

    session, try err := communicationProtocol.FinalProtocol(conn)
    client.session = session

    return client, nil

catch:
    return nil, err
}

Dari 59 baris turun menjadi 47.

Ini adalah panjang yang sama, tapi saya pikir ini sedikit lebih jelas daripada menggunakan defer :

func NewClient(...) (*Client, error) {
    var openedListener, openedConn bool
    listener, try err := net.Listen("tcp4", listenAddr)
    openedListener = true

    conn, try err := ConnectionManager{}.connect(server, tlsConfig)
    openedConn = true

    if forwardPort == 0 {
        env, err := environment.GetRuntimeEnvironment()
        if err != nil {
            log.Printf("not forwarding because: %v", err)
        } else {
            forwardPort, err = env.PortToForward()
            if err != nil {
                log.Printf("env couldn't provide forward port: %v", err)
            }
        }
    }
    var forwardOut *forwarding.ForwardOut
    if forwardPort != 0 {
        u, _ := url.Parse(fmt.Sprintf("http://127.0.0.1:%d", forwardPort))
        forwardOut = forwarding.NewOut(u)
    }

    client := &Client{...}

    toServer := communicationProtocol.Wrap(conn)
    try err = toServer.Send(&client.serverConfig)

    try err = toServer.Send(&stprotocol.ClientProtocolAck{ClientVersion: Version})

    session, try err := communicationProtocol.FinalProtocol(conn)
    client.session = session

    return client, nil

catch:
    if openedConn {
        conn.Close()
    }
    if openedListener {
        listener.Close()
    }
    return nil, err
}

Contoh itu mungkin akan lebih mudah diikuti dengan deferifnotnil atau sesuatu.
Tapi itu agak kembali ke satu baris jika hal yang banyak dari saran ini adalah tentang.

Setelah bermain dengan kode contoh sedikit, saya sekarang menentang varian try(label) name . Saya pikir jika Anda memiliki banyak hal mewah untuk dilakukan, cukup gunakan sistem saat ini if err != nil { ... } . Jika pada dasarnya Anda melakukan hal yang sama, seperti menyetel pesan kesalahan khusus, Anda dapat melakukan ini:

func WriteFooBar(w io.Writer) (err error) {
    msg := "thing1 went wrong"
    _, try err = io.WriteString(w, "foo")
    msg = "thing2 went wrong"
    _, try err = w.Write([]byte{','})
    msg = "thing3 went wrong"
    _, try err = io.WriteString(w, "bar")
    return nil

catch:
    return &CustomError{msg, err}
}

Jika ada yang pernah menggunakan Ruby, ini sangat mirip dengan sintaks rescue , yang menurut saya terbaca dengan cukup baik.

Satu hal yang dapat dilakukan adalah menjadikan nil sebagai nilai falsy dan agar nilai lain dievaluasi menjadi true, sehingga Anda mendapatkan:

err := doSomething()
if err { return err }

Tapi saya tidak yakin itu akan benar-benar berhasil dan itu hanya mencukur beberapa karakter.
Saya telah mengetik banyak hal, tetapi saya rasa saya tidak pernah mengetik != nil .

Membuat antarmuka benar/salah telah disebutkan sebelumnya, dan saya mengatakan bahwa itu akan membuat bug dengan flag lebih umum:

verbose := flag.Bool("v", false, "verbose logging")
flag.Parse()
if verbose { ... } // should be *verbose!

@carlmjohnson , dalam contoh yang Anda berikan tepat di atas, ada pesan kesalahan yang diselingi dengan kode jalur bahagia, yang agak aneh bagi saya. Jika Anda perlu memformat string itu, maka Anda melakukan banyak pekerjaan ekstra terlepas dari apakah ada yang salah, atau tidak:

func (f *foo) WriteFooBar(w io.Writer) (err error) {
    msg := fmt.Sprintf("While writing %s, thing1 went wrong", f.foo)
    _, try err = io.WriteString(w, f.foo)
    msg = fmt.Sprintf("While writing %s, thing2 went wrong", f.separator)
    _, try err = w.Write(f.separator)
    msg = fmt.Sprintf("While writing %s, thing3 went wrong", f.bar)
    _, try err = io.WriteString(w, f.bar)
    return nil

catch:
    return &CustomError{msg, err}
}

@object88 , saya pikir analisis SSA harus dapat mengetahui apakah tugas tertentu tidak digunakan dan mengatur ulang agar tidak terjadi jika tidak diperlukan (terlalu optimis?). Jika itu benar, ini harus efisien:

func (f *foo) WriteFooBar(w io.Writer) (err error) {
    var format string, args []interface{}

    msg = "While writing %s, thing1 went wrong", 
    args = []interface{f.foo}
    _, try err = io.WriteString(w, f.foo)

    format = "While writing %s, thing2 went wrong"
    args = []interface{f.separator}
    _, try err = w.Write(f.separator)

    format = "While writing %s, thing3 went wrong"
    args = []interface{f.bar}
    _, try err = io.WriteString(w, f.bar)
    return nil

catch:
    msg := fmt.Sprintf(format, args...)
    return &CustomError{msg, err}
}

Apakah ini sah?

func Foo() error {
catch:
    try _ = doThing()
    return nil
}

Saya pikir itu harus diulang sampai doThing() mengembalikan nihil, tetapi saya dapat diyakinkan sebaliknya.

@carlmjohnson

Setelah bermain-main dengan kode contoh sedikit, saya sekarang menentang varian nama try(label).

Ya, saya tidak yakin tentang sintaksnya. Saya tidak menyukainya karena itu membuat try terlihat seperti panggilan fungsi. Tapi saya bisa melihat nilai dari menentukan label yang berbeda.

Apakah ini sah?

Saya akan mengatakan ya karena try seharusnya hanya untuk forward. Jika Anda ingin melakukan itu, saya akan mengatakan Anda perlu melakukannya seperti ini:

func Foo() error {
tryAgain:
    if err := doThing(); err != nil {
        goto tryAgain
    }
    return nil
}

Atau seperti ini:

func Foo() error {
    for doThing() != nil {}
    return nil
}

@Azareal

Satu hal yang dapat dilakukan adalah membuat nil menjadi nilai falsy dan nilai lainnya menjadi true, sehingga Anda mendapatkan: err := doSomething() if err { return err }

Saya pikir ada nilai dalam memperpendeknya. Namun, saya tidak berpikir itu harus berlaku untuk nihil di semua situasi. Mungkin ada antarmuka baru seperti ini:

interface Truthy {
  True() bool
}

Kemudian nilai apa pun yang mengimplementasikan antarmuka ini dapat digunakan seperti yang Anda usulkan.

Ini akan berfungsi selama kesalahan mengimplementasikan antarmuka:

err := doSomething()
if err { return err }

Tapi ini tidak akan berhasil:

err := doSomething()
if err == true { return err } // err is not true

Saya benar-benar baru mengenal golang tetapi bagaimana pendapat Anda tentang memperkenalkan delegator bersyarat seperti di bawah ini?

func someFunc() error {

    errorHandler := delegator(arg1 Arg1, err error) error if err != nil {
        // ...
        return err // or modifiedErr
    }

    ret, err := doSomething()
    delegate errorHandler(ret, err)

    ret, err := doAnotherThing()
    delegate errorHandler(ret, err)

    return nil
}

delegator berfungsi seperti barang tapi

  • return artinya return from its caller context . (tipe pengembalian harus sama dengan penelepon)
  • Dibutuhkan opsional if sebelum { , dalam contoh di atas adalah if err != nil .
  • Itu harus didelegasikan oleh penelepon dengan kata kunci delegate

Mungkin bisa menghilangkan delegate untuk mendelegasikan, tapi saya pikir itu membuat sulit untuk membaca aliran fungsi.

Dan mungkin ini berguna tidak hanya untuk penanganan kesalahan, saya tidak yakin sekarang.

Sangat bagus untuk menambahkan check , tetapi Anda dapat melakukan lebih jauh sebelum kembali:

result, err := openFile(f);
if err != nil {
        log.Println(..., err)
    return 0, err 
}

menjadi

result, err := openFile(f);
check err

```Pergi
hasil, err := openFile(f);
periksa kesalahan {
log.Println(..., err)
}

```Go
reslt, _ := check openFile(f)
// If err is not nil direct return, does not execute the next step.

```Pergi
hasil, err := periksa openFile(f) {
log.Println(..., err)
}

It also attempts simplifying the error handling (#26712):
```Go
result, err := openFile(f);
check !err {
    // err is an interface with value nil or holds a nil pointer
    // it is unusable
    result.C...()
}

Itu juga mencoba menyederhanakan (oleh beberapa dianggap membosankan) penanganan kesalahan (#21161). Ini akan menjadi:

result, err := openFile(f);
check err {
   // handle error and return
    log.Println(..., err)
}

Tentu saja, Anda dapat menggunakan try dan kata kunci lain daripada check , jika lebih jelas.

reslt, _ := try openFile(f)
// If err is not nil direct return, does not execute the next step.

```Pergi
hasil, err := openFile(f);
coba salah {
// menangani kesalahan dan kembali
log.Println(..., err)
}

Reference:

A plain idea, with support for error decoration, but requiring a more drastic language change (obviously not for go1.10) is the introduction of a new check keyword.

It would have two forms: check A and check A, B.

Both A and B need to be error. The second form would only be used when error-decorating; people that do not need or wish to decorate their errors will use the simpler form.

1st form (check A)
check A evaluates A. If nil, it does nothing. If not nil, check acts like a return {<zero>}*, A.

Examples

If a function just returns an error, it can be used inline with check, so
```Go
err := UpdateDB()    // signature: func UpdateDb() error
if err != nil {
    return err
}

menjadi

check UpdateDB()

Untuk fungsi dengan beberapa nilai kembalian, Anda harus menetapkan, seperti yang kita lakukan sekarang.

a, b, err := Foo()    // signature: func Foo() (string, string, error)
if err != nil {
    return "", "", err
}

// use a and b

menjadi

a, b, err := Foo()
check err

// use a and b

Formulir ke-2 (centang A, B)
check A, B mengevaluasi A. Jika nihil, tidak melakukan apa-apa. Jika tidak nihil, centang berfungsi seperti pengembalian {}*, B.

Ini untuk kebutuhan dekorasi kesalahan. Kami masih memeriksa A, tetapi B yang digunakan dalam pengembalian implisit.

Contoh

a, err := Bar()    // signature: func Bar() (string, error)
if err != nil {
    return "", &BarError{"Bar", err}
}

menjadi

a, err := Foo()
check err, &BarError{"Bar", err}

Catatan
Ini adalah kesalahan kompilasi untuk

gunakan pernyataan centang pada hal-hal yang tidak mengevaluasi kesalahan
gunakan fungsi check in dengan nilai kembalian tidak dalam bentuk { type }*, error
Pemeriksaan formulir dua-ekspr A, B dihubung pendek. B tidak dievaluasi jika A adalah nihil.

Catatan tentang kepraktisan
Ada dukungan untuk kesalahan dekorasi, tetapi Anda membayar untuk sintaks A, B yang lebih kikuk hanya ketika Anda benar-benar perlu menghias kesalahan.

Untuk if err != nil { return nil, nil, err } boilerplate (yang sangat umum) periksa err sesingkat mungkin tanpa mengorbankan kejelasan (lihat catatan pada sintaks di bawah).

Catatan tentang sintaks
Saya berpendapat bahwa sintaks semacam ini (periksa .., di awal baris, mirip dengan pengembalian) adalah cara yang baik untuk menghilangkan kesalahan pengecekan boilerplate tanpa menyembunyikan gangguan aliran kontrol yang diperkenalkan oleh pengembalian implisit.

Kelemahan dari ide-ide seperti||danmenangkapdi atas, atau a, b = foo()? diusulkan di utas lain, adalah bahwa mereka menyembunyikan modifikasi aliran kontrol dengan cara yang membuat aliran lebih sulit untuk diikuti; mantan dengan ||mesin ditambahkan di akhir garis yang tampak biasa, yang terakhir dengan simbol kecil yang dapat muncul di mana-mana, termasuk di tengah dan di akhir garis kode yang tampak biasa, mungkin beberapa kali.

Pernyataan cek akan selalu menjadi level teratas di blok saat ini, memiliki keunggulan yang sama dengan pernyataan lain yang mengubah aliran kontrol (misalnya, pengembalian awal).

Inilah pemikiran lain.

Bayangkan pernyataan again yang mendefinisikan makro dengan label. Pernyataan pernyataan yang diberi label dapat diperluas lagi dengan substitusi tekstual nanti dalam fungsi (mengingatkan pada const/iota, dengan nuansa goto :-] ).

Sebagai contoh:

func(foo int) (int, error) {
    err := f(foo)
again check:
    if err != nil {
        return 0, errors.Wrap(err)
    }
    err = g(foo)
    check
    x, err := h()
    check
    return x, nil
}

akan sama persis dengan:

func(foo int) (int, error) {
    err := f(foo)
    if err != nil {
        return 0, errors.Wrap(err)
    }
    err = g(foo)
    if err != nil {
        return 0, errors.Wrap(err)
    }
    x, err := h()
    if err != nil {
        return 0, errors.Wrap(err)
    }
    return x, nil
}

Perhatikan bahwa ekspansi makro tidak memiliki argumen - ini berarti bahwa seharusnya ada lebih sedikit kebingungan tentang fakta bahwa itu adalah makro, karena kompilator tidak menyukai simbol sendiri .

Seperti pernyataan goto, ruang lingkup label berada dalam fungsi saat ini.

Ide yang menarik. Saya menyukai ide label tangkap, tetapi menurut saya itu tidak cocok dengan cakupan Go (dengan Go saat ini, Anda tidak dapat goto label dengan variabel baru yang ditentukan dalam cakupannya). Ide again memperbaiki masalah itu karena label muncul sebelum cakupan baru diperkenalkan.

Ini contoh mega lagi:

func NewClient(...) (*Client, error) {
    var (
        err      error
        listener net.Listener
        conn     net.Conn
    )
    catch {
        if conn != nil {
            conn.Close()
        }
        if listener != nil {
            listener.Close()
        }
        return nil, err
    }

    listener, try err = net.Listen("tcp4", listenAddr)

    conn, try err = ConnectionManager{}.connect(server, tlsConfig)

    if forwardPort == 0 {
        env, err := environment.GetRuntimeEnvironment()
        if err != nil {
            log.Printf("not forwarding because: %v", err)
        } else {
            forwardPort, err = env.PortToForward()
            if err != nil {
                log.Printf("env couldn't provide forward port: %v", err)
            }
        }
    }
    var forwardOut *forwarding.ForwardOut
    if forwardPort != 0 {
        u, _ := url.Parse(fmt.Sprintf("http://127.0.0.1:%d", forwardPort))
        forwardOut = forwarding.NewOut(u)
    }

    client := &Client{...}

    toServer := communicationProtocol.Wrap(conn)
    try err = toServer.Send(&client.serverConfig)

    try err = toServer.Send(&stprotocol.ClientProtocolAck{ClientVersion: Version})

    session, try err := communicationProtocol.FinalProtocol(conn)
    client.session = session

    return client, nil
}

Ini adalah versi yang lebih dekat dengan proposal Rog (saya tidak terlalu menyukainya):

func NewClient(...) (*Client, error) {
    var (
        err      error
        listener net.Listener
        conn     net.Conn
    )
again:
    if err != nil {
        if conn != nil {
            conn.Close()
        }
        if listener != nil {
            listener.Close()
        }
        return nil, err
    }

    listener, err = net.Listen("tcp4", listenAddr)
    check

    conn, err = ConnectionManager{}.connect(server, tlsConfig)
    check

    if forwardPort == 0 {
        env, err := environment.GetRuntimeEnvironment()
        if err != nil {
            log.Printf("not forwarding because: %v", err)
        } else {
            forwardPort, err = env.PortToForward()
            if err != nil {
                log.Printf("env couldn't provide forward port: %v", err)
            }
        }
    }
    var forwardOut *forwarding.ForwardOut
    if forwardPort != 0 {
        u, _ := url.Parse(fmt.Sprintf("http://127.0.0.1:%d", forwardPort))
        forwardOut = forwarding.NewOut(u)
    }

    client := &Client{...}

    toServer := communicationProtocol.Wrap(conn)
    err = toServer.Send(&client.serverConfig)
    check

    err = toServer.Send(&stprotocol.ClientProtocolAck{ClientVersion: Version})
    check

    session, err := communicationProtocol.FinalProtocol(conn)
    check
    client.session = session

    return client, nil
}

@carlmjohnson Sebagai catatan, bukan itu yang saya sarankan. Pengidentifikasi "periksa" tidak istimewa - perlu dideklarasikan dengan meletakkannya setelah kata kunci "lagi".

Juga, saya menyarankan bahwa contoh di atas tidak menggambarkan penggunaannya dengan baik - tidak ada pernyataan berlabel lagi di atas yang tidak dapat dilakukan dengan baik dalam pernyataan penangguhan. Dalam contoh coba/tangkap, kode tersebut tidak dapat (misalnya) membungkus kesalahan dengan informasi tentang lokasi sumber pengembalian kesalahan. Itu juga tidak akan berfungsi AFAICS jika Anda menambahkan "coba" di dalam salah satu pernyataan if (misalnya untuk memeriksa kesalahan yang dikembalikan oleh GetRuntimeEnvironment), karena "err" yang dirujuk oleh pernyataan catch berada dalam cakupan yang berbeda dari itu dideklarasikan di dalam blok.

Saya pikir satu-satunya masalah saya dengan kata kunci check adalah bahwa semua keluar ke suatu fungsi harus berupa return (atau setidaknya memiliki _some_ jenis konotasi "Saya akan meninggalkan fungsi"). Kami _mungkin_ mendapatkan become (untuk TCO), setidaknya become memiliki semacam "Kami menjadi fungsi yang berbeda"... tetapi kata "periksa" benar-benar tidak terdengar seperti itu akan menjadi jalan keluar untuk fungsi tersebut.

Titik keluar dari suatu fungsi sangat penting, dan saya tidak yakin apakah check benar-benar memiliki perasaan "titik keluar". Selain itu, saya sangat menyukai gagasan tentang apa yang dilakukan check , ini memungkinkan penanganan kesalahan yang jauh lebih ringkas, tetapi masih memungkinkan untuk menangani setiap kesalahan secara berbeda, atau untuk membungkus kesalahan sesuai keinginan Anda.

Bisakah saya menambahkan saran juga?
Bagaimana dengan sesuatu yang seperti ini:

func Open(filename string) os.File onerror (string, error) {
       f, e := os.Open(filename)
       if e != nil { 
              fail "some reason", e // instead of return keyword to go on the onerror 
       }
      return f
}

f := Open(somefile) onerror reason, e {
      log.Prinln(reason)
      // try to recover from error and reasign 'f' on success
      nf = os.Create(somefile) onerror err {
             panic(err)
      }
      return nf // return here must return whatever Open returns
}

Penugasan kesalahan dapat memiliki bentuk apa pun, bahkan menjadi sesuatu yang bodoh seperti

f := Open(name) =: e

Atau kembalikan serangkaian nilai yang berbeda jika terjadi kesalahan bukan hanya kesalahan, Dan juga blok coba tangkap akan menyenangkan.

try {
    f := Open("file1") // can fail here
    defer f.Close()
    f1 := Open("file2") // can fail here
    defer f1.Close()
    // do something with the files
} onerror err {
     log.Println(err)
}

@cthackers Saya pribadi percaya bahwa sangat bagus untuk kesalahan di Go untuk tidak memiliki perlakuan khusus. Mereka hanyalah nilai-nilai, dan saya pikir mereka harus tetap seperti itu.

Juga, try-catch (dan konstruksi serupa) hanya ada di sekitar konstruksi buruk yang mendorong praktik buruk. Setiap kesalahan harus ditangani secara terpisah, tidak ditangani oleh beberapa penangan kesalahan "tangkap semua".

https://go.googlesource.com/proposal/+/master/design/go2draft-error-handling-overview.md
ini terlalu rumit.

ide saya: |err| artinya periksa kesalahan : if err!=nil {}

// common util func
func parseInt(s string) (i int64, err error){
    return strconv.ParseInt(s, 10, 64)
}

// expression check err 1 : check and use err variable
func parseAndSum(a string ,b string) (int64,error) {
    sum := parseInt(a) + parseInt(b)  |err| return 0,err
    return sum,nil
} 

// expression check err 2 : unuse variable 
func parseAndSum(a string , b string) (int64,error) {
    a,err := parseInt(a) |_| return 0, fmt.Errorf("parseInt error: %s", a)
    b,err := parseInt(b) |_| { println(b); return 0,fmt.Errorf("parseInt error: %s", b);}
    return a+b,nil
} 

// block check err 
func parseAndSum(a string , b string) (  int64,  error) {
    {
      a := parseInt(a)  
      b := parseInt(b)  
      return a+b,nil
    }|err| return 0,err
} 

@chen56 dan semua komentator masa depan: Lihat https://go.googlesource.com/proposal/+/master/design/go2draft.md .

Saya menduga ini sudah usang utas ini sekarang dan tidak ada gunanya melanjutkan di sini. Halaman umpan balik Wiki adalah tempat yang mungkin akan terjadi di masa depan.

Contoh besar menggunakan proposal Go 2:

func NewClient(...) (*Client, error) {
    var (
        listener net.Listener
        conn     net.Conn
    )
    handle err {
        if conn != nil {
            conn.Close()
        }
        if listener != nil {
            listener.Close()
        }
        return nil, err
    }

    listener = check net.Listen("tcp4", listenAddr)

    conn = check ConnectionManager{}.connect(server, tlsConfig)

    if forwardPort == 0 {
        env, err := environment.GetRuntimeEnvironment()
        if err != nil {
            log.Printf("not forwarding because: %v", err)
        } else {
            forwardPort, err = env.PortToForward()
            if err != nil {
                log.Printf("env couldn't provide forward port: %v", err)
            }
        }
    }
    var forwardOut *forwarding.ForwardOut
    if forwardPort != 0 {
        u, _ := url.Parse(fmt.Sprintf("http://127.0.0.1:%d", forwardPort))
        forwardOut = forwarding.NewOut(u)
    }

    client := &Client{...}

    toServer := communicationProtocol.Wrap(conn)
    check toServer.Send(&client.serverConfig)

    check toServer.Send(&stprotocol.ClientProtocolAck{ClientVersion: Version})

    session := check communicationProtocol.FinalProtocol(conn)
    client.session = session

    return client, nil
}

Saya pikir ini sebersih yang bisa kita harapkan. Blok handle memiliki kualitas yang baik dari label rescue kata kunci again Ruby. Satu-satunya pertanyaan yang tersisa di benak saya adalah apakah akan menggunakan tanda baca atau kata kunci (menurut saya kata kunci) dan apakah mengizinkan kesalahan tanpa mengembalikannya.

Saya mencoba memahami proposal - tampaknya hanya ada satu blok pegangan per fungsi, daripada kemampuan untuk membuat respons berbeda terhadap kesalahan berbeda di seluruh proses eksekusi fungsi. Ini sepertinya kelemahan yang nyata.

Saya juga bertanya-tanya apakah kita mengabaikan kebutuhan kritis untuk mengembangkan harness pengujian di sistem kita juga. Mempertimbangkan bagaimana kita akan menjalankan jalur kesalahan selama pengujian harus menjadi bagian dari diskusi, tetapi saya juga tidak melihatnya,

@sdwarwick Saya rasa ini bukan tempat terbaik untuk membahas rancangan desain yang dijelaskan di https://go.googlesource.com/proposal/+/master/design/go2draft-error-handling.md . Pendekatan yang lebih baik adalah menambahkan tautan ke artikel di halaman wiki di https://github.com/golang/go/wiki/Go2ErrorHandlingFeedback .

Yang mengatakan, rancangan desain itu mengizinkan beberapa blok pegangan dalam suatu fungsi.

Masalah ini dimulai sebagai proposal khusus. Kami tidak akan mengadopsi proposal itu. Ada banyak diskusi hebat tentang masalah ini, dan saya berharap orang-orang akan menarik ide-ide bagus ke dalam proposal terpisah dan ke dalam diskusi tentang rancangan desain terbaru. Saya akan menutup masalah ini. Terima kasih untuk semua diskusinya.

Jika berbicara dalam kumpulan contoh ini:

r, err := os.Open(src)
    if err != nil {
        return err
    }

Yang ingin saya tulis dalam satu baris kira-kira demikian:

r, err := os.Open(src) try ("blah-blah: %v", err)

Alih-alih "mencoba", letakkan kata yang indah dan cocok.

Dengan sintaks seperti itu, kesalahan akan kembali dan sisanya akan menjadi beberapa nilai default tergantung pada jenisnya. Jika saya perlu kembali dengan kesalahan dan hal lain yang spesifik, daripada default, maka tidak ada yang membatalkan opsi klasik yang lebih banyak baris.

Bahkan lebih singkat (tanpa menambahkan semacam penanganan kesalahan):

r, err := os.Open(src) try

)
PS Permisi untuk bahasa Inggris saya))

Varian saya:

func CopyFile(src, dst string) string, error {
    r := check os.Open(src) // return nil, error
    defer r.Close()

    // if error: run 1 defer and retun error message
    w := check os.Create(dst) // return nil, error
    defer w.Close()

    // if error: run 2, 1 defer and retun error message
    if check io.Copy(w, r) // return nil, error

}

func StartCopyFile() error {
  res := check CopyFile("1.txt", "2.txt")

  return nil
}

func main() {
  err := StartCopyFile()
  if err!= nil{
    fmt.printLn(err)
  }
}

Halo,

Saya punya ide sederhana, yang didasarkan secara longgar pada cara kerja penanganan kesalahan di Shell, seperti proposal awal. Dalam kesalahan shell dikomunikasikan dengan mengembalikan nilai yang tidak sama dengan nol. Nilai kembalian dari perintah/panggilan terakhir disimpan di $? dalam cangkang. Selain nama variabel yang diberikan oleh pengguna, kami dapat secara otomatis menyimpan nilai kesalahan dari panggilan terbaru dalam variabel yang telah ditentukan dan membuatnya dapat diperiksa dengan sintaks yang telah ditentukan sebelumnya. saya sudah memilih? sebagai sintaks untuk mereferensikan nilai kesalahan terbaru, yang telah dikembalikan dari panggilan fungsi dalam cakupan saat ini. Saya telah memilih! sebagai singkatan dari jika ? != nihil {}. Pilihan untuk? dipengaruhi oleh shell, tetapi juga karena tampaknya masuk akal. Jika terjadi kesalahan, Anda tentu tertarik dengan apa yang terjadi. Ini menimbulkan pertanyaan. ? adalah tanda umum untuk pertanyaan yang diajukan dan oleh karena itu kami menggunakannya untuk merujuk nilai kesalahan terbaru yang dihasilkan dalam lingkup yang sama.
! digunakan sebagai kependekan dari if ? != nihil, karena menandakan bahwa perhatian harus diberikan jika terjadi kesalahan. ! artinya: jika ada yang salah, lakukan ini. ? referensi nilai kesalahan terbaru. Seperti biasa nilai ? sama dengan nihil jika tidak ada kesalahan.

val, err := someFunc(param)
! { return &SpecialError("someFunc", param, ?) }

Untuk membuat sintaks lebih menarik, saya mengizinkan penempatan ! baris tepat di belakang panggilan serta menghilangkan kawat gigi.
Dengan proposal ini, Anda juga dapat menangani kesalahan tanpa menggunakan pengenal yang ditentukan pemrogram.

Ini akan diizinkan:

val, _ := someFunc(param)
! return &SpecialError("someFunc", param, ?)

Ini akan diizinkan

val, _ := someFunc(param) ! return &SpecialError("someFunc", param, ?)

Di bawah proposal ini Anda tidak harus kembali dari fungsi saat terjadi kesalahan
dan Anda dapat mencoba memulihkan dari kesalahan.

val, _ := someFunc(param)
! {
val, _ := someFunc(paramAlternative)
  !{ return &SpecialError("someFunc alternative try failed too", paramAlternative, ?) }}

Di bawah proposal ini Anda dapat menggunakan ! dalam loop for untuk beberapa percobaan ulang seperti ini.

val, _ := someFunc(param)
for i :=0; ! && i <5; i++ {
  // Sleep or make a change or both
  val, _ := someFunc(param)
} ! { return &SpecialError("someFunc", param, ? }

Saya sadar itu! terutama digunakan untuk negasi ekspresi, sehingga sintaks yang diusulkan dapat menyebabkan kebingungan pada yang belum tahu. Idenya adalah itu! dengan sendirinya berkembang menjadi ? != nil ketika digunakan dalam ekspresi kondisional dalam kasus seperti yang ditunjukkan contoh di atas, di mana itu tidak dilampirkan ke ekspresi tertentu. Baris for atas tidak dapat dikompilasi dengan go saat ini, karena tidak masuk akal tanpa konteks. Di bawah proposal ini! dengan sendirinya benar, ketika kesalahan telah terjadi dalam panggilan fungsi terbaru, yang dapat mengembalikan kesalahan.

Pernyataan pengembalian untuk mengembalikan kesalahan disimpan, karena seperti yang dikomentari orang lain di sini, diinginkan untuk melihat sekilas di mana fungsi Anda kembali. Anda dapat menggunakan sintaks ini dalam skenario di mana kesalahan tidak mengharuskan Anda meninggalkan fungsi.

Proposal ini lebih sederhana daripada beberapa proposal lainnya, karena tidak ada upaya untuk membuat varian blok coba dan tangkap seperti sintaks yang dikenal dari bahasa lain. Itu terus berjalan filosofi saat ini menangani kesalahan secara langsung di mana mereka terjadi dan membuatnya lebih ringkas untuk melakukannya.

@tobimensch tolong posting saran baru ke wiki umpan balik Penanganan Kesalahan Go 2 . Posting tentang masalah tertutup ini mungkin diabaikan.

Jika Anda belum melihatnya, Anda mungkin ingin membaca Desain Draf Penanganan Kesalahan Go 2 .

Dan Anda mungkin tertarik dengan Persyaratan yang Perlu Dipertimbangkan untuk Penanganan Kesalahan Go 2 .

Mungkin agak terlambat untuk menunjukkannya, tetapi apa pun yang terasa seperti sihir javascript mengganggu saya. Saya berbicara tentang operator || yang entah bagaimana seharusnya bekerja secara ajaib dengan intedface error . Saya tidak menyukainya.

Apakah halaman ini membantu?
0 / 5 - 0 peringkat