Don't be S.T.U.P.I.D.

Пособие по спортивной ходьбе по граблям

Don't be S.T.U.P.I.D.

Пособие по спортивной ходьбе по граблям

Александр Паньшин

Любите ли вы грабли так, как люблю их я?

В мире разработки ПО существует много клевых аббревиатур вроде SOLID, KISS, DRY и т.д.

Все они описывают как надо. Мы пойдем другим путем!

STUPID — грабли на любой вкус!

Singleton



class DB
{
    private static $instance = null;
    public static function getInstance() {
        self::$instance = self::$instance ?: new self();

        return self::$instance;
    }
    final private function __construct() {

    }
    final private function __clone() {

    }
}

Все так делали:)

Типичный пример работы с БД из учебников по программированию для начинающих.

Что не так?

Все же прекрасно: вы можете откуда угодно получить доступ к БД через DB::getInstance(). А заодно вы точно уверены, что всегда активно только одно соединение с БД.

One ring to rule them all…

А что делать, если вам понадобится хранить что-то в другой БД? Это не такая надуманная задача, как может показаться:)

DB::getSecondInstance()

Классно я придумал, да?

На самом деле, надо разсинглтонить DB.

Ну а остальные синглтоны?

Ну хоть кто-нибудь же...

Честно говоря, я не могу придумать ситуацию, когда нам надо быть уверенным, что что-то должно существовать в единственном числе.

Есть идеи?

Static

Вторая проблема

Вторая причина, почему DB::getInstance() — это плохо, состоит в том, доступ к БД может утечь куда угодно.

Утечка состояния

Утечка состояния — скрытное использование БД, сессий, файловой системы и других ресурсов в коде.

Пример утечки

/** @return Something */
public function doSomething() {
    $data = Database::getInstance()->getSomeData();
    // Do whatever
}

Явная зависимость от состояния

/** @return Something */
public function doSomething(SomethingRepository $repository) {
    $data = $repository->getSomeData();
}

Утечка похитрее

class SomethingService {
    public function __construct(SomethingRepository $repository) {
        $this->repository = $repository;
    }
    public function doSomething() {
        $this->repository->getConnection()->doWhatever();
    }
}

Чистые функции

Функция, являющаяся чистой с точки зрения ФП, вполне может быть объявлена статической.

public static function square($number): float {
    return $number*$number;
}

Третья проблема

Вы не можете унаследовать DB!

Но я не могу расширить класс потому, что мы обращаемся к нему по имени везде!

Ужасающее решение

class _DB { /* Your old DB class */ }
class DB extends _DB { /* Your extended class */ }

Да, у меня тоже глаз дергается.

Пользуясь случаем, передаю привет разработчикам Kohana.

Вариант получше

class DB {
    public static function setInstance(DB $db);
}
class MyDB extends DB {}

Привет, Laravel! Этот вариант, в принципе норм.

Если только вам не понадобится расширить интерфейс класса.

Принцип №1: Побольше статики

Использование синглтонов и статических методов.

Tightly Coupling

Отношения между классами

Статический метод (в том числе обращение к синглтону) — это очень интимные отношения между классами.

DB::getInstance();

Отношения между классами

Инстанцирование другого класса в коде — это тоже слишком близко.

class User {
    public function __construct() {
        $this->session = new SessionStorage();
    }
}

Отношения между классами

Внедрение реализации как зависимости — неплохо в большинстве случаев.

class User {
    public function __construct(SessionStorage $session) {
        $this->session = $session;
    }
}

Отношения между классами

Передача интерфейса как зависимости — идеальный уровень отношений:)

class User {
    public function __construct(SessionStorageInterface $session) {
        $this->session = $session;
    }
}

Тень Dependency Injection Container

Да-да, именно в этом состоит цель существования DIC: минимизировать отношения между вашими классам.

Зачем это нужно?

Отношения между классами

Самый смак: циклические зависимости.

class B {
    public function setA(A $a) {
        $this->a = $a;
    }

    public function doSomethingElse() {
        $this->a->someStuff();
    }
}

class A {
    public function __construct(B $b) {
        $this->b = $b;
        $b->setA($this);
    }
    public function doSomething() {
        $b->doSomethingElse();
    }
}
    

Принцип №2: Нам нечего скрывать друг от друга

Чрезмерно близкие отношения между классами.

Untestability

Все знают, что тесты — это хорошо

Но никто их не пишет.

Почему никто не пишет тесты?

Потому что код сложно тестировать.

Причина сложности тестирования — чрезмерно интимные отношения. Чем интимнее отношения между вашими классам, тем сложнее их тестировать.

Нет времени писать тесты?

Если вам приходится тратить много времени на тесты — значит, с вашим кодом что-то не так.

Хороший код тестируется быстро.

Принцип №3: Тесты? Не, не слышал

Нетестируемый код.

Premature Optimization

Вы любите писать странные вещи?

if (isset($frm['title_german'][strcspn($frm['title_german'], '<>')])) {
    // ...
}

Попробуйте понять, что здесь происходит:)

Вы тоже не смогли, да?

На самом деле, этот код проверяет, содержит ли $frm['title_german'] символы < и >.

Немного объяснений

Немного объяснений

isset($str[strlen($str)])

Максимальный индекс в строке — длина строки минус 1, поэтому это всегда вернет false.

Если в строке содержится хотя бы один символ, функция вернет число, меньшее, чем длина строки и все выражение будет равно true.

Тоже самое, чуть проще

if (strlen($frm['title_german']) == strcspn($frm['title_german'], '<>'))) {
    // ...
}

Потому что автор этих строк прочитал, что isset() гораздо быстрее, чем strlen().

А вы знаете, что делает функция strcspn()?

Я пишу на PHP уже скоро 10 лет. Я не знаю. Нет, мне не стыдно.

Почему не так?

if (preg_match('(<|>)', $frm['title_german'])) {
    // ...
}

Потому что регулярные выражения — это медленно!

Что на самом деле, уже давно ложь. Регулярные выражения быстрее и мощнее, чем про них принято думать.

Результаты «оптимизации»

Стал ли код работать быстрее в результате такой оптимизации?

Нет. Просто потому, что не это — бутылочное горлышко.

Бутылочным горлышком был запрос с тремя джойнами в контроллере.

Принцип №4: Преждевременная оптимизация

Оптимизация кода до профилирования.

Все, чего вы добьетесь — сделаете свой код нечитаемым.

Indescriptive Naming

А вы знаете, что делает функция strpbrk() в PHP?

PHP вообще неисчерпаемый источник странных названий, да:)

Не удивительно

Я не верю, что в мире есть человек, который наберет в гугле это, когда ему надо будет найти функцию, которая будет искать подстроку по набору символов.

Откуда ноги растут?

Название функции унаследовано из стандартной библиотеки C и расшифровывается как string pointer break

Что особенно доставляет, ведь в PHP нет указателей.

Принцип №5: Хитрые названия

Я бы добавил к названиями хитрый код.

Я не против $i, я против strprbk() и $yystk.

Duplication

Откуда берется дублирование кода?

Ну правда, программисты по своей природе зверски ленивые существа, и два раза писать одно и то же?

И все-таки копипаста живет!

Настоящая причина

На самом деле, основная причина копипасты — следование второму принципу STUPID: Tightly Coupling.

Если ваш код слишом много знает, его просто невозможно переиспользовать.

Принцип №6: Дублируйте код

Дублирование кода — один из самых надежный путей в ад.

Шутки в сторону!

Don't be STUPID!

Think before you code!