# 1. Kvalita kódu
Kvalita ve vývoji softwarových systémů, atributy kvality a softwarové metriky. Taktiky pro zajištění kvality na úrovni jednotlivých atributů kvality. Principy Clean Code a SOLID, refaktoring kódu. Testování kódu, jednotkové testy, integrační testy, uživatelské a akceptační testy. Ladění a testování výkonu. Proces řízení kvality ve vývoji softwarových systémů. Příklady z praxe pro vše výše uvedené. (PV260, PA017, PA103)
---
## Kvalita vo vývoji softverových systémov, atribúty kvality a softvérové metriky
Kvalita softvérového vývoja je závislá na tom, aký úhol pohľadu si vyberieme.
V základe rozlišujeme **4 úhly pohľadu** (Top-Down):
1. Kvalita **použitia** (User view) - je s produktom koncový užívateľ spokojný?
2. Kvalita **externých vlastností** (Manufacturing view) - spĺňa produkt všetky testy (akceptačné, integračné, nefunkčné?)
3. Kvalita **interných vlastností** (Product view) - je produkt napísaný správne? (nízky technický dlh, maintainability, bugs)
4. Kvalita **procesu** - spĺňal vývoj nejaký štandardizovaný proces, zabezpečujúci kvalitu?
Z týchto uhľov pohľadu definujeme kvalitu ako **koncept**, ktorý začína **vývojovým procesom**, pokračuje **vytvorením softvérového produktu**, a končí s **výsledkami od koncového užívateľa**.
Kvalita je často definovaná iba ako **vlastnosť softvérového produktu spĺňať zadanie**, čo nemusí byť dostačujúce.
Podla úhľa pohľadu rozlišujeme **atribúty kvality** na viditeľné a neviditeľné:
- Viditeľné (z pohľadu užívateľa, it works)
- **Usability** - jednoduchosť použitia
- Accuracy - presnosť výstupov programu (vo všeobecnosti chyby výpočtov)
- **Reliability** - spolahlivosť, program nepadá
- **Performance** - rýchlosť, efektívne využitie HW prostriedkov
- **Security** - bezpečnosť (nehrozí zneužitie dát, nebezpečie okolia)
- Neviditeľné (z pohľadu programátora, it looks good inside)
- Modularity - modulárnosť, program je logicky členený
- Complexity - komplexita, zložitosť programu
- Resilience - odolnosť voči zlým vstupom, externým vplyvom
- Understandability - zrozumiteľnosť kódu (napr. Visual Basic vs assembler)
- **Testability** - testovateľnosť kódu (napr. žiadny singleton ale IoC, mockovateľné classy)
- Neviditeľné (z pohľadu manažéra, long-term goals)
- Adaptability - schopnosť jednoducho upravovať systém podľa požiadaviek
- Portability - schopnosť skompilovať systém na iných platformách
- Reusability - znovapoužitie častí systému na iných miestach
- **Maintainability** - udržateľnosť systému z dlhodobého hladiska, bez zvyšovania technického dlhu
- **Scalability** - škálovateľnosť systému, horizontálne alebo vertikálne
Niektoré atribúty sa dajú vyhodnotiť pomocou **metrík**. Tieto metriky nám môžu pomôcť ku zvýšeniu celkovej kvality kódu. Zvyčajne sa snažíme odpovedať na otázky:
- Ako môžem zmerať udržateľnosť (maintainability) môjho softvéru?
- Dokážem predpovedať počet defektov (bugov) z ohľadom na veľkosť projektu?
- Aká je produktivita tímu?
- Dokážem odmerať kvalitu môjho testovacieho procesu?
Rozlišujeme **objektívne** (napr. LOC) a **subjektívne** metriky (napr. čas, koľko niečo trvá z pohľadu užívateľa).
Metriky viazané na **veľkosť**:
- počet riadkov kódu (LOC)
- počet riadkov komentárov (CLOC)
- počet riadkov nekomentovaného kódu (NLOC)
- počet tried (NOC)
- počet metód (NOM)
- počet balíkov (NOP)
Tieto metriky sa používajú najmä ako **normalizačný faktor** iných (komplexných) metrík. Napr. vieme definovať hustotu komentárov ako $CD = \frac{CLOCs}{LOCs}$.
Vo všeobecnosti nemôžeme hodnotiť produktivitu tímov na základe metrík o veľkosti kódu.
Metriky viazané na **zložitosť**:
- cyklomatická zložitosť (CC)
- určuje počet všetkých možných prechodov skrz funkciu/program
- je daná control flow diagramom
- $v(G) = |E| - |N| + 2P$ alebo $\#branches + 1$
Metriky viazané na zložitosť hovoria iba o syntaktickej zložitosti, nie o sémantickej. Tj. kód s nízkou CC neznamená, že je čitateľný alebo dobre udržovateľný.
Metriky viazané na **triedy OOP**:
- vážený počet metód na triedu (WMC)
- hĺbka stromu dedičnosti (DIT)
- počet detí (NOC)
- coupling medzi triedami (CBO)
- response pre triedu (RFC)
- nedostatok kohéznosti metód (LCOM)
Ideálne chceme jedno číslo, ktoré nám povie, ako je náš kód kvalitný. Na tento účel vznikli komplexné metriky:
- Maintainability Index (MI)
- Goal Question Metrics (GQM)
- Software Quality Assessment Based on Lifecycle Expectations (SQALE)
GQM je **metóda**, pomocou ktorej vyvodzujeme merateľné veličiny na základe očakávaných výstupov biznisovej logiky. Skladá sa z troch vrstiev:
- Conceptual layer - the Measurement Goal (G)
- Operational layer - the Question (Q)
- Measurement layer - the Metric (M)
SQALE je **metóda**, pomocou ktorej vieme vypočítať **veľkosť technického dlhu** v projekte. Na základe každého **atribútu kvality** (Reusability, Portability, Maintainability...) sa definuje množina sub-atribútov (Fault tolerance, Unit Testing testability, Integration testing testability...), ku ktorým sa definuje požiadavka na zdrojový kód. Následne, riadok, ktorý porušuje dané požiadavky sa započíta s nejakým faktorom (napr. $0.1, 5, 1000$) a v sume dostaneme výslednú metriku technického dlhu v závislosti na atribúte kvality. Funkcia, ktorá dáva chybám v kóde nejakú hodnotu sa nazýva *remediation function*.
Zo SQALE získavame metriky, ako napríklad:
- Testability Index (STI)
- Reliability Index (SRI)
- Changeability Index (SCI)
- Efficiency Index (SEI)
- Security Index (SSI)
- Maintainability Index (SMI)
- Portability Index (SPI)
- Reusability Index (SRUL)
- Overall Quality Index (SQI)
SQALE je prakticky implementovaný v softvéri SonarCube, kde je dostupný aj ako pekný koláčový graf.
Nemôžeme sa však vždy spoliehať iba na metriky, môžeme mať chybu v metodike:
- Zbieranie zbytočných metrík, ktoré nám zhoršujú výsledky
- Nedostatočná analýza výsledkov
- Nastavenie nereálnych očakávaní (napr. 100% code coverage)
- Paralysis by analysis - investujem viac času do analýzy ako do programovania
## Taktiky pre zaistenie kvality na úrovni jednotlivých atribútov kvality
Základom je stanoviť si nejaký plán riadenia kvality (nižšie v dokumente).
- Viditeľné (z pohľadu užívateľa, it works)
- **Usability** - jednoduchosť použitia
- vhodný návrh UI/UX
- diskutovanie s užívateľmi, usability testing
- návrh vo Figme
- Accuracy - presnosť výstupov programu (vo všeobecnosti chyby výpočtov)
- testovanie
- **Reliability** - spolahlivosť, program nepadá
- aktívny monitoring
- acceptance checking
- event collection, logging
- exception handling
- defensive programming
- fault-tolerancy, v prípade, že provider API spadne, treba automaticky zvoliť iný
- restart/recovery after failure
- system diagnostics
- **Performance** - rýchlosť, efektívne využitie HW prostriedkov
- profiling
- examine complexity and frequency of computation (hotspots), flame graph
- check concurrency issues, length of critical sections, busy waiting, spinlocks..
- load balancing, access control, caching, replication
- **Security** - bezpečnosť (nehrozí zneužitie dát, nebezpečie okolia)
- kontrola závislostí, balíčkov
- automatizované CI/CD, GitHub Actions
- bounty hunting program
- testovanie, oprava chýb, defensive programming
- šifrovanie, hashovanie hesiel, fyzická bezpečnosť, zálohovanie
- OpenSSL, transport security
- Neviditeľné (z pohľadu programátora, it looks good inside)
- Modularity - modulárnosť, program je logicky členený
- metriky, kvalita kódu
- Complexity - komplexita, zložitosť programu
- metriky, kvalita kódu
- Resilience - odolnosť voči zlým vstupom, externým vplyvom
- defensive programming
- Understandability - zrozumiteľnosť kódu (napr. Visual Basic vs assembler)
- metriky, kvalita kódu, dodržiavanie konvencií pre daný jazyk
- **Testability** - testovateľnosť kódu (napr. žiadny singleton ale IoC, mockovateľné classy)
- write CLEAN code
- avoid global state
- separate interfaces from implementations
- explicit dependencies - IoC, DI
- separate factories from business logic
- Neviditeľné (z pohľadu manažéra, long-term goals)
- Adaptability - schopnosť jednoducho upravovať systém podľa požiadaviek
- write CLEAN code
- Portability - schopnosť skompilovať systém na iných platformách
- design system well, choose correct technologies
- Reusability - znovapoužitie častí systému na iných miestach
- write CLEAN code
- **Maintainability** - udržateľnosť systému z dlhodobého hladiska, bez zvyšovania technického dlhu
- write CLEAN code, keep code simple, no premature optimizations
- use interfaces, inheritance, polymorphism, design patterns
- design architecture carefully
- Law of Demeter, High Cohesion, Low Coupling
- **Scalability** - škálovateľnosť systému, horizontálne alebo vertikálne
- choose correct architecture that scales well (microarchitecture, SoA, API Gateway)
## Princípy clean code, SOLID, refaktoring kódu
**Clean code** spočíva v dodržiavaní nejakých konvencií:
- Treba dodržiavať konvencie špecifické jazku
- KISS - keep it simple, stupid - reduce complexity as much as possible
- Boy scout rule - refactoring vždy, keď sa dostanem k nejakému kódu
- Vždy treba nájsť koreň problému a vyriešiť ho
- Use dependency injection - IoC
- Law of Demeter - trieda musí komunikovať iba s jej priamymi závislosťami
- Konfigurácia by mala byť dostupná iba high-level, nie na úrovni kódu
- Vždy byť konzistentný
- Vhodne pomenovať premenné, názvy tried, metód
- Treba písať krátke funkcie, ktoré robia iba jednu vec, majú mať málo argumentov
- Treba písať zmysluplné komentáre, tam, kde to je treba a nikde inde
- [... a podobne](https://gist.github.com/wojteklu/73c6914cc446146b8b533c0988cf8d29)
Princíp **SOLID**:
- Single responsibility principle
- každá trieda má riešiť iba jednu primárnu funkcionalitu
- Low Coupling, High Cohesion
- Open-closed principle
- trieda má byť otvorená na rozšírenie a uzatvorená na modifikáciu
- môže zbytočne zvýšiť komplexitu triedy, treba používať iba tam, kde to je potrebné
- dedenie z interfacov, abstraktné triedy
- Liskov substitution principle
- každá trieda, ktorá je potomkom inej triedy, musí byť nahraditeľná za danú triedu tak, aby to nezmenilo chovanie programu
- Interface segregation principle
- snažíme sa rozdelovať veľké interfaces na menšie kontrakty ku každej inej triede, ktorá s nami potrebuje komunikovať
- Dependency inversion principle
- triedy musia byť závislé na interfacoch, nie na konkrétnych implementáciách
**Refactoring** je proces, pri ktorom meníme štruktúru kódu bez toho, aby sme ovplyvnili chovanie (implementáciu) programu.
Robíme to za účelom zvýšenia maintainability a zníženia technického dlhu. Avšak berie to čas, ktorý mohol byť investovaný inak. Preto je potrebné robiť to nejak logicky - zvoliť si časy, kedy robiť refactoring: boy scout rule, plánované, Test Driven Development, po implementovaní feature alebo použití nejakej technológie.
Bežné techniky refactoringu:
- Extract method, class, subclass, superclass
- Inline method, class
- Move method
- Replace method with method object
- Move field
- Replace data with value object
- Replace magic number with constant
- Encapsulate field
- Rename method
- Introduce parameter
- Hide method
- Pull up field, method
- Push down method
- Collapse hierarchy
- ... a podobne
Niekedy sa môže stať, že refactoring má za následok spomalenie programu - väčšinou to ale je výhodnejšie z dlhodobého hladiska kvality.
Refactorujeme nájdené **code smells**, tj. časti kódu, ktoré nam "smrdia":
- Kombinovanie rôznych úrovní abstrakcie
- Low cohesion
- Cyklické závislosti
- Duplikovaný kód
- Dlhý zoznam parametrov
- ... a podobne
Je dôležité používať IDE nástroje a statické analyzátory, ktoré nám často tieto problémy ukazujú a aj dávajú možnosť nápravy, či už automatickej alebo manuálnej.
## Testovanie kódu, jednotkové (unit) testy, integračné testy, uživateľské a akceptačné testy
Kód nechceme testovať manuálne, je to pre programátora otravné, časovo neefektívne a neopakovateľné. Na to, aby sme vedeli zaručit kvalitu kódu, musíme písať automatizované testy, ktoré vieme následne opakovateľne vyhodnocovať.
Testy spočívajú v overovaní funkcionality kódu, či splňujú nejaké požiadavky. Vo všeobecnosti by testy mal písať niekto iný ako autor daného kódu.
Testy rozlišujeme na:
- Whitebox - vidíme na zdrojový kód, štrukturálne testovanie, cielim na konkrétne miesta
- Blackbox - nevidíme na zdrojový kód, funkcionálne testovanie, overujem očakávanú funkcionalitu
Testujeme na rôznych úrovniach:
- jednotkové (unit) testy
- whitebox testing
- čo najmenší test, ktorý kontroluje iba určité chovanie bloku kódu
- riadi sa princípom AAA - Arrange, Act, Assert
- integračné testy
- whitebox / blackbox testing
- testy, ktoré kontrolujú komunikáciu väčších blokov kódu napr. services, ale ešte to nie je E2E testing
- uživateľské a systémové (E2E) testy
- blackbox testing
- overenie, že funguje prechod aplikáciou aj po tom, čo sa upravuje nejaká implementácia v kóde
- akceptačné testy
- blackbox testing
- simuluje sa kompletný prechod danou funkcionalitou ako od užívateľa
- väčšinou je potrebná definícia (user story) ako sa má proces chovať
- často sa komunikuje so zákazníkom
Častá terminológia pri testovaní:
- dummy object - nič nerobí, iba sa použije ako argument
- fake object - jednoduchá implementácia nejakého objektu
- stub - metóda alebo trieda, ktorá vracia nejakú hardcoded hodnotu
- spy - metóda alebo trieda, ktorá robí nejakú kontrolu nad volaním, napr. počítadlo, odkiaľ a koľko krát sa zavolala funkcia
- mock - metóda alebo trieda, ktorá obsahuje vlastnú funkcionalitu na testovanie, nepoužíva naozajstný kód
Príklad:
- WebDev - Selenium, Jest, Playwright
- Java - JUnit
- Rust - Cargo Test
- C# - MSTest
## Ladenie a testovanie výkonu
Pri systémovom testovaní alebo testovaní nefunkčných požiadavok robíme záťažové testy a testovanie výkonu aplikácie.
Napr. simulujeme užívateľov, ktorí prechádzajú skrz aplikácu (stress testing).
Na testovanie výkonu používame profiling alebo flame graph, počítadlá času funkcií, kontrolujeme veľkosť zaberanej pamäte programu a či nemáme memory leaky.
Príklad:
- Gatling
- Locust
- JMeter
- Visual Studio Diagnostic Tools
- Curl
## Proces riadenia kvality vo vývoji softvérových systémov
Aby sme zabezpečili kvalitu, tak musíme na celý proces nejakým spôsobom dohliadať. Väčšinou sa jedná o formu managementu:
- **Software quality management (SQM)** je súhrn všetkých procesov, ktoré zabezpečia, že softvérové produkty, služby a životné cykly spĺňajú stanovené ciele kvality a spĺňajú požiadavky stakeholderov.
- SQM definuje procesy, process owners, požiadavky na procesy, metriky na procesy a ich výstupy, a komunikačné kanály na feedback
SQM sa skladá zo **štyroch prvkov**:
- Software quality **planning** (SQP)
- Aké štandardy kvality sa majú dodržiavať, konkrétne požiadavky na výsledky
- Odhad, koľko úsilia, peňazí to bude vyžadovať
- Plánovanie čo, kedy, ako
- Software quality **assurance** (SQA)
- Definovanie a následné ohodnotenie procesov, či budú vhodné na zabezpečenie kvality
- Audity, tréning
- Naprieč celou organizáciou a produktami
- Software quality **control** (SQC)
- Kontrola metrík, či projekty spĺňajú ciele kvality
- Reviews, testing
- Týka sa iba konkrétnych projektov, produktov
- Software **process improvement** (SPI)
- Kontinuálne zlepšovanie procesov by malo viesť k zvyšovaniu kvality produktu
- Vznikli modely CMM a CMMI, Agile Process Maturity, Open Source Maturity Model
Príklady SQM:
- Cowboy coding - žiadny štandardizovaný proces
- Personal Software Process (PSP0-3)
- Team Software Process (TSP) - evolvovaný PSP pre tímy
- Six Sigma - implementovaný vo firme Motorola
Snažíme sa dosiahnuť nejaký vysoko optimalizovaný proces. Podľa úrovni tejto optimalizácie vznikol model CMM - **Capability Maturity Model**. Jeho nadstavbou je CMM Integrated (CMMI). Skladá sa z úrovní:
1. Initial - nepredvídateľný proces, menežovaný reaktívne
2. Managed - aktivity sú menežované
3. Defined - práca sa robí na základe procesov
4. Quantitatively Managed - procesy a aktivity sú vykonávane na základe meraní metrík
5. Optimized - procesy sú nastavené aby a opakovane optimalizovali