Techniki obsługi błędów Go [zamknięte]


108

Dopiero zaczynam z Go. Mój kod zaczyna mieć dużo tego:

   if err != nil {
      //handle err
   }

albo to

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

Czy są jakieś dobre idiomy / strategie / najlepsze praktyki dotyczące sprawdzania i obsługi błędów w Go?

EDYTUJ, aby wyjaśnić: nie oszukuję ani nie sugeruję, aby zespół Go wymyślił coś lepszego. Pytam, czy robię to dobrze, czy też przegapiłem jakąś technikę wymyśloną przez społeczność. Dziękuje wszystkim.


4
Nie, naprawdę nie ma. To często dyskutowany i rozsądny temat. Było też wiele propozycji ewolucji. Zespół wydaje się, że nie powinno to stanowić problemu w dobrze napisanym kodzie.
Denys Séguret


Zauważ, że to pokrewne pytanie nie jest takie samo jak to. Odpowiedzi są zbyt szczegółowe.
Denys Séguret

Istnieje również uzasadnienie tej irytacji: utrudnia to szybkie napisanie programu, ale także utrudnia tworzenie błędów przez zwykłe ponowne zgłaszanie błędów.
Denys Séguret

Andrew Gerrand i Brad Fitzpatrick piszą początki klienta HTTP / 2 w Go w mniej więcej podobny sposób youtube.com/watch?v=yG-UaBJXZ80
Supreet Sethi

Odpowiedzi:


61

Twój kod jest idiomatyczny i moim zdaniem jest to najlepsza dostępna praktyka. Niektórzy na pewno by się z tym nie zgodzili, ale ja argumentowałbym, że jest to styl widoczny we wszystkich standardowych bibliotekach w Golangu . Innymi słowy, autorzy Go piszą w ten sposób obsługę błędów.


12
„Go autorzy piszą obsługę błędów w ten sposób”. Dla mnie brzmi dobrze.
gmoore

„Niektórzy na pewno się nie zgodzą” : Nie jestem pewien, czy ktoś powiedziałby, że nie jest to najlepsza dostępna obecnie praktyka. Niektórzy proszą o zmianę składni lub inne zmiany, ale dziś nie sądzę, aby żaden poważny programista sprawdzał błędy w inny sposób.
Denys Séguret

@dystroy: OK, niektórzy mówią „ it sux ”, inni nazywają to „błędy są obsługiwane w zwracanych wartościach. Styl lat 70-tych”. i tak dalej ;-)
zzzz

2
@jnml Obsługa błędów w ten sposób jest kwestią projektu języka, co jest bardzo kontrowersyjnym tematem. Na szczęście do wyboru jest kilkadziesiąt języków.
fuz

4
To, co mnie po prostu zabija, to sposób, w jaki ten sam wzorzec jest używany dla każdego wywołania funkcji. To sprawia, że ​​kod jest dość hałaśliwy w niektórych miejscach i po prostu krzyczy o cukier składniowy, aby uprościć kod bez utraty jakichkolwiek informacji, co jest w istocie definicją zwięzłości (która moim zdaniem jest nadrzędnym atrybutem niż gadatliwość, ale jest to prawdopodobnie kontrowersyjne punkt). Zasada jest rozsądna, ale składnia pozostawia wiele do życzenia IMHO. Jednak narzekanie jest zabronione, więc wypiję teraz moją kool-pomoc ;-)
Thomas

30

Sześć miesięcy po zadaniu tego pytania Rob Pike napisał post na blogu zatytułowany Błędy to wartości .

W tym miejscu argumentuje, że nie trzeba programować w sposób przedstawiony przez PO i wspomina o kilku miejscach w bibliotece standardowej, w których używają innego wzorca.

Oczywiście typowym stwierdzeniem dotyczącym wartości błędu jest sprawdzenie, czy jest ona zerowa, ale istnieje niezliczona ilość innych rzeczy, które można zrobić z wartością błędu, a zastosowanie niektórych z tych innych rzeczy może ulepszyć program, eliminując większość schematu która powstaje, gdy każdy błąd jest sprawdzany za pomocą rote if instrukcji.

...

Użyj języka, aby uprościć obsługę błędów.

Ale pamiętaj: cokolwiek robisz, zawsze sprawdzaj błędy!

To dobra lektura.


Dzięki! Sprawdzę to.
gmoore

Artykuł jest niesamowity, w zasadzie przedstawia obiekt, który może być w stanie awarii, a jeśli tak, po prostu zignoruje wszystko, co z nim zrobisz i pozostanie w stanie awarii. Dla mnie brzmi jak prawie monada.
Waterlink

@Waterlink Twoje oświadczenie jest bez znaczenia. Wszystko, co ma stan, jest prawie monadą, jeśli się na to trochę zmruży. Wydaje mi się, że porównanie go do en.wikipedia.org/wiki/Null_Object_pattern jest bardziej przydatne.
user7610

@ user7610, Dzięki za informację zwrotną. Mogę się tylko zgodzić.
Waterlink

2
Pike: „Ale pamiętaj: cokolwiek robisz, zawsze sprawdzaj swoje błędy!” - to tak 80s. Błędy mogą wystąpić wszędzie, przestają obciążać programistów i ze względu na Pete'a zastosuj wyjątki.
Sławomir

22

Zgodziłbym się z odpowiedzią jnml, że oba są kodem idiomatycznym i dodam:

Twój pierwszy przykład:

if err != nil {
      //handle err
}

jest bardziej idiomatyczny, gdy mamy do czynienia z więcej niż jedną wartością zwracaną. na przykład:

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

Twój drugi przykład jest fajnym skrótem, jeśli chodzi tylko o errwartość. Ma to zastosowanie, jeśli funkcja zwraca tylko wartość an errorlub celowo zignorujesz zwrócone wartości inne niż error. Na przykład jest to czasami używane z funkcjami Readeri, Writerktóre zwracają intliczbę zapisanych bajtów (czasami niepotrzebne informacje) oraz error:

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

Drugi formularz jest określany jako użycie instrukcji inicjalizacji if .

Jeśli chodzi o najlepsze praktyki, o ile wiem (z wyjątkiem używania pakietu „errors” do tworzenia nowych błędów, gdy ich potrzebujesz), omówiłeś prawie wszystko, co musisz wiedzieć o błędach w Go!

EDIT: Jeśli okaże się, naprawdę nie mogą żyć bez wyjątków, można naśladować ich defer, panicirecover .


4

Stworzyłem bibliotekę do usprawnionej obsługi błędów i przesyłania przez kolejkę funkcji Go.

Możesz go znaleźć tutaj: https://github.com/go-on/queue

Ma zwarty i pełny wariant składni. Oto przykład krótkiej składni:

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:
           ...
       }
    }
}

Należy pamiętać, że istnieje niewielki narzut wydajności, ponieważ wykorzystuje refleksję.

Nie jest to również idiomatyczny kod go, więc będziesz chciał go używać we własnych projektach lub jeśli Twój zespół zgodzi się go używać.


3
To, że możesz to zrobić, nie oznacza, że ​​to dobry pomysł. Wygląda to jak wzorzec łańcucha odpowiedzialności , z wyjątkiem być może trudniejszych do odczytania (opinia). Sugerowałbym, że nie jest to „idiomatyczne Go”. Ciekawe jednak.
Steven Soroka

2

„Strategią” obsługi błędów w języku golang i innych językach jest ciągłe rozpowszechnianie błędów w stosie wywołań, dopóki nie osiągniesz wystarczająco wysokiego poziomu w stosie wywołań, aby obsłużyć ten błąd. Jeśli próbowałeś obsłużyć ten błąd zbyt wcześnie, prawdopodobnie w końcu powtórzysz kod. Jeśli poradzisz sobie z tym za późno, zepsujesz coś w swoim kodzie. Golang sprawia, że ​​ten proces jest bardzo łatwy, ponieważ bardzo jasno określa, czy obsługujesz błąd w danej lokalizacji, czy też go propagujesz.

Jeśli zamierzasz zignorować błąd, proste _ ujawni ten fakt bardzo wyraźnie. Jeśli go obsługujesz, to dokładnie, który przypadek błędu, który obsługujesz, jest jasny, ponieważ sprawdzisz go w instrukcji if.

Jak ludzie powiedzieli powyżej, błąd jest w rzeczywistości zwykłą wartością. To traktuje to jako takie.


2

Bogowie Go opublikowali "szkic projektu" do obsługi błędów w Go 2. Ma on na celu zmianę idiomu błędów:

Przegląd i projekt

Chcą opinii użytkowników!

Witryna wiki opinii

W skrócie wygląda to tak:

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

AKTUALIZACJA: Projekt projektu spotkał się z dużą krytyką, dlatego przygotowałem projekt Wymagania do rozważenia w przypadku obsługi błędów Go 2 z menu możliwości ewentualnego rozwiązania.


1

Większość w branży postępuje zgodnie ze standardowymi zasadami wymienionymi w dokumentacji firmy Golang. Obsługa błędów i gotowe . Pomaga także w generowaniu dokumentów dla projektu.


Zasadniczo jest to odpowiedź zawierająca tylko łącze. Proponuję dodać trochę treści do odpowiedzi, tak aby gdyby link stał się nieważny, twoja odpowiedź byłaby nadal przydatna.
Neo

dziękuję za cenny komentarz.
pschilakanti

0

Poniżej znajduje się moja opinia na temat ograniczenia obsługi błędów w Go, przykład dotyczy pobierania parametrów adresu URL HTTP:

(Wzorzec projektowy pochodzi z https://blog.golang.org/errors-are-values )

type HTTPAdapter struct {
    Error *common.AppError
}

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

wywołanie go dla wielu możliwych parametrów wyglądałoby następująco:

    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
    }

To nie jest srebrna kula, wadą jest to, że jeśli miałeś wiele błędów, możesz uzyskać tylko ostatni błąd.

Ale w tym przypadku jest to stosunkowo powtarzalne i niskie ryzyko, dlatego mogę uzyskać ostatni możliwy błąd.


-1

Państwo może oczyścić swój kod obsługi błędów dla podobnych błędów (ponieważ błędy są wartościami trzeba być ostrożnym tutaj) i napisać funkcję, którą nazywasz z błędem przeszedł do obsługi błędu. Nie będziesz wtedy musiał za każdym razem pisać "if err! = Nil {}". Ponownie, spowoduje to jedynie wyczyszczenie kodu, ale nie sądzę, że jest to idiomatyczny sposób robienia rzeczy.

Ponownie, tylko dlatego, że możesz , nie oznacza, że powinieneś .


-1

goerr umożliwia obsługę błędów za pomocą funkcji

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. Komplikowanie / ukrywanie logiki obsługi błędów, takiej jak ta, nie jest dobrym pomysłem IMO. Wynikowy kod (który wymaga wstępnego przetwarzania źródła przez goerr) jest trudniejszy do odczytania / uzasadnienia niż idiomatyczny kod Go.
Dave C

-4

Jeśli chcesz precyzyjnej kontroli błędów, może nie jest to rozwiązanie, ale dla mnie w większości przypadków każdy błąd jest zatrzymaniem pokazu.

Więc zamiast tego używam funkcji.

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

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

Takie wiadomości powinny trafiać stderrraczej do niż stdout, więc po prostu użyj log.Fatal(err)lub log.Fatalln("some message:", err). Ponieważ prawie nic poza mainpodjęciem takiej decyzji o zakończeniu całego programu (tj. Zwrócenie błędów z funkcji / metod, nie przerywaj) w rzadkich przypadkach jest to, co chcesz zrobić, jest to czystsze i lepiej zrobić to jawnie (tj. if err := someFunc(); err != nil { log.Fatal(err) }), a nie poprzez funkcję „pomocniczą”, która nie jest jasna co do tego, co robi (nazwa „Err” nie jest dobra, nie daje żadnej wskazówki, że może zakończyć program).
Dave C

Nauczyłem się nowej rzeczy! Dziękuję @DaveC
Gon
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.