# 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