IT rekvalifikácia. Seniorní programátori zarábajú až 6 000 €/mesiac a rekvalifikácia je prvým krokom. Zisti, ako na to!

8. diel - Best practices pre vývoj softvéru - Základné praktiky

V minulej lekcii, Najčastejšie chyby programátorov - Tvorba metód, sme si ukázali najčastejšie chyby a dobré praktiky pre tvorbu metód. Vyskúšali sme si aj návrhový vzor Method chaining.

V dnešnom tutoriále kurzu Best practices pre návrh softvéru sa budeme venovať základným praktikám. To sú všeobecne známe praktiky v návrhu softvéru, ktoré sa počas rokov osvedčili a pokiaľ ich budeme poznať, ušetria nám veľa nepríjemností.

Všetci ste sa určite niekedy stretli so zle napísaným softvérom, pripomínajúcim domček z kariet. Akákoľvek zmena v takejto aplikácii je pracná a nebezpečná, pretože môže spôsobiť ďalšie chyby a ohroziť aj budúcnosť projektu. Aby sme sa takýmto návrhovým chybám vyvarovali, nemusíme vynájsť koleso, stačí sa poučiť zo známych chýb ostatných a predísť im. Dobrých praktík existuje pre návrh softvéru pomerne veľké množstvo, my si dnes predstavíme tie najzákladnejšie.

KISS

Začnime tými najjednoduchšími a postupne prejdeme ku komplexnejším poučkám. Pravidlo KISS je akronym pre:

Keep It Simple, stupid!

Slovensky teda:

Rob to jednoducho, hlupák!

KISS naznačuje, že často existuje riešenie, ktoré je jednoduché, relatívne málo pracné a prinesie uspokojivý výsledok. Tendenciu ku komplikovaniu aplikácií majú najmä zákazníci, ktorí nerozumejú vnútornej štruktúre aplikácií a požiadavky si vymýšľajú ako ich to napadne. Býva dobrým zvykom prediskutovať nutnosť alebo podobu aspoň niektorých požiadaviek. Často zistíte, že zákazník vlastne potrebuje niečo iné, čo viete vyriešiť elegantnejšie.

Pravidlo KISS vo vývoji softvéru - Best practices pre návrh softvéru

Príklad

Ako príklad si uveďme napr. súkromné správy tu na ITnetwork. V čase ich programovania mal Facebook pri svojich súkromných správach nejaký javascriptový mechanizmus na načítanie ďalších správ smerom nahor a udržiavanie formulára na písanie novej správy dole pod nimi. Spýtali sme sa sami seba, či toto naozaj potrebujeme programovať a čoskoro nás napadlo otočiť poradie správ. Formulár na novú správu bol hore a na načítanie nových správ sa použil už existujúci skript, ktorý načíta dáta AJAXom smerom dole. Zrazu sme získali rovnakú funkcionalitu za zanedbateľný vývojový čas, len preto, že sa zmenil smer radenia. Toto sú presne situácie, kedy sa oplatí porozprávať so zákazníkom, pokiaľ neprogramujete softvér pre seba, prípadne zadanie ešte zvážiť.

Riešenie súkromných správ od ITnetwork - Best practices pre návrh softvéru

Riešenie súkromných správ od ITnetwork

Z vlastnej skúsenosti môžem potvrdiť, že softvér je stále zložitejší a zložitejší, časom budete zisťovať, že vo svojej aplikácii potrebujete ďalšie a ďalšie funkčnosti. Keď ich udržíte jednoduché, získate konkurenčnú výhodu nad firmami, ktoré všetko bastlia presne podľa predstavy niekoho, kto IT nerozumie, a vo svojom kóde sa už takmer nevyznajú.

SRP

Single Responsibility Principle, skrátene SRP, hovorí, že každý kus kódu, napr. trieda, by mal byť zodpovedný za jednu konkrétnu vec. Keď zložíme aplikáciu zo súčastí, kedy sa každá súčasť zameriava na jednu funkčnosť a tú robí dobre, máme pomerne vysokú šancu na úspech.

SRP úzko súvisí s pridelením zodpovednosti. Tomu sa do hĺbky venuje skupina návrhových vzorov GRASP.

Príklad

Praktizovanie SRP je napr. tvorba manažérov pre rôzne entity v aplikácii, svoj kód rozdeľujeme medzi triedy CustomerManager, InvoiceManager, StockManager a podobne. Každá trieda je zodpovedná za svoje entity. Nemáme len jeden Manager.

SRP môžeme praktizovať aj na metódy, aj keď to nie je základný princíp tejto poučky. Každá metóda by mala vykonávať jednu vec a jej činnosť by sme mali byť schopní popísať bez spojky „a“. Pokiaľ si odpovieme niečo v zmysle "Táto metóda načíta, vyfiltruje a zobrazí výsledky", mala by byť metóda rozdelená na viac metód.

SoC

Separation of Concerns, slovensky Rozdelenie záujmu, je princíp podobný SRP. Tu sa však spravidla zameriavame na širšie oblasti. Zatiaľ čo SRP oddeľuje napr. prácu s faktúrami od práce so zákazníkmi, SoC oddeľuje typicky aplikačnú logiku od prezentácie alebo definíciu dát od ďalšej logiky. To znamená, že logické operácie by mali byť sústredené v inej časti aplikácie, ako napr. výpis dát užívateľovi. Určite viete, že sa hovorí o rozdelení aplikácie do vrstiev, o ktorom si môžete bližšie prečítať v kurze Softvérovej architektúry a depencency injection. Známe architektúry ako MVC, MVP, MVVM a podobne sú všetko implementácie SoC.

Príklad - Miešanie logiky s definíciou dát

Nikdy nevkladáme rozsiahlu definíciu dát (čo môže byť zoznam všetkých PSČ v SR) do triedy, ktorá robí nejakú logiku (napr. tlačí štítok s adresou na tlačiareň). Keď máme triedu, ktorá na stovkách riadkov definuje rôzne PSČ:

Map<String, String> zipList = Map.of(
    "Prague 1", "10000",
    "Prague 10", "11000",
    ...
    ...
    ...
);

Jej zodpovednosťou je očividne definovať dáta a už by nemala robiť nič iné. Rozdiel by bol, keby trieda tieto informácie len načítala krátkym kódom z databázy, potom môže pokojne robiť aj ďalšie veci súvisiace s adresami.

DRY

Je jeden z najdôležitejších princípov v programovaní.

Don't Repeat Yourself

Slovensky potom:

Neopakujte sa

Najmä začiatočníci majú tendenciu kopírovať kód z jedného miesta programu na druhý.

Akonáhle sa vo vašej aplikácii vyskytujú kdekoľvek 2 rovnaké bloky kódu alebo napríklad aj 2 podobné kusy kódu, je automaticky zle.

Príklad

Táto chyba je taká častá, že využijeme časť jednej hry, ktorá nám bola do redakcie zaslaná. Konkrétne ide o prepínanie obrázkov (spritov) podľa smeru a veľkosti postavičky v jazyku JavaScript:

// right direction
if (direction == 0) {
    if (image == 1)
        div.className += "spriteLiveRightL";
    if (image == 2)
        div.className += "spriteLiveRightM";
    if (image == 3)
        div.className += "spriteLiveRightS";

    div.style.left = -200 + "px";
}

// left direction
if (direction == 1) {
    if (image == 1)
        div.className += "spriteLiveLeftL";
    if (image == 2)
        div.className += "spriteLiveLeftM";
    if (image == 3)
        div.className += "spriteLiveLeftS";

    div.style.left = screenWidth + 100 + "px";
}

Na prvý pohľad vidíme, že sa 2 bloky kódu líšia minimálne. Kód je určite možné minimálne skrátiť len na:

if (direction == 0) {
    let directionName = "Right";
    div.style.left = -200 + "px";
} else {
    let directionName = "Left";
    div.style.left = screenWidth + 100 + "px";
}

if (image == 1)
    div.className += "spriteLive" + directionName + "L";
if (image == 2)
    div.className += "spriteLive" + directionName + "M";
if (image == 3)
    div.className += "spriteLive" + directionName + "S";

Kód vyššie funguje úplne rovnako, ale má oveľa menej duplicít. Predstavte si, že by sa zmenilo pomenovanie obrázku z ...Right... a ...Left... na R a L. Pozrite sa, koľko kódu by sa muselo prepísať pri príklade porušujúceho DRY a koľko pri opravenom príklade. Kód však stále nie je ideálny.

DRY nie je len o duplicitnom kóde, ale o opakovaní ako takom. Bezduché dlhé vetvenia bývajú takmer vždy nahraditeľné múdrejšími konštrukciami, obvykle cyklami alebo poľom. Urobme ďalšiu úpravu:

if (direction == 0) {
    let directionName = "Right";
    div.style.left = -200 + "px";
} else {
    let directionName = "Left";
    div.style.left = screenWidth + 100 + "px";
}

let images = [1: "L", 2: "M", 3: "S"];
div.className += "spriteLive" + directionName + images[image];

V našom prípade sme si pomenovanie veľkosti postavičky L, M a S uložili do slovníka pod číslo, ktorým sa veľkosť (obrázok) vyberá. Nie je potom nič jednoduchšie, než vybrať písmenko veľkosti podľa kľúča slovníka.

Teraz na kód aplikujme aj pravidlo KISS. Obrázky môžeme totiž jednoducho pomenovať číselne, aby smer a veľkosť v ich názve súhlasili s reprezentáciou týchto hodnôt v kóde. Prečo to robiť zložito? Obrázky rovnako nie sú určené pre užívateľov. Výsledok:

div.style.left = `${direction ? screenWidth + 100 : -200}px`;
div.className += `spriteLive${direction}_${image}`;

Kód sa skrátil na 2 riadky z pôvodných 18(!) a robí úplne to isté. To všetko len za pomoci KISS a DRY. Predstavte si, čo sa stane, keď Best practices aplikujete na celú aplikáciu. Zrazu nemusíte písať desiatky tisíc riadkov kódu a prevalcujete svoju konkurenciu za niekoľko mesiacov. Dobré praktiky určite nepodceňujte :)

Ukážkou DRY by mohli byť carousely na ITnetwork, ktoré je možné rôzne nastavovať:

Carousel na ITnetwork - Best practices pre návrh softvéru

Carousel s fotografiami

Carousel s iným nastavením na ITnetwork - Best practices pre návrh softvéru

Carousel s HTML obsahom

To iste ešte nie je nič veľkolepé. Vnútorne ale navyše tieto carousely dedia z komponentu, ktorý prepína záložky:

  • public static int[] selectionSort(int[] numbers) {
       for (int i = 0; i < (numbers.length - 1); i++) {
          int indexMin = i;
          for (int j = i + 1; j < numbers.length; j++) {
               if (numbers[j] < numbers[indexMin]) {
                   indexMin = j;
               }
          }
          int next = numbers[i];
          numbers[i] = numbers[indexMin];
          numbers[indexMin] = next;
       }
       return numbers;
    }
  • public static int[] SelectionSort(int[] numbers)
    {
       for (int i = 0; i < (numbers.Length - 1); i++)
       {
          int indexMin = i;
          for (int j = i + 1; j < numbers.Length; j++)
          {
             if (numbers[j] < numbers[indexMin])
             {
                indexMin = j;
             }
          }
          int next = numbers[i];
          numbers[i] = numbers[indexMin];
          numbers[indexMin] = next;
       }
       return numbers;
    }
  • def selection_sort(numbers):
        for i in range(len(numbers) - 1):
            index_min = i
            for j in range(i + 1, len(numbers)):
                if numbers[j] < numbers[index_min]:
                    index_min = j
            next = numbers[i]
            numbers[i] = numbers[index_min]
            numbers[index_min] = next
        return numbers
  • function selectionSort(array $numbers): array {
        $length = count($numbers);
        for ($i = 0; $i < ($length - 1); $i++) {
            $indexMin = $i;
            for ($j = $i + 1; $j < $length; $j++) {
                if ($numbers[$j] < $numbers[$indexMin]) {
                    $indexMin = $j;
                }
            }
            $next = $numbers[$i];
            $numbers[$i] = $numbers[$indexMin];
            $numbers[$indexMin] = $next;
        }
        return $numbers;
    }
  • function selectionSort(numbers) {
       for (let i = 0; i < (numbers.length - 1); i++) {
          let indexMin = i;
          for (let j = i + 1; j < numbers.length ; j++) {
               if (numbers[j] < numbers[indexMin]) {
                   indexMin = j;
               }
          }
          let next = numbers[i];
          numbers[i] = numbers[indexMin];
          numbers[indexMin] = next;
       }
       return numbers;
    }

Tabcontrol so zdrojovými kódmi algoritmu pre rôzne jazyky

Veľa programátorov by napadlo napísať carousel a záložky zvlášť, ale keď sa bližšie zamyslíte, robia v podstate to isté, len vyzerajú inak. Jeden komponent je možné vytvoriť len menšou obmenou tej druhej.

Ďalšími príkladmi DRY by mohlo byť napr. vytvorenie kvalitných všeobecných CSS štýlov, ktoré nie sú obmedzené na konkrétne prvky na stránke a možno ich parametrizovať, ako to má napr. CSS framework Bootstrap:

<table class="table table-striped">

Vyššie uvedený kód nastaví tabuľke základný štýl a zároveň pruhovanie.

Shy

Možno poznáte poučku:

Keep it DRY, shy, a tell the other guy.

Slovensky voľne preložené ako:

Neopakuj sa, píš hanblivo a povedz to ostatným.

DRY a opakovanie sme si už vysvetlili, ale ako je to s hanblivým kódom? Hanblivý kód sa chová úplne rovnako, ako hanbliví ľudia. Komunikuje s ostatným kódom teda len keď je to nevyhnutné a nemá ani viac informácií o ostatných, než sám nevyhnutne potrebuje. SHY je vlastne iné označenie pre zákon Deméter, viď nižšie.

LoD

Law of Demeter, teda zákon Deméter, gréckej Bohyne úrody, je v podstate o low/loose coupling, teda o udržiavaní najmenšieho možného počtu väzieb medzi komponentmi. Je definovaný tromi pravidlami, kde zavádza pojem jednotka pre zapuzdrenú časť kódu, teda pre triedu:

  • Každá jednotka by mala mať obmedzenú znalosť o ostatných jednotkách, iba jednotkách, ktoré sú danej jednotke blízke.
  • Každá jednotka by mala "hovoriť" iba so svojimi "priateľmi", nehovorte s cudzími.
  • Hovorte iba so svojimi priamymi priateľmi.

Z pravidiel je vidieť, že každý objekt by mal byť univerzálny a úplne odtienený od zvyšku aplikácie. Poznať minimum informácií o celku a zdieľať minimum informácií o sebe celku.

Príklad

Praktický príklad si určite dokážete predstaviť. Jedná sa o triedy s dobre zapuzdrenou vnútornou logikou a všeobecnou funkčnosťou, aby nemuseli poznať detaily konkrétneho systému a bolo ich možné použiť kdekoľvek. Napr. vytvoríte generátor objednávok tak, aby ho bolo možné 100% prispôsobiť a nebol závislý na potrebách jedného informačného systému. Možno vás napadá, či nie je plytvanie vývojovým časom a peniazmi vytvárať funkcie, ktoré v danom projekte nepotrebujete. To by plytvanie určite bolo. S týmito funkciami stačí iba počítať a navrhovať komponenty tak, aby do nich išlo v budúcnosti niečo ľahko pridať.

LoD sa týka tiež odovzdávania závislostí, ktorých by malo byť čo najmenej a trieda by mala závisieť od abstrakcií, nie konkrétnych tried. O tom hovorí Dependency Inversion Principle, ktorý je súčasťou poučiek SOLID.

IoC

Princíp Inversion of Control by ste mali dobre poznať. Jedná sa o skupinu návrhových vzorov, súčasťou ktorej je aj populárna Dependency Injection, jediný správny spôsob, ako v objektových aplikáciách závislosti odovzdávať. IoC tvrdí, že objekty v aplikácii by mali byť riadené nejakým vyšším mechanizmom, ktorý vytvára inštancie tried a odovzdáva im potrebné závislosti. To je opačný prístup k staršiemu spôsobu práce so závislosťami, kedy si triedy svojej závislosti vyťahovali samy zo systému, napr. cez Singleton alebo iné antipatterny. Keďže sa jedná o extrémne dôležitú problematiku, pripravili sme pre vás o DI samostatný kurz Softvérovej architektúry a depencency injection, kde je podrobne vysvetlená na reálnych príkladoch.

V budúcej lekcii, Best practices pre vývoj softvéru - Praktiky SOLID, si ukážeme praktické príklady a ilustrácie dobrých praktík SOLID, princípov SRP, Open/Closed, Liskov substitution a Interface segregation.


 

Predchádzajúci článok
Najčastejšie chyby programátorov - Tvorba metód
Všetky články v sekcii
Best practices pre návrh softvéru
Preskočiť článok
(neodporúčame)
Best practices pre vývoj softvéru - Praktiky SOLID
Článok pre vás napísal David Hartinger
Avatar
Užívateľské hodnotenie:
1 hlasov
David je zakladatelem ITnetwork a programování se profesionálně věnuje 15 let. Má rád Nirvanu, nemovitosti a svobodu podnikání.
Unicorn university David sa informačné technológie naučil na Unicorn University - prestížnej súkromnej vysokej škole IT a ekonómie.
Aktivity