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

Zdroják » Různé » Dependency injection a metody globálního prostoru v PHP

Dependency injection a metody globálního prostoru v PHP

Články Různé

Poslední dobou se celkem intenzivně zabývám dependency injection a s ním spojenými problémy. Při zkoumání DI jsem narazil na problém, který vám zde budu prezentovat.

Rychlý úvod do dependency injection

Dependency injection je princip, který nám zjednodušeně řečeno říká, že cokoli třída ke svému fungování potřebuje, má vyžadovat po svém okolí, místo toho, aby si to získávala sama. V důsledku to znamená, že třída by vůbec neměla přijít do kontaktu s globálním prostorem – pokud dostane to, co potřebuje, udělá svoji práci, ale nikdy si nic sama neshání. Pokud tedy potřebuje nějakou jinou service, pak o ní požádá v konstruktoru (jako povinný parametr) nebo alespoň pomocí setteru nebo public property. Výsledkem je zjednodušení aplikace, lepší čitelnost návrhu a lépe testovatelný kód. Pokud se o dependency injection chcete dozvědět více, doporučuji vám seriál Vaška Purcharta – Jak na dependency injection, který vyšel zde na Zdrojáku.

Globální prostor

V globální prostoru se nachází všechno možné – globální proměnné, údaje o čase, lokalizace, $_GET, $_POST atd. Pokud tedy některá z našich tříd pracuje přímo s proměnnou $_GET, vnášíme tím do systému neočekávatelné závislosti. Třída se bude chovat různě podle toho, co proměnná $_GET zrovna obsahuje. Nicméně se bez studia jejího kódu nedozvíme, proč se třída občas chová jinak. Pokud naopak třída bude vyžadovat předání pole s parametry místo toho, aby si je získávala sama, bude se vždy chovat podle toho, jaké parametry jí zrovna předáme, a tedy zcela podle očekávání. Pokud se pak rozhodneme, že místo proměnné $_GET budeme pracovat s proměnnou $argv, nemusíme implementaci třídy měnit.

Globální prostor ale obsahuje ještě jednu věc, a sice funkce definované přímo v jádru PHP, například strlen() nebo rand() a také jazykové konstrukty jako include nebo array. Měli bychom se jejich používání také vyhnout? To je otázka, které se chci v tomto článku věnovat.

Odkrývání závislostí

Pokud některou z těchto globálních funkcí použijeme uvnitř třídy, pak jsme podle všeho porušili pravidlo, podle kterého by naše třídy neměly své závislosti skrývat. Ukážeme si to na příkladu.

Mějme zcela triviální generátor hesel.

class RandomPasswordGenerator {
    public function generateRandomPassword() {
        return 'abc' . rand(1, 9);
    }
}

Generovaná hesla sice nejsou příliš bezpečná, ale jinak vypadá příklad v pořádku. Na první pohled není porušení DI principu patrné. Ukažme si ale protipříklad. Představme si, že v PHP žádná funkce rand() vůbec neexistuje a nikdy neexistovala. Místo ní nám tvůrci PHP dali k dispozici třídu, která slouží ke generování náhodných čísel. Příklad by pak vypadat asi takhle:

class RandomPasswordGenerator {
    public function generateRandomPassword() {
        $random = new RandomNumberGenerator();
        return 'abc' . $random->getRandomInteger(1, 9);
    }
}

Tady už je porušení DI mnohem lépe vidět. Co kdybychom se totiž rozhodli použít jinou implementaci generátoru náhodných čísel například pro účely testování? Typickým příkladem může být výměna funkce rand() za mt_rand(). Při tomto přístupu to ale není možné, museli bychom třídu přepsat.

To se dá naštěstí jednoduše opravit. Provedeme refaktoring třídy tak, aby svou závislost na generátoru náhodných čísel deklarovala ve svém konstruktoru.

class RandomPasswordGenerator {
    private $random;

    public function __construct(RandomNumberGenerator $random) {
        $this->random = $random;
    }

    public function generateRandomPassword() {
        return 'abc' . $this->random->getRandomInteger(1, 9);
    }
}

Teď už naše třída vypadá úplně jinak než na začátku. První i poslední implementace dělají úplně to samé. Poslední implementace ale dává své závislosti jasně najevo.

U generátoru náhodných čísel je problém celkem dobře vidět. V testech budeme určitě potřebovat, aby třída pracovala s nám známým „náhodným“ číslem, abychom mohli kontrolovat její výstupy. Proto vytvoříme mock třídy RandomNumberGe­nerator, v mocku upravíme metodu getRandomInteger tak, aby vracela stále stejné číslo a můžeme začít testovat. Pokud bychom použili implementaci, kdy si třída RandomPasswor­dGenerator obstarává náhodné číslo sama, byla by třída neotestovatelná.

Je to opravdu tak hrozné?

Jak je to ale s jinými metodami? Těch se problém podle mě týká úplně stejně. Mohlo by se zdát, že u nich nebudeme nikdy potřebovat používat jinou implementaci. Vždyť jsou přímo v jádru PHP, proč bychom je tedy měnili? Podívejte se ale na metodu strlen(). Ta sice je součástí jádra PHP, ale alternativní implementaci používáme každou chvíli a důvody proč jsou zcela jasné. Za pravdu mi dá asi každý, kdo někdy pracoval s externí knihovnou, jejíž třídy uvnitř používají metodu strlen(). Jakmile začnete používat multibytové kódování, přestane spousta věcí fungovat. Máte v takovém případě dvě možnosti, buď všechna místa, kde se strlen() používá, ručně přepsat a smířit se s tím, že to budete dělat pokaždé, když vyjde pro tuto knihovnu nový update, a nebo přestat knihovnu používat. Častým protiargumentem je, že to přece není tak hrozné, a že dnešní IDE dokážou nahrazení udělat za vás. Schválně jsem ale vybral příklad s metodou strlen(). Automaticky ji vyměnit za mb_strlen() je i s dnešními IDE komplikované a téměř vždy se při tom něco rozbije.

Kdyby ale třídy této knihovny nějakým způsobem jasně definovaly svoji závislost na funkci strlen(), pak jim stačí předávat jinou implementaci. V takovém případě by se jednalo jen o přepsání pár řádků v konfiguračním souboru pro DI kontejner.

Návrh řešení

Jenže co teď s tím? Jak už jsem řekl, sám si nejsem úplně jistý, jak tento problém elegantně vyřešit. Jednou z možností je „zabalit“ všechny metody, které používáme, do objektového rozhraní. Příkladem může být už zmiňovaná třída Rand. Tento přístup se mi celkem líbí, ale má jistá úskalí.

Naše třídy budou moci veřejně deklarovat své závislosti na „obalových třídách“. V případě potřeby změny si můžeme vybrat, jestli změníme už hotovou implementaci, nebo si napíšeme novou třídu, implementující stejné rozhraní a začneme místo původní instance předávat instanci nové třídy. Náš kód bude mnohem lépe testovatelný. Budeme moci kdykoli změnit implementaci a to dokonce za běhu, bez potřeby cokoli přepisovat.

Nevýhoda samozřejmě spočívá v tom, že ty třídy budeme muset napsat, což může být obrovská spousta práce. Funkcí v jádru PHP je úctyhodné množství. Na první pohled to tedy není příliš dobrý nápad. Na druhý pohled to ale není zas tak špatné. Schválně si zkuste uvědomit, kolik funkcí běžně používáte. Já jsem si takový odhad udělal a víc než 30 jich určitě nebude. Samozřejmě jich používám mnohem víc, ale většinu z nich jen zcela výjimečně. Těch, které používám, opravdu často není moc. Prvotní náklad by sice byl celkem značný, ale není to nic, co by se nedalo zvládnout.

Celý nápad se sice může zdát přitažený za vlasy, ale uvědomte si, že je to něco, co se v PHP dělá už dávno. Podívejte se na všechna ta PDO, dibi, DateTime a další. Nikdy se nejedná o nic jiného, než objektové obálky nad globálními funkcemi. A myslím, že procedurální volání funkcí pro práci s databází snad nepoužívá nikdo, kdo netrpí sebemrskačskými sklony.

Druhou možností je vložit funkce do nějaké knihovny jako statické metody. Pak bychom ke všem funkcím přistupovali pomocí statického volání. Tím sice zamlčíme závislosti našich tříd na těchto metodách, je ale otázkou, jestli nás to musí trápit. To samé se děje i v případě, že používáme globální funkce. Výhoda tohoto přístupu je v tom, že máme alespoň možnost změnit implementaci v případě, že zjistíme, že současná implementace nám nevyhovuje. Pokud bychom chtěli tento přístup použít, musíme si uvědomit, že volání statické metody nebo globální funkce je prakticky to samé. V jednom případě jsme si ale funkci definovali sami.

Komentáře

Subscribe
Upozornit na
guest
21 Komentářů
Nejstarší
Nejnovější Most Voted
Inline Feedbacks
View all comments
David Grudl

Rozhodně zajímavý článek.

Příklad s funkcí strlen není úplně šťastně zvolený, protože pokud nějaká knihovna používá funkci pro zjištění délky řetězce v bajtech v místě, kde má být správně funkce pro zjištění délky řetězce ve znacích, je to jednoduše bug této knihovny a měl by být opraven v jejím kódu.

Nechci zpochybněním příkladu jít proti myšlence článku – naopak: položme si otázku, co to ona závislost, z pohledu DI, je. Můžeme mít závislost na datech (jméno souboru, parametry připojení k DB atd.) a můžeme mít závislost na algoritmu. V situaci, kdy lze určitou věc řešit právě jedním jediným způsobem, nedává smysl algoritmus exponovat do podoby závislosti. Jako programátor se tedy spoléhám, že mi strlen vrací to, co má, a vnímal bych jako díru do logiky, kdyby se to dalo měnit.

Vlastně je pro mě děsivé, že nastavením některých globálních parametrů PHP lze změnit chování strlen a že se nelze spolehnout ani na to, co vrátí strtolower('ABC').

Tedy jakákoliv systémová funkce není závislostí. Tj. nejde o algoritmus, který by dávalo smysl uživatelsky měnit. Pokud bych na takový případ narazil, a spíš odhaduji, že jich bude málo, tak bych ho řešil podobně, jako jsi to udělal s funkcí rand().

Tedy jednotlivě. Uvádět jako závislost celou obalovou třídu, tj. předávat si Service locator, není nejčistší řešení.

Stejně tak volat statické metody místo globálních je z pohledu DI totéž. A jakožto finta pro změnu implementace je to nečisté, to už raději nasadit Runkit. Často dokonce stačí, že třída je umístěna ve jmenném prostoru, a lze ji podstrčit jinou funkci jen tím, že ji v tomto prostoru re-definujeme. Málokdo totiž píše strlen s lomítkem. Ale to už skutečně nemá s DI co dělat.

Dundee5

Hezké zamyšlení, díky!

Souhlasím s Davidem, ne všechno je potřeba mít konfigurovatelné a nahraditelné. My jsme si zatím v práci vystačili se třídami DateTimeFactory a Oraculum, tedy s obálkami nad funkcemi, které pracují s nějakým globálním stavem.

Mastodont

Dobrý den,

úplně by stačilo přestat považovat DI za nové, nedotknutelné božstvo, kterému se díky Heverymu a dalším výtečníkům nyní všichni klaní :-)

Interní závislosti nejsou vůbec ničím tragickým a dá se s nim vyžít, pokud si člověk pořádně rozmyslí architekturu. Viz Pragmatic Programmer (In fact, by reversing the Law of Demeter and tightly coupling several modules, you may realize an important performance gain. As long as it is well known and acceptable for those modules to be coupled, your design is fine.)

… procedurální volání funkcí pro práci s databází snad nepoužívá nikdo, kdo netrpí sebemrskačskými sklony …

Nepoužívá, ale IMHO spíš proto, že tyhle funkce se vždy volají jako součást delšího kódu, který lze převést na funkci/metodu.

msx

Kedysi som sa chcel trochu zdokonaliť v OOP a chcel som prepísať jednu jednoduchú hru do C#. Problém bol v tom, že hra potrebovala grafiku a objekty hry museli s ňou byť nejako prepojené. Nechcel som robiť odkaz na „globálne“ objekty, lebo by som stratil zapuzdrenosť objektov.

Jednalo sa o klasickú hru s červíkom. Ten musel požierať kapustičky, aby rástol a musel byť zobrazený na nejakej grafickej ploche. Spravil som dva objekty:
1. záhradu
2. červíka

Tieto objekty sa ale museli niekde zobrazovať. Takže to potrebovalo nejaké prepojenie s grafickým objektom. Nakoniec som to spravil takto:

Konštruktor objektu Záhrada dostal odkaz na grafický objekt, kam sa má zobrazovať záhrada a konštruktor objektu Červík dostal odkaz na objekt Záhrada, aby sa mal kde zobraziť.

Celé som to preberal na jednej poradni s inými diskutujúcimi a nakoniec diskusia skončila asi tak, že sa vymykám OOP a takto by sa to určite robiť nemalo. Ako pozerám, nie som jediný, kto uvažuje podobne a som rád, že moje konečné rozhodnutie bolo správne. Nevedel som totiž, ako by sa to dalo inak spraviť lepšie.

Samozrejme nešiel som až do takých detailov ako vytiahnutie samotného generátora náhodných čísel von. Podľa mňa až tak do detailov netreba zachádzať pokiaľ to nie je nutné a v tejto hre to naozaj nutné nebolo.

Takže mi to nedalo a musím podporiť autora článku a nemôžem inak, len súhlasiť s jeho názorom.

Aleš Roubíček

Jenže předávat business objektu odkaz na jeho vykreslovací komponentu je pěkné porušení Dependency Inversion Principle.

michal.kocarek

Podle mne článek míchá dohromady několik spolu nesouvisejících věcí. Pokusím se to trochu zasadit do kontextu.

Nevhodné srovnání rand() a strlen().

Autor zcela opomíjí základní rozdíl mezi těmito funkcemi, a to že rand() je nedeterministická funkce, zatímco strlen() obsahuje deterministický, přesně se chovající algoritmus. Logicky, programujeme-li stylem DI, a chceme testovat metodu využívající (aka. závislou) rand(), musíme ji nějakým způsobem mocknout, a proto je vhodné ji extrahovat.
Extrakce je zde z důvodu umožnění testování, což je omluvitelné.

Na druhou stranu hovoří-li autor o extrakci funkce strlen() a její pozdější náhradě za mb_strlen(), hovoříme zde o změně implementace, což nemá s testováním nic společného. Jestliže autor nějakého frameworku nemyslí v UTF-8, ale já ano, je to jiný problém, než potřeba testovat data z funkce, která má nepredikovatelný vástup.

Balení PHP funkcí do wrapperů

Trochu mi to připomíná Wrapper Wrapper… Ale vážně: jak jsem říkal, PHPí funkce mají (by měly mít) předepsané chování, a tím pádem není problém je v kódu využívat. Chci tím říct, že vždy budou mezi programovacím jazykem, jeho funkcemi a zdrojovým kódem existovat závislosti. Argumentace o zabalení DateTime, strlen()… možná… ale kde je ta tenká hranice, která určuje, co je ještě syntaxe jazyka, a co už je „jen“ knihovní funkce, kterou na kterou je potřeba si brát gumové rukavice. Tedy: co třeba isset(), class_exists(), new ReflectionClass()… mohli bychom dojít k tomu, že si zabalíme také volání operátoru +.

…maximálně 30 funkcí…

…tohle mne asi nejvíce vyděsilo. Dobrý programátor v PHP by měl kromě jiného v hlavě nosit mirror manuálu k funkcím pro práci s poli a řetězci – aby dokázal ve vhodné chvíli použit jak strlen() i array_intersec­t_ukey() a spoustu dalších. Ty funkce jsou tady proto, aby zrychlily kód, aby si člověk nemusel psát jejich implementaci znova. A jelikož jsou deterministické (chovají se vždy stejně), opravdu není nutné je balit do wrapperů.

TL;DR

  • chceme-li testovat něco závislé na rand(), musíme to extrahovat, protože funkce se nechová deterministicky
  • extrahovat volání strlen() do wrapperu, abychom to později mohli zaměnit za mb_strlen(), je hloupost. Pokud používám knihovnu, která je na strlen(), a já ji chci mít nad mb_strlen(), toto není řešení.
  • Knihovní PHP funkce jsou zde od toho, aby se používaly. Jsou napsány přímo v C, tudíž jsou rychlé, a jsou přesně popsané, tedy vždy stejně se chovající. Je na inteligenci programátora, aby pochopil, co je potřeba extrahovat, protože jinak to testovat nelze (rand()).
...

ad) array_intersec­t_ukey

jelikož dnes člověk většinu dat tahá z databáze(a např. všechny DB intersect umí) tak je podle mě důležitější umět správně pracovat s DB a vytahovat z ní data než znát komplet knihovnu funkcí PHP

v6ak

Přesnější než „deterministická“ by bylo „referenčně transparentní“. V Haskellu by toto použití rand znamenalo typovou chybu :)

Jinak celkem souhlas.

Jan Machala

Naprosty souhlas s komentarem. rand(), time() a podobne extrahovat, aby se dali mokovat, zbytek knihovnich funkci zpravidla ne, protoze jsou deterministicke.

reddish

Netýká se to úplně článku, ale rád bych třeba věděl, co byste kdo dělal v tomto scénáři:

Mám třídu, která pracuje s bajty a má metodu toSHA1(), která vezme všechny bajty a udělá z nich SHA-1 hash. Proč bych měl této třídě (nebo metodě) předávat objekt, který z bajtů umí hash vytvořit, když jediné, co po té metodě chci je dostat string s hashem těch bytů. Nechci měnit implementaci, nechci to nijak mockovat, chci jen dostat hash. Je pak správné toto:

public function toSHA1(SHA1MessageDigest $digest) {
    return $digest->...
}

a nebo toto

public function toSHA1() {
    $digest = new SHA1MessageDigest();
    return $digest->...
}

? A proč?

disclaimer: i když jsem toho o DI četl spoustu, nijak o tom moc nepřemýšlím, když programuju, takže se stále mám co učit.

David Grudl

Správně jsou obě, druhé přesně odpovídá zadání, první je tzv. over-engineered.

Obdobně se použije `throw new AnyException` bez nutnosti uživatelsky předávat typ výjimky.

reddish

S tou výjimkou jsem to nějak nepobral.

Vojtěch Dobeš

Výjimka je také objekt. A nikdy si ho nepředáváme přes Dependency Injection, abychom ho mohli vyhodit.

v6ak

Jsou chvíle, kdy je legitimní mít možnost předat hashovací funkci. Třeba u přihlašování uživatelů (určité komplikace teď nezmiňuju). Jsou ale chvíle, kdy je to blbost. Třeba tady:

Mám API, které z nějakých dat na serveru má vrátit hash. Ano, čistě teoreticky bychom mohli brát nějaký Hasher, stáhnout data a zahashovat. Jenže v tom případě by tato metoda nebyla potřeba. Obvykle v takovém případě chceme přenést hash mezi klientem a serverem a nepřenášet celá data.

Serveru těžko můžeme poslat implementaci Sha1Hasher nebo Md5Hasher. Máme tu sice serializaci, ale:
* Server může běžet na úplně jiné platformě.
* Obecně to asi nemůžeme napsat, aniž by měl klient nepatřičný přístup k serveru či naopak.

Prakticky tak je v tomto případě asi nejlepší přímo v protokolu definovat množinu (klidně jednoprvkovou) hashovacích funkcí. Samozřejmě naprázdnou a konečnou množinu :) API pak bude umět přesně toto.

ondra.novacisko.cz

Já tedy ve svém programování používám superglobální „services“. To je globální objekt, který registruje různé služby, které jsou definované svým rozhraním. Je to právě výhodné pro věci, které mají být jedenkrát, nebo které fungují jako „default“.

Třídy pak mají dvojí možnost, jak services využít. Buď klasicky, jak je zde nazýváno „Dependency Injection“ očekávají předávání odkazu v konstruktoru, pak services může využít ten, který třídu vytváří… a nebo se ta třída sama zeptá na na aktuální implementaci služby…

Aby byla představa o tom, o co jde. Tak mám tam službu pro posílání RPC dotazů, službu pro zápis do logu, službu pro otevření URL v prohlížeč, službu pro otevírání souborů, službu pro vytvoření HTTP spojení. Samozřejmě, že bych to mohl dělat tak, že bych každému objektu předal seznam objektů zajišťující služby. Nebo mu mohu předat objekt obsahující služby, no nebo mohu zařídit konfigurovatelný globální objekt poskytující služby.

Ne vždy existuje ideální řešení.

K.

Jedna technická: když můj kód závisí na triviálních funkcích, tak je přece nemusím balit do tříd, abych si je mohl předat jako závislosti. Stačilo by místo funkce ‚a‘ předat funkci ‚b‘ a aby odpovídala rozhraní si vypomoct curryingem a částečnou aplikací.

ZiziTheFirst

Nemůžu se ubránit dojmu, že je to překombinované. Možná je to jen nevhodně zvolený příklad. Takto přemýšlející programátor by musel abstrahovat od všech základních konstruktů používaného jazyka (co kdyby foreach začal procházet pole v nedefinovaném pořadí? Nebo if začal jinak vyhodnocovat podmínky?), což by ho při důsledné tohoto principu dovedlo k tomu, že si musí napsat jazyk vlastní.

Co se testovatelnosti a závislostí týče, tak autor problém stejně nevyřešil, jenom odsunul jinam, a to do třídy RandomNumberGe­nerator, která se tím namísto třídy RandomPasswor­dGenerator stává netestovatelnou a nemající dostatečně popsané závislosti, protože někde uvnitř ní je zřejmě stejně funkce rand použitá.

Souhlasím s Davidem Grudlem v tom, že použití deterministicky fungujících funkcí jazyka není zatahováním závislostí. Bral bych je za něco jako axiomy. A mít možnost měnit jejich funkci je sice myšlenka lákavá, ale podle mého si autor říká o více problémů než přínosů. Jazyky, které umožňují (i za běhu) měnit fungování svých funkcí, např. Ruby, bývají právě kvůli takto nedeterministickému chování kritizovány. Funkcím, které jsou ze své podstaty nedeterministické (jako v tomto případě – zřejmě nevhodně – zvolený rand) přibývá argument týkající se testování, ale jinak je to podobný případ.

Vena

Podle me je nejlepsim pravidlem vzdy naslouchat testum. Pokud potrebuju zavislost injektovat, abych mohl lepe nejakou komponentu otestovat, tak pouziju DI.
V ostatnich pripadech bych DI spise nepouzival, neni treba.

Vena

Tim nechci rict, ze DI je vhodne jenom, kdyz testuju. Tim chci rict, ze testovani mi pomaha identifikovat zavislosti, ktere bych mohl chtit v budoucnosti nahradit za jinou implementaci.
To jen aby bylo jasno.

Nocturnius

ale obecně u článků tohoto typu vždycky vnitřně pěním. Kolikrát zjišťuju, že něco co dělám zcela běžně má nějaký honosný název. To je ovšem vedlejší, hlavně mi vadí, že pak někteří berou uvedené jako jedinou správnou možnou cestu a pak je ti druzí, kteří jsou zase úplně proti. Programování obecně je hledání adekvátních řešení pro danou aplikaci, nikoliv vytváření ultimátních pravd. Ono přepspřílišné zapouzdření může mít dokonce kumulativní efekt – kopete všechny v týmu aby používali zapouzdřenou variantu Strings::instance()->nasStrLen($strin­g), která momentálně obsahuje jen return strlen($value) a po dvou letech se příjde na to, že by ji bylo třeba nahradit něčím jiným, ano sice pokud je pěkně pouzdřeno náhrada potrvá pár vteřin, na druhou stranu kolik úsilí bylo věnováno tomu že není použit standardní strlen? Což by zase nemuselo platit o md5, která bude dozajista saltována. A už se určitě objevil někdo kdo půjde do opozice…

Jakub Vrána

Řešit chybu knihovny tím, že jí místo špatné funkce podstrčíme tu správnou, je absurdní. Proč knihovna rovnou nepoužívá tu správnou? To už rozebíral David Grudl.

Příklad s rand() dává smysl trochu lepší. Už jen proto, že máme dva různé problémy: generovat náhodná čísla, což lze dělat mnoha různými způsoby ( rand(), mt_rand(), /dev/random, …), a využívat tato náhodná čísla pro generování náhodného hesla. Takže oddělit tyto dva problémy do dvou různých tříd dává smysl.

Co mě vyděsilo, je navržený způsob testování. Opravdu chcete vytvořená náhodná hesla kontrolovat tak, že z nich prvek náhody úplně odeberete? Takže když někdo změní implementaci tak, že náhodu používat úplně přestane a místo toho bude vracet konstantní řetězce, tak nás na to test nijak neupozorní. To mi trochu připomnělo problém opravený v PHP 5.3.8.

Myslím, že lepší testování by prvek náhody ponechalo a raději testovalo vlastnosti, které od hesla požadujeme (počet písmen a jiných znaků, entropii a tak dále).

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.