12 posts tagged

программирование

 8   2016   web   мысли   программирование

Почему я не люблю Битрикс

Почему-то существует огромный пласт клиентов, максимально лояльных к Битриксу. Настолько, что они готовы переплатить только, что бы их сайт был на нем сделан. Так получилось, что я ни разу не писал сайты на Битриксе с 0, но мне часто приходилось их поддерживать и развивать.
Не знаю, дело в моем везении или в чем-то другом, но каждый раз в таком проекте код максимально плохой. Почему-то, всегда логика запихана в представление и наоборот. Так написано в официальный гайдах Битрикса?

А вот сегодня, например, была банальная задача — подправить верстку. Вроде, ничего экстраординарного. Но где находится эта верстка в проекте найти не удавалось никак. В итоге путь получился примерно такой:

/bitrix/templates/template.main/components/ma/catalog/template1/ma/catalog.element/.default/

И в этой папке лежал и HTML и CSS и, даже, картинки. «Джентельменский набор» для отображения одного из блоков на странице. Все остальная верстка лежала вообще не там. Но самая жесть, присмотритесь — скрытая диррректория. По умолчанию в Linux, если название начинается с точки, то фаил или папке не отображаются. И, разумеется, не синхронизируются с IDE.

Что заставило разработчиков сделать именно так? Не надо так.

 5 comments    8   2016   web   мысли   программирование

Абстрактная фабрика и примеси в PHP (Часть 2)

Первая часть

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

<?php

abstract class Infantry {
    //общие для всей пехоты методы
    abstract public function Attack();
    abstract public function Defend();
}
abstract class Archer {
    abstract public function Shoot();
}
abstract class SiegeWeapons {
    abstract public function Fire();
}

class GoodInfantry extends Infantry {
    //Спецэфическая реализация для доброго пехотинца
    public function Attack();
    public function Defend();
    
    public function Treat();
}
class GoodArcher extends Archer{
    public function Shoot();
    
    public function Treat();
}
class GoodSiegeWeapons extends SiegeWeapons{
    public function Fire(); 
    
    public function Treat();
}

class EvilInfantry extends Infantry{
    //Спецэфическая реализация для злого пехотинца
    public function Attack();
    public function Defend();
    
    public function Invisible();
}
class EvilArcher extends Archer{
    public function Shoot();
    
    public function Invisible();
}
class EvilSiegeWeapons extends SiegeWeapons{
    public function Fire();
    
    public function Invisible();
}

abstract class AbstractFactory
{
    abstract public function createInfantry();
    abstract public function createArcher();
    abstract public function createSiegeWeapons();
}

class GoodFactory extends AbstractFactory
{
    /**
     * @return Infantry
     */
    public function createInfantry()
    {
        return new GoodInfantry();
    }

    /**
     * @return Archer
     */
    public function createArcher()
    {
        return new GoodArcher();
    }

    /**
     * @return SiegeWeapons
     */
    public function createSiegeWeapons()
    {
        return new GoodSiegeWeapons();
    }
}

class EvilFactory extends AbstractFactory
{
    /**
     * @return Infantry
     */
    public function createInfantry()
    {
        return new EvilInfantry();
    }

    /**
     * @return Archer
     */
    public function createArcher()
    {
        return new EvilArcher();
    }

    /**
     * @return SiegeWeapons
     */
    public function createSiegeWeapons()
    {
        return new EvilSiegeWeapons();
    }
}

Код стал не таким красивым по тому, что появились методы, которые приходится копировать. В php нет множественного наследования и приходится выбирать наследоваться белому лучнику от белых или от лучников. Мы априори выбрали второй вариант и теперь думаем, как сделать наш код красивее, а его поддержку удобнее. НР предлагает использовать примеси (trait), которые появились в php 5.4. Вынесем обшие методы в примесь.

<?php

abstract class Infantry {
    //общие для всей пехоты методы
    abstract public function Attack();
    abstract public function Defend();
}
abstract class Archer {
    abstract public function Shoot();
}
abstract class SiegeWeapons {
    abstract public function Fire();
}

trait Good {
    public function Treat();
}

trait Evil {
    public function Invisible();
}

class GoodInfantry extends Infantry {
    use Good;
    
    //Спецэфическая реализация для доброго пехотинца
    public function Attack();
    public function Defend();
}
class GoodArcher extends Archer{
    use Good;
    public function Shoot();
}
class GoodSiegeWeapons extends SiegeWeapons{
    use Good;
    public function Fire();
}

class EvilInfantry extends Infantry{
    use Evil;
    
    //Спецэфическая реализация для злого пехотинца
    public function Attack();
    public function Defend();
}
class EvilArcher extends Archer{
    use Evil;
    public function Shoot();
}
class EvilSiegeWeapons extends SiegeWeapons{
    use Evil;
    public function Fire();
}

abstract class AbstractFactory
{
    abstract public function createInfantry();
    abstract public function createArcher();
    abstract public function createSiegeWeapons();
}

class GoodFactory extends AbstractFactory
{
    /**
     * @return Infantry
     */
    public function createInfantry()
    {
        return new GoodInfantry();
    }

    /**
     * @return Archer
     */
    public function createArcher()
    {
        return new GoodArcher();
    }

    /**
     * @return SiegeWeapons
     */
    public function createSiegeWeapons()
    {
        return new GoodSiegeWeapons();
    }
}

class EvilFactory extends AbstractFactory
{
    /**
     * @return Infantry
     */
    public function createInfantry()
    {
        return new EvilInfantry();
    }

    /**
     * @return Archer
     */
    public function createArcher()
    {
        return new EvilArcher();
    }

    /**
     * @return SiegeWeapons
     */
    public function createSiegeWeapons()
    {
        return new EvilSiegeWeapons();
    }
}

Теперь, какой бы сложной не была реализация лечения и невидимости, нам придется написать еге всего один раз и править в едином месте. Про абстрактную фабрику и про примеси есть куча литературы, но я ни разу не видел их описание в одной статье. А тем временем, практически каждое использование этого паттерна приводит к необходимости введения общих методов и свойств для групп обьектов, которые мы разделили нашими фабриками.

P.S. Мне очень хотелось бы услышать критику моего изложения общеизвестных вещей и моего варианта использования примесей от коллег. Я открыл для себя паттерны проектирования пару лет назад, но использовать их начал только сейчас.
И если будут у кого-то вопросы, то с радостью попробую ответить.

 5 comments    15   2016   web   программирование

Абстрактная фабрика и примеси в PHP (Часть 1)

Существует очень много статей о различных инструментах программирования и практиках их применения. Не бывает боевых проектов, которые реализуют единственный шаблон проектирования или используют единственный инструмент. В реальных условиях всегда приходится миксовать знания и опыт.

Мне хотелось бы описать связь между различными практиками программирования, которые я встречал в своих проектах. Если опыт будет удачным, то попробую сделать цикл подобных постов.

#Джуниор-разработчик и игра
Допустим, мы хотим написать браузерную игру, в которой «добро» борется со «злом». Пусть у обеих сторон есть пехота, лучники и осадные орудия. Для начала посадим за IDE джуниора. Через некоторое время мы, скорее всего, увидим следуюшие классы:

class GoodInfantry{}
class GoodArcher{}
class GoodSiegeWeapons{}

class EvilInfantry{}
class EvilArcher{}
class EvilSiegeWeapons{}

#Опытный разработчик и абстрактная фабрика

Придет опытный разработчик (ОР) и скажет, что это же класика для шаблона проектирования «абстрактная фабрика». Шаблоны проектирования разбирали уже тысячи раз на различных языках, но все же я хочу сделать статью самодостаточной и опишу еще разок. Если вы знаете «банду четырех», как свои пять пальцев, то перейдите к следующему подзаголовку.

ОР знает, что «добрая» сторона обязательно будет иметь свои особенности, отличные от «злой». При этом так же понятно, что пехота с обоих сторон так же будет иметь нечто общее. Таким образом каждый юнит фактически должен унаследовать какие-то свойства от своей стороны, а какие-то от своего типа.

Для того чтобы система оставалась независимой от специфики той или иной стороны необходимо использовать общие интерфейсы для всех основных типов юнитов. Это означает, что необходимо использовать три абстрактных базовых класса для каждого типа воинов. Производные от них классы будут реализовывать специфику соответствующего типа воинов той или иной армии.

Второй момент, который стоит учесть, связан с тем, что юнитов в игре будет много и создавать их будет не программист, а игрок. То есть программист не сможет просто прописать в коде нужное количество раз создание, допустим, пехотинца. Вместо этого он должен учесть создание пехотинца какой-то функцией. А, вспомнив, что мы находимся в концепции ООП, скорее методом какого-то более общего объекта. Допустим, что юнитов создает у нас «фабрика света» на «доброй» стороне и «фабрика тьмы» на «злой». Светлая фабрика будет выглядеть так:

Вспомним так же, что все пехотинцы будут наследовать класс Infantry. И подумаем о том, что фабрики, вероятно, будут иметь одинаковый набор методов, а значит хорошо бы им унаследовать единый абстрактный класс. Такой подход гарантирует нам, что ОР, создав абстрактные классы может идти курить и доверить написание реализации джуниору, ведь любая ошибка в реализации будет сразу видна. То, что у нас получилось, фактически, и есть паттерн Абстрактная фабрика. стрелки без подписи «создает» обозначают наследование.

Такая структура — пример хорошего кода для общего случая создания групп связанных обьектов. То есть всегда, когда у нас есть необходимость создавать экземпляры классов не в ручном режиме и когда у нас есть группы связанных объектов (в нашем случае все светлые и все темные связаны) паттерн подходит. Удобно то, что он раширяется, как горизонтально (можно добавлять еще стороны, кроме света и тьмы), так и вертикально без переписывания кода многократно.

<?php

abstract class Infantry {
    //общие для всей пехоты методы
    abstract public function Attack();
    abstract public function Defend();
}
abstract class Archer {
    abstract public function Shoot();
}
abstract class SiegeWeapons {
    abstract public function Fire();
}

class GoodInfantry extends Infantry {
    //Спецэфическая реализация для доброго пехотинца
    public function Attack();
    public function Defend();
}
class GoodArcher extends Archer{
    public function Shoot();
}
class GoodSiegeWeapons extends SiegeWeapons{
    public function Fire();
}

class EvilInfantry extends Infantry{
    //Спецэфическая реализация для злого пехотинца
    public function Attack();
    public function Defend();
}
class EvilArcher extends Archer{
    public function Shoot();
}
class EvilSiegeWeapons extends SiegeWeapons{
    public function Fire();
}

abstract class AbstractFactory
{
    abstract public function createInfantry();
    abstract public function createArcher();
    abstract public function createSiegeWeapons();
}

class GoodFactory extends AbstractFactory
{
    /**
     * @return Infantry
     */
    public function createInfantry()
    {
        return new GoodInfantry();
    }

    /**
     * @return Archer
     */
    public function createArcher()
    {
        return new GoodArcher();
    }

    /**
     * @return SiegeWeapons
     */
    public function createSiegeWeapons()
    {
        return new GoodSiegeWeapons();
    }
}

class EvilFactory extends AbstractFactory
{
    /**
     * @return Infantry
     */
    public function createInfantry()
    {
        return new EvilInfantry();
    }

    /**
     * @return Archer
     */
    public function createArcher()
    {
        return new EvilArcher();
    }

    /**
     * @return SiegeWeapons
     */
    public function createSiegeWeapons()
    {
        return new EvilSiegeWeapons();
    }
}

Причем же тут примеси? Продолжение следует.

 No comments    13   2016   web   программирование

Как я открыл для себя миграции

Как правило, перенос сайта с одного сервера на другой состоит из двух этапов: перенос файлов и базы данных. Когда мы говорим о разработке и поддержке серьезного проекта, обычно, мы имеем несколько серверов с разными версиями. Например сервер разработки (DEV), сервер тестирования (TEST) и «боевой» (PROD). А иногда, по копии сайта для каждого разработчика.

При такой схеме с двухфакторный перенос вызывает серьезные сложности. Во-первых, кто-то может не уследить и версия кода разойдется с версией базы данных, что может привести к сложно-обнаруживаемым багам и даже гейзенбагам.
Во-вторых, обычно бывает так, что структура базы данных на сервере разработчиков поменялась при очередной итерации доработок, но и сами данные на «боевом сервере» обновились. А это значит, что нужно сначала слить актуальные данные и актуальную структуру, а только потом базу можно подключать. Я однажды при такой операции перепутал окошко консоли и удалил обновленную структуру данных. Возможности восстановить её автоматически не было и пришлось следующие несколько часов вносить изменения заново. В этот момент я был бы счастлив, если бы прочитал эту свою сегодняшнюю заметку.

Решение, как все гениальное, кажется очевидным, когда ты его знаешь. Как избавится от двух этапов переноса? Сделать так, что бы один этап автоматически «подтягивал» второй. Например, хранить все изменения для БД в файловой системе. Благо, SQL нам в этом помогает очень сильно. Такие изменения назвали миграциями.

Я впервые познакомился с механизмом миграций в CMS yupe, на базе фраемворка Yii. По этому все, что напишу далее относится к этой конкретной реализации, хотя идея, в целом, остается неизменной везде.

Итак, если мы хотим внести изменения в базу, то вместо того, что бы лезть туда руками, мы создаем скрипт, который знает что нужно делать для применения изменений и для их отмены. Например, для этого создается класс с двумя методами up(), который создает новую таблицу, и down(), который её удаляет. Все остальное, что ему нужно он наследует от системного класса.

Далее, в системе есть способ (специальный скрипт), который для применения миграции создает объект этого класса и выполняет up, а для отмены down. Разумеется, в базе нам так же нужно хранить информацию о том, какие миграции к ней уже применились, а какие еще нет. Так же важен механизм очередности миграций, который так же зашит где-то в системе. Таким образом мы:

  1. Всегда уверены в актуальности бд. Всегда точно знаем, что к ней уже применилось, а что еще нет.
  2. Храним всю историю в системе контроля версий вместе со всей остальной историей разработки.
  3. Можем в любой момент откатить не только состояние кода, но и базы данных к любому этапу.
  4. Легко сливаем изменения от нескольких разработчиков. Теоретически, могут возникнуть конфликты, но на практике ни разу у меня не возникали.

Для того, что бы этот механизм принес максимум пользы, я выделил для себя ряд правил «гигиены».

  1. Создавать всю базу с самого начала миграциями. Казалось бы, в начале разработки, до того, как сайт перекочует дальше с первого сервера разработки проблем, о которых я писал в начале, не должно быть. Но, если уж начал делать миграции становится сложно остановиться. А серьезно, такое правило позволяет с самого начала делать все по одной схеме и не будет такого, что вот до этого момента мы можем посмотреть все изменения базы, а ранее она монолитна.
  2. Стараться делать миграции с привязкой к коммитам. Идеально: одна задача — один коммит — одна миграция. Последней конечно может и не быть. Сами задачки при этом должны быть небольшими 1-4 часа.
  3. При соблюдении предыдущего правила в название миграции хорошо бы включать id задачи. Но не ограничиваться только им, а написать еще и на человекопонятном языке.
 No comments    2   2016   web   программирование
Earlier Ctrl + ↓