Izuzetak (programiranje)

Izuzeci u programiranju označavaju situacije u kojima određeni program ili neki njegov dio ne može nastaviti svoj normalan rad zbog određenih problema. Problemi obično potiču od loših parametara koje zadaje korisnik, nemogućnosti povezivanja sa određenim resursom, sistemskih kvarova itd.

Hronologija događaja

uredi
 
Neuhvaćeni izuzetak „isplivava“ do najbližeg nivoa koji koji je pripremljen za hvatanje izuzetaka

Izuzeci mogu biti različitog porijekla. Sam programer može da prijavi određenu grešku kao izuzetak, ali i različite programske biblioteke, u zavisnosti od toga koju koristimo, prijavljuju određene probleme kao izuzetke.

Kada se takav izuzetak desi, on se prijavljuje programskom bloku višeg nivoa, tj. roditeljskom bloku, u formi nekog podatka koji ga opisuje. Tako, izuzetak se može prijaviti u obliku teksta „Desio se izuzetak“, u obliku cijelog broja, objekta neke korisničke klase itd. dokle god taj podatak na dobar način opisuje spomenuti izuzetak. Obično se kaže da je izuzetak „bačen“, kada se govori o prijavljivanju izuzetka roditeljskom bloku.

Roditeljski blok može da odabere da ignoriše bilo kakve izuzetke, i u tom slučaju izuzetak će automatski biti proslijeđen njegovom roditeljskom bloku. Ukoliko se, međutim, roditeljski blok unaprijed pripremi za izuzetke određenog tipa, kaže se da on „hvata“ izuzetak i od njega zavisi kako će se izuzetkom rukovati.

Ukoliko nijedan blok ne uhvati izuzetak, on se penje do prvog, početkog programskog bloka. Ako ga ni on ne uhvati, operativni sistem obično reaguje nasilnim prekidanjem rada programa; to se obično izvodi slanjem signala ABORT. Signal ABORT se može ignorisati, ali čak i tada proces ostaje u stanju poremećene funkcionalnosti pa se to ne preporučuje. Iz ovih razloga se preporučuje pažljiva obrada izuzetaka, da bi se problem mogao prijaviti korisniku i, eventualno, situacija popraviti.

Primjer

uredi

Neka je zadat niz karaktera koji predstavljaju cifre nekog broja. Treba napisati funkciju koja prevodi taj niz u cio broj čije su to cifre u dekadnom sistemu.

Pri pisanju ove funkcije, logično se nameće da će rezultat funkcije biti cio broj, odnosno da će funkcija vraćati cio broj. Međutim, nameće se problem kako obavijestiti pozivaoca ako se desila greška. Ukoliko odredimo -1 da bude indikator greške, onda funkcija neće moći da vrati taj broj kao regularan rezultat; zapravo, svi cijeli brojevi su „pokriveni“ ovom funkcijom, i nijedan od njih ne možemo iskoristiti kao indikator greške. U programskim jezicima koji ne podržavaju izuzetke, ovaj problem se obično rješava postavljanjem vrijednosti neke spoljašnje, globalne promjenljive ili prosljeđivanjem pokazivača ili reference na neku promjenljivu čija će se vrijednost postaviti na npr. 1 ako se desi greška. U programskim jezicima koji podržavaju izuzetke, međutim, u datoj situaciji ćemo jednostavno baciti izuzetak. Slijedi rješenje zadatka u pseudokodu:

функција низУБрој(ниска н )

иди на задњу цифру
резултат = 0
степен=1
док не дођемо до прве цифре
ради следеће:
ако је цифра између 0 и 9
онда:
резултат = резултат + степен*цифра
иначе:
баци изузетак
помјери се на претходну цифру
врати резултат

крај функције

главни програм:

низ = "1234"
ризичан блок:
број = низУБрој(низ )
штампај("Резултат је: ", број )
хватај изузетке:
штампај("Десио се изузетак, програм је завршен“ )

крај програма

Dakle, iz ovog primjera se vidi da funkcija baca izuzetak kada naiđe na nevaljanu cifru, tj. uopšteno govoreći kad god naiđe na uslove u kojima ne može nastaviti normalan rad. Glavni program je u ovom slučaju taj koji poziva ovu funkciju. Funkcija koja baca izuzetke je za program „potencijalna opasnost“ (može bacati izuzetke, premda obično ne baca, kad su svi uslovi zadovoljeni), te je ograđujemo u tzv. „rizični blok“ — odmah nakon rizičnog bloka slijedi blok koji obrađuje izuzetke. Na ovaj način se neće desiti da niko ne „uhvati“ izuzetak, što bi najvjerovatnije uzrokovalo da program bude nasilno ugašen od strane operativnog sistema.

Sintaksa izuzetaka u jeziku C++

uredi

U programskom jeziku C++, jedna od novina u odnosu na C su bili upravo izuzeci. Potaknuti problemom vraćanja greške kada je cio opseg izlazne promjenljive zauzet potencijalnim rezultatom, arhitekte C++-a su uvele izuzetke kao moćno sredstvo obrađivanja grešaka. Izabrali su ključne riječi try (engl. покушати, пробати) za označavanje „rizičnog“ bloka, catch (engl. хватати) za označavanje bloka u kojem se obrađuju izuzeci iz rizičnog bloka i throw (engl. бацити) za bacanje izuzetaka.

Primjer

uredi

Primjer koji smo imali napisan u pseudokodu bi imao sljedeći oblik u C++-u:

#include <iostream>
using namespace std;

int nizUBroj(char * niz )
{
    int i;
    int stepen = 1;
    int rezultat = 0;
    for (i = 0; niz[i] != '\0'; i++ )
        ; // да бисмо дошли до знака за крај ниске

    i--;  // да бисмо се позиционирали на задњу цифру
    while (i >= 0) {
        if (niz[i] - '0' > 9 || niz[i] - '0' < 0) // ако није у питању једна од нама познатих цифара...
            throw Низ садржи недозвољене знакове"; // ... баци изузетак
        rezultat += stepen*(niz[i]-'0');
        stepen *= 10;
        i--;
    }
    return rezultat;
}

int main()
{
    try { // блок команди у којем се могу појавити изузеци се ограничава кључном ријечју "try"
        cout << nizUBroj("1234") << endl;
    } catch (const char * tekstualniIzuzetak) { // уколико се у блоку "try" баци неки изузетак
                                                  // типа "const char * ", он ће овде бити ухваћен
        cerr << Грешка: " << tekstualniIzuzetak << endl;
        return 1;
    }
    return 0;
}

U slučaju da se nijedan izuzetak ne pojavi, tj. u slučaju da su sve cifre valjane, blok catch u glavnom programu će biti ignorisan i program će se uspješno završiti. Ukoliko se u bloku try, međutim, baci izuzetak, onda će automatski biti prekinuto izvršavanje cjelokupnog bloka try i započeto izvršavanje bloka catch. To uzrokuje poruku o grešci i završavanje programa sa statusom 1.

Hvatanje različitih tipova izuzetaka

uredi

Iz rizičnog bloka mogu biti bačeni različiti tipovi podataka. Na primjer, određena funkcija će bacati brojeve 1, 2, 3 itd. u zavisnosti od toga koja vrsta greške se desi. Druga funkcija, međutim, može bacati niske „loš argument“, „nedovoljno argumenata“, „dijeljenje sa nulom“, itd. opet u zavisnosti od vrste greške. Dalje, neka treća funkcija može bacati izuzetke klase string, vector, pa čak i neke korisničke klase A, B, Tacka itd. U programskom jeziku C++ je potrebno napisati posebne blokove catch za svaki očekivani tip izuzetka. Ako se bačeni izuzetak ne poklopi po tipu ni sa jednim od predstavljenih blokova catch, izuzetak će biti ignorisan i poslat na nivo iznad.

Primjer

uredi

Napisati funkciju za dijeljenje dva broja. Zatim napisati funkciju za računanje prosjeka zadatog niza. Napisati i glavni program koji testira napisane funkcije.

double podijeli(double a, double b )
{
    if (b == 0)   // ако је у питању дијељење са нулом, ово је немогућ задатак...
         throw 1;   // ... па бацамо изузетак (у облику цјелобројне вриједности)
    return a/b;
}

double prosjek(double niz[], int n )
{
    if (n == 0)   // ако је број елемената једнак нули, не можемо израчунати просјек...
         throw Просјек је немогуће израчунати"; // ... па бацамо изузетак, у облику низа карактера
    double suma = 0;
    for (int i = 0; i < n; i++ )
         suma += niz[i];
    return podijeli(suma, n );
}

int main()
{
    double niz[] = { 10.0, 0.1, 5.0, 0.5 };
    double pr;
    try {
        pr = prosjek(niz, 4 );
    } catch (const char * tekstualniIzuzetak) { // у овом блоку ће бити ухваћени сви
                                                  // изузеци типа const char *
        cerr << Грешка: " << tekstualniIzuzetak << endl;
        return 1;
    } catch (int cjelobrojniIzuzetak) {         // у овом блоку ће, међутим, бити ухваћени
                                                  // сви изузеци бачени у облику цјелобројне
                                                  // вриједности.
        cerr << Грешка бр. " << cjelobrojniIzuzetak << endl;
        return 2;
    }
    return 0;
}

Ukoliko se desi da se funkciji podijeli() nekom greškom naredi da dijeli sa nulom, ona će baciti izuzetak. Primijetimo, međutim, da funkcija prosjek() ne hvata izuzetke (nema blokova try i catch), te će eventualni izuzetak jednostavno biti proslijeđen na viši nivo, tj. funkciji main(). Ona, međutim, ima odgovarajući blok catch koji hvata izuzetke tipa int, pa će izuzetak biti uspješno obrađen, poruka o grešci odštampana i program završen sa statusom 2.

Sintaksa izuzetaka u Javi

uredi

Programski jezik Java koristi isti princip i iste ključne riječi za izuzetke kao i C++. Međutim, Java ide i dalje te izuzecima poklanja posebnu pažnju. Za razliku od programskog jezika C++, Java ima zasebnu hijerarhiju klasa koje mogu, i samo one, biti bačene kao izuzeci. Vrh hijerarhije ovih klasa je klasa po nazivu Throwable (bukvalno, engl. које може бити бачено), pa samo objekti te klase, ili objekti klasa koje su posredno ili direktno izvedene iz nje, mogu biti „bačeni“ kao izuzeci. U Javi postoje brojne klase koje su izvedene iz ove, ali i programer može da nasljeđuje klasu Throwable i baca objekte sopstvene klase, koja mu odgovara.

Java svoju „strogost“ po pitanju izuzetaka iskazuje i na druge načine; svaka funkcija koja pod nekim uslovima baca izuzetke, mora biti deklarisana kao takva. U te svrhe je rezervisana ključna riječ „throws“ (engl. „баца“), koja se koristi uz deklaraciju takve funkcije, da bi opisala koje sve vrste izuzetaka mogu biti bačene iz nje. Čak ukoliko sama funkcija ne baca izuzetak, ali poziva neku koja baca a ne hvata njene izuzetke, deklaracija funkcije mora to da opisuje. Tako, funkcija u Javi može izgledati na sljedeći način:

public double prosjek(double [] niz) throws StringException, IntException
{
    // ...
}

Drugi programski jezici

uredi

Različiti programski jezici višeg nivoa koji su svoju sintaksu naslijedili od programskih jezika C i C++, najčešće su i sintaksu izuzetaka pozajmili iz jezika C++. Tako, Javaskript, PHP, C#, i drugi, su svi naslijedili, posredno ili neposredno, sintaksu vezanu za izuzetke iz jezika C++. Pojedini, poput Jave i C#-a su izuzetke dodatno proširili i uobličili u skladu sa svojom filozofijom.

Spoljašnje veze

uredi