Přejít k navigační liště

Zdroják » PHP » Testování v PHP: tvorba testovatelného kódu II.

Testování v PHP: tvorba testovatelného kódu II.

Články PHP

V závěrečném díle seriálu si ukážeme tipy, jak odstínit závislosti v legacy kódu, jak podobným závislostem čelit a jak psát kód ještě jednodušší a testovatelnější.

V minulém díle jsme si ukázali, jak vyřešit problém s (ne)testovatelností chráněných metod a také jak refaktorovat třídu, aby byla mnohem lépe rozšiřitelná a nepřinášela při testování více zbytečné práce navíc než užitku. Dnes se podíváme na další překážky, které nás mohou při tvorbě testovatelného kódu potrápit.

Pokud si vzpomínáte, posledním krokem refaktoringu třídy Configuration bylo vyjmutí odpovědnosti za vytvoření instance konkrétního loaderu a její přenesení do tovární metody. Rovnou jsme vyhodili i statické volání třídy Logger a nahradili jej výjimkou.

Pojďme se vrátit o jeden krok zpět a podrobněji se podívat na důvod poslední úpravy. Před změnou mohl kód tovární metody vypadat zhruba takto:

class Configuration_LoaderFactory
{
    public function getLoader($filename)
    {
        if (substr($filename, -3) == "ini") {
            return new Configuration_IniLoader();
        } elseif (substr($filename, -3) == "xml") {
            return new Configuration_XmlLoader();
        } else {
            Logger::getInstance()->log("Unknown file type");
        }
    }
}

Při návrhu původní třídy Configuration jsme, v záplavě nadšení, implementovali Logger podle návrhového vzoru Singleton – potřebujeme přece jen jedinou instanci:

class Logger
{
    protected static $instance = null;

    private function __construct() {}

    public static function getInstance()
    {
        if (self::$instance === null) {
            self::$instance = new self;
        }

        return self::$instance;
    }

    public function log($msg)
    {
        // ... log message to a file
    }
}

Co je na tomto přístupu špatně? Kód je přece jasný – pokud je možné rozpoznat správný loader, pak je vrácen, v opačném případě je zalogována chyba… Problémů je hned několik.

Statické volání

Static methods are death to testability. (Miško Hevery)

Při pokusu o testování výše uvedeného kódu okamžitě narazíme na zákeřného protivníka – statické volání. Klidně si jej přeložte jako „globální volání“, vyjde to úplně na stejno. Jedno ze základních pravidel dobrých testů říká, že testy by měly být nezávislé, což tady očividně neplatí a při použití jakéhokoli statického přístupu ani nikdy platit nebude. Třída Configuration_LoaderFactory je závislá na třídě Logger a co je horší – na první pohled (bez znalosti kódu metody getLoader) to nejsme schopni nijak zjistit! V lepším případě to zjistíme v momentě, kdy nám testy začnou zaplňovat logy. Co s tím?

Určitě nechceme aby unit testy třídy Configuration_LoaderFactory jakkoli sahaly na logy, tzn. bylo by dobré třídu Logger pro potřeby testování nahradit nějakým mockem. Ale jak mockovat něco, k čemu nemáme přístup? Existuje workaround pomocí reflexe:

class Configuration_LoaderFactoryTest extends PHPUnit_Framework_TestCase
{
    public function testGetLoader()
    {
        // pripravime si mock:
        // konstruktor tridy Logger je chraneny,
        // proto jej nebudeme volat
        $builder = $this->getMockBuilder("Logger")->disableOriginalConstructor();
        $mock = $builder->getMock();
        $mock->expects($this->once())
            ->method("log");

        $class = new ReflectionClass("Logger");
        $property = $class->getProperty("instance");
        // zpristupnime property $instance
        $property->setAccessible(true);
        // nastavime do ni mock
        $property->setValue($mock);

        $conf = new Configuration_LoaderFactory();
        // pripona .doc nebude rozpoznana, bude volan Logger
        $conf->getLoader("data.doc");
    }
}

Sice jsme vyřešili problém s (ne)testovatelností, ale opět jsme řešili důsledek a nikoli příčinu. Kromě toho jsme si do testů zanesli hodně nepříjemnou závislost – hacknutý Singleton Logger bude i nadále vracet mockovanou instanci! Jak je to možné? Má ji přece uloženou ve své statické (globální) property! Statický přístup je krása, že? :-)

Zase to vstřikování

Jestliže třída Configuration_LoaderFactory opravdu potřebuje instanci třídy Logger, tak proč tuto závislost otevřeně nepřiznat?

class Configuration_LoaderFactory
{
    public function getLoader($filename, Logger $logger)
    {
        if (substr($filename, -3) == "ini") {
            return new Configuration_IniLoader();
        } elseif (substr($filename, -3) == "xml") {
            return new Configuration_XmlLoader();
        } else {
            $logger->log("Unknown file type");
        }
    }
}

Uživatel naší třídy nyní jasně vidí, že pro volání metody getLoader potřebuje instanci třídy Logger a nemusí to nikde složitě zjišťovat. Tento postup je znám jako Dependency injection – tedy předávání závislostí namísto jejich vytváření. Toť vše, nehledejte za tím žádnou složitost nebo magii.

Nabízí se otázka – jak postupovat v případě, že bychom namísto Singletonu vytvářeli novou instanci přímo v metodě getLoader?

class Configuration_LoaderFactory
{
    public function getLoader($filename)
    {
        if (substr($filename, -3) == "ini") {
            return new Configuration_IniLoader();
        } elseif (substr($filename, -3) == "xml") {
            return new Configuration_XmlLoader();
        } else {
            $logger = new Logger();
            $logger->log("Unknown file type");
        }
    }
}

Ideální řešení je úplně stejné jako předchozí – pomocí dependency injection: metoda má závislost na třídě Logger, deklarujme toto jasně v jejím rozhraní. Kde „se vezme“ instance Logger, nás nemusí vůbec zajímat, to už je starost volajícího. My oznamujeme „chceš-li použít tuto metodu, budeš k tomu potřebovat X a Y“.

Workaround pro legacy kód také existuje, ale už je složitější než v případě Singletonu. Můžeme sáhnout například po extension php-test-helpers (https://github.com/sebastianbergmann/php-test-helpers), jehož autorem je Sebastian Bergmann, konkrétně po funkci set_new_overload.

S její pomocí je možné „přetížit“ operátor new tak, aby namísto instance třídy uvedené v testovaném kódu vrátil instanci třídy jiné – např. nějakého „fake“.

function callback($className) {
    if ($className == "Logger") {
        $className = "FakeLogger";
    }

    return $className;
}

set_new_overload('callback');

Podobně je možné „přetěžovat“ i volání funkcí nebo chování funkce exit, která je často pro testy také smrtelná.

Přílišné nadšení

Ale zpět k tématu. Okolo dependency injection koluje spousta mýtů a polopravd, v případě Dependency injection container (dále jen DIC) to platí dvojnásob. DIC je možné si představit jako konfigurovatelnou továrnu na objekty. Chceme instanci třídy A, která má závislost na třídě B? Požádáme o ni kontejner a on nám ji vyrobí. Prosté. Hned se nabízí otázka – proč tedy explicitně uvádět závislosti tříd, když by stačilo, aby všechny třídy byly závislé na DIC:

class Configuration_LoaderFactory
{
    public function getLoader($filename, Dic $container)
    {
        if (substr($filename, -3) == "ini") {
            return new Configuration_IniLoader();
        } elseif (substr($filename, -3) == "xml") {
            return new Configuration_XmlLoader();
        } else {
            $container->get("logger")->log("Unknown file type");
        }
    }
}

Bude-li potřeba další závislost, pak si ji jednoduše vytáhneme z containeru… Nebo jinak – všechny třídy budou dědit od třídy MagicObject, která bude mít DIC jako property! Pak budeme mít DIC dostupný všude a nemusíme jej stále předávat!

abstract class MagicObject
{
    protected $container;

    public function __construct()
    {
        $this->container = new Dic();
    }
}

class Configuration_LoaderFactory extends MagicObject
{
    public function getLoader($filename)
    {
        if (substr($filename, -3) == "ini") {
            return new Configuration_IniLoader();
        } elseif (substr($filename, -3) == "xml") {
            return new Configuration_XmlLoader();
        } else {
            $this->container->get("logger")
                ->log("Unknown file type");
        }
    }
}

Budete se možná divit, ale některé hloupé implementace MVC frameworků to takto skutečně mají nebo v době „DIC fanatizmu“ měly! Asi už tušíte, že oba postupy jsou postavené na hlavu. Psát testy pro podobný kód je horor, protože se „umockujete“ k smrti. Pokud by byla třída Configuration_LoaderFactory podobně závislá na třídě Logger, jejíž instance si sama získává z DI containeru, pak bychom museli nejprve připravit mock DIC a teprve do něj vložit mock třídy Logger. V případě, že by třída Logger měla své vlastní závislosti, pak bychom samozřejmě museli mockovat i tyto závislosti. A tak dále…

Stejně jako jsme v minulém díle, víceméně náhodou, narazili na dva principy objektového návrhu, nyní se nám rýsuje další – Law of Demeter. Ve zkratce říká – „nemluv s cizími“, tzn. nevolejme metody instancí, které nám např. vrátí jiná metoda jiné instance. Fluent interface je sice sexy, ale pro testování je to hotové peklo.

Kiss!

Je-li celá třída Configuration_LoaderFactory nebo její metoda getLoader závislá na třídě Logger, pak toto deklarujme jasně a neschovávejme to za žádným kontejnerem nebo jinou magií!

Zamysleme se na závěr ještě nad jednou (zdánlivě) drobností – požadujeme-li loader k souboru známého typu, pak je vše OK, ale v opačném případě? Sice dojde k nějakému zalogování chyby, ale volání metody getLoader tiše skončí. Metoda patrně vrátí null a způsobí volajícímu řadu nepříjemností.

To není moc dobré. To opět zavání tím, že metoda řeší více, než by měla. Je opravdu odpovědností metody getLoader logovat problémy? Samozřejmě že ne! Je to jako bychom říkali: „vytvoř mi instanci loaderu podle tohoto souboru a kdyby to náhodou nešlo, tak to zapiš pomocí tohoto loggeru“. Budeme-li se držet obecného doporučení, známého pod zkratkou KISS (Keep it simple, stupid), pak by naše metoda měla buď udělat, co má, nebo selhat. Nic víc. Fakt, že namísto vytvoření instance byla vyvolána výjimka, by měl řešit volající a nikoli volaný.

class Configuration_LoaderFactory
{
    /**
     * @return Configuration_Loader
     * @throws InvalidArgumentException
     */
    public function getLoader($filename)
    {
        if (substr($filename, -3) == "ini") {
            return new Configuration_IniLoader();
        } elseif (substr($filename, -3) == "xml") {
            return new Configuration_XmlLoader();
        } else {
            throw new InvalidArgumentException("Unknown file type");
        }
    }
}

Dobrým zvykem je upozornit uživatele metody na vše, co od ní může očekávat, pomocí Javadoc anotace. Zde: metoda vrací instance typu Configuration_Loader a může vyvolat výjimku třídy InvalidArgumentException.

Dependency injection je mocná zbraň, ale jak už to bývá – i tato zbraň může být dvojsečná, pokud není správně pochopena nebo používána s přílišným nadšením. Ano, předávejme závislosti namísto jejich vytváření, ale jen ty, které skutečně potřebujeme, a udržujme kód „stupid simple“. Jen tak budeme schopni náš kód snadno testovat a také v budoucnu rozvíjet.

Závěr

Tím jsme se dostali na úplný závěr seriálu o testování v PHP. Jak jste v posledních dvou dílech mohli vidět – díky testování jsme schopni psát mnohem lepší kód i bez znalosti nějakých obecných principů softwarového návrhu. Prostě k nim sami dojdeme. A touto myšlenkou bych rád seriál ukončil:

Pište, jak nejlépe umíte, a vše testujte. Na ostatní už přijdete sami…

Zdrojové kódy příkladů uvedených v seriálu najdete na GitHubu: https://github.com/josefzamrzla/serial-testovani-v-php. Kdyby vás cokoli zajímalo nebo něco nebylo úplně jasné, napište. Zastihnout mě můžete buď na mailu: josef.zamrzla(at)gmail.com nebo na Twitteru: @JosefZamrzla.

Komentáře

Subscribe
Upozornit na
guest
31 Komentářů
Nejstarší
Nejnovější Most Voted
Inline Feedbacks
View all comments
5o

Poučné, jedinú výhradu mám k metóde getLoader(), ktorá má parametre. Nie je to accessor a preto by som ju premenoval na createLoader().

maryo

V posledním bloku kódu je zapomenutý argument Dic.


 /**
  * @return Configuration_Loader
  * @throws InvalidArgumentException
  */


public function getLoader($filename, Di $container)

BTW. Já bych do tý factory ty Loadery registroval. Např. nějak takto.

$factory->registerLoader('xml', new XmlLoader())

maryo

Vlastně blbost :)… To už by nebyla žádná factory. Asi bych měl nějakej obecnej Loader a tam bych registroval jednotlivý dekodéry. A to bych naplnil třeba přes ten DIC pomocí tagů. Ale možná je to taky špatnej přístup (i když třeba v Symfony běžný), takový trochu programování v konfiguraci. Ale přijde mi to ifování trochu divný… No… to je jedno :).

anti war

A jakou cestu autor preferuje pro logování korektních stavů aplikace? (často v Enterprise, např. záznam o provedené transakci, průběh interace, údaje pro ladění výkonu, přihlášení uživatele)
Ono logovat chybové stavy je ještě snadné, ale jak nejlépe objektově a výkonově na logování všech stavů…

honza

Díky za zajímavý seriál. Měl bych dotaz k Dependency Injection. Vyjdu z příkladu, kde je testovatelnost funkce GetLogger umožněna tím, že se Logger místo vytváření předává jako parametr. Tento krok je mi jasný, teď je možné tuto metodu lépe testovat. Neznamená to ale, že jsem problém místo vyřešení jen přesunul pomocí DI o úroveň výš? Ten Logger stejně musí někdo vyrobit, pokud to není volaná funkce, tak to bude volající, řekněme třeba nějaká funkce InitCosi, ve které chci GetLoader použit. Jak budu potom testovat tu? Nebudu mít při testování InitCosi stejný problém?

Martin Mystik Jonáš

Právě to přesunutí o úroveň výše je to na čem je DI postaveno. Jde o princip „mě je jedno jak, ale předej mi něco co můžu použít“. Odděluje vytváření objektů od jejich používání a tím separuje zodpovědnosti a snižuje provázanost a závislosti.

Nakonec samozřejmě musí objekty někdo vytvářet. Při správném používání ale tato zodpovědnost probublá až úplně nahoru. Na nejvyšší úrovni je pak nějaký bootloader, init funkce nebo DI container, ať už to nazveme nebo uděláme jakkoliv a ta se stará o vytváření objektů.

Protože ale jediné co toto „horní parto“ dělá je to vytváření objektů tak ho můžu mnohem snáze testovat. Jen ho požádám o vytvoření objektů a zkontroluju, že je udělala správně.

honza

Ok, to mi připadá logické. Ve složitějším systému si ale neumím moc představit, že by se nejdřív úplně všechno vyrobilo a pak se to „jen“ používalo.
Spousta objektů musí vznikat za běhu podle aktuální situace a někdo je vytvářet musí – a takové místo pak tedy bude špatně testovatelné? Neumím si představit, že bych to dokázal omezit jen na jedno místo v aplikaci, protože to by mi pak vznikla ta (právem zavrhovaná) „univerzální factory“ na výrobu všeho.
Nicméně zrovna v případě loggeru dává navrhovaný postup dobrý smysl.

Martin Mystik Jonáš

Pokud potřebuju objekty vytvářet za běhu tak se to řeší tak, že si injectuju továrnu, která mi ty objekty vytváří.

Tj. na nejdříve opravdu sestavím úplně všechno včetně těch továren. A když za běhu potřebuju vyrobit nový objekt požádám o to továrnu. Na každý typ objektu můžu mít vlastní továrnu.

Hlavní výhoda tohoto řešení je, pokud potřebuju změnit způsob jakým se objekty vytváří. Pokud používám továrnu provedu změnu jen na jednom místě – v továrně. Nebo pokud bysme chtěli držet i Open-closed priciple tak si vytvořím potomka továrny, který bude objekty vytvářet jinak. Pak jen na tom centrálním místě vyměním jednu továrnu za jinou.

honza

To zní rozumně. Akorát předpokládám, že to vede k tomu, že volání na vyšší úrovni pak mají obrovské množství parametrů, protože se jim musí dát všechny ty továrny a další singletony, aby je pak mohly injectovat postupně do dalších a dalších úrovní. Asi si to budu muset vyzkoušet, abych viděl, jestli to stojí za to, připadá mi, že to musí strašně znepřehlednit hlavičky metod.

Martin Mystik Jonáš

No to je druhá stránka věci – pokud mají jednotlivé součásti spousty parametrů tak je to často příznak nějakých chyb v návrhu. Ve výsledku nás používání DI vede i k návrhu s méně vazbami/méně parametry.

Pak samozřejmě existují i jiný způsoby jak závislosti předávat – nejen přes konstruktor, ale i přes settery, public atributy, nějakou reflexion magic, …

Pokročilejší DI kontejner pak zvládá i nějaký autowiring (sám ti injectuje podle nějakých podmínek zapsaných v konfiguraci/anotacích), …

Doporučuju si nejdřív přečíst nějaké články přímo o DI než se do toho pustíš sám.

Hezký jsou třeba články od Davida Grudla:
http://phpfashion.com/co-je-dependency-injection

Nebo zmiňvané články na zdrojáku od Vaška Purcharta:
http://www.zdrojak.cz/serialy/jak-na-dependency-injection/

honza

Díky, kouknu na to. Takhle na první pohled mi to připadá, že řešením problému s předáváním příliš mnoha závislostí pomocí reflexe nebo autowiringu se dostávám tam, kde jsem byl na začátku před DI, tedy k obtížně testovatelnému kódu, ale nejdřív si přečtu odkazované články, třeba to má nějaké krásné jednoduché řešení.

Yetty

Ne, to nemá žádné krásné jednoduché řešení ;) Protože kdyby bylo, mohl by programovat každý. Naštěstí je nad tím potřeba ještě pořád přemýšlet. A tak je na programátorovi, jestli dokáže kód udržet *krásný* a *jednoduchý*. Metody jako DI k tomu mohou jenom pomoci. Ne jsou samospásné :)

oo

?

honza

Ještě dotaz – tohle je konec seriálu? Nebude nějaké shrnutí nebo nějaká ukázka, jak se to použije v reálu?
Hlavně jsem tak nějak pořád čekal na nějaké komplexnější testy, kterými dokážu opravdu pořádně otestovat, že metoda dělá to co má v různých připadech. Všechny ukázky zatím byly vždy jen „dej tenhle vstup, je výstup tohle – ok“. Předpokládal jsem, že to byly jen zjednodušené příklady a nějaký nezjednodušený příklad ještě přijde.

Jan

Pořád hledáš nejjednodušší, nejlehčí řešení. Pořád čekáš, že ti někdo řekne, jak to máš dělat, jak máš testovat, nebo co? Pořád si něco neumíš představit něco ve složitějším případu, ale nemáš žádný příklad z praxe, protože seš nějaký teoretik, který testuje klikáním na webu, jak už víme z tvých „chytrých“ komentářů od prvního dílu, co se snažíš testování označovat za zbytečné a neužitečné. Radši si dál klikej a netestuj. Stejně univerzální návod na tvorbu testů neexistuje, ale ty tvoje komplexní testy určitě nehledej mezi unit testy (pokud dodržíš OOP návrh).

honza

No, představoval jsem si něco jako že tomu dám sadu vstupů a sadu výstupů, nebo že to bude procházet v nějakém cyklu přes nějak definovanou množinu zadání nebo tak něco. S jedním vstupem a k němu odopvídajícím výstupem se opravdu nespokojím, protože to ve sktuečnosti nic netestuje, jak jsme viděli v jedné z ukázek v předchozích dílech. Asi jako kdybych měl funkci, na sčítání, která vždy vrací 4 a napsal si test ze vstupem 2+2. Ok, prošlo, odškrtnuto, vše funguje.

Honza Marek

Hoď do googlu „phpunit data provider“ ;)

arron

Ale no tak…každý jsme nějak začínali :-)

pav

Pokud ten článek neměl za úkol pomoci čtenáři, proč tedy existuje?

Souhlasím s předřečníkem. Tohle všechno se hezky čte, ale současně je toho na webu mraky. Nic reálně vypadajícího nebo obecného, jen silně specifické scénáře.

Bohužel díky tomu je i pro mne seriál zklamáním. Dopadl dle očekávání: autor ukázal, že tomu rozumí, ti co to používají jásají, že proběhla osvěta, ale údajné cílové skupině to prakticky nic nedalo.

arron

Ale jo, asi to chápu, protože podobně jsem začínal s unit testy já, ačkoliv mám torchu problém se do té doby zpětně vžít :-) Proto mám asi pocit, že bylo řečeno vše podstatné. Nicméně udělejme to jinak, pošlete mi třídu, kterou by sis představoval ukázat otestovanou a uvidíme, co se dá dělat (tomas.lembacher(at)gmail.com). :-)

Honza Marek

Co si pamatuju, tak pro mě bylo kdysi velkým překvapením, že pokud chci mít testy, tak musím psát jinak vypadající a o dost jednodušší kód. Pak jsem se to rok učil a nakonec jsem zjistil, že to jde. Jediná cesta jak se to naučit je zkusit si to a vydržet, jen ze článků to nejde.

arron

Jako jo. Nicméně já si vzpomínám na dobu, kdy jsem si kladl podobné otázky. „Jak to mám sakra udělat, když všude jsou jenom takový stupidní příklady???“ Takže říkám ok, pokud mi někdo pošle nějaký vhodný kus kódu, který považuje za dostatečně komplexní, tak se pokusím napsat článek. Zatím se na to každý tak maximálně vyprdnul ;-)

Pepa

Poslal bych, ale nevím kam ;)

arron

V jednom z příspěvků nahoře jsem psal adresu ;-)
tomas.lembacher(at)gmail.com

Petr

Serial skoncil, uz zbyva jej jen poplivat… Jak typicky ceske… Co se namisto „hejtovani“ pokusit treba o pokracovani, hm?

arron

Chápu, že to možná není tak zřejmé, ale o to se právě chci pokusit :-)

arron

Ale vždyť přece minulý a tento díl seriálu byly už docela zajímavými ukázkami ne? Ve složitějších případech se testy jenom trochu rozrostou, nic víc :-) Pak něco jiného je, když jsou někde nějaké staticky volané metody apod. (obecně těžko testovatelné věci), ale to už pak není o testování, ale o refaktorování. Chce to jistou praxi, aby člověk věděl co s tím udělat tak, aby se to vůbec dalo otestovat, ale není to zase tak těžký a nelogický. Hlavně v tom nehledat zbytečné složitosti. Stějně je to jenom o tom, že si udělám mock závislostí, připravím si nějaká data, nastavím nějaká očekávání (expect), zavolám testovanou metodu a pak si ověřím, že se stalo to, co se stát mělo. Nic složitějšího v tom není potřeba hledat :-)

Clary

+1

Rob

Je to asi trochu OT, ale s testováním to rozhodně souvisí.
Jak napsat testy když správných výsledků může být celá řada? Konkrétně – chtěl jsme napsat zvýrazňovač kódu ….
Jenže třeba již na jednoduchý vstup typu:
echo ‚aaa‘;
může být správným výstupem třeba:
1. echo ‚aaa‘;
Nebo
2. echo ‚aaa‘
a spousta dalších včetně variant class=“xx yy“ kontra class=“yy xx“

Prohlížeč prostě všechny tyto HTML fragmenty zobrazí stejně – takže podle mě správný výsledek.
Náhodně si vybrat jeden konkrétní HTML tvar správného výsledku a ten se snažit naprogramovat podle mě není zrovna šťastný přístup, protože se v průběhu programování může zjistit, že tvorba regexp zrovna pro tento tvar je 10x složitější (a on je v provozu mnohem pomalejší) než by byla nějaká jiná varianta.
Ale přepisovat předpokládaný výsledek testu uprostřed práce, asi také není to správné.

Rob

Mělo tam být: (místo špičatých závorek dávám hranaté)
1. [SPAN class=“phplang“][SPAN class=“phpkw“]echo[/SPAN] [SPAN class=“phpstr“]echo[/SPAN][/SPAN]
2. [SPAN class=“phplang phpkw“]echo[/SPAN][SPAN class=“phplang“] [/SPAN][SPAN class=“phplang phpstr“]echo[/SPAN]

Omlouvám se za chybné zadání… neuvědomil jsme si, že lze zapisovat přímo HTML kód.

failer

Je na čase trochu změnit přístup. Základem jsou vždy data. A základní otázka zní: Kterých 5 základních operací provádíme nad daty? Nemusí se vždy používat všechny. Osobně jsem nenarazil na případ, kdy bych nad nimi dělal něco jiného. Proč se ptám? S tímto přístupem je to strašně ukecané a začíná tím podle mne vznikat špageta. A kdo to má dokumentovat…

Enum a statická analýza kódu

Mám jednu univerzální radu pro začínající programátorty. V učení sice neexistují rychlé zkratky, ovšem tuhle radu můžete snadno začít používat a zrychlit tak tempo učení. Tou tajemnou ingrediencí je statická analýza kódu. Ukážeme si to na příkladu enum.