Slide 1

Slide 1 text

Usnadněte si práci
 silně typovaným kódem 
 a statickou analýzou! Ondřej Mirtes

Slide 2

Slide 2 text

100 % Code coverage Jediný dnes uznávaný způsob, jak mít spolehlivou aplikaci bez chyb, je 100 % pokrytí testy. Žádná reálná aplikace to ovšem nesplňuje a snažit se o 100 % ani nemá ekonomický smysl.

Slide 3

Slide 3 text

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.

Slide 4

Slide 4 text

Wiring se dá totiž zažitými postupy testovat pouze integračně. Integrační testy jsou pomalé, křehké a nerad je píšu. Pokud bychom na wiring psali unit testy, tak bychom museli psát spoustu mocků a to taky není ideální. Testy se ale u wiringu dají nahradit pořádnými typy.

Slide 5

Slide 5 text

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.

Slide 6

Slide 6 text

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.

Slide 7

Slide 7 text

Stringly–typed code $container->get('fooService')->doFoo(); Za stringy-typed kód nepovažuji pouze reprezentaci všeho jako stringů, ale jakékoli odkazování na základě magických stringů. Až při běhu programu můžu zjistit, že požadovaná servisa neexistuje.

Slide 8

Slide 8 text

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.

Slide 9

Slide 9 text

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ší).

Slide 10

Slide 10 text

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.

Slide 11

Slide 11 text

"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í.

Slide 12

Slide 12 text

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.

Slide 13

Slide 13 text

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í.

Slide 14

Slide 14 text

Boolean parametery foo([1, 2, 3], false); foo([1, 2, 3], true);

Slide 15

Slide 15 text

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.

Slide 16

Slide 16 text

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.

Slide 17

Slide 17 text

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í.

Slide 18

Slide 18 text

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.

Slide 19

Slide 19 text

Controller Weak HTTP request Strong Model Controller HTTP response Pokud necháte typy prorůst vaší aplikací, bude celá daleko spolehlivější, protože budete co nejmíň věcí mít "mixed". Typy jsou i důvod, proč rád používám ORM.

Slide 20

Slide 20 text

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.

Slide 21

Slide 21 text

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.

Slide 22

Slide 22 text

Statická analýza Statický analýza hledá chyby v kódu, aniž by ho spouštěla, pouze zkoumáním zdrojáků. Zkontroluje, že aplikace nespadne na něčem nedefinovaném, že všude předávám správné typy, že vracím, co jsem slíbil atd.

Slide 23

Slide 23 text

composer require --dev phpstan/phpstan github.com/phpstan/phpstan

Slide 24

Slide 24 text

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.

Slide 25

Slide 25 text

Levely v PHPStanu $%&'() Abych omezil množství chyb, které PHPStan po nainstalování začne hlásit, zavedl jsem umělé levely. Začněte ho používat na levelu 0 a opravte těch pár chyb, abyste měli zelený build. A až budete mít příště čas, navyšte level a opravte další chyby.

Slide 26

Slide 26 text

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.

Slide 27

Slide 27 text

Foo::NONEXISTENT_CONSTANT Foo::$nonexistentProperty $this->nonexistentProperty $this->nonexistentMethod() nonexistentFunction()

Slide 28

Slide 28 text

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í.

Slide 29

Slide 29 text

vs. PHPStan? Proč používat PHPStan, když mám PhpStorm? PHPStan byste měli integrovat a pouštět v rámci CI buildu (Travis, TeamCity, Jenkins, GitLab CI…). Ne všichni vývojáři používají PhpStorm a mají ho stejně nakonfigurovaný, a nevšimnou si každé chyby, co IDE podtrhne.

Slide 30

Slide 30 text

youtu.be/h5jC7l0-jRI Pro více informací o PHPStanu, co všechno umí a jaké možnosti konfigurace a rozšíření skýtá, mrkněte na záznam z Poslední soboty.

Slide 31

Slide 31 text

phpstan/phpstan @OndrejMirtes ondrej.mirtes.cz