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

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

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

95a298d2bffa1b46fd023a286a8a7e86?s=128

Ondřej Mirtes

April 01, 2017
Tweet

Transcript

  1. Usnadněte si práci
 silně typovaným kódem 
 a statickou analýzou!

    Ondřej Mirtes
  2. 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.
  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.
  4. 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.
  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.
  7. 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.
  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í.
  14. Boolean parametery foo([1, 2, 3], false); foo([1, 2, 3], true);

  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.
  19. 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.
  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.
  22. 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.
  23. composer require --dev phpstan/phpstan github.com/phpstan/phpstan

  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.
  25. 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.
  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.
  27. Foo::NONEXISTENT_CONSTANT Foo::$nonexistentProperty $this->nonexistentProperty $this->nonexistentMethod() nonexistentFunction()

  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í.
  29. 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.
  30. 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.
  31. phpstan/phpstan @OndrejMirtes ondrej.mirtes.cz