Dowiedz się, jak obsługiwać błędy w języku Go

Ukończone

Podczas pisania programów należy wziąć pod uwagę różne sposoby, w jakie programy mogą zakończyć się niepowodzeniem i musisz zarządzać awariami. Użytkownicy nie muszą widzieć długiego i mylącego błędu śledzenia stosu. Lepiej jest, jeśli widzą znaczące informacje o tym, co poszło nie tak. Jak już wiesz, środowisko Go ma wbudowane funkcje, takie jak panic i recover do zarządzania wyjątkami lub nieoczekiwanym zachowaniem w programach. Jednak błędy są znanymi błędami, które powinny zostać skompilowane w celu obsługi programów.

Podejście go do obsługi błędów jest po prostu mechanizmem przepływu sterowania, w którym potrzebne są tylko if instrukcje return i. Na przykład podczas wywoływania funkcji w celu uzyskania informacji z employee obiektu warto wiedzieć, czy pracownik istnieje. Go's opinionated sposób obsługi takiego oczekiwanego błędu będzie wyglądać następująco:

employee, err := getInformation(1000)
if err != nil {
    // Something is wrong. Do something.
}

Zwróć uwagę, getInformation że funkcja zwraca employee strukturę, a także błąd jako drugą wartość. Błąd może mieć wartość nil. Jeśli błąd to nil, oznacza to powodzenie. Jeśli tak nie niljest , oznacza to niepowodzenie. nil Wystąpił błąd z komunikatem o błędzie, który można wydrukować lub, najlepiej, zarejestrować. W ten sposób można obsługiwać błędy w języku Go. Omówimy kilka innych strategii w następnej sekcji.

Prawdopodobnie zauważysz, że obsługa błędów w żądaniach języka Go zwraca większą uwagę na sposób raportowania i obsługi błędu. To jest dokładnie punkt. Przyjrzyjmy się innym przykładom, aby lepiej zrozumieć podejście języka Go do obsługi błędów.

Użyjemy fragmentu kodu, który użyliśmy dla struktur, aby przećwiczyć różne strategie obsługi błędów:

package main

import (
    "fmt"
    "os"
)

type Employee struct {
    ID        int
    FirstName string
    LastName  string
    Address   string
}

func main() {
    employee, err := getInformation(1001)
    if err != nil {
        // Something is wrong. Do something.
    } else {
        fmt.Print(employee)
    }
}

func getInformation(id int) (*Employee, error) {
    employee, err := apiCallEmployee(1000)
    return employee, err
}

func apiCallEmployee(id int) (*Employee, error) {
    employee := Employee{LastName: "Doe", FirstName: "John"}
    return &employee, nil
}

W tym miejscu skoncentrujemy się na modyfikowaniu getInformationfunkcji , apiCallEmployeei main , aby pokazać, jak obsługiwać błędy.

Strategie obsługi błędów

Gdy funkcja zwraca błąd, zazwyczaj będzie to ostatnia zwracana wartość. Jest to odpowiedzialność osoby wywołującej za sprawdzenie, czy istnieje błąd i obsługa go, jak pokazano w poprzedniej sekcji. Powszechną strategią jest kontynuowanie używania tego wzorca w celu propagowania błędu w podroutynie. Na przykład podroutyna (na getInformation przykład w poprzednim przykładzie) może zwrócić błąd do wywołującego bez wykonywania czegokolwiek innego, w następujący sposób:

func getInformation(id int) (*Employee, error) {
    employee, err := apiCallEmployee(1000)
    if err != nil {
        return nil, err // Simply return the error to the caller.
    }
    return employee, nil
}

Przed propagacją błędu możesz również dołączyć więcej informacji. W tym celu można użyć fmt.Errorf() funkcji, która jest podobna do tego, co widzieliśmy wcześniej, ale zwraca błąd. Możesz na przykład dodać więcej kontekstu do błędu i nadal zwracać oryginalny błąd, w następujący sposób:

func getInformation(id int) (*Employee, error) {
    employee, err := apiCallEmployee(1000)
    if err != nil {
        return nil, fmt.Errorf("Got an error when getting the employee information: %v", err)
    }
    return employee, nil
}

Inną strategią jest uruchomienie logiki ponawiania prób, gdy błędy są przejściowe. Można na przykład użyć zasad ponawiania prób, aby wywołać funkcję trzy razy i poczekać dwie sekundy, w następujący sposób:

func getInformation(id int) (*Employee, error) {
    for tries := 0; tries < 3; tries++ {
        employee, err := apiCallEmployee(1000)
        if err == nil {
            return employee, nil
        }

        fmt.Println("Server is not responding, retrying ...")
        time.Sleep(time.Second * 2)
    }

    return nil, fmt.Errorf("server has failed to respond to get the employee information")
}

Na koniec zamiast drukować błędy w konsoli programu można rejestrować błędy i ukrywać wszelkie szczegóły implementacji od użytkowników końcowych. W następnym module omówimy rejestrowanie. Na razie przyjrzyjmy się sposobom tworzenia i używania błędów niestandardowych.

Tworzenie błędów wielokrotnego użytku

Czasami liczba komunikatów o błędach zwiększa się i chcesz zachować kolejność. Możesz też utworzyć bibliotekę dla typowych komunikatów o błędach, które chcesz użyć ponownie. W języku Go możesz użyć errors.New() funkcji , aby utworzyć błędy i użyć ich ponownie w kilku częściach, w następujący sposób:

var ErrNotFound = errors.New("Employee not found!")

func getInformation(id int) (*Employee, error) {
    if id != 1001 {
        return nil, ErrNotFound
    }

    employee := Employee{LastName: "Doe", FirstName: "John"}
    return &employee, nil
}

Kod funkcji getInformation wygląda lepiej, a jeśli musisz zmienić komunikat o błędzie, zrób to tylko w jednym miejscu. Należy również zauważyć, że konwencja ma zawierać Err prefiks zmiennych błędów.

Na koniec, jeśli masz zmienną błędu, możesz być bardziej szczegółowy podczas obsługi błędu w funkcji wywołującej. Funkcja errors.Is() umożliwia porównanie typu błędu, który otrzymujesz, w następujący sposób:

employee, err := getInformation(1000)
if errors.Is(err, ErrNotFound) {
    fmt.Printf("NOT FOUND: %v\n", err)
} else {
    fmt.Print(employee)
}

W przypadku obsługi błędów w języku Go poniżej przedstawiono zalecane rozwiązania, które należy wziąć pod uwagę:

  • Zawsze sprawdzaj błędy, nawet jeśli ich nie oczekujesz. Następnie należy je prawidłowo obsługiwać, aby uniknąć ujawnienia niepotrzebnych informacji użytkownikom końcowym.
  • Dołącz prefiks w komunikacie o błędzie, aby znać źródło błędu. Można na przykład uwzględnić nazwę pakietu i funkcji.
  • Utwórz zmienne błędów wielokrotnego użytku tak samo, jak to możliwe.
  • Zapoznaj się z różnicą między używaniem zwracanych błędów a paniką. Panika, gdy nie ma nic innego, co można zrobić. Jeśli na przykład zależność nie jest gotowa, nie ma sensu, aby program działał (chyba że chcesz uruchomić domyślne zachowanie).
  • Rejestruj błędy z jak największą liczbą szczegółów (omówimy, jak w następnej sekcji) i wyświetlą błędy, które użytkownik końcowy może zrozumieć.