Upgrade to Pro — share decks privately, control downloads, hide ads and more …

«Анализируем код с PHPStan» — Никита Сапогов, «...

Badoo Tech
April 09, 2018
8.6k

«Анализируем код с PHPStan» — Никита Сапогов, «Ситилинк»

Выступление на Badoo PHP Meetup 7.04.2018.

Никита поделится опытом применения статического анализатора PHPStan в большом проекте. Посмотрим, как PHPStan находит проблемные места в коде и неочевидные ошибки, упрощая жизнь команде. Рассмотрим ошибки, которые поймал PHPStan, на примере сервиса Badoo, а также устроим проверку на прочность: вместе покопаемся в коде и найдем ошибку, которую PHPStan пропустил. Доклад будет полезен командам и тимлидам, которые хотят улучшить качество кода и вместе с тем сократить время на ревью.

Badoo Tech

April 09, 2018
Tweet

More Decks by Badoo Tech

Transcript

  1. Обо мне • Программирую 12 лет • Работаю в компании

    «Ситилинк» с 2014 года • Мастер need-work’ов !2
  2. PHPStan - PHP Static Analyzer Tool • Автор - Ondřej

    Mirtes • Написан на PHP7 • Анализирует код PHP5/7 • На основе nikic/php-parser !5
  3. С чем мы работаем • ~1 500 классов • ~180

    000 строк кода • ~12 программистов PHP • PSR !6
  4. Как мы следим за качеством • Жесткий код-стайл • Ревью

    кода • Придирчивый досмотр решения • Приёмочные тесты • Unit тесты
  5. class Product {} class ProductRepository { /** @return Product */

    function find($productId) { return $productId === 0 ? null : new Product(); } } Method ProductRepository::find() should return Product but returns Product|null. !10
  6. class Product {} class ProductRepository { /** @return Product|null */

    function find($productId) { return $productId === 0 ? null : new Product(); } } !11 [OK] No errors
  7. class Product { private $name; public function getName() { return

    $this->name; } } class ProductRepository { /** @return Product|null */ public function find($productId) { return $productId === 0 ? null : new Product(); } } $productRepository = new ProductRepository(); $product = $productRepository->find(0); echo $product->getName(); Calling method getName() on possibly null value of type Product|null. !12
  8. class Product { private $name; public function getName() { return

    $this->name; } } class ProductRepository { /** @return Product|null */ public function find($productId) { return $productId === 0 ? null : new Product(); } } $productRepository = new ProductRepository(); $product = $productRepository->find(0); if ($product !== null) { echo $product->getName(); } !13 [OK] No errors
  9. class Product { private $name; public function getName() { return

    $this->name; } } class ProductRepository { /** @return Product|null */ public function find($productId) { return $productId === 0 ? null : new Product(); } } $productRepository = new ProductRepository(); $product = $productRepository->find(0); if ($product != null) { echo $product->getName(); } !14 [OK] No errors
  10. class Product { private $name; public function getName() { return

    $this->name; } } class ProductRepository { /** @return Product|null */ public function find($productId) { return $productId === 0 ? null : new Product(); } } $productRepository = new ProductRepository(); $product = $productRepository->find(0); if (!empty($product)) { echo $product->getName(); } !15 [OK] No errors
  11. function method(array $properties = []) { if (isset($properties['s:referer'])) { $properties['s:referer']

    = 23; } if (isset($data['s:uri'])) { $properties['s:uri'] = 43; } // .. } Variable $data in isset() is never defined. !16
  12. function method(array $properties = []) { if (isset($properties['s:referer'])) { $properties['s:referer']

    = 23; } if (isset($properties[’s:uri'])) { $properties['s:uri'] = 43; } // .. } !17 [OK] No errors
  13. class A { private $field; private $values; private $any_not_null; function

    __construct($field, $values, $any_not_null = false) { $this->field = $field; $this->values = $values; $this->any_not_null = false; } } Constructor of class A has an unused parameter $any_not_null. !18
  14. class A { private $field; private $values; private $any_not_null; function

    __construct($field, $values) { $this->field = $field; $this->values = $values; $this->any_not_null = false; } } !19 [OK] No errors
  15. class A { private $field; private $values; private $any_not_null; function

    __construct($field, $values, $any_not_null = false) { $this->field = $field; $this->values = $values; $this->any_not_null = $any_not_null; } } !20 [OK] No errors
  16. $array = [1,2,3,4,5,6]; $sortedArray = \sort($array); foreach ($sortedArray as $item)

    { echo $item; } Argument of an invalid type bool supplied for foreach, only iterables are supported. !21
  17. /** * @param string $filename * @return string */ function

    read($filename) { return file_get_contents($filename); } echo read('filename'); Function read() should return string but returns false| string !25
  18. /** * @param string $filename * @return string */ function

    read($filename) { return (string)file_get_contents($filename); } echo read('filename'); [OK] No errors !26
  19. /** * @param string $filename * @return string */ function

    read($filename) { $content = file_get_contents($filename); if ($content === false) { return ''; } return $content; } echo read('filename'); [OK] No errors !27
  20. /** * @param string $filename * @return string */ function

    read($filename) { return file_get_contents($filename) ?: ''; } echo read('filename'); [OK] No errors !28
  21. /** * @param string $filename * @return string */ function

    read($filename) { return (string)file_get_contents($filename); } /** @var string $filename */ $filename = new \stdClass(); read($filename); [OK] No errors !30
  22. class A { public function calc() { return 1; }

    } class AFactory { /** @return object */ public function createA() { return new A(); } } $factory = new AFactory(); $a = $factory->createA(); /** @var A $a */ $a->calc(); Call to an undefined method object::calc(). !31
  23. class A { public function calc() { return 1; }

    } class AFactory { /** @return object */ public function createA() { return new A(); } } $factory = new AFactory(); /** @var A $a */ $a = $factory->createA(); $a->calc(); [OK] No errors !32
  24. /** * @param int $a * @param int $b *

    @return int */ function calc($a, $b) { return (int)$a + (int)$b; } $r = calc(1,2); •Casting to int something that's already int. •Casting to int something that's already int. !33
  25. /** * @param int|float|string|bool $a * @param int|float|string|bool $b *

    @return int */ function calc($a, $b) { return (int)$a + (int)$b; } $r = calc(1,2); [OK] No errors !34
  26. declare(strict_types=1); /** * @param int $a * @param int $b

    * @return int */ function calc(int $a, int $b): int { return $a + $b; } $r = calc(1,2); [OK] No errors !35
  27. /** * @param int $a * @param int $b *

    @return int */ function calc($a, $b) { return (int)$a + (int)$b; } $r = calc(1,2); [OK] No errors !36 parameters: ignoreErrors: - '#Casting to #'
  28. !39 class A { /** @var string */ private $a;

    /** @var int */ private $b; /** * @param int $a * @param int $b */ public function __construct($a, $b) { $this->a = $a; $this->b = (int)$b; } /** @param int $x */ public function setX($x) { $this->x = $x ?: CONSTANT_VALUE; } /** @param int $b */ public function setB($b) { $this->b = $b; } /** @return int */ public function getC() { return $this->b === 2 ? null : $this->b; } } $a = new A(2, 15); $a->setX('23'); $a->setB($a ?: 2); $c = $a->getC(); Пример с одной ошибкой из каждого уровня Он ужасен!
  29. PHPStan Phan Workflow ДА ДА Need PHP Extension НЕТ ДА

    Can suppress specific statement НЕТ ДА Can use in IDE НЕТ ДА (с демоном) Multiple Cores НЕТ* ДА* Plugins ДА ДА Own Rule Set ДА ДА Memory Usage 256MB* 2GB* Time 13 секунд* 31 секунду*
  30. Итого • Экономим время на ревью • Качество проекта -

    выше • Гибкость и расширяемость • Скалярные типы + strict_types = easy • Для средних и больших проектов