IT rekvalifikace s garancí práce. Seniorní programátoři vydělávají až 160 000 Kč/měsíc a rekvalifikace je prvním krokem. Zjisti, jak na to!
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.

Lekce 8 - Best practices pro vývoj softwaru - Základní praktiky

V minulé lekci, Nejčastější chyby programátorů - Tvorba metod, jsme si ukázali nejčastější chyby a dobré praktiky pro tvorbu metod. Vyzkoušeli jsme si také návrhový vzor Method chaining.

V dnešním tutoriálu kurzu Best practices pro návrh softwaru se budeme věnovat základním praktikám. To jsou obecně známé praktiky v návrhu softwaru, které se během let osvědčily a pokud je budeme znát, ušetří nám spoustu nepříjemností.

Všichni jste se určitě někdy setkali se špatně napsaným softwarem, připomínajícím domeček z karet. Jakákoli změna v takovéto aplikaci je pracná a nebezpečná, protože může způsobit další chyby a ohrozit i budoucnost projektu. Abychom se takovým návrhovým chybám vyvarovali, nemusíme vynalézat kolo, stačí se poučit ze známých chyb ostatních a předejít jim. Dobrých praktik existuje pro návrh softwaru poměrně velké množství, my si dnes představíme ty nejzákladnější.

KISS

Začněme těmi nejjednoduššími a postupně přejdeme ke komplexnějším poučkám. Pravidlo KISS je akronym pro:

Keep It Simple, stupid!

Česky tedy:

Dělej to jednoduše, hlupáku!

KISS naznačuje, že často existuje řešení, které je jednoduché, relativně málo pracné a přinese uspokojivý výsledek. Tendenci ke komplikování aplikací mají zejména zákazníci, kteří nerozumí vnitřní struktuře aplikací a požadavky si vymýšlejí jak je to napadne. Bývá dobrým zvykem prodiskutovat nutnost nebo podobu alespoň některých požadavků. Často zjistíte, že zákazník vlastně potřebuje něco jiného, co umíte vyřešit elegantněji.

Pravidlo KISS ve vývoji softwaru - Best practices pro návrh softwaru

Příklad

Jako příklad si uveďme např. soukromé zprávy zde na ITnetwork. V době jejich programování měl Facebook u svých soukromých zpráv nějaký javascriptový mechanismus pro načítání dalších zpráv směrem nahoru a udržování formuláře pro psaní nové zprávy dole pod nimi. Zeptali jsme se sami sebe, zda toto opravdu potřebujeme programovat a záhy nás napadlo otočit pořadí zpráv. Formulář na novou zprávu byl nahoře a pro načítání nových zpráv se použil již existující skript, který načítá data AJAXem směrem dolů. Najednou jsme získali stejnou funkcionalitu za zanedbatelný vývojový čas, jen proto, že se změnil směr řazení. Toto jsou přesně situace, kdy se vyplatí promluvit se zákazníkem, pokud neprogramujete software pro sebe, případně zadání ještě zvážit.

Řešení soukromých zpráv od Facebooku - Best practices pro návrh softwaru

Řešení soukromých zpráv od Facebooku

Řešení soukromých zpráv od ITnetwork - Best practices pro návrh softwaru

Řešení soukromých zpráv od ITnetwork

Z vlastní zkušenosti mohu potvrdit, že software je stále složitější a složitější, časem budete zjišťovat, že ve své aplikaci potřebujete další a další funkčnosti. Když je udržíte jednoduché, získáte konkurenční výhodu nad firmami, které vše bastlí přesně podle představy někoho, kdo IT nerozumí, a ve svém kódu se již téměř nevyznají.

SRP

Single Responsibility Principle, zkráceně SRP, říká, že každý kus kódu, např. třída, by měl být zodpovědný za jednu konkrétní věc. Když složíme aplikaci ze součástí, kdy se každá součást zaměřuje na jednu funkčnost a tu dělá dobře, máme poměrně vysokou šanci na úspěch.

SRP úzce souvisí s přidělením odpovědnosti. Tomu se do hloubky věnuje skupina návrhových vzorů GRASP.

Příklad

Praktikování SRP je např. tvorba manažerů pro různé entity v aplikaci, svůj kód rozdělujeme mezi třídy CustomerManager, InvoiceManager, StockManager a podobně. Každá třída je odpovědná za své entity. Nemáme jen jeden Manager.

SRP můžeme praktikovat i na metody, i když to není základní princip této poučky. Každá metoda by měla provádět jednu věc a její činnost bychom měli být schopni popsat bez spojky "a". Pokud si odpovíme něco ve smyslu "Tato metoda načte, vyfiltruje a zobrazí výsledky", měla by být metoda rozdělena na více metod.

SoC

Separation of Concerns, česky Rozdělení zájmu, je princip podobný SRP. Zde se ovšem zpravidla zaměřujeme na širší oblasti. Zatím, co SRP odděluje např. práci s fakturami od práce se zákazníky, SoC odděluje typicky aplikační logiku od prezentace nebo definici dat od další logiky. To znamená, že logické operace by měly být soustředěné v jiné části aplikace, než např. výpis dat uživateli. Určitě víte, že se hovoří o rozdělení aplikace do vrstev, o kterém si můžete blíže přečíst v kurzu Softwarové architektury a depencency injection. Známé architektury jako MVC, MVP, MVVM a podobně jsou vše implementace SoC.

Příklad - Míchání logiky s definicí dat

Nikdy nevkládáme rozsáhlou definici dat (což může být seznam všech PSČ v ČR) do třídy, která dělá nějakou logiku (např. tiskne štítek s adresou na tiskárnu). Když máme třídu, která na stovkách řádků definuje různá PSČ:

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

Její odpovědností je očividně definovat data a už by neměla dělat nic jiného. Rozdíl by byl, kdyby třída tyto informace jen načítala krátkým kódem z databáze, pak může klidně dělat i další věci související s adresami.

DRY

Je jeden z nejdůležitějších principů v programování.

Don't Repeat Yourself

Česky pak:

Neopakujte se

Zejména začátečníci mají tendenci kopírovat kód z jednoho místa programu na druhý.

Jakmile se ve vaší aplikaci vyskytují kdekoli 2 stejné bloky kódu nebo třeba i 2 podobné kusy kódu, je automaticky špatně.

Příklad

Tato chyba je tak častá, že využijeme část jedné hry, která nám byla do redakce zaslána. Konkrétně jde o přepínání obrázků (spritů) podle směru a velikosti postavičky v jazyce 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 první pohled vidíme, že se 2 bloky kódu liší minimálně. Kód je jistě možné minimálně zkrátit jen 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 výše funguje úplně stejně, ale má mnohem méně duplicit. Představte si, že by se změnilo pojmenování obrázku z ...Right... a ...Left... na R a L. Podívejte se, kolik kódu by se muselo přepsat u příkladu porušujícího DRY a kolik u opraveného příkladu. Kód ovšem stále není ideální.

DRY není jen o duplicitním kódu, ale o opakování jako takovém. Bezduchá dlouhá větvení bývají téměř vždy nahraditelná chytřejšími konstrukcemi, obvykle cykly nebo polem. Udělejme další ú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šem případě jsme si pojmenování velikosti postavičky L, M a S uložili do slovníku pod číslo, kterým se velikost (obrázek) vybírá. Není poté nic jednoduššího, než vybrat písmenko velikosti podle klíče slovníku.

Nyní na kód aplikujme také pravidlo KISS. Obrázky můžeme totiž jednoduše pojmenovat číselně, aby směr a velikost v jejich názvu souhlasily s reprezentací těchto hodnot v kódu. Proč to dělat složitě? Obrázky stejně nejsou určené pro uživatele. Výsledek:

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

Kód se zkrátil na 2 řádky z původních 18(!) a dělá úplně to samé. To vše jen za pomoci KISS a DRY. Představte si, co se stane, když Best practices aplikujete na celou aplikaci. Najednou nemusíte psát desítky tisíc řádků kódu a převálcujete svou konkurenci za několik měsíců. Dobré praktiky určitě nepodceňujte :)

Ukázkou DRY by mohly být carousely na ITnetwork, které lze různě nastavovat:

Carousel na ITnetwork - Best practices pro návrh softwaru

Carousel s fotografiemi

Carousel s jiným nastavením na ITnetwork - Best practices pro návrh softwaru

Carousel s HTML obsahem

To jistě ještě není nic velkolepého. Vnitřně ale navíc tyto carousely dědí z komponenty, která přepíná záložky:

Tabcontrol jako předek pro ITnetwork carousel - Best practices pro návrh softwaru

Tabcontrol se zdrojovými kódy algoritmu pro různé jazyky

Mnoho programátorů by napadlo napsat carousel a záložky zvlášť, ale když se blíže zamyslíte, dělají v podstatě to samé, jen vypadají jinak. Jedna komponenta lze vytvořit jen menší obměnou té druhé.

Dalšími příklady DRY by mohlo být např. vytvoření kvalitních obecných CSS stylů, které nejsou omezené na konkrétní prvky na stránce a lze je parametrizovat, jako to má např. CSS framework Bootstrap:

<table class="table table-striped">

Výše uvedený kód nastaví tabulce základní styl a zároveň pruhování.

Shy

Možná znáte poučku:

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

Česky volně přeloženo jako:

Neopakuj se, piš stydlivě a řekni to ostatním.

DRY a opakování jsme si již vysvětlili, ale jak je to se stydlivým kódem? Stydlivý kód se chová úplně stejně, jako stydliví lidé. Komunikuje s ostatním kódem tedy jen když je to nezbytně potřeba a nemá ani více informací o ostatních, než sám nezbytně potřebuje. SHY je vlastně jiné označení pro zákon Deméter, viz níže.

LoD

Law of Demeter, tedy zákon Deméter, řecké Bohyně úrody, je v podstatě o low/loose coupling, tedy o udržování nejmenšího možného počtu vazeb mezi komponentami. Je definován třemi pravidly, kde zavádí pojem jednotka pro zapouzdřenou část kódu, tedy pro třídu:

  • Každá jednotka by měla mít omezenou znalost o ostatních jednotkách, pouze jednotkách, které jsou dané jednotce blízké.
  • Každá jednotka by měla "mluvit" pouze se svými "přáteli", nemluvte s cizími.
  • Mluvte pouze se svými přímými přáteli.

Z pravidel je vidět, že každý objekt by měl být univerzální a naprosto odstíněný od zbytku aplikace. Znát minimum informací o celku a sdílet minimum informací o sobě celku.

Příklad

Praktický příklad si určitě dokážete představit. Jedná se o třídy s dobře zapouzdřenou vnitřní logikou a obecnou funkčností, aby nemusely znát detaily konkrétního systému a bylo je možné použít kdekoli. Např. vytvoříte generátor objednávek tak, aby jej bylo možné 100% přizpůsobit a nebyl závislý na potřebách jednoho informačního systému. Možná vás napadá, zda není plýtvání vývojovým časem a penězi vytvářet funkce, které v daném projektu nepotřebujete. To by plýtvání určitě bylo. S těmito funkcemi stačí jen počítat a navrhovat komponenty tak, aby do nich šlo v budoucnu něco snadno přidat.

LoD se týká také předávání závislostí, kterých by mělo být co nejméně a třída by měla záviset na abstrakcích, ne konkrétních třídách. O tom hovoří Dependency Inversion Principle, který je součástí pouček SOLID.

IoC

Princip Inversion of Control byste měli dobře znát. Jedná se o skupinu návrhových vzorů, jejíž součástí je i populární Dependency Injection, jediný správný způsob, jak v objektových aplikacích závislosti předávat. IoC tvrdí, že objekty v aplikaci by měly být řízené nějakým vyšším mechanismem, který vytváří instance tříd a předává jim potřebné závislosti. To je opačný přístup ke staršímu způsobu práce se závislostmi, kdy si třídy své závislosti vytahovaly samy ze systému, např. přes Singleton nebo jiné antipatterny. Jelikož se jedná o extrémně důležitou problematiku, připravili jsme pro vás o DI samostatný kurz Softwarové architektury a depencency injection, kde je podrobně vysvětlena na reálných příkladech.

V příští lekci, Best practices pro vývoj softwaru - Praktiky SOLID, si ukážeme praktické příklady a ilustrace dobrých praktik SOLID, principů SRP, Open/Closed, Liskov substitution a Interface segregation.


 

Předchozí článek
Nejčastější chyby programátorů - Tvorba metod
Všechny články v sekci
Best practices pro návrh softwaru
Přeskočit článek
(nedoporučujeme)
Best practices pro vývoj softwaru - Praktiky SOLID
Článek pro vás napsal David Hartinger
Avatar
Uživatelské hodnocení:
101 hlasů
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 se informační technologie naučil na Unicorn University - prestižní soukromé vysoké škole IT a ekonomie.
Aktivity