NOVINKA - Online rekvalifikační kurz Python programátor. Oblíbená a studenty ověřená rekvalifikace - nyní i online.
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ých návrhových chyb vyvarovali, nemusíme vynalézat kolo, stačí nám se poučit ze známých chyb ostatních a předejít jim. Dobrých praktik pro návrh softwaru existuje poměrně velké množství, my si dnes představíme ty nejzákladnější.

KISS

Začněme těmi nejjednoduššími poučkami a postupně přejděme ke komplexnější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 takové, které přinese uspokojivý výsledek. Tendenci ke komplikování aplikací mají zejména zákazníci, kteří nerozumí vnitřní struktuře aplikací a kteří si požadavky vymýšlejí, jak je to zrovna napadne. Bývá dobrým zvykem prodiskutovat nutnost nebo podobu alespoň některých požadavků. Často zjistíme, že zákazník vlastně potřebuje něco jiného, co umíme 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 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ář pro novou zprávu byl nahoře a pro načítání nových zpráv jsme použili již existující skript, který načítá data technologií AJAX směrem dolů. Najednou jsme získali stejnou funkcionalitu za zanedbatelný vývojový čas, a to jen díky tomu, že se změnil směr řazení. Toto jsou přesně situace, kdy se vyplatí promluvit si se zákazníkem, neprogramujete-li 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 diletantsky tvoří přesně podle představy někoho, kdo IT nerozumí, a ve svém vlastním kódu se již téměř nevyznají.

SRP

Single Responsibility Principle, zkráceně SRP, nám ří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í, jež se zaměřují každá na jednu funkčnost, kterou 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ím pravidla SRP je např. tvorba manažerů pro různé entity v aplikaci, kdy 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, ačkoli to není základním principem 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 stylem "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ímco 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 čemž si můžete blíže přečíst v kurzu Softwarové architektury a dependency injection. Známé architektury jako MVC, MVP, MVVM a podobně jsou vše implementacemi SoC.

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

Nikdy nevkládáme rozsáhlou definici dat (což může být např. seznam všech PSČ v ČR) do třídy, která provádí 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. V tom případě může klidně dělat i další věci související s adresami.

DRY

Je jedním 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 kdekoli ve vaší aplikaci vyskytují dva stejné bloky kódu nebo třeba i dva podobné kusy kódu, je automaticky napsaný š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 dva 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ředstavme si, že by se změnilo pojmenování obrázku z ...Right... a ...Left... na R a L. Všimněme si, 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 však stále není ideální.

DRY není záležitostí pouze duplicitního kódu, ale opakování jako takového. 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 z původních osmnácti (!) řádků zkrátil na dva, a přitom dělá úplně totéž. To vše jen pomocí pravidel KISS a DRY. Představme si, co se stane, když best practices aplikujeme na celou aplikaci. Najednou nemusíme psát desítky tisíc řádků kódu a svou konkurenci převálcujeme za několik měsíců. Dobré praktiky tedy 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ě však 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íme, dělají v podstatě totéž, 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, jak 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 jen tehdy, když je to nezbytně nutné, a nemá více informací o ostatních, než sám nezbytně potřebuje. Shy je vlastně jiné označení pro Demeteřin zákon, viz níže.

LoD

Law of Demeter, tedy Demeteřin zákon, řecké bohyně úrody, je v podstatě záležitostí low/loose couplingu, tedy udržování nejmenšího možného počtu vazeb mezi komponentami. Zákon je definován třemi pravidly, kdy 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, měla by vědět pouze o jednotkách, které jsou dané jednotce blízké.
  • Každá jednotka by měla "mluvit" pouze se svými "přáteli" a "nemluvit" s cizími.
  • Mluvit pouze se svými přímými přáteli.

Z pravidel je zřejmé, ž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í celku o sobě.

Příklad

Praktický příklad si určitě dokážeme 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říme generátor objednávek tak, aby jej bylo možné stoprocentně přizpůsobit a aby nebyl závislý na potřebách jednoho informačního systému. Možná vás napadá, zda není vytváření funkcí, které v daném projektu nepotřebujete, plýtváním vývojovým časem a penězi. To by plýtvání určitě bylo. S těmito funkcemi však 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ě. 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 bychom 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 předávat závislosti. 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ř. pomocí Singletonu nebo jiného antipatternu. Jelikož se jedná o extrémně důležitou problematiku, připravili jsme pro vás o DI samostatný kurz Softwarové architektury a dependency 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í:
214 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