$30 off During Our Annual Pro Sale. View Details »

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

Badoo Tech
April 09, 2018
8.4k

«Анализируем код с 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. Анализируем код
    с PHPStan
    Никита Сапогов

    «Ситилинк», teamlead

    View Slide

  2. Обо мне
    • Программирую 12 лет

    • Работаю в компании «Ситилинк» с 2014 года

    • Мастер need-work’ов
    !2

    View Slide

  3. Виды анализаторов
    • Статический

    • Динамический

    View Slide

  4. Статические
    анализаторы
    • Представители:

    • PHPCS

    • PHP-CS-Fixer

    • PHPMD

    • Inspections в PHPStorm

    View Slide

  5. PHPStan - PHP Static
    Analyzer Tool
    • Автор - Ondřej Mirtes

    • Написан на PHP7

    • Анализирует код PHP5/7

    • На основе nikic/php-parser
    !5

    View Slide

  6. С чем мы работаем
    • ~1 500 классов

    • ~180 000 строк кода

    • ~12 программистов PHP

    • PSR
    !6

    View Slide

  7. Как мы следим за
    качеством
    • Жесткий код-стайл

    • Ревью кода

    • Придирчивый досмотр решения

    • Приёмочные тесты

    • Unit тесты

    View Slide

  8. 882 207
    360
    Потенциальные ошибки
    Ложные срабатывания
    Проблема с типами
    1449 замечания

    View Slide

  9. Поиск слабых мест
    Попробуем вместе с PHPStan поискать
    потенциальные ошибки и неточности в коде

    View Slide

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

    View Slide

  11. class Product {}
    class ProductRepository {
    /** @return Product|null */
    function find($productId) {
    return $productId === 0 ? null : new Product();
    }
    }
    !11
    [OK] No errors

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  21. $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

    View Slide

  22. $array = [1,2,3,4,5,6];
    \sort($array);
    foreach ($array as $item) {
    echo $item;
    }
    [OK] No errors
    !22

    View Slide

  23. $a = new \stdoutClass();
    Instantiated class stdoutClass not found.
    !23

    View Slide

  24. $a = new \stdClass();
    [OK] No errors
    !24

    View Slide

  25. /**
    * @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

    View Slide

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

    View Slide

  27. /**
    * @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

    View Slide

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

    View Slide

  29. Особенности
    Как работает PHPStan при разных
    обстоятельствах

    View Slide

  30. /**
    * @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

    View Slide

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

    View Slide

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

    View Slide

  33. /**
    * @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

    View Slide

  34. /**
    * @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

    View Slide

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

    View Slide

  36. /**
    * @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 #'

    View Slide

  37. Постепенное
    внедрение

    View Slide

  38. View Slide

  39. !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();
    Пример с одной ошибкой из
    каждого уровня
    Он ужасен!

    View Slide

  40. • 0 уровень

    • 1-2 уровни

    • 3-7 уровни

    View Slide

  41. Сравнение
    С другими продуктами

    View Slide

  42. С чем будем сравнивать
    • PHAN

    • PHP Inspections (PHPStorm Plugin)
    !42

    View Slide

  43. 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 секунду*

    View Slide

  44. Кто нашёл больше?

    View Slide

  45. Итого
    • Экономим время на ревью

    • Качество проекта - выше

    • Гибкость и расширяемость

    • Скалярные типы + strict_types = easy

    • Для средних и больших проектов

    View Slide

  46. Спасибо!
    !46
    Никита Сапогов

    Email: amstaffi[email protected]

    GitHub: https://github.com/AmsTaFFix
    PHPStan: https://github.com/phpstan/phpstan

    View Slide