GoSuda

Dlaczego w języku Go nie ma konstrukcji Try-Catch?

By Rabbit Princess
views ...

Go celowo nie obsługuje try-catch, a jedynie składnię panic-recover. To wywołuje sprzeciw wielu programistów, którzy są przyzwyczajeni do obsługi błędów za pomocą try-catch. Jaki jest więc powód, dla którego nie wprowadzono try-catch? Powodem jest fakt, że try-catch niesie ze sobą szereg problemów.

try-catch-finally

Konstrukcja try-catch służy do obsługi błędów i wyjątków, które mogą wystąpić podczas wykonywania programu (w czasie działania). Ponadto, w klauzuli finally umieszcza się kod, który musi zostać wykonany niezależnie od tego, czy wystąpił wyjątek, czy nie.

Obsługa błędów i odpowiedzialność

W latach 80. i 90. obsługa błędów była bardzo prosta. Komunikaty o błędach ograniczały się do "Dysk elastyczny pełny", "Brak dysku elastycznego w napędzie", "Brak uprawnień do zapisu na dysku elastycznym". W tamtych czasach programista, gdy napotkał błąd, zgłaszał go (throw) do miejsca, gdzie był on obsługiwany w sposób ujednolicony. W takich okolicznościach try-catch działał efektywnie.

Jednak z upływem czasu sytuacja uległa zmianie. Logika biznesowa stała się bardziej złożona, pojawiły się bazy danych i transakcje, trzeba było interpretować liczne komunikaty żądań podczas wywoływania API przez sieć, a nawet, wraz z pojawieniem się programowania współbieżnego, konieczne stało się obsługiwanie błędów w wątkach innych niż główny.

Błędy stały się zbyt złożone, aby można je było obsługiwać w jednym miejscu, a nikt nie mógł być odpowiedzialny za wszystkie błędy. W tym miejscu pojawia się poważny problem z try-catch.

Przerzucanie odpowiedzialności przez try-catch

Try-catch to, mówiąc najprościej, sposób, w jaki podmiot, który spowodował błąd, przerzuca odpowiedzialność (za jego skutki) na kogoś innego. Tą osobą może być klauzula catch, metoda nadrzędna, rodzic rodzica rodzica... Ktoś. Innymi słowy, w świecie, w którym obsługa błędów jest coraz częstsza i bardziej złożona, metoda przyjęta przez try-catch to "ktoś się tym zajmie". Spójrzmy na poniższy kod.

1try {
2    data = readFile("hello.txt");
3    structuredData = parseData(data);
4    insertDBStatus(structuredData[1]);
5    startHTTPServer(structuredData[2]);
6} catch (Exception e) {
7    e.printStackTrace();
8}

Problem z powyższym kodem polega na tym, że nie jest jasne, kto jest odpowiedzialny za obsługę printStackTrace, a także nie wiadomo, w której części kodu wystąpił błąd. Co gorsza, im więcej logiki znajduje się wewnątrz bloku try, tym problem staje się poważniejszy. Paradoksalnie jednak, im bardziej złożone staje się programowanie, tym bardziej programiści uzależniali się od try-catch, przerzucającego odpowiedzialność, przestawali zastanawiać się nad obsługą błędów, a ich poczucie odpowiedzialności malało, aż w końcu zapomnieli o istocie obsługi błędów i wyjątków. Jak zatem golang rozwiązał ten problem?

panic, recover

Jedną z zalet go jest to, że posiada szereg systemów, które nie sprowadzają programisty na złą drogę, ale pomagają mu stać się lepszym programistą. panic - recover jest tego przykładem.

Na pierwszy rzut oka panic i recover nie różnią się od try-catch, jednak różnica polega na tym, że w przypadku wystąpienia problemu, odpowiedzialność nie jest przerzucana na zewnątrz. Gdy w go wystąpi panic, zamiast przekazywać wartość na zewnątrz, problem należy rozwiązać w miejscu, w którym wystąpił panic. To zmusza programistę do wzięcia odpowiedzialności za obsługę błędów i do głębszego zastanowienia się, gdzie, kto i jak powinien je obsługiwać. Zapewnia użytkownikowi autonomię, a jednocześnie pozostawia mu przestrzeń do refleksji.

Dobór słowa panic jest również bardzo trafny. W przeciwieństwie do try-recover, panic-recover już samymi słowami zmusza programistę do używania go tylko w sytuacjach wyraźnego błędu, a nie wyjątku. Naturalnie programista nie nadużywa recover i używa go tylko tam, gdzie jest to potrzebne. To w znacznym stopniu pomaga w pisaniu zwięzłego kodu w go.