MODUL 1 - LEKCIJA 5

Programerski Principi u Javnoj Upravi

Temelji pisanja čistog, održivog i skalabilnog koda: SOLID, DRY, KISS i YAGNI objašnjeni kroz specifične primjere iz sistema državne uprave i javnih servisa.

⏱️ Trajanje: ~60 min 📚 Nivo: Srednji 🎯 Koncepti: SOLID, DRY, KISS, YAGNI, SoC

🏛️ Zašto su principi važni u e-Upravi?

Softverski sistemi u javnoj upravi (kao što su registri građana, sistemi za izdavanje dokumenata, portali e-Uprave, poreske evidencije) karakteristični su po tome što moraju raditi besprijekorno desetinama godina, moraju se stalno prilagođavati novim zakonima, i na njima rade timovi inženjera koji se s vremenom mijenjaju.

Pisanje koda koji "samo radi" u ovakvom okruženju je izuzetno opasno. Kôd mora biti napisan tako da ga je lako čitati, održavati, te nadograđivati u skladu s novim regulativama bez straha da će nešto puknuti u kritičnom sistemu za građane. Zato postoje programerski principi - set smjernica koje su godinama razvijali najiskusniji softver inženjeri kako bi spriječili propadanje koda u nečitljivi "špageti kôd".

📊 Brzi Pregled Principa

Akronim Puni Naziv Suština u kontekstu Uprave
SOLID (5 principa OOP-a) Osnova za sisteme koji prate zakon: Lako dodavanje novih propisa bez rušenja postojećih servisa.
DRY Don't Repeat Yourself Ako jednom napišemo validaciju JMBG-a, ne kopiramo je u svakoj formi za zahtjeve.
KISS Keep It Simple, Stupid Ne komplikujte sistem općinskih taksi nečim što sistem čini pretjerano kompleksnim i skupim za održavanje.
YAGNI You Aren't Gonna Need It Ne pravite infrastrukturu za biometrijsko prepoznavanje ako zakon trenutno traži samo PIN kod.

💎 SOLID Principi kroz prizmu Javnog Sektora

SOLID je akronim od pet osnovnih principa objektno-orijentisanog dizajna koje je promovisao Robert C. Martin (Uncle Bob). Kada se državni IT sistemi projektuju po ovim principima, izmjena Zakona o upravnom postupku neće značiti ponovno pisanje cijele aplikacije, već samo kreiranje novih minijaturnih blokova koji se nadovezuju na sistem.

S - Single Responsibility Principle (SRP)

Klasa treba da ima samo jedan razlog za promjenu, tj. jednu specifičnu odgovornost.
Zamislimo digitalni servis za odobravanje zahtjeva za izdavanje građevinske dozvole. Ako se proces izdavanja dozvole promijeni (npr. treba novo obavještenje, ili se snima u drugačiju bazu), trebali bismo mijenjati samo onaj dio koda koji je zadužen isključivo za taj aspekt.

❌ Loš Primjer - "SuperSlužbenik" klasa koja radi sve odjednom
// Loše: ProcessorZahtjeva klasa je zadužena za VIZUELNU validaciju, BAZU PODATAKA i EMAIL obavještenja!
public class ProcessorGradjevinskihDozvola 
{
    public void ObradiZahtjev(Zahtjev zahtjev, Gradjanin gradjanin) 
    {
        // Odgovornost 1: Validacija (Šta ako zakon promijeni uvjete validacije?)
        if(string.IsNullOrEmpty(zahtjev.BrojParcele)) 
            throw new Exception("Broj parcele je obavezan po Zakonu čl. 42.");
        
        // Odgovornost 2: Perzistencija / Rad s bazom (Šta ako pređemo sa SQL baze na neku državnu cloud platformu?)
        using(var db = new VladinaBazaContext())
        {
            db.Zahtjevi.Add(zahtjev);
            db.SaveChanges();
        }
        
        // Odgovornost 3: Obavještavanje (Šta ako država uvede obavezni SMS umjesto Email-a?)
        var smtpClient = new SmtpClient("mail.vlada.gov.ba");
        smtpClient.Send("[email protected]", gradjanin.Email, "Vaš zahtjev je zaprimljen", "...");
        
        // Odgovornost 4: Auditiranje / Dnevnik rada
        System.IO.File.AppendAllText(@"C:\Logs\UpravaLogs.txt", $"Zahtjev obradio ID: 1042 za parcela {zahtjev.BrojParcele}");
    }
}

Problem: Ova klasa je postala ogroman "Silos". Ako se promijeni sistem logovanja, moramo dirati klasu koja direktno barata izdavanjem građevinskih dozvola! Time rizikujemo da slučajno pokvarimo logiku izdavanja dozvola dok "popravljamo" obavještenja.

✅ Dobar Primjer - Razdvojeni servisi, sistem kao tim stručnjaka
// 1. Specijalista za Pravnu Validaciju
public class GradjevinskaDozvolaValidator
{
    public bool JeLiZakonito(Zahtjev zahtjev)
    {
        return !string.IsNullOrEmpty(zahtjev.BrojParcele) && zahtjev.KatastarskaOpstina != null;
    }
}

// 2. Specijalista za Arhiviranje (Bazu Podataka)
public class ZahtjevRepository
{
    private readonly VladinaBazaContext _db;
    public void Sacuvaj(Zahtjev zahtjev) { /* spašavanje... */ }
}

// 3. Specijalista za Odnose s Javnošću (Slanje poruka)
public class GradjaninNotifikator
{
    public void PosaljiPotvrduPrijema(Gradjanin gradjanin) { /* slanje... */ }
}

// Koordinator postupka (Šef odjela) spaja ove specijaliste:!
public class SluzbenikZaDozvole
{
    private readonly GradjevinskaDozvolaValidator _validator;
    private readonly ZahtjevRepository _repozitorij;
    private readonly GradjaninNotifikator _notifikator;
    
    public void ObradiZahtjev(Zahtjev zahtjev, Gradjanin gradjanin)
    {
        if(!_validator.JeLiZakonito(zahtjev))
            throw new Exception("Zahtjev nije u skladu sa zakonom.");
            
        _repozitorij.Sacuvaj(zahtjev);
        _notifikator.PosaljiPotvrduPrijema(gradjanin);
    }
}

O - Open/Closed Principle (OCP)

Klase trebaju biti otvorene za proširenje, ali zatvorene za izmjenu postojećeg koda.
U upravi se pravila neprestano mijenjaju i donose se nove uredbe (npr. "Od 1. marta uvodimo novu taksu za ubrzani proces, a ukidamo plaćanje za penzionere"). Ako svaki put zbog toga moramo otvarati i prepravljati glavne klase za obračun finansija, rizikujemo kreiranje bagova u već testiranom i funkcionalnom sistemu.

❌ Loš Primjer - Ogromni IF/ELSE blokovi koji krše OCP
public class ObracunAdministrativneTakse
{
    public decimal IzracunajTaksu(Zahtjev zahtjev, KategorijaLica kategorija)
    {
        decimal osnovnaTaksa = 50.0m;

        // Ako se sutra doda nova kategorija, MORAMO mijenjati ovaj metod!
        if(kategorija.Tip == "Student")
            return osnovnaTaksa * 0.5m; // 50% popusta
        else if(kategorija.Tip == "Penzioner")
            return 0m; // oslobođeni plaćanja
        else if(kategorija.Tip == "UbrzaniPostupak")
            return osnovnaTaksa * 2.0m; // dupla taksa
        else if(kategorija.Tip == "RatniVojniInvalid")
            return osnovnaTaksa * 0.2m; // 80% popusta
            
        return osnovnaTaksa;
    }
}
✅ Dobar Primjer - Oslanjanje na Polimorfizam (Proširivo bez promjene starog koda)
// Kreiramo zajedničko pravilo (Interfejs ili Apstraktnu klasu)
public abstract class PraviloZaTaksu
{
    protected decimal OsnovnaTaksa = 50.0m;
    public abstract decimal IzracunajIznos();
}

// Svaka kategorija ima svoju klasu. Ako zakon ostane isti za studente, nju nikada više ne diramo!
public class StandardnaTaksa : PraviloZaTaksu
{
    public override decimal IzracunajIznos() => OsnovnaTaksa;
}

public class PenzionerOslobadjanje : PraviloZaTaksu
{
    public override decimal IzracunajIznos() => 0m; 
}

// 🌟 MAGIJA: Ako općinsko vijeće sutra izglasa novu taksu za online zahtjeve, 
// DODAJE SE NOVA KLASA. Postojeći kod se NE MIJENJA!
public class OnlineObradaPopustTaksa : PraviloZaTaksu
{
    public override decimal IzracunajIznos() => OsnovnaTaksa * 0.8m; // 20% popusta na e-uslugu
}

L - Liskov Substitution Principle (LSP)

Objekti bazne klase trebaju biti zamjenjivi objektima njenih naslijeđenih klasa bez da to izazove pucanje aplikacije.
Zamislimo elektronsku arhivu dokumenata državne agencije. Svi dokumenti se arhiviraju, ali "DržavnaTajna" dokumenti moraju proći drugačiju proceduru, ili uopšte ne bi smjeli biti digitalno prenošeni.

❌ Loš Primjer - Narušavanje semantike
public class JavnoDostupanDokument
{
    public virtual string DobijLinkZaJavnost()
    {
        return "https://transparentnost.vlada.ba/doc/123";
    }
}

// StrogoPovjerljivo nasljeđuje od JavnoDostupanDokument (greška u dizajnu!)
public class StrogoPovjerljivoDokument : JavnoDostupanDokument
{
    public override string DobijLinkZaJavnost()
    {
        // Ovdje sistem puca jer ovaj dokument ne smije imati javni link!
        throw new InvalidOperationException("Državna tajna se ne može javno dijeliti!");
    }
}

Problem: Servis za objavu na portalu će proći kroz sve dokumente, očekujući da klasa `StrogoPovjerljivoDokument` radi isto kao i njena bazna klasa, a ona će u sred noći baciti Exception i srušiti server.

✅ Dobar Primjer - Očuvanje arhitektonskog ugovora
// Apstraktni pojam Dokumenta
public abstract class ArhivskiDokument
{
    public string BrojProtokola { get; set; }
    public DateTime DatumPrijema { get; set; }
}

// Interfejs samo za one dokumente koji smiju u javnost
public interface IJavnoObjavljiv
{
    string DobijLinkZaJavnost();
}

public class ObicnaOdluka : ArhivskiDokument, IJavnoObjavljiv
{
    public string DobijLinkZaJavnost() => $"https://transparentnost.vlada.ba/{BrojProtokola}";
}

public class TajniIzvjestaj : ArhivskiDokument
{
    public int NivoKlasifikacije { get; set; }
    // Nema "IJavnoObjavljiv", time je potpuno sigurno da niko ne može tražiti public link!
}

I - Interface Segregation Principle (ISP)

Klijente ne treba tjerati da zavise od interfejsa (metoda) koje ne koriste.
Jednostavno rečeno, napravite sitne, pametne interfejse, umjesto ogromnih univerzalnih interfejsa. U javnoj upravi imamo različite uloge: šalterski službenici, inspektori na terenu, i šefovi odsjeka. Nema smisla da šalterski radnik implementira funkciju koja se zove `ZakaziInspekcijskiNadzor`.

❌ Loš Primjer - "Svemoćni" interfejs zaposlenika Uprave
public interface IUpravniRadnik
{
    void IzdajUvjerenje();
    void ZaprimiZahtjev();
    void OdobriBudzet();
    void NapisiPrekrsajniNalog();
}

Problem: Šalterski službenik sad mora implementirati OdobriBudzet i NapisiPrekrsajniNalog. Kako? Moraće napisati funkciju koja vraća throw new Exception("Nemam ovlaštenja"). To garantuje haos u sistemu.

✅ Dobar Primjer - Razdvojene nadležnosti
public interface IRadSaStrankama
{
    void IzdajUvjerenje();
    void ZaprimiZahtjev();
}

public interface IInspekcijskaOvlastenja
{
    void NapisiPrekrsajniNalog();
}

public interface IBudzetskoOvlastenje
{
    void OdobriBudzet();
}

// Konkretna implementacija se bazira samo na ovlastima
public class SalterskiSluzbenik : IRadSaStrankama
{
    public void IzdajUvjerenje() { /* ... */ }
    public void ZaprimiZahtjev() { /* ... */ }
}

public class GlavniInspektor : IRadSaStrankama, IInspekcijskaOvlastenja
{
    public void IzdajUvjerenje() { /* ... */ }
    public void ZaprimiZahtjev() { /* ... */ }
    public void NapisiPrekrsajniNalog() { /* ... */ }
}

D - Dependency Inversion Principle (DIP)

Moduli visokog nivoa ne bi trebali zavisiti od modula niskog nivoa; oba bi trebala zavisiti od apstrakcija.
Zamislimo državni "Centralni Registar Obavještenja". Ako vaša aplikacija direktno komunicira sa BH Telecom SMS Gateway-em, šta će se dogoditi ako država na javnom tenderu sljedeće godine odabere novog operatera? Moraćete mijenjati glavni kod.

❌ Loš Primjer - Čvrsto uvezan kod
public class SistemZaSlanjePoreznihRjesenja
{
    // Cijeli sistem ZAVISI direktno od konkretne implementacije M:Tel API-ja!
    private MTelSmsSender _smsServis = new MTelSmsSender(); 
    
    public void ObavijestiPoreskogObveznika(string brojTelefona, string poruka)
    {
        _smsServis.PosaljiSms(brojTelefona, poruka);
    }
}
✅ Dobar Primjer - Zavisnost o Apstrakciji (Dependency Injection)
// 1. Definisanje apstrakcije (Ugovora)
public interface IDrzavniSmsGateway
{
    void PosaljiPoruku(string broj, string tekst);
}

// 2. Modul visokog nivoa bavi se samo interfejsom, briga njega ko je pobijedio na tenderu!
public class SistemZaSlanjePoreznihRjesenja
{
    private readonly IDrzavniSmsGateway _smsGateway;
    
    // Dependency Injection (Ubrizgavanje zavisnosti spolja)
    public SistemZaSlanjePoreznihRjesenja(IDrzavniSmsGateway smsGateway)
    {
        _smsGateway = smsGateway;
    }
    
    public void ObavijestiPoreskogObveznika(string brojTelefona, string poruka)
    {
        _smsGateway.PosaljiPoruku(brojTelefona, poruka);
    }
}

// 3. Konkretne implementacije se prosljeđuju aplikaciji (najčešće pri pokretanju servera)
public class TelecomSmsProvider : IDrzavniSmsGateway { ... }
public class EronetSmsProvider : IDrzavniSmsGateway { ... }

🧩 Separation of Concerns (SoC) - Odvajanje nadležnosti u arhitekturi

Dok su SOLID principi fokusirani na dizajn na nivou klasa, SoC se često primjenjuje na makro-arhitekturu sistema. Zamislite to kao podjelu vlasti na zakonodavnu, izvršnu i sudsku - svaka institucija drži svoj domen rada kako bi se spriječila zloupotreba, a sistem funkcionisao efikasno.

U razvoju e-Vladinih aplikacija (koje se npr. vrte na ASP.NET Core-u), strogo odvajamo "slojeve" aplikacije:

Tipični slojevi e-Servisa (N-Tier Architecture):

  • UI / Presentation Layer (Frontend, Kontroleri): Samo formira vizuelni ekran (web stranicu za građane) na osnovu podataka. NEMA logiku izračuna ni konekcije na bazu.
  • Business Logic / Service Layer: Provjerava pravo na ostvarenje socijalne pomoći na osnovu primanja. Ovdje su svi zakoni isprogramirani.
  • Data Access Layer: Isključivo slanje SQL upita i konekcija ka Federalnoj Bazi Podataka. Nema prepoznavanja zakona ovdje.

Zašto je to važno za Javnu Upravu? Ako Vlada odluči da pored web portala kreira i mobilnu aplikaciju (novi Presentation sloj), Business Logika ostaje identična! Nema prepisivanja složenih zakonskih izračuna taksi ili prava, samo se naslanjamo na isti Service Layer.

♻️ DRY (Don't Repeat Yourself) u Državnim Sistemima

Princip kaže da svaki dio znanja, logike i podataka, unutar sistema mora imati jednu nedvosmislenu lokaciju na kojoj se održava.

Primjer u praksi javne uprave: Sistem za registraciju zahtjeva. Svaki put kada neko aplicira za dječiji doplatak, borački dodatak, ili naknadu za nezaposlene, građanin mora ukucati svoj JMBG. JMBG u Bosni i Hercegovini ima tačno formulisan algoritam validacije (dan i godina rođenja, regija, moduo kontrole brojeva). Ako programer kopira logiku validacije (if (Jmbg.Length != 13) ... ) na 15 različitih mjesta u programu, ta logika je osuđena na greške.

💡 Kako primijeniti DRY?

Napravite globalno dostupnu (ili injektovanu) komponentu JmbgValidatorServis. Gdje god je potrebno unijeti matični broj, aplikacija poziva ovu jednu tačku istine. Ako se sistem ikada počne koristiti za strane državljane, ili se promijeni logika kontrole pogrešaka, mijenjate samo ovu jednu klasu. Sve forme širom države koje nasljeđuju, automatski rade ispravno.

😘 KISS (Keep It Simple, Stupid) - Jednostavnost ispred svega

Javne nabavke su često leglo prekomplikovanih zahtjeva, ali softver koji prati procedure mora biti koliko god je to moguće jednostavan. Ne koristite Microservise, Machine Learning pretrage ili asinkrone Event Bus arhitekture ako vam treba samo jedan ekran na kojem činovnik unese tri polja o novom ugovoru i to ode u bazu podataka.

Što je kod kompleksniji, teže ga je održavati i teže je "provaliti" u šta je greška kada podatak o plati zaposlenog ne valja. Koristite moćno i komplicirano oružje samo na teške probleme. Za administrativne obrade i unos teksta, prosta forma i CRUD pristup su optimalni.

🔮 YAGNI (You Aren't Gonna Need It) - Borba protiv predviđanja budućnosti

"Hajde da sada odmah implementiramo podršku za prepoznavanje šarenice oka korisnika kada se prijavljuje u katastar, možda će CIK to donijeti kao obavezu u zakonu za 5 godina!" - Klasična greška.

Pravilo YAGNI govori: Ne implementirajte funkcionalnosti u aplikaciju sve dok vam zakonito i striktno trenutni zahtjev to ne zatraži.. Dodavanjem unaprijed zamišljenih scenarija vi komplicirate sistem resursima koji VAM TRENUTNO (možda i nikada) ne trebaju. Kod se gomila, testovi postaju spori, učenje koda od strane novih programera je teže. Reagujte i dodajte funkcionalnosti kada dobijete stvarni funkcionalni zahtjev od Uprave.