Пособие по спортивной ходьбе по граблям
В мире разработки ПО существует много клевых аббревиатур вроде SOLID, KISS, DRY и т.д.
Все они описывают как надо. Мы пойдем другим путем!
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()
. А заодно вы точно уверены, что всегда активно только одно соединение с БД.
А что делать, если вам понадобится хранить что-то в другой БД? Это не такая надуманная задача, как может показаться:)
Классно я придумал, да?
На самом деле, надо разсинглтонить DB.
Request
. Я слышал, в этом мире бывают подзапросы. А еще, вы начнете использовать сторонний
веб-сервис.
Logger
. А давайте будем писать воооон те события в другое место?Честно говоря, я не могу придумать ситуацию, когда нам надо быть уверенным, что что-то должно существовать в единственном числе.
Есть идеи?
Вторая причина, почему 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! Этот вариант, в принципе норм.
Если только вам не понадобится расширить интерфейс класса.
Использование синглтонов и статических методов.
Статический метод (в том числе обращение к синглтону) — это очень интимные отношения между классами.
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;
}
}
Да-да, именно в этом состоит цель существования 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();
}
}
Чрезмерно близкие отношения между классами.
Но никто их не пишет.
Потому что код сложно тестировать.
Причина сложности тестирования — чрезмерно интимные отношения. Чем интимнее отношения между вашими классам, тем сложнее их тестировать.
Если вам приходится тратить много времени на тесты — значит, с вашим кодом что-то не так.
Хороший код тестируется быстро.
Нетестируемый код.
if (isset($frm['title_german'][strcspn($frm['title_german'], '<>')])) {
// ...
}
Попробуйте понять, что здесь происходит:)
На самом деле, этот код проверяет, содержит ли $frm['title_german']
символы < и >.
Если < и > не содержатся в строке, strcspn() вернет длину строки. В этом случае код можно свести к:
isset($str[strlen($str)])
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'])) {
// ...
}
Потому что регулярные выражения — это медленно!
Что на самом деле, уже давно ложь. Регулярные выражения быстрее и мощнее, чем про них принято думать.
Стал ли код работать быстрее в результате такой оптимизации?
Нет. Просто потому, что не это — бутылочное горлышко.
Бутылочным горлышком был запрос с тремя джойнами в контроллере.
Оптимизация кода до профилирования.
Все, чего вы добьетесь — сделаете свой код нечитаемым.
PHP вообще неисчерпаемый источник странных названий, да:)
Я не верю, что в мире есть человек, который наберет в гугле это, когда ему надо будет найти функцию, которая будет искать подстроку по набору символов.
Название функции унаследовано из стандартной библиотеки C и расшифровывается как string pointer break
Что особенно доставляет, ведь в PHP нет указателей.
Я бы добавил к названиями хитрый код.
Я не против $i, я против strprbk() и $yystk.
Ну правда, программисты по своей природе зверски ленивые существа, и два раза писать одно и то же?
На самом деле, основная причина копипасты — следование второму принципу STUPID: Tightly Coupling.
Если ваш код слишом много знает, его просто невозможно переиспользовать.
Дублирование кода — один из самых надежный путей в ад.