Poglavlje 4: Implementacija i čisti kod

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

Literatura. Osnovna literatura predmeta:

  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.

Riječi ovog poglavlja.

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

Č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

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.

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.

  • 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:

  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

Ukratko.

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

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

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

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

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

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

Ukratko.

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

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.

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.

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

  • 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

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.

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

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.

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.

  • 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:

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.

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

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

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.

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

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

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žba za studenta 1 – imena.

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

Rješenje. 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.

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.

Rješenje. 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.

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

Rješenje. (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.

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?

Rješenje. 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?

Odgovori na pitanja za ponavljanje.

  1. Porota u Oklahomi je u oktobru 2013. proglasila Toyotu odgovornom za nesreću iz 2007. Vještak Michael Barr opisao je softver za upravljanje gasom kao špageti kod s oko 11.000 globalnih varijabli i funkcijama koje se ne mogu testirati. Iako tačan kvar nije pokazan, kod je bio toliko zamršen da niko nije mogao dokazati da je ispravan; čitljivost je tako postala pitanje odgovornosti.
  2. Bojenje i dopunjavanje koda, prijava grešaka dok pišete, navigacija, sigurne izmjene (preimenovanje), debager, pokretanje testova, integracija s Gitom i AI asistentom. Debager zaustavi program na izabranoj liniji i pokaže sve vrijednosti, bez mijenjanja koda i bez zaboravljenih print naredbi.
  3. U module i pakete po odgovornosti (npr. domen, servisi, podaci, api, vanjski). Zavisnosti idu prema pravilima poslovanja: api koristi servisi, servisi koriste domen, a domen ne zna za web ni bazu.
  4. U Pythonu polje počinje donjom crtom (_stanje) i čita se kroz @property, a mijenja samo metodom koja provjerava pravila; privatnost je dogovor. U C# svojstvo ima { get; private set; }, pa prevodilac ne dozvoljava promjenu spolja.
  5. Vaš kod poziva biblioteku, a okvir poziva vaš kod. Holivudski princip: „ne zovite nas, mi ćemo zvati vas” – okvir odlučuje kada će pozvati vašu funkciju.
  6. Prednosti: ne piše se ponovo isto, provjerena sigurnosna rješenja, novi članovi se brže snalaze. Cijena: okvir propisuje strukturu, treba ga učiti, i proizvod je vezan za njegove verzije.
  7. ORM je alat koji prevodi objekte u redove tabela i obrnuto. Migracija je skripta koja mijenja strukturu baze iz verzije u verziju i čuva podatke; u DeliveryApp 2.0 dodaje tabelu tarifa i kolonu grad, a postojećim restoranima upisuje Grad A.
  8. MVC dijeli kod na model (podaci i pravila), prikaz (izgled) i kontroler (prima radnju i poziva model). MVVM između prikaza i modela stavlja ViewModel, za koji je prikaz vezan, pa se ekran sam osvježava. Logika ne ide u prikaz jer se tamo ne može testirati i ponavlja se na svakom uređaju.
  9. Imena kažu šta nešto jeste ili radi; funkcije su kratke i rade jednu stvar; komentari objašnjavaju zašto, a ne šta; greške se javljaju izuzecima s jasnom porukom, a ne posebnim vrijednostima poput −1.
  10. DRY – svako znanje na jednom mjestu; KISS – najjednostavnije rješenje koje radi; YAGNI – ne praviti danas ono što možda zatreba sutra.
  11. Duga funkcija (izdvojiti manje), dupliran kod (izdvojiti na jedno mjesto), dugačka lista parametara (grupisati u objekat), magični brojevi (imenovane konstante), grananje po tipu (podaci ili polimorfizam), globalno stanje (enkapsulacija).
  12. Tehnički dug je budući trošak lošijeg, bržeg rješenja, kao kamata na kredit. Refaktoring je promjena unutrašnje strukture koda bez promjene ponašanja. Preduslov su testovi koji opisuju sadašnje ponašanje i prolaze poslije svakog malog koraka.