Hata İşleme Tekniklerine Git [kapalı]


108

Go ile yeni başlıyorum. Kodum bundan çok şey almaya başlıyor:

   if err != nil {
      //handle err
   }

veya bu

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

Go'da hataları kontrol etmek ve işlemek için bazı iyi deyimler / stratejiler / en iyi uygulamalar var mı?

Açıklığa kavuşturmak için DÜZENLEME: Karıncalanmıyorum veya Go ekibinin daha iyi bir şey bulmasını önermiyorum. Bunu doğru yapıp yapmadığımı veya topluluğun bulduğu bazı teknikleri özledim mi diye soruyorum. Hepinize teşekkürler.


4
Hayır, gerçekten yok. Bu sıkça tartışılan bir konu ve mantıklı bir konu. Çok sayıda evrim önerisi de vardı. Ekibin cevabı, iyi yazılmış bir kodda sorun olmaması gerektiği şeklinde görünüyor.
Denys Séguret


Bu ilgili sorunun aslında bununla aynı olmadığını unutmayın. Cevaplar çok özel.
Denys Séguret

Bu rahatsızlığın da bir mantığı var: Bir programı hızlı bir şekilde yazmayı zorlaştırır, ancak aynı zamanda hataları yeniden atarak hata oluşturmayı da zorlaştırır.
Denys Séguret

Andrew Gerrand ve Brad Fitzpatrick az ya da çok benzer moda içinde git bir HTTP / 2 müşterinin başlangıçlar yazma bulabilirsiniz youtube.com/watch?v=yG-UaBJXZ80
Supreet Sethi

Yanıtlar:


61

Kodunuz deyimseldir ve bence mevcut en iyi uygulamadır. Bazıları kesinlikle aynı fikirde olmayacaktı, ancak bunun Golang'daki standart kütüphanelerin her yerinde görülen stil olduğunu iddia ediyorum . Başka bir deyişle, Go yazarları hata işlemeyi bu şekilde yazar.


12
"Go yazarları hata işlemeyi bu şekilde yazar." Bana uyar.
gmoore

"Bazıları kesinlikle aynı fikirde değil " : Birisinin bugün mevcut olan en iyi uygulama olmadığını söyleyeceğinden emin değilim. Bazıları sözdizimi şekeri veya başka değişiklikler soruyor, ancak bugün ciddi bir kodlayıcının hataları başka türlü kontrol edeceğini düşünmüyorum.
Denys Séguret

@dystroy: Tamam, bazıları " bu sux " diyor, diğerleri buna "hatalar dönüş değerlerinde işleniyor. 70'in stili" diyor. ve benzeri ;-)
zzzz

2
@jnml Hataları bu şekilde ele almak, oldukça tartışmalı bir konu olan dil tasarımı meselesidir. Neyse ki aralarından seçim yapabileceğiniz düzinelerce dil var.
fuz

4
Beni öldüren şey, her bir işlev çağrısı için aynı kalıbın nasıl kullanıldığıdır. Bu, kodu bazı yerlerde oldukça gürültülü hale getirir ve sadece herhangi bir bilgiyi kaybetmeden kodu basitleştirmek için sözdizimsel şeker için çığlık atıyor, bu özlü olmanın tanımıdır (bence ayrıntıdan daha üstün bir özellik olduğuna inanıyorum, ancak bu tartışmalı bir nokta). Prensip sağlam, ancak sözdizimi istenen IMHO'ya çok şey bırakıyor. Ancak şikayet etmek yasak, bu yüzden şimdi sadece kool-aidimi içeceğim ;-)
Thomas

30

Bu sorunun sorulmasından altı ay sonra Rob Pike, Hatalar Değerlerdir başlıklı bir blog yazısı yazdı. .

Orada, OP tarafından sunulan şekilde programlamanız gerekmediğini savunuyor ve standart kitaplıkta farklı bir model kullandıkları birkaç yerden bahsediyor.

Elbette, bir hata değeri içeren yaygın bir ifade, sıfır olup olmadığını test etmektir, ancak bir hata değeriyle yapılabilecek sayısız başka şey vardır ve bu diğer şeylerden bazılarının uygulanması, programınızı daha iyi hale getirebilir ve standartların çoğunu ortadan kaldırabilir. bu, her hatanın bir rote if ifadesiyle kontrol edilmesi durumunda ortaya çıkar.

...

Hata işlemenizi basitleştirmek için dili kullanın.

Ancak unutmayın: Ne yaparsanız yapın, her zaman hatalarınızı kontrol edin!

İyi bir okuma.


Teşekkürler! Bunu kontrol edeceğim.
gmoore

Makale harika, temelde başarısız durumda olabilecek bir nesneyi tanıtıyor ve eğer öyleyse, onunla yaptığınız her şeyi görmezden gelecek ve başarısız durumda kalacaktır. Bana neredeyse monad gibi geliyor.
Waterlink

@Waterlink İfadeniz anlamsız. Biraz gözlerinizi kısarsanız, bir durumu olan her şey neredeyse monaddır. Bunu en.wikipedia.org/wiki/Null_Object_pattern ile karşılaştırmak bence daha kullanışlı.
user7610

@ user7610, Geri bildiriminiz için teşekkürler. Ben sadece katılıyorum.
Waterlink

2
Pike: "Ama unutma: Ne yaparsan yap, her zaman hatalarını kontrol et!" - bu çok 80'ler. Hatalar her yerde ortaya çıkabilir, programcılara yük yüklemeyi durdurur ve Pete'in hatırı için istisnalar benimser.
Slawomir

22

Jnml'nin her ikisinin de deyimsel kod olduğu yanıtına katılıyorum ve aşağıdakileri ekleyeceğim:

İlk örneğiniz:

if err != nil {
      //handle err
}

birden fazla dönüş değeri söz konusu olduğunda daha deyimseldir. Örneğin:

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

İkinci örneğiniz, yalnızca errdeğerle uğraşırken güzel bir kısaltmadır . Bu, işlev yalnızca bir döndürüyorsa errorveya döndürülen değerleri kasıtlı olarak yok sayarsanız geçerlidir error. Bir örnek olarak, bu bazen kullanılır Readerve Writerbir dönüş fonksiyonları intyazılı bayt (bazen gereksiz bilgiler) sayısının ve error:

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

İkinci form, eğer başlatma ifadesi olarak kullanılır .

Bu nedenle, en iyi uygulamalarla ilgili olarak, bildiğim kadarıyla ( ihtiyaç duyduğunuzda yeni hatalar oluşturmak için "hatalar" paketini kullanmak dışında ), Go'daki hatalar hakkında bilmeniz gereken hemen hemen her şeyi ele aldınız!

DÜZENLEME: İstisnasız gerçekten yaşayamayacağınızı anlarsanız defer, panic&recover ile onları taklit edebilirsiniz .


4

Sorunsuz hata işleme ve bir Go işlevi kuyruğu aracılığıyla borulama için bir kitaplık yaptım.

Burada bulabilirsiniz: https://github.com/go-on/queue

Kompakt ve ayrıntılı sözdizimsel bir varyantı vardır. İşte kısa sözdizimi için bir örnek:

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

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

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

Yansımadan yararlandığı için biraz performans yükü olduğunu lütfen unutmayın.

Ayrıca bu deyimsel go kodu değildir, bu nedenle kendi projelerinizde kullanmak isteyeceksiniz veya ekibiniz kullanmayı kabul ederse.


3
Eğer Sırf olabilir Bunu yapmak, bunun iyi bir fikir olduğu anlamına gelmez. Bu , belki okunması daha zor (görüş) dışında , Sorumluluk Zinciri modeline benziyor . Bunun "deyimsel Go" olmadığını öneririm. Yine de ilginç.
Steven Soroka

2

Golang'daki ve diğer dillerdeki hataları ele almak için bir "strateji", bu hatayı ele almak için çağrı yığınında yeterince yüksek olana kadar sürekli olarak çağrı yığınındaki hataları yaymaktır. Bu hatayı çok erken halletmeyi denediyseniz, muhtemelen kodu tekrarlamakla sonuçlanacaksınız. Çok geç hallederseniz, kodunuzdaki bir şeyi kırarsınız. Golang, belirli bir konumdaki bir hatayı mı ele aldığınızı veya onu yaydığınızı çok net hale getirdiği için bu süreci süper kolaylaştırır.

Hatayı görmezden gelecekseniz, basit bir _ bu gerçeği çok net bir şekilde ortaya çıkaracaktır. Eğer bunu hallediyorsanız, o zaman işlemekte olduğunuz hatanın tam olarak hangi durumu, if ifadesinde kontrol edeceğiniz için açıktır.

İnsanların yukarıda söylediği gibi, hata aslında sadece normal bir değerdir. Bu ona öyle davranır.


2

Go tanrıları, Go 2'de hata işleme için bir "taslak tasarım" yayınladı. Hata deyimini değiştirmeyi hedefliyor:

Genel Bakış ve Tasarım

Kullanıcılardan geri bildirim istiyorlar!

Geri bildirim wiki

Kısaca şöyle görünür:

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

GÜNCELLEME: Taslak tasarım çok fazla eleştiri aldı, bu yüzden nihai bir çözüm için bir olasılıklar menüsü ile Go 2 Hata İşleme için Dikkate Alınması Gereken Gereksinimler tasarladım .


1

Endüstrideki çoğu golang dokümantasyonu Hata işleme ve Git'te belirtilen standart kuralları takip ediyor . Ayrıca proje için doküman oluşturmaya da yardımcı olur.


Bu aslında yalnızca bağlantıya dayalı bir cevaptır. Cevaba bir miktar içerik eklemenizi öneririm, böylece bağlantı geçersiz hale gelirse, cevabınız yine de işe yarayabilir.
Neo

değerli yorumunuz için teşekkür ederim.
pschilakanti

0

Aşağıda, Go için hata işlemeyi azaltma konusundaki yaklaşımım, örnek HTTP URL parametrelerini almak içindir:

( Https://blog.golang.org/errors-are-values ​​adresinden türetilen tasarım deseni )

type HTTPAdapter struct {
    Error *common.AppError
}

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

birden fazla olası parametre için çağrı aşağıdaki gibi olacaktır:

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

Bu sihirli bir mermi değil, dezavantajı, birden fazla hatanız varsa, yalnızca son hatayı alabilirsiniz.

Ancak bu durumda, nispeten tekrarlayan ve düşük riskli, bu nedenle mümkün olan en son hatayı alabilirim.


-1

Sen can (hatalar burada dikkatli olmak zorunda değerlerdir beri) benzer hatalar için hata işleme kodunu temizlemek ve hatayı işlemek için geçilen hata ile çağrı bir fonksiyon yazın. Her seferinde "if err! = Nil {}" yazmanız gerekmez. Yine, bu yalnızca kodu temizlemeyle sonuçlanacaktır, ancak bunun bir şeyleri yapmanın deyimsel yolu olduğunu düşünmüyorum.

Yine, yapabiliyor olman , yapman gerektiği anlamına gelmez .


-1

goerr , işlevlerdeki hataları işlemeye izin verir

package main

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

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

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

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

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

    // handle errors

    fmt.Println(err_do_something)
}

Ick. Hata işleme mantığını bunun gibi karmaşıklaştırmak / gizlemek iyi bir fikir değildir IMO. Ortaya çıkan kod (goerr tarafından kaynak ön işlemesi gerektirir), deyimsel Go kodundan daha zordur.
Dave C

-4

Hataların hassas kontrolünü istiyorsanız, çözüm bu olmayabilir, ancak benim için çoğu zaman herhangi bir hata bir gösteri durdurucusudur.

Yani bunun yerine işlevleri kullanıyorum.

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

fi, err := os.Open("mmm.txt")
Err(err)

Bu tür mesajlar stderryerine gitmelidir stdout, bu yüzden sadece log.Fatal(err)veya kullanın log.Fatalln("some message:", err). Neredeyse hiçbir şey maintüm programı sonlandırmak için böyle bir karar vermemesi gerektiğinden (yani işlevlerden / yöntemlerden hataları döndür, iptal etmeyin) nadir durumlarda yapmak istediğiniz şey budur, daha temiz ve açık bir şekilde yapmak daha iyidir (örn. if err := someFunc(); err != nil { log.Fatal(err) }) ne yaptığı konusunda belirsiz olan bir "yardımcı" işlevi yerine ("Err" adı iyi değildir, programı sonlandırabileceğine dair hiçbir belirti vermez).
Dave C

Yeni bir şey öğrendim! Teşekkür ederim @DaveC
Gon
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.