Razvoj programskih proizvoda · Poglavlje 4 · Predavanje 4

Implementacija i čisti kod

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

Šta učimo ovog semestra (1–5)

Deset predavanja: dva časa teorije i jedan čas dopunske prakse. Danas: poglavlje 4 (označeno). Isti primjer kroz knjigu: DeliveryApp 2.0.

Pred.Dio 1Dio 2Praksa
1Projekat i proizvod, trendovi, etikaInženjerski pristup, alati, automatizacijaRepozitorij i vizija proizvoda
2Modeli, uloge, planiranjeRizici, tim, kvalitet, poboljšanje procesaPlan izdanja, registar rizika
3Konceptualni model, UML, SOLIDPrototip, UX, specifikacija dizajnaWireframe i SDD
4IDE, organizacija koda, OOP, okviriPodaci, MVC/MVVM, čisti kod, refaktoringRefaktoring funkcije
5Git, pull request, CITestovi, CD, kontejneriGitHub Actions
DeliveryApp 2.0 · narudžba N-1001 · 24,50 €

Šta učimo ovog semestra (6–10)

Deset predavanja: dva časa teorije i jedan čas dopunske prakse. Danas je poglavlje 4. Isti primjer kroz knjigu: DeliveryApp 2.0.

Pred.Dio 1Dio 2Praksa
6Klasifikacija, mjerljivostTaktike i redizajnMjerenje i keš
7Odluke, pogledi, stiloviTroslojna i čista arhitektura, predlošciADR i C4
8CBSE, odlike, interfejsiPrincipi, smjernice, provođenjePaket sa verzijom
9SaaS, SOA, IoT, proizvodne linijeAspekti, reaktivno programiranjeDekorator i tok događaja
10Vrijednost, troškovi, razvoj ili kupnja, TCOMetrike, procjena, NPV, povratNPV u Pythonu
DeliveryApp 2.0 · narudžba N-1001 · 24,50 €

Priča za početak

Priča za početak

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.

Priča za početak

Priča za početak

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.

Literatura

  1. I. Sommerville: Software Engineering, 10th ed., Pearson, 2016 (poglavlje 7).
  2. R. C. Martin: Clean Code, Prentice Hall, 2008.
  3. M. Fowler: Refactoring, 2nd ed., Addison-Wesley, 2018.

Zašto ovo poglavlje

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.

Riječi ovog poglavlja (1/3)

  • IDE – integrisano razvojno okruženje: editor, pokretanje, otklanjanje grešaka i testovi u jednom programu.
  • Modul i paket – fajl s kodom (modul) i folder s više modula (paket).
  • Globalna varijabla – podatak koji može pročitati i promijeniti bilo koji dio programa.
  • Biblioteka – gotov kod koji vi pozivate.
  • Razvojni okvir (framework) – gotov kostur aplikacije koji poziva vaš kod.
  • ORM (object-relational mapping) – alat koji redove tabele pretvara u objekte i obrnuto.

Riječi ovog poglavlja (2/3)

  • Migracija baze – skripta koja mijenja strukturu baze iz jedne verzije u sljedeću.
  • MVC i MVVM – obrasci koji odvajaju podatke i logiku od prikaza na ekranu.
  • Čisti kod – kod koji drugi programer brzo razumije i bez straha mijenja.
  • Miris koda (code smell) – znak u kodu koji upućuje na dublji problem dizajna.
  • Tehnički dug – budući trošak koji nastaje kad danas izaberemo brže, a lošije rješenje.
  • Refaktoring – promjena unutrašnje strukture koda bez promjene njegovog ponašanja.

Riječi ovog poglavlja (3/3)

  • Ciklomatska složenost – broj nezavisnih putanja kroz funkciju; što veći, to teže za test.

Ciljevi poglavlja

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.

Plan poglavlja – Predavanje 4: dva časa teorije i jedan čas dopunske prakse

ČasSadržajNa času
Teorija 14.1–4.4 IDE, organizacija koda, OOP u Pythonu i C#, okviriIsta klasa u dva jezika; biblioteka ili okvir?
Teorija 24.5–4.8 Podaci, MVC/MVVM, čisti kod, mirisi i refaktoringFunkcija obracun() prije i poslije
Dopunska praksaRefaktoring uz testove48 testova prolazi prije i poslije
Dio 1 · Predavanje 4

Dio 1 – Od dizajna do koda

4.1 Razvojna okruženja

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:

  1. Bojenje i dopunjavanje koda – predlaže imena funkcija i polja dok kucate.
  2. Prijava grešaka dok pišete – podvuče grešku prije nego što pokrenete program.
  3. Navigacija – „idi na definiciju”, „nađi sva mjesta gdje se koristi”.
  4. Sigurne izmjene – preimenovanje funkcije na svim mjestima odjednom.
  5. Debager – zaustavi program na izabranoj liniji (breakpoint) i pokaže vrijednosti varijabli.
  6. Pokretanje testova – jednim klikom, s crvenim i zelenim oznakama.
  7. Integracija s Gitom i AI asistentom – izmjene, commit, prijedlozi koda.

4.1 Razvojna okruženja

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.

Ukratko – 4.1 Razvojna okruženja

  • IDE objedinjuje editor, provjeru, navigaciju, sigurne izmjene, debager, testove i Git.
  • Debager zaustavlja program i pokazuje vrijednosti varijabli; brži je od niza print naredbi.
  • Izbor IDE-a je stvar navike tima; važno je da svi koriste iste postavke formata koda.

4.2 Organizacija koda

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:

4.2 Organizacija koda

  1. Imena govore šta je unutra. Folder domen sadrži pravila poslovanja; utils i razno su znak da niko nije znao gdje nešto pripada.
  2. Zavisnosti idu u jednom smjeru. 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).
  3. Isti oblik u cijelom timu. Automatski formater (u Pythonu ruff format ili black, u C# dotnet format) izjednači razmake i navodnike, da se pregled koda ne troši na stil.
Slika 4.1 – Zavisnosti idu u jednom smjeru: od weba i baze prema pravilima
Slika 4.1 – Zavisnosti idu u jednom smjeru: od weba i baze prema pravilima

Ukratko – 4.2 Organizacija koda

  • Kod se dijeli na module i pakete po odgovornosti.
  • Imena foldera govore šta je unutra; „utils” i „razno” su znak lošeg reda.
  • Zavisnosti idu u jednom smjeru, prema pravilima poslovanja.
  • Formater izjednačava stil koda u cijelom timu.

4.3 Objektno orijentirani principi u Pythonu i C#

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 = novo

4.3 Objektno orijentirani principi u Pythonu i C#

class 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”.

4.3 Objektno orijentirani principi u Pythonu i C#

IdejaPythonC#
skriveno polje_stanje (dogovor)private set (prevodilac čuva)
svojstvo samo za čitanje@property{ get; }
objekat s podacima@dataclass(frozen=True)record
interfejsProtocol ili apstraktna klasainterface
greškaraise ValueErrorthrow new InvalidOperationException
Česta zabluda

„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.

Ukratko – 4.3 Objektno orijentirani principi u Pythonu i C#

  • Python je dinamičan i privatnost je dogovor; C# je statičan i privatnost provjerava prevodilac.
  • Enkapsulacija znači da se stanje mijenja samo kroz operacije koje poštuju pravila.
  • Isti dizajn se može napisati u oba jezika; razlikuje se samo zapis.

4.4 Razvojni i programski okviri

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#.

4.4 Razvojni i programski okviri

VrstaPythonC# / .NETDruge platforme
web APIFastAPI, Django REST, FlaskASP.NET CoreSpring (Java), Express (Node.js)
web aplikacijaDjangoASP.NET Core MVC, BlazorRuby on Rails, Laravel (PHP)
baza (ORM)SQLAlchemy, Django ORMEntity Framework CoreHibernate (Java)
testovipytestxUnit, NUnitJUnit (Java)
sučelje u pregledniku–BlazorReact, Angular, Vue
mobilna aplikacija–.NET MAUIFlutter, 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).

4.4 Razvojni i programski okviri

Na času

Da li je math.ceil dio biblioteke ili okvira? A FastAPI? Objasnite pomoću pitanja „ko koga poziva”.

Ukratko – 4.4 Razvojni i programski okviri

  • Vaš kod poziva biblioteku; okvir poziva vaš kod (holivudski princip).
  • Okviri štede ponavljanje i nose provjerena rješenja, ali propisuju strukturu i vezuju za verzije.
  • DeliveryApp 2.0 koristi FastAPI (Python) i ASP.NET Core (C#), povezane preko API-ja.

Provjeri se

  1. Šta radi tačka prekida u debageru? Zaustavi program na izabranoj liniji da bi se vidjele vrijednosti varijabli.
  2. U kom smjeru idu zavisnosti između foldera api, servisi i domen? Od api prema servisi prema domen; domen ne zna za web ni bazu.
  3. Šta je holivudski princip? Okvir poziva vaš kod, a ne vi njega: „ne zovite nas, mi ćemo zvati vas”.

Odgovori: tipka R ili klik na zamućeno polje.

Dio 2 · Predavanje 4

Dio 2 – Kod koji se čita

4.5 Rad s podacima

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.

4.5 Rad s podacima

Objekat u koduTabela u bazi
klasa Narudzbatabela narudzba
polje brojkolona broj (primarni ključ)
lista stavketabela stavka sa stranim ključem narudzba_broj
Tarifa gradatabela 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.

4.5 Rad s podacima

Česta zabluda

„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.

Ukratko – 4.5 Rad s podacima

  • ORM prevodi objekte u redove tabela i obrnuto (SQLAlchemy, Entity Framework Core).
  • Migracije mijenjaju strukturu baze iz verzije u verziju i čuvaju podatke.
  • Repozitorij skriva bazu od ostatka koda.
  • ORM ne oslobađa od znanja SQL-a.

4.6 Korisničko sučelje u kodu: MVC i MVVM

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.

  • Model – podaci i pravila (narudžba, tarifa).
  • Prikaz (View) – kako se podaci vide (HTML stranica, ekran mobitela).
  • Kontroler (Controller) – prima radnju korisnika („Ponovi”), poziva model i bira prikaz.

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.

Slika 4.2 – MVC i MVVM: ko s kim razgovara
Slika 4.2 – MVC i MVVM: ko s kim razgovara

4.6 Korisničko sučelje u kodu: MVC i MVVM

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.

Ukratko – 4.6 Korisničko sučelje u kodu: MVC i MVVM

  • MVC dijeli kod na model, prikaz i kontroler; MVVM dodaje ViewModel i vezivanje podataka.
  • Logika ne ide u prikaz, jer se tamo ne može testirati i ponavlja se na svakom uređaju.
  • Pristupačnost se ugrađuje u prikaz od početka.

4.7 Čisti kod

Č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.

4.7 Čisti kod

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:

  • DRY (Don't Repeat Yourself) – svako znanje na jednom mjestu. Ako je pravilo dostave napisano tri puta, prije ili kasnije tri kopije će se razići.
  • KISS (Keep It Simple) – najjednostavnije rješenje koje radi.
  • YAGNI (You Aren't Gonna Need It) – ne pravi danas ono što „možda zatreba” sutra.

4.7 Čisti kod

Iz stvarnog svijeta

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.

Ukratko – 4.7 Čisti kod

  • Kod se više čita nego piše; piše se za čitaoca.
  • Imena govore šta nešto jeste, funkcije rade jednu stvar, komentari kažu zašto.
  • Magični brojevi postaju imenovane konstante, a greške izuzeci s jasnom porukom.
  • DRY, KISS i YAGNI: jedno znanje na jednom mjestu, jednostavno, bez nagađanja budućnosti.

4.8 Mirisi koda, tehnički dug i refaktoring

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:

4.8 Mirisi koda, tehnički dug i refaktoring

MirisKako se prepoznajeŠta se radi
duga funkcijane staje na ekranizdvojiti manje funkcije
dupliran kodisti blok na više mjestaizdvojiti na jedno mjesto (DRY)
dugačka lista parametaraobracun(s, d, k, g, x, y)grupisati u objekat
magični brojeviif t >= 3000imenovane konstante
grananje po tipuif g == "A" … elif g == "B" …podaci ili polimorfizam
globalno stanjevarijabla koju mijenjaju svienkapsulacija

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.

Slika 4.3 – Tehnički dug: kamata raste dok se dug ne vrati
Slika 4.3 – Tehnički dug: kamata raste dok se dug ne vrati

4.8 Mirisi koda, tehnički dug i refaktoring

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 brojevi

4.8 Mirisi koda, tehnički dug i refaktoring

Mirisi: 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) + dostava

Najveć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.

4.8 Mirisi koda, tehnički dug i refaktoring

Česta zabluda

„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.

Ukratko – 4.8 Mirisi koda, tehnički dug i refaktoring

  • Miris koda upozorava na problem dizajna: duga funkcija, duplikat, magični broj, grananje po tipu, globalno stanje.
  • Tehnički dug je kao kredit: danas brže, a kasnije se plaća kamata pri svakoj izmjeni.
  • Refaktoring mijenja strukturu bez promjene ponašanja, malim koracima, uz testove.
  • Funkcija obracun je refaktorisana sa složenosti 16 na funkcije složenosti najviše 5, uz 48 testova.

Provjeri se

  1. Šta je bolje ime od t? Npr. iznos_artikala, ime koje kaže šta varijabla sadrži.
  2. Kako bi Cunningham objasnio tehnički dug? Kao kredit: brže danas, a kamata se plaća pri svakoj budućoj izmjeni.
  3. Šta mora postojati prije refaktoringa? Testovi koji opisuju sadašnje ponašanje.

Odgovori: tipka R ili klik na zamućeno polje.

Dopunska praksa · Predavanje 4

Dopunska praksa – refaktoring uz testove

Dopunska praksa – refaktoring uz testove

Cilj je samostalno refaktorisati funkciju obracun i dokazati testovima da se ponašanje nije promijenilo. Trajanje: jedan čas.

Korak 1 – Priprema

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.py

Zapišite izmjerenu složenost (16).

Korak 2 – Mreža testova

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.

Korak 3 – Mali koraci

Refaktorišite u koracima, i poslije svakog pokrenite pytest:

  1. preimenujte parametre (s → stavke, d → udaljenost_km, k → kod, g → grad) pomoću opcije IDE-a Rename Symbol;
  2. izdvojite iznos_artikala(stavke);
  3. izdvojite popust(iznos, kod) s imenovanim konstantama;
  4. zamijenite tri bloka za gradove pozivom TARIFE[grad].dostava(...);
  5. zamijenite povratne vrijednosti -1 i -2 izuzecima.

Korak 4 – Mjerenje i commit

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.

Korak 5 – C# (opcionalno)

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.

Vježbe

Vježbe za studenta

Rješenja su sakrivena – tipka R ili klik na okvir.

Vježba za studenta 1 – imena.

Zadatak

Predložite bolja imena: def f(a, b) (računa dostavu), x = 3000, lista2, temp, podaci, flag.

Vježba za studenta 1 – imena. – rješenje

Rješenje (R)

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.

Vježba za studenta 2 – biblioteka ili okvir.

Zadatak

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.

Vježba za studenta 2 – biblioteka ili okvir. – rješenje

Rješenje (R)

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).

Vježba za studenta 3 – mirisi koda.

Zadatak

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

Vježba za studenta 3 – mirisi koda. – rješenje

Rješenje (R)

(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” (>=).

Vježba za studenta 4 – tehnički dug.

Zadatak

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?

Vježba za studenta 4 – tehnički dug. – rješenje

Rješenje (R)

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.

Sažetak u pet rečenica

  1. U slučaju Toyote 2013. godine špageti kod s hiljadama globalnih varijabli bio je toliko zamršen da niko nije mogao dokazati da je ispravan, i čitljivost je postala pitanje odgovornosti.
  2. IDE s debagerom je mjesto rada programera, a kod se organizuje u module i pakete po odgovornosti, sa zavisnostima koje idu u jednom smjeru, prema pravilima poslovanja.
  3. Isti objektno orijentirani dizajn piše se u Pythonu i C#, a okviri (FastAPI, ASP.NET Core) pozivaju naš kod i štede nam ponavljanje, uz cijenu vezivanja za njihovu strukturu i verzije.
  4. ORM i migracije povezuju objekte s bazom, a MVC i MVVM odvajaju logiku od prikaza, da bi se logika mogla testirati.
  5. Čisti kod ima jasna imena, kratke funkcije i imenovane konstante, tehnički dug je kredit koji se vraća refaktoringom, a refaktoring ide malim koracima uz testove, kao kad je obracun sa složenosti 16 pretvoren u kratke funkcije uz 48 testova koji prolaze.

Pitanja za ponavljanje

  1. Šta se desilo u slučaju Toyota 2013. i kakve to veze ima s čitljivošću koda?
  2. Nabrojte sedam stvari koje dobar IDE radi. Zašto je debager bolji od niza print naredbi?
  3. Kako se kod organizuje u pakete i u kom smjeru idu zavisnosti?
  4. Kako izgleda enkapsulirano stanje u Pythonu, a kako u C#? Po čemu se razlikuju?
  5. Šta je razlika između biblioteke i okvira? Šta je holivudski princip?
  6. Navedite prednosti i cijenu korištenja okvira.
  7. Šta je ORM? Šta je migracija baze i zašto je potrebna u DeliveryApp 2.0?
  8. Objasnite MVC i MVVM. Zašto logika ne ide u prikaz?
  9. Navedite pravila čistog koda za imena, funkcije, komentare i greške.
  10. Šta znače DRY, KISS i YAGNI?
  11. Nabrojte pet mirisa koda i šta se s njima radi.
  12. Šta je tehnički dug, šta je refaktoring, i šta je preduslov za siguran refaktoring?