Hvorfor Go velger eksplisitt feilretur og panic-recover i stedet for try-catch-unntakskasting

Go har med vilje ikke støtte try-catch, og støtter kun panic-recover-syntaksen. Dette har møtt misnøye fra de mange utviklerne som er vant til feilhåndtering med try-catch. Hvorfor er då try-catch ikke tatt med? Det er fordi try-catch har flere problemer.
try-catch-finally
try-catch-setningen er en syntaks for å håndtere feilsituasjoner og unntakstilstander som kan oppstå under kjøring av et program (runtime). Finally-setningen inneholder kode som alltid skal kjøres, uavhengig av om et unntak har oppstått eller ikke.
Feilhåndtering og ansvar
På 80- og 90-tallet var feilhåndtering svært enkel. Feilmeldingene var kun «disketten er full», «ingen diskett i stasjonen» og «ingen skrivetilgang til disketten», og utviklerne på den tiden kastet feilen (throw) opp til et sentralt punkt for feilhåndtering, der den ble håndtert på en felles måte. I denne situasjonen fungerte try-catch-setningen effektivt.
Men etter hvert som tiden gikk, endret situasjonen seg. Forretningslogikken ble mer kompleks, det oppstod databaser og transaksjoner, og det ble nødvendig å kalle mange API-er over nettverket og tolke et stort antall forespørselsmeldinger. Med fremveksten av samtidighetsprogrammering måtte man til og med håndtere feil i andre tråder enn hovedtråden.
Feil ble etter hvert så komplekse at de ikke lenger kunne håndteres på ett enkelt sted, og ingen enkelt del av systemet kunne ta ansvar for alle feil. Her oppstår et alvorlig problem med try-catch.
Ansvarsfraskrivelse i try-catch
Kort sagt er try-catch en metode der den som forårsaker en feil overlater ansvaret for oppryddingen (etterarbeidet) til noen andre. Denne andre kan være catch-setningen, den overordnede metoden, eller noen høyere oppe i kallstakken. Med andre ord, i en verden der feilhåndtering blir stadig mer omfattende og komplisert, er try-catchs løsning nettopp «noen vil nok ordne det». Se på koden nedenfor.
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}
Problemet med koden over er at det ikke er tydelig hvem som har ansvar for å håndtere printStackTrace, og det blir umulig å se i hvilken kodelinje feilen oppstod. Når try-blokken inneholder mer logikk, blir problemet enda verre. Paradoksalt nok, jo mer komplekst utviklingen ble, desto mer ble utviklerne avhengige av try-catch, som lar dem overlate ansvaret. De sluttet å tenke på feilhåndtering, ansvarsfølelsen svekket seg, og til slutt glemte de hva feil- og unntakshåndtering egentlig handler om. Hvordan løste golang dette problemet?
panic, recover
En av fordelene med Go er at språket har flere mekanismer som hjelper utvikleren å unngå dårlige løsninger og utvikle seg til en god utvikler. Panic og recover er et eksempel på dette.
Panic og recover ser ved første øyekast ut som try-catch, men de skiller seg fra hverandre ved at de ikke overfører ansvaret til andre når et problem oppstår. I Go skal en panic, når den inntreffer, håndteres der den oppstod, i stedet for å sendes videre som en verdi. Dette gir utvikleren ansvar for feilhåndteringen og oppfordrer til grundigere refleksjon over hvor, av hvem og hvordan feilen skal håndteres. Det gir utvikleren handlingsfrihet, samtidig som det åpner rom for å tenke seg om.
Navnet panic er også svært treffende. Til forskjell fra try-recover gir panic-recover, bare ved å bruke ordet, utvikleren et press om at det kun skal brukes i tydelige feilsituasjoner og ikke i unntakstilfeller. Naturlig nok unngår utvikleren å misbruke recover og bruker den bare der det er nødvendig. Dette bidrar i stor grad til å skrive kortfattet kode i Go.