Как стать автором
Обновить

Комментарии 24

Про прмер принципа Лисков https://youtu.be/FquSm_LOmS4?t=1041

Чисто ради интереса, понимает ли докладчик, что показывает прмер использования Symfony Console Component и почему он считает что именно это пример принципа подстановки Барбары Лисков?

Про unit тестирование https://youtu.be/FquSm_LOmS4?t=1304

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

Про нарушение принципов SOLID https://youtu.be/FquSm_LOmS4?t=1344

Мне интересно где это вычитал докладчик? В какой конкретно статье или литературе? Или он только ссылается на Mаgento? Да, использовать в construct в качестве тайп хинта конкретный класс вместо интерфейса не привествуется, но и не описывается как вопиющее нарушение принципа. А если нам в конкретной реализации класса нужно будет внедрить несколько хелперов и сторонний класс которые не имеют интерфейсов?

не буду отвечать пока за автора на вопросы адресованные ему.
Отвечу на замечание по поводу Unit тестов в Magento 1.

Unit тестирование, оно же модульное предполагает выделение Unit-а, который является наименьшей тестируемой единицей, и покрытие этого модуля тестами в изоляции от работы всей системы. В классическом представлении роль Unit-а выполняет класс.

в Magento 1 вам
никто не запрещал писать тесты
но эти тесты сложно было назвать Unit тестами.
Потому что M1 активно использовала шаблон Service Locator для создания сущностей внутри системы. Сущности создавались по мере выполнения логики класса, этим достигался эффект Lazy Loading внешних зависимостей. В коде это было реализовано с помощью God-класса Mage и его статических методов getModel, getSingleton, getHelper. Из-за отсутствия Dependency Injection, а также из-за использования статических методов для инстанциации внешних зависимостей — разработчики были лишены возможности создать моки (Mock) или стабы (Stub) для внешних зависимостей, чтобы покрыть тестами класс в изоляции от внешних зависимостей.

Поэтому тесты, которые писались под М1 были либо функциональными либо интеграционными — тестирующие код в интеграции с системой и не использующие моки для внешних зависимостей.
Обратите еще внимание, что класс Mage был объявлен как final
https://github.com/engineyard/magento-ce-1.9/blob/master/app/Mage.php

Теоретически, вы могли написать свой фреймворк, где бы вы создали тестовое окружение (environment), в котором по-другому инициализировали бы Mage object
https://github.com/engineyard/magento-ce-1.9/blame/master/app/Mage.php#L606
через _setConfigModel($options = array())

Но опять таки — это не Unit тестирование в классическом понимании. Более того, это настолько не просто и не очевидно. Что так никто не делал.
Вопрос не в том сложно их было назвать unit или нет. Вопрос в том, что докладчик утверждает что unit тесты отстутствуют в M1(наверное потому что не шли с под коробки), а это как минимум не совсем корректно. Опять же наскидку про unit tests упоминается на Inchoo и на Atwix

Обратите еще внимание, что класс Mage был объявлен как final
https://github.com/engineyard/magento-ce-1.9/blob/master/app/Mage.php


Ну если сильно извратится то можно мокнуть и финальный класс , но изврат еще тот, так что согласен.
ну если вы Unit-ом назовете всю систему, то да, это Unit-тесты :)

Как я говорил выше, тестирование достигалось путем определенной инициализации Mage::app класса.
Где определенные базовые сущности, например Layout, Session объекты подменялись на предопределенные моки, т.е. Всю систему конфигурировали так, что она запускалась в тестовом режиме.
Собственно две статьи выше демонстрируют именно эту технику.
Почувствуйте разницу — протестировать объект в изоляции от всей системы vs сконфигурировать систему, что она запускается в режиме для тестирования.

Кстати, код теста из статьи
    	/* You'll have to load Magento app in any test classes in this method */
    	$app = Mage::app('default');
    	/* You will need a layout for block tests */
        $this->_layout = $app->getLayout();
        /* Let's create the block instance for further tests */
        $this->_block = new Company_Module_Block_Blockname;
        /* We are required to set layouts before we can do anything with blocks */
        $this->_block->setLayout($this->_layout);


четко показывает, что здесь мокается только зависимость, которая передается через сеттерный метод.

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

прелесть в том, что вы Можете его не использовать, если вам он не нравится, или вам кажется это магией. В M2 Unit тесты могут писаться и без его использования. Кстати, таких достаточно много, так как у нас внутри нет требования его использовать, и многие программисты считают, что лучше создавать все «вручную».

Его задача — избавить вас от надобности нагенеривать кучу моков вручную. Опять же к разговору как Magento борется с шаблонным кодом (boilerplate code).
Пользуясь случаем. раз мне ответил сам Magento 2 Architect(если верить описанию), хочу задать пару вопросов.

Почему в magento2 была выбрана стратегия кодогенерации?
Почему в magento2 не сделали явное описание создания сервисов, как например в symfony? Зачем нужна эта магия?
Написанному можно верить :)
Почему в magento2 была выбрана стратегия кодогенерации?
Вы про код генерацию в принципе или про что-то конкретное Interceptor, Factory, Proxy?

Мы используем код генерацию там, где считаем она избавит программиста от написания шаблонного кода (boilerplate code).
Например, логика Factory и Proxy в шаблонном случае одинакова — поэтому мы кодгенерим классы просто когда программист указывает нужный суфикс (Factory или Proxy) добавляя его к имени сущности, которую он хочет создать.

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

Я так понимаю, вопрос продиктован некоторой сложностью дебага кода с интерсептерами. Но уже существуют плагины к PHP Storm, которые решают эту проблему и убирают из стека служебные классы, для удобста чтения.
Почему в magento2 не сделали явное описание создания сервисов, как например в symfony? Зачем нужна эта магия?
В Magento 2 используется концепт Service Layer, и API находятся в папках каждого модуля. На эти API мапятся Web API. Например, здесь можно почитать как API добавлялись для одного из модулей.
Я не понял какая именно магия имеется в виду.

Вы про код генерацию в принципе или про что-то конкретное Interceptor, Factory, Proxy?

Мы используем код генерацию там, где считаем она избавит программиста от написания шаблонного кода (boilerplate code).
Например, логика Factory и Proxy в шаблонном случае одинакова — поэтому мы кодгенерим классы просто когда программист указывает нужный суфикс (Factory или Proxy) добавляя его к имени сущности, которую он хочет создать.

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


Да именно про это. Ок, допустим. Но теперь времяна разработку увеличилось, за счет того что при добавлении новой зависимости в класс, например кастомной CollectionFactory(и кстати это не очевидно, поскольку на момент добавления зависимости класс еще не сгенерирован), нужно заново перегенирировать код. У меня на ноуте(i3, 8GB Ram, 512 SSD) на перегенерацию кода на чистой мадженте с одним катсомным модулем уходит 4 минуты! И при том, я так понимаю, про механизм отслеживания изменений и перегенерацию только необходимого никто не задумывался и реализовывать не будет?

В Magento 2 используется концепт Service Layer, и API находятся в папках каждого модуля. На эти API мапятся Web API. Например, здесь можно почитать как API добавлялись для одного из модулей.
Я не понял какая именно магия имеется в виду.


Я прошу прощения, если ввел в заблуждение. Когда я говрил сервис, я имел ввиду описание создание класса для DI Container как это наприммер сделано в symfony, пример 1, пример 2
Если вы хотите добавить кастомную фабрику, т.е. ее логика будет отличатсья от кодогенерируемой. Вы ее просто пишете, и ничего код-генериться не будет.

Мадженто работает по-разному, в зависимости от режима: develop и production
в develop код генерация происходит на лету, в production подразумевается, что вы заранее произвели всю код генерацию и ничего больше генерировать не надо, таким образом производительность будет выше. Т.е. в production режиме не будет механизма по отслеживанию изменений, потому что в нем нет надобности — вы не должны изменять код в решиме production.

Теперь по поводу настройки IoC контейнера (ObjectManager).
В Мадженто система настройки гораздо гибче, чем в Symfony
у нас это di.xml файлы, которые хранят описание
preference, type, virtualType, plugin
здесь можно почитать подробней об этом — http://devdocs.magento.com/guides/v2.0/extension-dev-guide/build/di-xml-file.html
мы сделали auto-injection в DI, в отличие от Symfony опять же, чтобы избавить программиста от написания шаблонного кода.

И чтобы он конфигурировал, только то, что ему нужно конфигурировать. А не все зависимости класса.
когда пишешь код в режиме Develop (а именно в этом режиме обычно пишут код программисты) нет надобности перегенеривать после любых изменений все код-генерированные сущности.

Достатчоно удалить, только нужные файлы, которые были изменены. И система пересоздаст только их. Не нужно удалять всю папку 'generated' при этом. Это не должно занять много времени.

Вероятно сейчас после каждых изменений Вы запускаете DI компиляцию всего. Этого делать не нужно
мы сделали auto-injection в DI, в отличие от Symfony опять же, чтобы избавить программиста от написания шаблонного кода.


Интересно, шаблонный код это явно указание зависимости, например, в yml?

И чтобы он конфигурировал, только то, что ему нужно конфигурировать. А не все зависимости класса.


В симфони для этого существует parent, который избавляет от внедрения всех зависимостей, а так же повторного вызова методов необходимых для создания класса.

когда пишешь код в режиме Develop (а именно в этом режиме обычно пишут код программисты) нет надобности перегенеривать после любых изменений все код-генерированные сущности.

Достатчоно удалить, только нужные файлы, которые были изменены. И система пересоздаст только их. Не нужно удалять всю папку 'generated' при этом. Это не должно занять много времени.

Вероятно сейчас после каждых изменений Вы запускаете DI компиляцию всего. Этого делать не нужно


Хорошо, допустим. Почему тогда это нигде не отражено в офф документации? Почему об этом нигде не написанно в той же офф документации? И почему бы не реализовать механизм автоматического отслеживания измененых файлов что бы перегенерировать только измененные код-герерированые сущности? Или как с методами save и load в модели. @deprecated есть, а see нет. И ищи информацию по форумам))) Хорошо что хоть описание в последнюю версию добавили.
Интересно, шаблонный код это явно указание зависимости, например, в yml?
Мне больше нравится как оно звучит на английском языке, лучше передает суть — boilerplate code.
Да, именно такого кода, который программист обычно copy-paste-ит мы хотим избегать.

Magento по-факту сама вставит вам кастомизированную зависимость в Child, если кто-то кастомизировал ее для Parent, а для Child она не переопределена. Вам для этого не нужно ничего указывать.

Декларацию parent, как в Symfony мы также хотим избегать, во-первых, по причине описанной выше, а во-вторых, потому что Magento не рекомендует использовать Inheritance Based API, т.е. расширение путем наследования в целом. Мадженто для этого предоставляет достатоно других механизмов взамен. И рекомендованным путем является композиция объектов.

Существующие цепочки наследования, например существование Абстрактных: модели, контроллера, блока — можно расценивать как легаси код в системе, который мы пока не убрали в первую очередь из-за требований обратной совместимости, которые мы соблюдаем в 2.* релизах.
Собственно именно поэтому мы сделали deprecated методы save и load на абстрактной модели, как вы заметили.

По поводу документации, сейчас требования к документированию кода очень высоки.
Не зря вы заметили, что добавилось описание к deprecation save и load.

Вот так, например, комментиуется новый код.

Но Magento — это Open Source проект, об этом была моя первая презентация, видео которой тут выложено. Поэтому если Вы видите как Вы можете что-то улучшить в коде или в документации, Вы можете поставить Pull Request, и если он соответствует нашим требованиям, то мы его обязательно приймем, а если не соответсвует, но идея покажется нам полезной — поможем доработать его до вида, чтобы влить его в мейнлайн.
Это касается и автоматического отслеживания изменений и тегов @ see, которые мы пока не бекпортировали в 2.1.*
Мне больше нравится как оно звучит на английском языке, лучше передает суть — boilerplate code.
Да, именно такого кода, который программист обычно copy-paste-ит мы хотим избегать.


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

Magento по-факту сама вставит вам кастомизированную зависимость в Child, если кто-то кастомизировал ее для Parent, а для Child она не переопределена. Вам для этого не нужно ничего указывать.


А это в каком случае? В случае использования Context? В случаях в preference или в случаях virtual types?

Декларацию parent, как в Symfony мы также хотим избегать, во-первых, по причине описанной выше, а во-вторых, потому что Magento не рекомендует использовать Inheritance Based API, т.е. расширение путем наследования в целом. Мадженто для этого предоставляет достатоно других механизмов взамен. И рекомендованным путем является композиция объектов.


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

Но Magento — это Open Source проект, об этом была моя первая презентация, видео которой тут выложено. Поэтому если Вы видите как Вы можете что-то улучшить в коде или в документации, Вы можете поставить Pull Request, и если он соответствует нашим требованиям, то мы его обязательно приймем, а если не соответсвует, но идея покажется нам полезной — поможем доработать его до вида, чтобы влить его в мейнлайн.
Это касается и автоматического отслеживания изменений и тегов @ see, которые мы пока не бекпортировали в 2.1.*


Я ждал несто подобное) Увы, пока есть куча работы основной, но как только выдастся минутка обязательно внесу свой вклад. И раз пошла такая петрушка, куда я могу оформить баг, при котором админка мадженты уходит в цыклический редирект?
Немного смешно звучит учитывая что при создании модуля мы постоянно делаем рутинные действия, например создание модели, с ресурс моделью и коллекцией.
Не вижу здесь ничего смешного. Это называется разделение ответственностей в многослойной архитектуре. Или вы хотите, чтобы модель попрежнему себя сохраняла? Вы же сами выше писали про Single Responsibility.

Для большей гибкости и кастомизации, которая предоставит возможность подменять минимальное кол-во логики для стороннего разработчика (а не переписывать весь модуль) мы и делаем это разделение.
Например, вот хороший пример написания новой логики в Magento 2 — новая реализация Inventory.
Вы видите здесь примеры boilerplate кода?

А это в каком случае? В случае использования Context? В случаях в preference или в случаях virtual types?
Это во всех случаях.
Например, есть 

класс А, который имеет внешнюю зависимость на XInterface, 
у XInterface есть две реализации 
X1 и Х2.
Есть базовый preference XInerface -> X2


и есть настройка Type для класса А (XInerface -> X1)


Когда существует класс B, который наследуется от А, 
то ему прийдет в зависимость для XInerface 
-> реализация X1

Опять же это одно из проявлений Liskov Substitution

Ну для начала parent в Symfony это не более чем механизм который позволяет избавится от дублирующего кода,
Еще раз — сравните сколько вы пишите конфигурации в Magento и сколько бы написали в Symfony для большого проекта. В Magento вы пишите меньше, потому что декларируете только то, что вам действительно нужно сконфигурировать.

Ну и потом, звучит немного странно на фоне того, что при создании модуля мы все так же наследуем базовые классы(раз, два, три, четыре, пять, шесть),
Не странно, то что вы указали — это примеры legacy кода, который мы пока не меняем, чтобы не нарушить обратную совместимость в новых релизах и не поломать текущие инсталяции.
Базовые абстрактные класса такие как AbstractBlock, AbstractModel, AbstractController это яркий пример layer super type
Например, вот одна из задача из нашего беклога, которой мы делимся с сообществом, чтобы избавиться от абстрактного контроллера
Eliminate the need for inheritance for action controllers
при том что ничего плохого в наследовании нет, поскольку композиция не всегда является гибким решением.
В самом по себе в наследовании нет ничего плохого, но ошибочно используют наследование очень многие. Наследование вводит самый жестки coupling который только может быть, и потом уже ничем его не подменишь

И раз пошла такая петрушка, куда я могу оформить баг, при котором админка мадженты уходит в цыклический редирект?

Вы можете завести GitHub issue
Описав проблему и предоставив шаги, чтобы ее воспроизвести и кто-то из команды Magento или из сообщества вам поможет в решении этой проблемы.
Про прмер принципа Лисков https://youtu.be/FquSm_LOmS4?t=1041
Чисто ради интереса, понимает ли докладчик, что показывает прмер использования Symfony Console Component и почему он считает что именно это пример принципа подстановки Барбары Лисков?
Принцип Лисков в первую очередь говорит о том, что Наследование это очень сложное отношение между объектами, которое добавляет большой coupling, и многие его используют не правильно. Большинство использует его только чтобы избавиться от дублирования кода. Но для наследования должно еще выполняться отношение is_a (является).
То что докладчик выбрал механизм консольных команд представленный в Magento 2 (которые действительно расширяют Symfony\Component\Console\Command\Command) не есть проблемой.
В данном примере, новая команда чистит кеш, но при этом отношение is_a по отношению к родительской команде соблюдается.

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

Принцип Лисков в первую очередь говорит о том, что Наследование это очень сложное отношение между объектами, которое добавляет большой coupling, и многие его используют не правильно. Большинство использует его только чтобы избавиться от дублирования кода. Но для наследования должно еще выполняться отношение is_a (является).
То что докладчик выбрал механизм консольных команд представленный в Magento 2 (которые действительно расширяют Symfony\Component\Console\Command\Command) не есть проблемой.
В данном примере, новая команда чистит кеш, но при этом отношение is_a по отношению к родительской команде соблюдается.


Спасибо за ответ. Пересмотрел класс родитель, да, если прочитать Ваш ответ, так и есть.

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


Да, но докладчик говорит о нарушении ссылаясь на «конкретную реализации», т.е. ссылаясь на предыдущий пункт Dependency Inversion. И из-за этого складывается ощущение что он сам слабо понимает о чем говорит.
Спасибо за вопросы.
1. Конечно докладчик понимает что в Magento 2 используется Symfony Сonsole Сomponent, в том числе и в примере приведенном в докладе. Почему для демонстрации реализации выбран именно этот пример? В данном примере по мнению автора удачно выбрана абстракция для наследования, что является основной целью LSP.
2. Я и не говорю что Unit тестирование было запрeщено в Magento 1. Я говорю о том, что в виду архитектуры M1 было сложно организовать действительно изолированное тестирование каждого класса.
3. Не совсем понятен вопрос. Тоесть таки нарушаем но чуть чуть? Это как? Вы проде бы и согласны что построение зависимости на основе реализации, а не на основе абстракций это нарушение принципа OCP, но вроде бы и не согласны. Более того я говорю о том что иногда мы можем отходить от следования данного принципа, но все таки это будет отхождение а не «вопиющее» или не «вопиющие» нарушение. Вопрос в том оправдано ли оно. Если мы знаем что мы никогда не будем менять зависимость, то возможно нам и не нужно создавать зависимость на абстракции. Но в случае Magento 2, когда нам предоставлен механизм указания нужной реализации, я считаю, что все таки стоит избегать таких случаев. Где вычитал — Agile Principles, Patterns, and Practices in C# By Martin C. Robert, Martin Micah (http://druss.co/wp-content/uploads/2013/10/Agile-Principles-Patterns-and-Practices-in-C.pdf).
3. Не совсем понятен вопрос. Тоесть таки нарушаем но чуть чуть? Это как? Вы проде бы и согласны что построение зависимости на основе реализации, а не на основе абстракций это нарушение принципа OCP, но вроде бы и не согласны. Более того я говорю о том что иногда мы можем отходить от следования данного принципа, но все таки это будет отхождение а не «вопиющее» или не «вопиющие» нарушение. Вопрос в том оправдано ли оно. Если мы знаем что мы никогда не будем менять зависимость, то возможно нам и не нужно создавать зависимость на абстракции. Но в случае Magento 2, когда нам предоставлен механизм указания нужной реализации, я считаю, что все таки стоит избегать таких случаев. Где вычитал — Agile Principles, Patterns, and Practices in C# By Martin C. Robert, Martin Micah (http://druss.co/wp-content/uploads/2013/10/Agile-Principles-Patterns-and-Practices-in-C.pdf).


Да в принципе в другой ветке был дан ответ на вопрос по поводу нарушения solid. Magento2 представляет механизм несовсем указания. Посути это просто механизм подмены интерфейса или конкретного класса, поскольку можно написать

preference for="Magento\Framework\Logger\Monolog" type="Coolryan\PreferenceExample\Model\Log"

И спокойно подменить логер.

В любом случае спасибо за ответ.
То как вы описали,
preference for=«Magento\Framework\Logger\Monolog» type=«Coolryan\PreferenceExample\Model\Log»

Это не рекомендованый подход. В данном случае вы будете использовать механизм Duck Typing.
Который не безопасный и не объектно ориентированный по своей сути.
А что именно вы понимаете под теримином «Утиная типизация»?
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации