Error, panic & recover

Error

Në Go, gabimet dërgohen si vlerë e veçantë kthyese e një funksioni. Pra, në Go nuk kemi të bëjmë me Exceptions me struktura try/catch sikurse në Java, PHP, etj.

Programet në Go i përdorin vlerat e tipit error për të dhënë indikacion mbi një problem, gabim, gjendje abnormale.

Kur brenda një funksioni ka ardhur deri te një gabim, formohet raporti tekstual i tipit Error dhe i njëjti kthehet si një prej vlerave kthyese të funksionit, që më pas atë vlerë ta lexojmë nga variabla korrresponduese brenda funksionit thirrës, dhe nëse vlera nuk është nil (që e verifikojmë me strukturën if), vendosim se çfarë të ndodh më tej me rrjedhën e ekzekutimit të programit.

Pakoja errors implementon funksione për manipulimin me raportet e gabimeve.

Funksioni New krijon gabimet përmbajtj e të cilave është mesazh tekstual.

package main

import (
    "errors"
    "fmt"
)

func pjesto(a, b float32) (float32, error) {
    if b == 0 {
        return 0.0, errors.New("Nuk mund të pjestoj me zero")
    }
    return a / b, nil
}

func main() {

    c, e := pjesto(9, 2)
    if e != nil {
        fmt.Println(e)
    } else {
        fmt.Println(c)
    }
}

https://play.golang.org/p/gb2YT3xhYi3

Rezultati:

4.5

Nëse funksionit i japim këto argumente: c, e := pjesto(9, 2), atëherë rezultati do të jetë:

Nuk mund të pjestoj me zero

Ndonëse jemi të lirë ta vendosim vlerën e gabimit kudo në vlerat kthyese të funksionit, me konvencion rekomandohet që të jetë vlera e fundit.

Kur raportit duam t’ia bashkangjisim edhe informata shtesë, në vend të string do të përdorim një struct. Kësisoj, raporti mund të ketë detaje sqaruese mbi parametra të ndryshëm që kanë sjellur deri te gabimi.

package main

import (
    "fmt"
)

type raportGabimi struct {
    arg1   float32
    arg2   float32
    gabimi string
}

func (r *raportGabimi) Error() string {
    return fmt.Sprintf("%v %v - %s", r.arg1, r.arg2, r.gabimi)
}

func pjesto(a, b float32) (float32, error) {
    if b == 0 {
        return 0.00, &raportGabimi{a, b, "Nuk mund të pjestoj me zero"}
    }
    return a / b, nil
}
func main() {
    rezultati, e := pjesto(9, 2)

    if rg, ok := e.(*raportGabimi); ok {
        fmt.Println("Argumenti 1: ", rg.arg1)
        fmt.Println("Argumenti 2: ", rg.arg2)
        fmt.Println("Gabimi: ", rg.gabimi)
        fmt.Println("Vlera: ", rezultati)
    } else {
        fmt.Println(rezultati)
    }
}

https://play.golang.org/p/hCu-E48E4bj

Rezultati:

4.5

Nëse funksionit i japim këto argumente: c, e := pjesto(9, 2), atëherë rezultati do të jetë:

Argumenti 1:  9
Argumenti 2:  0
Gabimi:  Nuk mund të pjestoj me zero
Vlera:  0

Raportin në formë stringu mund ta fitojmë me:

fmt.Println("Gabimi: ", rg.Error())

Te funksioni pjesto(), parametri i dytë duhet të jetë i tipit error. Kur si vlerë kthyese duhet të kthehet se nuk ka ndodhur gabim, si vlerë të dytë kthyese e kthejmë vlerën nil.

return a / b, nil

Çdo funksion që kthen si vlerë kthyese e ka të deklaruar tipin error, duhet të kthejë vlera të tipit error. Për shembull, funksioni Open nga pakoja os, si vlerë të dytë kthyese e ka variablin err të tipit error.

func Open(name string) (file *File, err error)

Prandaj, për të verifikuar nëse ka pasur gabim gjatë hapjes së fajllit me funksionin os.Open(), rezultatin e funksionit e vendosim në dy variabla, ku variabli i parë do ta përmbajë përmbajtjen e fajllit, ndërsa i dyti do ta përmbajë raportin e gabimit nëse ka pasur gabim. Nëse nuk ka pasur, vlera e variablit të dytë do të jetë nil.

package main

import (
    "fmt"
    "os"
)

func main() {
    f, err := os.Open("file.txt")
    if err != nil {
        fmt.Println(err)
    } else {
        fmt.Println(f)
    }
}

https://play.golang.org/p/790oGr4BJuO

Rezultati:

Nëse nuk gjendet fajlli i specifikuar:

open file.txt: No such file or directory

Nëse gjendet, atëherë do të shfaqet përmbajtja e fajllit.

Tipi error i përket tipit intefejs. Një variabël i gabimit mund të përmbajë çfarëdo vlere që mund të prezantohet si string.

type error interface {
    Error() string
}

Tipi error është tip i paradeklaruar dhe është i definuar në universe block, d.m.th. në nivelin e kodit të tërësishëm dhe jo vetëm brenda një pakoje apo funksioni.

Implementimi më i shpeshtë i pakos errors haset në përdorimin e tipit errorString.

type error interface {
    Error() string
}

type errorString struct {
    s string
}

func (e *errorString) Error() string {
    return e.s
}

Konstruktimi i vlerave të tipit error bëhet me funksionin errors.New, të cilit ia bartim vlerën e tipit string, ndërsa ky funksion kthen vlerë të tipit error.

func New(text string) error {
    return &errorString{text}
}

Shembull si mund të implementohet:

func Sqrt(f float64) (float64, error) {
    if f < 0 {
        return 0, errors.New("Rrënjë katrore e numrit negativ!")
    }
    // kodi që llogarit rrënjën katrore
}

Kështu, kur e thërrasim funksionin Sqrt(), i përdorim dy variabla, një për rezultatin, tjetrin për gabimin:

f, err := Sqrt(-1)
if err != nil {
    fmt.Println(err)
}

Në këtë rast, variabli err, nëse kemi dhënë si argument funksionit Sqrt() një vlerë negative, do të jetë: Rrënjë katrore e numrit negativ!.

Pakoja fmt e formaton vlerën e tipit error duke e thirrur metodën Error() që kthen tipin string.

Është detyrë e implementimit se si do të formulohet raporti i gabimit; raporti mund të jetë vetëm tekst, por edhe të përmbajë vlera të tjera për sqarim më të mirë të kontekstit të ndodhjes së gabimit.

Për shembull, tek rasti me rrënjën katrore, raporti mund të formulohet asisoj që ta tregojë edhe vlerën e argumentit për shkak të të cilit nuk mund të kryhet llogaritja:

if f < 0 {
    return 0, fmt.Errorf("Rrënjë katrore e numrit negativ!", f)
}

fmt.Errorf() merr si argumente tipa të ndryshëm, bën bashkangjitjen e tyre, ndërsa si rezultat kthen vlerë të tipit error.

Interfejsi error kërkon të implementohet vetëm metoda Error(). Megjithatë, nëpër implementimi të ndryshme të tipit error mund të kërkohen edhe metoda shtesë. Për shembull, pakoja net kthen gabim të tipit error, por në disa implementime të caktuara mund të kërkohen metoda shtesë të definuara në interfejsin net.Error.

package net

type Error interface {
    error
    Timeout() bool   // Is the error a timeout?
    Temporary() bool // Is the error temporary?
}

Në bazë të vlerës së Timeout() do ta dijmë se ky gabim a ka të bëjë me kalimin e kohës së caktuar për arritjen e përgjigjes, ndërsa me Temporary() kuptojmë nëse gabimi ka karakter të përkohshëm.

Panic

Janë disa operacione që mund të shkaktojnë panic, siç janë:

  • Pjestimi i një integeri me 0
  • Qasje në një anëtar të vargut me indeks inekzistent
  • Dërgimi në një kanal të mbyllur
  • Dereferencimi i një nil pointeri
  • Përdorimi i thirrjes rekurzive të një funksioni që e mbush stack-un

Panic duhet të përdoret për gabimet që ndodhin papritmas dhe që normalisht nuk mund të kenë rikuperim. Rikuperimi nga një panic duhet të jetë vetëm përpjekje për të ndërmarrë diçka në lidhje me atë gabim para se të dilet nga aplikacioni. Nëse shfaqet në problem i papritur, kjo ndodh nëse gabimi nuk është menaxhuar si duhet apo mungojnë disa verifikime.

Përdorimi tipit i panic është për ndërprerjen e programit kur një funksion kthen një vlerë të tipit error të cilin nuk dijmë si ta menaxhojmë, apo nëse vazhdimi i ekzekutimit të programit nuk ka kuptim në rast se është shfaqur ai gabim.

package main

import (
    "fmt"
    "os"
)

func main() {
    _, err := os.Create("fajlli.txt")
    if err != nil {
        panic(err)
    } else {
        fmt.Println("U krijua fajlli.txt")
    }
}

https://play.golang.org/p/U3xn0TFPLvA

Rezultati:

Në rast suksesi:

U krijua fajlli.txt

Në rast dështimi:

panic: open fajlli.txt: No such file or directory
goroutine 1 [running]:
main.main()
    /tmp/sandbox490522843/prog.go:11 +0xe0

Funksioni panic() është funksion i Go që do ta ndërpresë rrjedhën normale të ekzekutimit të programit dhe do të lajmërojë gabim. Kur një funksion F() e thirr funksionin panic(), çdo funksion i shtyrë (deferred functions) brenda funksionit F() do të ekzekutohet normalisht, pastaj funksioni F() i kthen rezultat thirrësit.

Ndaj thirrësit, funksioni F() sillet si thirrje për panic. Procesi vazhdon derisa të thirren të gjitha gorutinat dhe në fund programi e ndërpret ekzekutimin. Paniku mund të inicohen dhe e thirrur drejtpërsëdrejti funksionin panic(). Por, një panic mund të shkaktohet edhe për shkak të gabimeve gjatë ekzekutimit (runtime errors), siç është për shembull rasti kur e kërkojmë një anëtar të vargut me indeks inekzistent.

Recover

Funksioni recover() është funksion që rimerr kontrollin pas një gorutine që ka shkaktuar panik.
Ky funksion është i dobishëm vetëm brenda funksioneve të shtyra (deferred functions). Gjatë ekzekutimit normal, një thirrje për rikuperim (recover) do të kthejë vlerën nil dhe nuk do të ketë efekt tjetër. Nëse gorutina aktuale është duke shkaktuar panik, një thirrje e recover() do ta kapë vlerën e dhënë funksionit panic(), ndërsa ekzekutimi i programit do të vazhdojë normalisht.

Shembull nga https://blog.golang.org/defer-panic-and-recover

package main

import "fmt"

func main() {
    f()
    fmt.Println("Returned normally from f.")
}

func f() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Recovered in f", r)
        }
    }()
    fmt.Println("Calling g.")
    g(0)
    fmt.Println("Returned normally from g.")
}

func g(i int) {
    if i > 3 {
        fmt.Println("Panicking!")
        panic(fmt.Sprintf("%v", i))
    }
    defer fmt.Println("Defer in g", i)
    fmt.Println("Printing in g", i)
    g(i + 1)
}

https://play.golang.org/p/Ujf1dRatTMb

Rezultati:

Calling g.
Printing in g 0
Printing in g 1
Printing in g 2
Printing in g 3
Panicking!
Defer in g 3
Defer in g 2
Defer in g 1
Defer in g 0
Recovered in f 4
Returned normally from f.

Tash do ta largojmë funksionin anonim të shtyrë që bënte recover(), dhe kur vlera e i bëhet 4, do të thirret panic(), nga i cili nuk ka rikuperim, prandaj të gjitha funksionet e shtyra do të ekzekutohen në renditje të mbrapshtë, ndërsa funksionit main() thirrja e funksionit f() i cili nga funksioni g() ka pranuar panic do të kthehet po ashtu panic me çka main() do ta ndërpresë ekzekutimin prandaj edhe rreshti fmt.Println("Returned normally from f.") nuk do të ekzekutohet fare.

package main

import "fmt"

func main() {
    f()
    fmt.Println("Returned normally from f.")
}

func f() {
    fmt.Println("Calling g.")
    g(0)
    fmt.Println("Returned normally from g.")
}

func g(i int) {
    if i > 3 {
        fmt.Println("Panicking!")
        panic(fmt.Sprintf("%v", i))
    }
    defer fmt.Println("Defer in g", i)
    fmt.Println("Printing in g", i)
    g(i + 1)
}

https://play.golang.org/p/hLQoozxSYOC

Rezultati:

“`
Calling g.
Printing in g 0
Printing in g 1
Printing in g 2
Printing in g 3
Panicking!
Defer in g 3
Defer in g 2
Defer in g 1
Defer in g 0
panic: 4

goroutine 1 [running]:
main.g(0x4, 0x40c110)
/tmp/sandbox693479664/prog.go:19 +0x340
main.g(0x3, 0x40c110)
/tmp/sandbox693479664/prog.go:23 +0x180
main.g(0x2, 0x40c110)
/tmp/sandbox693479664/prog.go:23 +0x180
main.g(0x1, 0x40c110)
/tmp/sandbox693479664/prog.go:23 +0x180
main.g(0x0, 0x40c110)
/tmp/sandbox693479664/prog.go:23 +0x180
main.f()
/tmp/sandbox693479664/prog.go:12 +0xa0
main.main()
/tmp/sandbox693479664/prog.go:6 +0x20
“`

All Rights Reserved Theme by 404 THEME.