Izuzeci u programiranju
Uvod
Pedagoški pristup u izučavanju programiranja, obično podrazumeva da se u početnim stadijumima ne obraća prevelika pažnja na određene anomalije koje se mogu javiti u toku izvršavanja programa (ukoliko korisnik unese podatke koji dovode u pitanje korektnost izvršavanja programa, ako se takvi podaci pojave u toku obrade, ili ako dođe do drugih nepredviđenih okolnosti).
U navedenim situacijama, u kojima je prioritet da se polaznici upoznaju sa osnovnim mehanizmima za obradu podataka, podrazumeva se da je korisnički unos ispravan - iz čega proizilazi da program uvek vraća korektan rezultat (naravno, ukoliko je program uopšte pravilno osmišljen i zapisan).
Međutim - van konteksta pisanja osnovnih programa u cilju upoznavanja sa tehnikama programiranja (prosto rečeno, 'u realnim situacijama') - jasno je da korisnički unos ne mora biti ispravan, jasno je da se tokom izvršavanja programa mogu pojaviti anomalije, i stoga - i te kako postoji potreba da se pravilno odreaguje u nepovoljnim okolnostima.
Recimo, ukoliko pišemo funkciju koja vraća obim pravougaonika (za unete stranice a i b), sve će biti u redu ukoliko se unesu vrednosti kao što su 3.5 i 7.2, međutim, kako (tačno) program treba da odreaguje ako se unesu vrednosti 12.4 i 0, ili 5 i -5?
U prvom slučaju dobija se rezultat 24.8, koji deluje dobro, ali, u pitanju je rezultat do koga se dolazi pod "sumnjivim okolnostima", dok je u drugom slučaju i sama vrednost koju funkcija vraća (0), krajnje diskutabilna.
U navedenim okolnostima (kao i mnogim drugim), * dobro dođe mehanizam koji proverava unete vrednosti, obustavlja dalje izvršavanje problematičnih delova koda ukoliko vrednosti nisu pravilno unete ** i (naravno) - obaveštava programere (po potrebi, i krajnje korisnike), o tome da je do grešaka došlo.
Pojam izuzetka
Pre nego što se detaljnije pozabavimo izuzecima (što, naravno, ostaje glavna tema članka), razmotrićemo - preko jednostavnih primera - razliku između situacija u kojima nije potrebno koristiti mehanizme za obradu izuzetaka da bi se dobile sve informacije o obradi koja je izvršena * (korektan rezultat, ili poruka o grešci, u zavisnosti od ulaznih podataka), i situacija u kojima jeste potrebno koristiti mehanizme za obradu izuzetaka.
Kraća diskusija o situacijama koje se mogu rešiti bez korišćenja izuzetaka
Funkcija za pronalaženje prve pojave niske s2
unutar niske s1
, verovatno je najuobičajeniji primer funkcije u kojoj se "sigurnosni mehanizam" može implementirati bez hvatanja i obrade izuzetaka.
Ukoliko se niska s2
pojavljuje unutar niske s1
, funkcija će vratiti indeks prvog poklapanja.
U pitanju su povratne vrednosti za situacije u kojima dolazi do poklapanja, međutim, šta funkcija treba da vrati ukoliko nema poklapanja (s obzirom na to da je povratni tip funkcije celobrojna vrednost)?
Uz (sasvim) malo promišljanja i snalaženja, lako se dolazi do zaključka da je dovoljno (u prethodno navedenim okolnostima), da funkcija vrati bilo koju negativnu vrednost (tipično -1
).
Negativna celobrojna vrednost ne predstavlja 'mogući indeks' u niski znakova (vrlo očigledno), i stoga se može protumačiti kao svojevrstan "signal" da nije došlo do poklapanja.
Funkcija se sada može implementirati na jednostavan način (doduše, prikazaćemo samo "šemu" funkcije; konkretnu implementaciju koja podrazumeva korišćenje nekog od algoritma za proveru poklapanja niski, na ovom mestu ćemo ipak izostaviti, zarad preglednosti):
Sa druge strane, postoje i primeri (reklo bi se znatno brojniji), u kojima se prikazani pristup ne može koristiti, a kao verovatno najtipičniji primer (koji je idejno blizak čak i čitaocima koji nemaju iskustva sa web programiranjem), možemo uzeti funkcije za povezivanje sa bazama podataka na udaljenom serveru.
Funkcije za povezivanje sa bazom obično sadrže četiri parametra: ime servera, korisničko ime za pristup serveru, lozinku, i ime baze, a povratna vrednost je objekat preko koga se ostvaruje veza sa bazom (i nadalje, čitanje i obrada podataka).
Naravno, to je ono što se dešava ukoliko su argumenti predati korektno i ukoliko je veza uspešno uspostavljena, dok, u suprotnom - funkcija vraća "izuzetak".
Definicija izuzetka
Izuzetak je neuobičajeni događaj u toku izvršavanja programa, koji prekida (očekivani/"uobičajeni") tok izvršavanja programa, i zahteva posebnu pažnju.
U najpraktičnijem smislu ("kolokvijalno"), termin izuzetak često se vezuje i za programski kod koji se tiče dalje obrade - u slučaju pojave "izuzetka".
U najosnovnijem obliku, na izuzetke se može reagovati prosleđivanjem prostih tekstualnih poruka o tome da je došlo do greške, ali, poruke se mogu formatirati i u vidu objekata koji sadrže detaljan opis * okolnosti koje su dovele do pojave greške (to jest, dodatne informacije o prirodi greške).
Praktičan primer
U "konkretnijem" primeru (koji smo pominjali), kao što je povezivanje sa bazom, tipično su predviđeni sledeći mehanizmi: ukoliko dođe do greške, moguće je - uz korišćenje prikladno formatiranih izuzetaka - dobiti informaciju o tome šta je tačno "krenulo naopako" pri povezivanju sa bazom podataka (da li je predato pogrešno korisničko ime, ili naziv baze, ili kombinacija - ili se desilo nešto 'sasvim treće'), što je svakako mnogo bolje nego da program samo saopšti da "nešto" nije u redu (pri čemu ne bismo znali šta konkretno nije u redu), ili - što bi bilo mnogo gore - da program 'sakrije' da je do greške uopšte došlo! *
U nastavku, bavićemo se izuzecima (s tim što ćemo se vratiti na jednostavniji primer sa obimom pravougaonika, koji lako možete isprobavati samostalno).
Kreiranje izuzetaka
Kreiranje izuzetaka, ni izdaleka nije komplikovana procedura (ni u idejnom, ni u tehničkom smislu (niti treba da bude :))), i stoga ćemo odmah razmotriti praktičan primer upotrebe, koji prikazuje obradu izuzetaka u okviru jednostavne funkcije za računanje obima pravougaonika (takva funkcija može biti: i samostalna funkcija, i metoda unutar klase).
Primer kreiranja izuzetaka u programskom jeziku C++
Ukoliko dođe do greške unutar (korisničke) funkcije racunanjeObima
, poziva se specijalizovana funkcija throw
.
U pitanju je mehanizam koji obustavlja dalje izvršavanje funkcije (slično kao da je navedena naredba return
) - pri čemu se pozivajućoj funkciji prosleđuje poruka o grešci:
Kao što vidimo (u gornjem primeru), naizgled su 'pokrivene' sve okolnosti, međutim, ukoliko samo pozovemo sledeći kod:
.... na ekranu se neće pojaviti nijedna od prethodno navedenih poruka (koje su definisane u funkciji racunanjeObima
), kao ni poruka "Da li vidimo ovaj tekst?!".
Kada program naiđe na izuzetak za koji nisu predviđeni odgovarajući mehanizmi za obradu - jednostavno će prestati sa izvršavanjem (program naizgled "puca", "bez objašnjenja", što: niti je u skladu sa principima dobrog programiranja, niti je elegantno).
Blok try-catch - Obrada izuzetaka
Da bi izuzeci, koji se generišu ukoliko dođe do grešaka pri izvršavanju funkcija, mogli da se upotrebe na pravi način - potrebno je koristiti blok try-catch
.
U pitanju je (naizgled), svojevrsno grananje u programu, sa sintaksom nalik na standardni blok if-else
(sa kojim ste se sretali mnogo puta do sad), ali, zapravo je u pitanju sintaksa koja je specijalizovana za prihvat i obradu izuzetaka.
Pogledajmo šemu izvršavanja bloka try-catch
:
Princip izvršavanja koda je sledeći
- u odeljku
try
navode se opšte naredbe za obradu podataka (u praktičnom smislu, u pitanju je: "ono što treba da se desi - ukoliko ne dođe do grešaka"), međutim, podrazumeva se da među navedenim naredbama postoji programski kod koji, u slučaju pojave greške - generiše izuzetak - u odeljku
catch
navode se naredbe koje treba izvršiti u slučaju da je neka od naredbi iz odeljkatry
generisala izuzetak - ukoliko ni u jednom pozivu unutar bloka
try
ne dođe do greške, blokcatch
se (uopšte) neće izvršavati (biće preskočen) - ukoliko bilo koja od naredbi iz bloka
try
vrati izuzetak, momentalno prestaje izvršavanje naredbi iz blokatry
- i prelazi se na odeljakcatch
(pojava naredbethrow
, u idejnom smislu je veoma slična pojavi naredbereturn
u funkcijama)
Vraćamo se na računanje obima (uz korišćenje bloka try-catch
):
Ovoga puta, program se izvršava na sledeći način:
Primer koji smo videli, sledi pravila koja su navedena na početku odeljka:
- blok
try
podeli smo na tri (gotovo) identična dela - u svakom od delova nalaze se dve naredbe dodele (za stranice
a
ib
), naredba ispisa (za vrednosti stranica), poziv metoderacunanjeObima
i, na kraju, naredba ispisa preko koje se ispisuje vrednost obima - prvi blok naredbi izvršava se na očekivani način
- u drugom bloku, naredba dodele biće izvršena, prva naredba ispisa se takođe izvršava, ali, poziv funkcije
racunanjeObima
- generiše izuzetak! - izvršavanje bloka
try
se momentalno prekida i prelazi se na izvršavanje blokacatch
Preko bloka catch
ispisuje se:
- opšta poruka koju smo naveli
- tekst koji je vezan za izuzetak (koji je definisan u funkciji
racunanjeObima
) - druga opšta poruka koju smo naveli
Poruka --DODATNA OBRADA--
(koju ste mogli videti u kodu), praktično sugeriše, da - u situacijama kada se izuzeci pojave (a pri tom ih "uhvatimo" preko bloka catch
) - postoji mogućnost korišćenja naredbi koje po potrebi usmeravaju program na odgovarajući način (na primer, moguće je zahtevati novi unos podataka, obustaviti izvršavanje programa, ili - uraditi "nešto treće").
Kompleksniji primer izuzetka sa više poruka
U prethodnom slučaju kreirali smo funkcionalan mehanizam provere koji obustavlja dalje izvršavanje programa i vraća poruku o grešci, međutim, prikazani mehanizam ima i nekoliko potencijalnih nedostataka:
- korisnici nisu obavešteni o svim greškama, već samo o prvoj grešci koja se pojavi
- program prekida izvršavanje čim naiđe na prvi pravougaonik sa neodgovarajućim stranicama
.... što ostavlja mesta za dalja unapređenja.
Drugi navedeni nedostatak lako se može rešiti kreiranjem zasebnih blokova try-catch
za svaki pravougaonik (prethodno smo stavili sva tri pravougaonika u isti blok da bismo prikazali osnovni način funkcionisanja bloka try-catch
), dok je za ispis svih poruka, ipak potrebno uložiti malo više truda.
Da bismo što bolje razumeli različite principe upotrebe izuzetaka (u prethodno navedenom kontekstu), osvrnućemo se za trenutak na koristan i zanimljiv primer iz druge grane industrije.
U strujnim instalacijama, "osigurači" su uređaji koji štite instalacione kablove i druge (veće / skuplje / kompleksnije) uređaje, od pojave prevelikih struja (što može izazvati pregrevanje komponenti i druga, veća oštećenja): "spori" osigurači štite robusne industrijske uređaje (koji imaju izvesnu toleranciju prema kratkotrajnoj pojavi struja koje su veće od dozvoljenih - i stoga "spori" osigurači ne reaguju "baš odmah"), dok "brzi" osigurači štite relativno osetljive uređaje kakvi se sreću u domaćinstvima (takvi uređaji nemaju preveliku toleranciju prema pojavi struja koje su veće od dozvoljenih - i stoga "brzi" osigurači reaguju "baš odmah").
Kada su u pitanju izuzeci, takođe postoje (nalik prethodnom primeru sa električnim instalacijama) - dve različite situacije:
- nekada je najbitnije prekinuti dalje izvršavanje čim se pojavi bilo kakva anomalija u funkcionisanju programa (i time prekinuti dalje "rasipanje resursa", pojavu "još većih grešaka" i sl)
- u drugim situacijama je najbitnije prikupiti što više informacija o okolnostima koje su dovele do nepravilnog funkcionisanja programa (da se greške ne bi ponavljale u budućnosti i sl)
(Naravno, kada su u pitanju izuzeci, postoje i razni "međuslučajevi".)
U nastavku, osvrnućemo se na sledeći primer: prepravićemo funkciju racunanjeObima
tako da poruka, u slučaju pojave izuzetka, sadrži podatke o svim parametrima pravougaonika koji nisu dobro navedeni.
Za početak, pripremićemo klasu preko koje se mogu skladištiti sve informacije o izuzecima:
Sada možemo prepraviti metodu za računanje obima:
Kao što smo ranije naveli, dosledno se prikupljaju sve informacije, pre nego što metoda generiše izuzetak (naravno, ako je bilo grešaka).
Sada će i odeljak catch
biti nešto drugačiji ....
.... u tom smislu da se poziva metoda za ispis, umesto da se "hvataju" i ispisuju niske (koje je funkcija racunanjeObima
vratila kao izuzetak).
Pošto smo se 'usput' upoznali sa tim da funkcije mogu vraćati izuzetke preko objekata različitih klasa, pogledaćemo još jedan "šematski prikaz".
Blok try-catch sa višestrukim odeljkom catch
Vratićemo se na primer koji se tiče povezivanja sa bazom podatka, i razmotrićemo šemu koja predstavlja razgranati blok try-catch
koji se može upotrebiti u navedenoj situaciji (zarad obrade 'raznovrsnih' izuzetaka):
Dakle, moguće je nadovezivati odeljke catch
i udesiti da svaki od odeljaka bude namenjen obradi specifičnog tipa izuzetka.
Za kraj ....
U ovom članku, dotakli smo se najvažnijih detalja koji su vezani za implementaciju izuzetaka, i nadamo se da sada imate bolju predstavu o tome šta je potrebno učiniti (odnosno, o tome šta se sve može učiniti), ukoliko postoji mogućnost da funkcionalnost određenih delova programskog koda bude narušena zbog pogrešnog korisničkog unosa ili drugih okolnosti (dok - ukoliko takva mogućnost u praktičnom smislu ne postoji - treba biti racionalan i ne treba trošiti vreme na kreiranje delova koda preko kojih se obrađuju izuzeci).
Za vežbu, probajte (na primer), da sami implementirate klasu Izuzeci
koja je specifično namenjena obradi grešaka do kojih može doći u radu sa ulančanim listama o kojima smo pisali u prethodnom članku.