Najlepsze praktyki - obsługa błędów

To pierwszy artykuł z serii lekcji, których nauczyłem się przez kilka lat pracy z Go przy produkcji. Prowadzimy dużą liczbę usług Go w produkcji w Saltside Technologies (psst, zatrudniam na wiele stanowisk w Bangalore dla Saltside), a także prowadzę własną działalność, w której Go jest integralną częścią.

Omówimy szeroki zakres tematów, dużych i małych.

Pierwszym tematem, który chciałem omówić w tej serii, jest obsługa błędów. Często powoduje zamieszanie i irytację dla nowych programistów Go.

Trochę tła - interfejs błędu

Właśnie dlatego jesteśmy na tej samej stronie. Jak być może wiesz, błąd w Go to po prostu wszystko, co implementuje interfejs błędu. Tak wygląda definicja interfejsu:

wpisz błąd interfejsu {
    Ciąg Error ()
}

Tak więc wszystko, co implementuje metodę ciągu Error (), może być użyte jako błąd.

Sprawdzanie błędów

Korzystanie ze struktur błędów i sprawdzania typu

Kiedy zaczynałem pisać Go, często porównywałem ciąg komunikatów o błędach, aby zobaczyć, jaki jest typ błędu (tak, żenujące, ale czasem trzeba spojrzeć wstecz, aby przejść dalej).

Lepszym rozwiązaniem jest użycie typów błędów. Możesz więc (oczywiście) stworzyć struktury, które implementują interfejs błędu, a następnie dokonać porównania typów w instrukcji switch.

Oto przykładowa implementacja błędu.

wpisz ErrZeroDivision struct {
    ciąg wiadomości
}
func NewErrZeroDivision (ciąg komunikatu) * ErrZeroDivision {
    return & ErrZeroDivision {
        wiadomość: wiadomość,
    }
}
func (e * ErrZeroDivision) Error () string {
    zwrot e.message
}

Teraz można użyć tego błędu w ten sposób.

func main () {
    wynik, err: = dzielenie (1,0; 0,0)
    if err! = zero {
        przełącznik err. (typ) {
        case * ErrZeroDivision:
            fmt.Println (err.Error ())
        domyślna:
            fmt.Println („Co do cholery właśnie się stało?”)
        }
    }
    fmt.Println (wynik)
}
func divide (a, b float64) (float64, błąd) {
    jeśli b == 0,0 {
        return 0.0, NewErrZeroDivision („Nie można podzielić przez zero”)
    }
    zwraca a / b, zero
}

Oto link Go Play dla pełnego przykładu. Zwróć uwagę na wzorzec błędu przełączania (typ), który umożliwia sprawdzanie różnych typów błędów zamiast czegoś innego (np. Porównanie ciągu znaków lub coś podobnego).

Korzystanie z pakietu błędów i bezpośrednie porównanie

Powyższe podejście można alternatywnie obsłużyć za pomocą pakietu błędów. Takie podejście jest zalecane w przypadku kontroli błędów w pakiecie, w których potrzebna jest szybka reprezentacja błędów.

var errNotFound = error.New („Nie znaleziono elementu”)
func main () {
    err: = getItem (123) // Wyrzuci to errNotFound
    if err! = zero {
        switch err {
        case errNotFound:
            log.Println („Nie znaleziono żądanego elementu”)
        domyślna:
            log.Println („Wystąpił nieznany błąd”)
        }
    }
}

To podejście jest mniej dobre, gdy potrzebujesz bardziej złożonych obiektów błędów, np. kody błędów itp. W takim przypadku należy utworzyć własny typ, który implementuje interfejs błędu.

Natychmiastowa obsługa błędów

Czasami natrafiam na kod taki jak poniżej (ale zwykle z większym puchem wokół ...):

błąd func example1 () {
    err: = call1 ()
    zwróć błąd
}

Chodzi o to, że błąd nie jest obsługiwany natychmiast. Jest to delikatne podejście, ponieważ ktoś może wstawić kod między err: = call1 () a zwracanym błędem, co złamałoby zamiar, ponieważ może to cień pierwszego błędu. Dwa alternatywne podejścia:

// Zwiń zwrot i błąd.
błąd func example2 () {
    oddzwoń call1 ()
}
// Wykonaj jawną obsługę błędów zaraz po wywołaniu.
błąd func example3 () {
    err: = call1 ()
    if err! = zero {
        zwróć błąd
    }
    zwróć zero
}

Oba powyższe podejścia są ze mną w porządku. Osiągają to samo, co jest; jeśli ktoś musi coś dodać po wywołaniu call1 (), musi zająć się obsługą błędów.

To wszystko na dzisiaj

Zaglądaj do następnego artykułu o Go Best Practices. Silny :).

func main () {
    err: = readArticle („Idź najlepsze praktyki - obsługa błędów”)
    if err! = zero {
        ping („@ sebdah”)
    }
}