Dio 1 – IDE, organizacija koda, OOP, okviri
Dio 2 – Podaci, MVC/MVVM, čisti kod, refaktoring
Dopunska praksa – Refaktoring funkcije
Materijale priredio Erkan Islamović
erkan.islamovic@eubd.edu.ba · erkan.islamovic@gmail.com
Deset predavanja: dva časa teorije i jedan čas dopunske prakse. Danas: poglavlje 4 (označeno). Isti primjer kroz knjigu: DeliveryApp 2.0.
| Pred. | Dio 1 | Dio 2 | Praksa |
|---|---|---|---|
| 1 | Projekat i proizvod, trendovi, etika | Inženjerski pristup, alati, automatizacija | Repozitorij i vizija proizvoda |
| 2 | Modeli, uloge, planiranje | Rizici, tim, kvalitet, poboljšanje procesa | Plan izdanja, registar rizika |
| 3 | Konceptualni model, UML, SOLID | Prototip, UX, specifikacija dizajna | Wireframe i SDD |
| 4 | IDE, organizacija koda, OOP, okviri | Podaci, MVC/MVVM, čisti kod, refaktoring | Refaktoring funkcije |
| 5 | Git, pull request, CI | Testovi, CD, kontejneri | GitHub Actions |
Deset predavanja: dva časa teorije i jedan čas dopunske prakse. Danas je poglavlje 4. Isti primjer kroz knjigu: DeliveryApp 2.0.
| Pred. | Dio 1 | Dio 2 | Praksa |
|---|---|---|---|
| 6 | Klasifikacija, mjerljivost | Taktike i redizajn | Mjerenje i keš |
| 7 | Odluke, pogledi, stilovi | Troslojna i čista arhitektura, predlošci | ADR i C4 |
| 8 | CBSE, odlike, interfejsi | Principi, smjernice, provođenje | Paket sa verzijom |
| 9 | SaaS, SOA, IoT, proizvodne linije | Aspekti, reaktivno programiranje | Dekorator i tok događaja |
| 10 | Vrijednost, troškovi, razvoj ili kupnja, TCO | Metrike, procjena, NPV, povrat | NPV u Pythonu |
U oktobru 2013. godine porota u američkoj saveznoj državi Oklahoma donijela je presudu koja je uzbunila programere širom svijeta. Godine 2007. automobil Toyota Camry naglo je ubrzao i udario u nasip; vozačica Jean Bookout je povrijeđena, a njena saputnica Barbara Schwarz je poginula. Porota je Toyotu proglasila odgovornom i dosudila po 1,5 miliona dolara vozačici i porodici poginule. Ključno je bilo svjedočenje vještaka za ugrađeni softver Michaela Barra, koji je sa svojim timom mjesecima čitao kod koji upravlja gasom.
Opisao ga je kao „špageti kod”: oko 11.000 globalnih varijabli (podataka koje može promijeniti bilo koji dio programa), desetine funkcija toliko složenih da se praktično ne mogu testirati, i zaštite koje ne hvataju sve greške. Ranije istrage američkih vlasti i NASA-e nisu dokazale softversku grešku, a ni na suđenju nije pokazan tačan kvar u tom automobilu. Ali porotu je uvjerilo nešto drugo: kod je bio toliko zamršen da niko nije mogao dokazati da je ispravan. Čitljivost koda tog dana nije bila stvar ukusa, nego odgovornosti.
U poglavlju 3 dizajnirali smo DeliveryApp 2.0: konceptualni model, tarifa kao podatak, principi SOLID, prototip ekrana. Sada dizajn treba pretvoriti u kod. Prvi dio govori o mjestu rada (razvojno okruženje), o tome kako se kod organizuje, kako izgleda objektno orijentirani kod u Pythonu i C#, i šta su razvojni okviri. Drugi dio govori o radu s podacima, o tome kako se sučelje odvaja od logike (MVC, MVVM), i najviše o čistom kodu: kodu koji se lako čita, mijenja i testira.
Nakon poglavlja možete:
nabrojati šta IDE radi za programera i koristiti debager; organizovati kod u module i pakete po odgovornosti; napisati klasu s enkapsuliranim stanjem u Pythonu i C#; objasniti razliku između biblioteke i okvira; objasniti ORM i migracije; objasniti MVC i MVVM; primijeniti pravila čistog koda (imena, funkcije, komentari, DRY, KISS, YAGNI); prepoznati pet mirisa koda; objasniti tehnički dug analogijom s kreditom; provesti refaktoring uz testove koji čuvaju ponašanje.
| Čas | Sadržaj | Na času |
|---|---|---|
| Teorija 1 | 4.1–4.4 IDE, organizacija koda, OOP u Pythonu i C#, okviri | Ista klasa u dva jezika; biblioteka ili okvir? |
| Teorija 2 | 4.5–4.8 Podaci, MVC/MVVM, čisti kod, mirisi i refaktoring | Funkcija obracun() prije i poslije |
| Dopunska praksa | Refaktoring uz testove | 48 testova prolazi prije i poslije |
Programer provede veći dio radnog dana u jednom programu: IDE-u (integrated development environment). U timu DeliveryApp Python programeri koriste VS Code s ekstenzijama za Python, a C# programeri Visual Studio ili VS Code s ekstenzijom za C#. Bez obzira na izbor, dobar IDE radi sedam stvari:
Debager umjesto print. Sara traži zašto DeliveryApp za adresu od 4,2 km u Gradu B naplaćuje 3,20 € umjesto 3,80 €. Umjesto da dodaje print na deset mjesta, stavi tačku prekida na liniju gdje se računaju kilometri i pokrene program u debageru. Program stane, a IDE pokaže km - 2 = 2.2 i int(2.2) = 2. Greška je nađena za minut: int odsijeca, a pravilo traži zaokruživanje naviše (math.ceil). Istu grešku smo vidjeli u poglavlju 1, u kodu koji je predložio AI asistent.
print naredbi.Kad je kod u jednom fajlu od 3.000 linija, svaka izmjena je kao traženje igle u plastu sijena. Kod se zato dijeli na module (fajlove) i pakete (foldere), i to po odgovornosti, isto kao što se kuhinja dijeli na ladice za pribor, posuđe i začine.
Struktura repozitorija DeliveryApp 2.0 (Python dio):
deliveryapp-2/
├── README.md
├── docs/ vizija, plan izdanja, rizici, dizajn
├── deliveryapp/
│ ├── domen/ narudžba, stavka, tarifa – čista pravila, bez baze i weba
│ ├── servisi/ slučajevi korištenja: naruči, ponovi, otkaži
│ ├── podaci/ rad s bazom (repozitoriji)
│ ├── api/ web sloj: rute, JSON
│ └── vanjski/ adapteri: mape, plaćanje, SMS
├── tests/ testovi, ista struktura kao deliveryapp/
├── pyproject.toml zavisnosti i podešavanja alata
└── .github/workflows/ automatska provjera (poglavlje 5)Tri pravila:
domen sadrži pravila poslovanja; utils i razno su znak da niko nije znao gdje nešto pripada.api smije koristiti servisi, a servisi smiju koristiti domen; obrnuto ne. Pravila poslovanja ne smiju znati da postoji web ili baza. O tome detaljno u poglavlju 7 (čista arhitektura).ruff format ili black, u C# dotnet format) izjednači razmake i navodnike, da se pregled koda ne troši na stil.
Python i C# su oba objektno orijentirani jezici, ali s različitim karakterom. Python je dinamičan: tip varijable se provjerava dok program radi, a pravila o „privatnosti” su dogovor (ime koje počinje donjom crtom _stanje znači „ne diraj spolja”). C# je statičan: prevodilac provjerava tipove prije pokretanja, a private je zaista privatno.
Ista narudžba s enkapsuliranim stanjem, u oba jezika. Stanje se spolja može samo čitati, a mijenja se jedino kroz metodu promijeni, koja poštuje dozvoljene prelaze (pravilo PP-6: narudžba u pripremi se više ne otkazuje).
class Narudzba:
def __init__(self, broj: str):
self.broj = broj
self._stanje = "NOVA" # "privatno": mijenja se samo preko promijeni()
@property
def stanje(self) -> str:
return self._stanje
def promijeni(self, novo: str) -> None:
if novo not in PRELAZI.get(self._stanje, set()):
raise ValueError(f"nedozvoljen prelaz {self._stanje} -> {novo}")
self._stanje = novoclass Narudzba(string broj)
{
public string Broj { get; } = broj;
public string Stanje { get; private set; } = "NOVA"; // spolja se samo čita
public void Promijeni(string novo)
{
if (!Prelazi.TryGetValue(Stanje, out var dozvoljeni) || !dozvoljeni.Contains(novo))
throw new InvalidOperationException($"nedozvoljen prelaz {Stanje} -> {novo}");
Stanje = novo;
}
}Oba programa (primjeri/python/narudzba.py, primjeri/csharp/Narudzba.cs) prođu NOVA → PRIHVAĆENA → U_PRIPREMI i odbiju otkazivanje porukom „nedozvoljen prelaz U_PRIPREMI -> OTKAZANA”.
| Ideja | Python | C# |
|---|---|---|
| skriveno polje | _stanje (dogovor) | private set (prevodilac čuva) |
| svojstvo samo za čitanje | @property | { get; } |
| objekat s podacima | @dataclass(frozen=True) | record |
| interfejs | Protocol ili apstraktna klasa | interface |
| greška | raise ValueError | throw new InvalidOperationException |
„Getteri i setteri za svako polje su enkapsulacija.” Ako svako polje ima i get i javni set, klasa ne skriva ništa: svako može postaviti stanje na „ISPORUČENA” preskačući pravila. Enkapsulacija znači da objekat sam čuva svoja pravila.
Biblioteka je kutija alata: vi odlučujete kada ćete uzeti čekić. Okvir (framework) je montažna kuća: kostur je već postavljen, a vi popunjavate sobe. Razlika je u tome ko koga poziva. Vaš kod poziva biblioteku; okvir poziva vaš kod. To se zove holivudski princip: „Ne zovite nas, mi ćemo zvati vas.”
U web okviru FastAPI programer napiše samo funkciju i kaže na koju adresu odgovara; okvir prima HTTP zahtjev, pretvara JSON u Python objekte, poziva funkciju i vraća odgovor. U ASP.NET Core je isto, samo u C#.
| Vrsta | Python | C# / .NET | Druge platforme |
|---|---|---|---|
| web API | FastAPI, Django REST, Flask | ASP.NET Core | Spring (Java), Express (Node.js) |
| web aplikacija | Django | ASP.NET Core MVC, Blazor | Ruby on Rails, Laravel (PHP) |
| baza (ORM) | SQLAlchemy, Django ORM | Entity Framework Core | Hibernate (Java) |
| testovi | pytest | xUnit, NUnit | JUnit (Java) |
| sučelje u pregledniku | – | Blazor | React, Angular, Vue |
| mobilna aplikacija | – | .NET MAUI | Flutter, React Native, Swift, Kotlin |
Prednosti okvira: ne pišete po stoti put isto (prijava, rute, baza), dobijate provjerena sigurnosna rješenja, i novi član tima koji zna okvir odmah se snalazi. Cijena: okvir propisuje strukturu, morate ga učiti, i vezani ste za njegove verzije. Kad okvir izbaci novu glavnu verziju, i vaš proizvod mora preći na nju.
Za DeliveryApp 2.0 tim bira FastAPI za servise narudžbi i dostave (Python, tim ga zna iz verzije 1.x) i ASP.NET Core za novi modul izvještaja za udruženje (Dino i Emir bolje znaju C#, a firma koja će ga održavati radi u .NET-u). Dva jezika u jednom proizvodu su moguća jer dijelovi razgovaraju preko API-ja (poglavlja 7 i 9).
Da li je math.ceil dio biblioteke ili okvira? A FastAPI? Objasnite pomoću pitanja „ko koga poziva”.
api, servisi i domen? Od api prema servisi prema domen; domen ne zna za web ni bazu.Odgovori: tipka R ili klik na zamućeno polje.
Skoro svaki proizvod čuva podatke u bazi. DeliveryApp ima tabele za gradove, tarife, restorane, jela, kupce, narudžbe i stavke. Programer ima dva svijeta: u kodu su objekti (Narudzba sa stavkama), a u bazi su redovi tabela. Neko ih mora prevoditi.
ORM (object-relational mapping) je alat koji radi to prevođenje: programer piše narudzbe.dodaj(n), a ORM napiše SQL naredbu INSERT. U Pythonu se najčešće koriste SQLAlchemy i Django ORM, u C# Entity Framework Core.
| Objekat u kodu | Tabela u bazi |
|---|---|
klasa Narudzba | tabela narudzba |
polje broj | kolona broj (primarni ključ) |
lista stavke | tabela stavka sa stranim ključem narudzba_broj |
Tarifa grada | tabela tarifa s kolonom grad |
Migracije. DeliveryApp 2.0 dodaje tabelu tarifa i kolonu grad u tabelu restoran. Baza u Gradu A ima podatke godinu dana, i ne smije se obrisati i napraviti iznova. Migracija je skripta koja mijenja strukturu baze iz verzije u verziju i čuva podatke (npr. svim postojećim restoranima upiše grad = 'A'). Migracije se čuvaju u Gitu kao i kod (alati: Alembic za SQLAlchemy, dotnet ef migrations za Entity Framework).
Repozitorij. Bez obzira na ORM, dobar dizajn skriva bazu iza jednog objekta, repozitorija, koji ima operacije poput nadji(broj) i sacuvaj(narudzba). Ostatak koda ne zna da li je ispod SQLite, PostgreSQL ili SQL Server. Obrazac repozitorija detaljnije opisuje poglavlje 7.
„S ORM-om ne moram znati SQL.” ORM piše SQL umjesto vas, ali ne uvijek dobar. Poznat problem je „N + 1 upita”: lista od 100 narudžbi sa stavkama napravi 1 upit za narudžbe i još 100 upita za stavke, umjesto jednog. Ko ne zna SQL, ne vidi zašto je ekran spor.
Ekran „Moje narudžbe” iz poglavlja 3 prikazuje podatke (narudžbe), reaguje na dodir (dugme „Ponovi”) i mijenja se kad se stanje promijeni. Ako su sva tri posla izmiješana u jednom fajlu, svaka promjena izgleda ekrana rizikuje da pokvari logiku, i obrnuto.
MVC (Model – View – Controller) dijeli posao na tri dijela. Obrazac je opisao Trygve Reenskaug 1979. u Xerox PARC-u, u istoj laboratoriji koju smo posjetili u poglavlju 3.
MVVM (Model – View – ViewModel) je varijanta koju je Microsoft razvio za aplikacije s bogatim sučeljem (WPF, a danas .NET MAUI). Između prikaza i modela stoji ViewModel: objekat koji drži sve što ekran treba (npr. listu narudžbi i tekst „Ukupno: 24,50 €”). Prikaz je za ViewModel vezan (data binding): kad se vrijednost u ViewModelu promijeni, ekran se sam osvježi. Isti princip, pod drugim imenima, koriste i React, Angular i Flutter.

Pravilo za UX u kodu: logika ne ide u prikaz. Ako se cijena dostave računa u HTML šablonu, ta logika se ne može testirati, a mobilna aplikacija će je napisati ponovo, možda drugačije. Ekran samo prikazuje ono što mu da model ili ViewModel. Pristupačnost (opisi slika, kontrast, redoslijed fokusa za tastaturu) ugrađuje se u prikaz od početka, a ne na kraju.
Čisti kod je kod koji drugi programer brzo razumije i bez straha mijenja. Robert C. Martin u knjizi Clean Code (2008) kaže da se kod mnogo više čita nego što se piše, pa se isplati pisati ga za čitaoca. Pravila koja tim DeliveryApp koristi:
Imena. Ime kaže šta nešto jeste ili radi. d ne kaže ništa; udaljenost_km kaže sve. obracun(s, d, k, g) tjera čitaoca da pogađa; ukupno_za_naplatu(stavke, udaljenost_km, grad, kod) ne.
Funkcije. Kratke, rade jednu stvar, na jednom nivou apstrakcije. Ako funkcija ima riječ „i” u opisu („računa iznos i popust i dostavu”), vjerovatno treba biti tri funkcije.
Komentari. Dobar kod objašnjava sam sebe; komentar objašnjava zašto, a ne šta. Loše: # saberi. Dobro: # iznosi u centima, da izbjegnemo greške zaokruživanja.
Magični brojevi. Broj 3000 u sredini koda je zagonetka; BESPLATNA_OD = 3000 je pravilo PP-4.
Greške. Greška se javlja izuzetkom s jasnom porukom („adresa je izvan zone dostave”), a ne posebnom vrijednošću poput -1 ili -2 koju pozivalac mora zapamtiti.
Tri kratka principa:
U slučaju Toyote iz priče za početak, vještak je kao jedan od glavnih problema naveo hiljade globalnih varijabli. Globalna varijabla je suprotnost enkapsulaciji: bilo koji dio programa je može promijeniti, pa se ne može znati ko ju je promijenio i zašto. U kodu koji upravlja gasom automobila, to znači da niko ne može sa sigurnošću reći šta će program uraditi.
Miris koda je znak da nešto nije u redu, kao miris plina u stanu: sam po sebi nije kvar, ali upozorava. Najčešći mirisi:
| Miris | Kako se prepoznaje | Šta se radi |
|---|---|---|
| duga funkcija | ne staje na ekran | izdvojiti manje funkcije |
| dupliran kod | isti blok na više mjesta | izdvojiti na jedno mjesto (DRY) |
| dugačka lista parametara | obracun(s, d, k, g, x, y) | grupisati u objekat |
| magični brojevi | if t >= 3000 | imenovane konstante |
| grananje po tipu | if g == "A" … elif g == "B" … | podaci ili polimorfizam |
| globalno stanje | varijabla koju mijenjaju svi | enkapsulacija |
Tehnički dug. Ward Cunningham je 1992. uveo poređenje s kreditom. Kad tim izabere brzo, a lošije rješenje (npr. kopira pravilo dostave umjesto da ga izdvoji), to je kao uzeti kredit: danas dobijemo vrijeme, ali poslije plaćamo kamatu svaki put kad mijenjamo taj kod. Kredit nije uvijek loš (nekad je rok važniji), ali neplaćen kredit raste dok ne proguta sav budžet.

Refaktoring je vraćanje duga: promjena unutrašnje strukture koda bez promjene ponašanja. Martin Fowler ga je opisao u knjizi Refactoring (1999, drugo izdanje 2018). Ključni uslov je mreža testova: prije refaktoringa napišu se testovi koji opisuju sadašnje ponašanje, a poslije svakog malog koraka testovi moraju i dalje prolaziti.
Primjer iz DeliveryApp. U verziji 1.x funkcija obracun je narasla kad su dodani gradovi B i C (primjeri/python/obracun_prije.py, skraćeno):
def obracun(s, d, k, g):
t = 0
for x in s:
t = t + x[0] * x[1]
if t < 500:
return -1
if g == "A":
if d > 10:
return -2
if t >= 3000:
dd = 0
else:
if d <= 3:
dd = 200
else:
dd = 200 + math.ceil(d - 3) * 50
elif g == "B":
... # isti blok, drugi brojevi
else:
... # isti blok, treći brojeviMirisi: kratka i nejasna imena, magični brojevi, isti blok tri puta, grananje po gradu, greške kao -1 i -2. Alat radon izmjeri ciklomatsku složenost (broj nezavisnih putanja kroz funkciju) i daje 16; preko 10 se smatra teškim za testiranje.
Nakon refaktoringa (primjeri/python/obracun_poslije.py) pravilo dostave živi u klasi Tarifa iz poglavlja 3, a obračun je tri kratke funkcije:
def iznos_artikala(stavke):
"""Zbir stavki; stavka je par (cijena_u_centima, količina)."""
return sum(cijena * kolicina for cijena, kolicina in stavke)
def popust(iznos, kod):
procenat = KODOVI_POPUSTA.get((kod or "").strip().upper(), 0)
return min(round(iznos * procenat / 100), NAJVECI_POPUST)
def ukupno_za_naplatu(stavke, udaljenost_km, grad, kod=None):
"""Artikli − popust + dostava po tarifi grada, u centima."""
iznos = iznos_artikala(stavke)
dostava = TARIFE[grad].dostava(udaljenost_km, iznos)
return iznos - popust(iznos, kod) + dostavaNajveća složenost je sada 5 (metoda Tarifa.dostava), a ostale funkcije imaju 1 ili 2. Fajl test_obracun.py ima 48 testova, među kojima i 42 koja porede staru i novu funkciju za tri grada, sedam udaljenosti i dva koda popusta. Svi prolaze, pa znamo da se ponašanje nije promijenilo.
„Refaktoring je kad prepišemo sve iznova.” Prepisivanje od nule nije refaktoring, nego nov projekat s novim greškama. Refaktoring ide malim koracima, uz testove koji prolaze poslije svakog koraka, i nikad ne mijenja ono što korisnik vidi.
obracun je refaktorisana sa složenosti 16 na funkcije složenosti najviše 5, uz 48 testova.t? Npr. iznos_artikala, ime koje kaže šta varijabla sadrži.Odgovori: tipka R ili klik na zamućeno polje.
Cilj je samostalno refaktorisati funkciju obracun i dokazati testovima da se ponašanje nije promijenilo. Trajanje: jedan čas.
Kopirajte u svoj repozitorij fajlove dostava.py i obracun_prije.py iz foldera primjeri/python ovog udžbenika. Instalirajte alate:
python -m pip install pytest radon ruff
python -m radon cc -s obracun_prije.pyZapišite izmjerenu složenost (16).
Napravite test_obracun.py s testom koji za N-1001 u tri grada provjerava 2450, 2530 i 2500 centi, i s testom koji poredi staru funkciju sa onim što ćete napisati (vidi odjeljak 4.8). Pokrenite pytest i provjerite da prolazi bar test stare funkcije.
Refaktorišite u koracima, i poslije svakog pokrenite pytest:
s → stavke, d → udaljenost_km, k → kod, g → grad) pomoću opcije IDE-a Rename Symbol;iznos_artikala(stavke);popust(iznos, kod) s imenovanim konstantama;TARIFE[grad].dostava(...);-1 i -2 izuzecima.Ponovo izmjerite složenost (radon cc -s) i pokrenite ruff check. Napravite commit s porukom koja kaže šta je urađeno i da se ponašanje nije promijenilo.
Napišite isti UkupnoZaNaplatu u C# koristeći record Tarifa iz primjeri/csharp/Dostava.cs i provjerite da za N-1001 ispisuje 24,50, 25,30 i 25,00.
Rezultat: commit s refaktorisanim kodom, testovima koji prolaze i zapisanom složenosti prije i poslije.
Rješenja su sakrivena – tipka R ili klik na okvir.
Predložite bolja imena: def f(a, b) (računa dostavu), x = 3000, lista2, temp, podaci, flag.
def cijena_dostave(udaljenost_km, iznos); BESPLATNA_DOSTAVA_OD = 3000; npr. aktivni_restorani; ime prema sadržaju, npr. stara_cijena; npr. narudzbe_danas; npr. je_placeno. Ime kaže šta varijabla sadrži ili šta funkcija radi.
Za svaki alat recite da li je biblioteka ili okvir: a) math u Pythonu; b) ASP.NET Core; c) json u Pythonu; d) Django; e) xUnit.
a) biblioteka: vi pozivate math.ceil; b) okvir: on prima zahtjev i poziva vaš kontroler; c) biblioteka; d) okvir; e) okvir za testove: on pronalazi i poziva vaše testne metode (holivudski princip).
Pronađite tri mirisa u kodu i predložite ispravku:
def obradi(n):
if n.status == 1:
n.status = 2
posalji_sms(n.kupac.tel, "Prihvaćeno")
if n.ukupno > 3000:
n.dostava = 0(1) Magični brojevi 1, 2 i 3000: stanja kao imenovane vrijednosti (NOVA, PRIHVAĆENA), a granica kao BESPLATNA_OD. (2) Funkcija radi više stvari (mijenja stanje, šalje SMS, računa dostavu) i ime obradi ništa ne kaže: razdvojiti na prihvati(narudzba), servis obavještenja i obračun dostave. (3) Stanje se mijenja direktno (n.status = 2) zaobilazeći pravila prelaza: koristiti narudzba.promijeni("PRIHVAĆENA"). Uz to, > 3000 je pogrešno jer pravilo kaže „od 30,00 € i više” (>=).
Pred isporuku za Grad C, Amar predlaže da se tarifa Grada C privremeno „zakuca” u kod kopiranjem bloka za Grad A, „pa ćemo popraviti poslije”. Kada je to razumno, i šta tim mora uraditi da dug ne naraste?
Razumno je ako je rok zaista kritičan i ako je dug svjesno izabran. Tim mora: zapisati dug kao stavku u backlogu s procjenom, napisati testove za Grad C odmah (da refaktoring kasnije bude siguran), i planirati vraćanje duga u jednom od sljedeća dva sprinta. Dug koji nije zapisan, zaboravlja se i raste.
obracun sa složenosti 16 pretvoren u kratke funkcije uz 48 testova koji prolaze.print naredbi?