Málokterý vývojář může s čistým svědomím tvrdit, že má 100 % kódu pokryto testy. Ale to nevadí. Ve většině reálných aplikací je nereálné a neekonomické toho dosáhnout. Ukážu, jak psát kód tak, aby vám na něj testy ani nechyběly, a přesto se na něj mohli spolehnout díky statické analýze. Představím nástroj PHPStan, který chyby v aplikaci hledá za vás. Po mé přednášce testy psát nepřestanete, ale zaměříte se s nimi na místa, kde se vyplatí.
3. Business logika
Wiring
* nezměřeno vědeckou metodou
Jak z toho ven? Ne všechny zdrojáky jsou si rovny a něco se vyplatí testovat víc a něco míň.
Já kód rozděluji na business logiku, kterou je důležité testovat, a wiring (kterého je v aplikaci
nadpoloviční většina*), kde testy nepotřebujeme a spolehlivost zajistíme jinak.
Wiring = boilerplate.
Controllery, fasády,
vytahování výsledků a
přeposílání jinam - do
modelu, do šablony.
Gettery, settery,
přiřazování do properties.
Čím složitější architektura,
tím více wiringu.
Business logika = to
důležité, co aplikace
dělá. Omezení hodnot,
validace, filtrování a
řazení, parsování,
počítání,
zaokrouhlování. Mělo
by na ni jít psát rychlé
unit testy.
5. Stringly–typed code
function foo(string $id, string $name,
string $email, string $directory,
string $filename, string $address,
string $price, string $date
) { }
Pokud někdo nepíše silně typovaný kód, tak to vypadá nějak takhle. Všechno je string,
případně jiné skalární typy. Pokud si odmyslíme typehinty úplně, může nám všude dorazit
null, což znamená další kontroly a možné chyby navíc.
6. Strongly–typed code
function foo(
Email $email,
SplFileInfo $directory,
Address $address,
Money $price,
DateTimeImmutable $date
): FooBar { }
Co znamená používat silné typy? Využívat na všechno co nejpřesnější typ, objekt. Co tím
získám? Správný objekt může vzniknout jedině konzistentní a zvalidovaný. Typehintem říkám,
co jedině lze do metody předat. A v neposlední řadě, vím, co daný objekt dokáže, pohledem
na jeho metody. A nezapomeňme na co nejpřesnější return typehinty.
8. Strongly–typed code
/** @var FooService */
private $fooService;
…
$this->fooService->doFoo();
Mnohem lepší je vyžádat si servisu přes konstruktor s typehintem, čímž zajistím a informuji
vývojáře/IDE/statický analyzátor o tom, co v daném parametru a property je. Proto rád
používám DI i v controllerech, protože se při kompilaci kontejneru dozvím, jestli je vše OK.
9. Nadužívání polí
function foo(array $values) {
…
$values['name']
…
}
S pořádnými typehinty nelze metodu zavolat špatně, ale v poli vždy může chybět
důležitý klíč. A taky nevíme typy hodnot jednotlivých klíčů (pokud se mezi sebou liší).
10. Pole jako seznam hodnot ✅
/**
* @param User[] $users
*/
function foo(array $users)
{
foreach ($users as $user) { }
}
Použít pole jako kolekci hodnot stejného typu, nad kterou iteruji, je v pořádku. V této ukázce
si mohu být jistý tím, co bude v $user.
11. "Umím zpracovat cokoli"
/**
* @param array|string $values
*/
function foo($values) {
if (is_array($values)) {
…
} else {
…
}
}
Metoda, co přijímá více různých typů, znamená, že musíme napsat if - složitější kód, více
větví k testování.
12. function foo(array $values) {
…
}
function bar(string $value) {
…
}
"Umím zpracovat jeden typ"
Přitom můžeme napsat dvě funkce, které přijímají konkrétní typ a z každého místa zavolat tu
správnou. To samé platí pro návratové typy - vždy vracet jeden konkrétní typ.
13. Boolean parametery
function foo($values, bool $force) {
if ($force) {
…
} else {
…
}
}
Boolean parametr většinou znamená, že metoda dělá trochu něco jiného. Opět znamená
napsat if a více větví k testování.
15. foo([1, 2, 3]);
forceFoo([1, 2, 3]);
Rozdělení na více metod
Správné řešení namísto boolean parametru je opět rozdělit na více metod a z různých míst
zavolat tu správnou, co potřebuji.
16. Nullable parametry
function foo($a = null, $b = null,
$c = null, $d = null, $e = null,
$f = null, $g = null
) { }
U metod s větším množstvím nullable či nepovinných argumentů většinou platí jen některé
kombinace, např. první tři parametry nenulové spolu apod. To není nijak zanesené do
typového systému a navíc tyto kombinace obvykle vyjadřují různé use casy. Řešení -
rozdělení na více metod bez nullable parametrů. Výsledek - mnohem robustnější kód.
17. function foo(
string $country,
string $currency,
string $errorCode
)
Výčty – enumy
Pokud nějaký parametr přijímá omezený obor hodnot, který je pevně daný v kódu aplikace,
použití skalárních typů není vhodné, protože hodnotu nijak neomezují.
18. Výčty – enumy
github.com/consistence/consistence
function foo(
Country $country,
Currency $currency,
ErrorCode $errorCode
)
Reprezentujte výčty pomocí objektů, např. skrze knihovnu Consistence. Vyjádříte tím, co
volající musí do metody předat, a zajistíte validitu předané hodnoty.
20. Zpětná vazba od typů
Nevhodné interfaces
Naimplementovat interface a u půlky metod vyhazovat výjimku - porušení Barbary Liskov,
nevhodný interface. Musíme naslouchat, jaký nám kód dává feedback o našich typech.
Pořádný typový systém je nemilosrdnější než testy, hodnotí, co vše lze někam předat, nejen
to, jaké vstupy jsme otestovali.
21. Zpětná vazba od typů
Využívání informace,
která není v typech
Pokud něco není zanesené do typů a jen díky předchozí kontrole či proprietárním znalostem
vím, že mi z volané metody přijde nějaká konkrétní třída, která není v typehintu.
24. První spuštění
• Parse errory
• Třídy, které nejdou vůbec načíst
• Špatně nastavený autoloading
Projekt, který je již nějakou dobu vyvíjen bez statické analýzy, naakumuluje soubory, které
třeba ani nejdou načíst, takže se nedivte, když takové PHPStan u vás najde.
26. if ($foo instanceof Foo)
catch (FooException $e)
Foo::class
function foo(Bar $bar)
PHP samotnému nevadí, pokud se v těchto případech odkazujete na neexistující třídy.
PHPStan takové případy najde a donutí vás je opravit.
28. Na vyšších levelech
• Nejen na $this
• Předávané typy, nejen počty
• Návratové hodnoty
• Nedefinované proměnné
• …a řada dalších kontrol
PHPStan na levelu 0 hledá chyby na statických voláních a na $this. Tam si může být jistý
typem. Na vyšších levelech hledá chyby na všem, ale už při tom musí číst a spoléhat se
na phpDocy, ve kterých může být také chyba, kterou vám ochotně nahlásí.