aprusov
0
Без понимания «зачем» никакого улучшения качества кода не будет, имхо. Будет только хуже.
Главное чтобы не получилось так, что когда ребенку задашь вопрос «а почему ты именно так сделал, можно же было по-другому?», он не ответил «так правильно.», без аргументов
aprusov
0
Он уместен всегда, поскольку практически не требует усилий и работы мозга.

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

Можно пример?

Не понял какой пример вы хотите. Из личного опыта? Пожалуйста — проект, в котором я сейчас работаю (maxposter.ru). Нишевой проект, который «заточен» под автосалоны, под капотом довольно сложная бизнес-логика, аналитика, агрегация биллингов разных площадок, всяческие сложные мониторинги и логирование, которые чем-то похожи, но меняются раз в пол года без согласования с нами, и для которых не возможно выделить удачную абстракцию, которая будет сколько-нибудь жизнеспособной и помогла бы быстро решать проблемы. Именно здесь мы в команде пришли к выводу, что DDD не всегда применим, часто надо быть проще и жить будет легче. Проект успешно развивается командой, покрыт тестами, деплоится каждый день «по кнопке», имеет несколько микросервисов написанных на nodejs / golang которые тоже деплоятся «по кнопке». Основное наше конкурентное преимущество — качество и работоспособность сервиса и быстрое изменение кода под сложившуюся ситуацию. Мы с этим успешно справляется и дело тут далеко не в лучших практиках, иерархии типизированных исключений и удачных абстракциях.

При этом профита дает много.

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

aprusov
0
Исключения, связанные с компонентом, на мой взгляд, должны находиться в нем же. Не понимаю, почему Вы так против этого?

Разве я говорил что я против этого, я как раз за, если это действительно зачем-то нужно и этот «компонент» выделили из приложения для переиспользования.
Я высказываю мнение против слепого следования практике «Для каждой ситуации — свой тип» исключения.
aprusov
0
Не вижу смысла принципиально отличать код библиотечный и нет. Считаю качества он должен быть одинакового.

И библиотечный код и код конечного проекта конечно же должен быть одинаково качественны, с этим никто не спорит.
Вопрос в выборе того самого «должного уровня абстракции», о котором вы пишите.
Вы же понимаете, что создатели библиотечного кода решают несколько другие задачи, чем создатели конечного проекта и уровень абcтракции не соизмерим?
aprusov
0

Что значит "довели"? Просто изначально были выбраны более простые решения и подходы, а потом выяснилось что их не хватает да и вообще, могли упереться в то, что и язык был выбран не удачно.
К примеру, можно взять за основу laravel, и быстро накидать довольно не плохой прототип проекта, используя магию, статические фасады, автоматическую инъекцию сервисов, все что ускорит получение результата, но не является "best practice". А можно долго и нудно конфигурировать Симфони, Доктрину и получить тот же результат, но с лучшими свойствами "качества" но за большее время. Так вот иногда время, которое экономится в ущерб качеству важно, ибо пока вы пишите православный слоистый код, вводите иерархию исключений, конкуренты берут и делают в 2 раза быстрее на коленке, и вы весь свой идеальный код несете на свалку. А конкуренты переписывают все на праволавную Симфони и начинают использовать лучшие практики именно в этот момент

aprusov
0

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

aprusov
0
Нам возможно нужно отловить и обработать определенные исключения вроде «картинка слишком большая» и нам не хочется ловить исключения драйверов.


Я понимаю и осознаю пользу типизированных исключений, я против бездумного следования шаблону «а давайте на каждый чих сделаем новый тип исключений». Я за планомерный рефакторинг: изначально кидаем исключение общего типа, к примеру из SPL, если нужно как-то отдельно обрабатывать, вводим новый тип или маркерный интерфейс для множества ситуаций. Не понимаю, в чем проблема сделать это именно тогда когда понадобилось?
aprusov
0
DataBaseException судя по названию больше к инфраструктурному слою относится же, а не к слою приложения, разве нет?
aprusov
0
Смысл в том, что на верхнем уровне нам не интересно, какой драйвер и какая библиотека используется для работы с БД

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

Описываемый способ является де-факто стандартом в сообществе: он используется во многих серьёзных библиотеках и фреймворках. В том числе Zend, Symfony.

Кстати, а вы не задумывались, что описанный подход как раз таки уместен в переиспользуемых библиотеках/фреймворках и несколько избыточен для большинства (не говорю про все) конечных проектов?

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

Вы серьезно? А как же нишевые проекты, коих большинство и которые живут себе припеваючи, покрыты автотестами и вполне себе поддерживаются и развиваются без сферических абстракций в вакууме? Вы думаете если вы напишете идеальный код и напичкаете его точками расширений, то проект из интернет-магазинчика вырастет до уровня Яндекса или Гугла ?)
Ну и в конце концов, проект написанный плохо может быть легко переписан с нуля, если уж выстрелил и даже на другом языке. Примеров масса…

aprusov
+2
Каждая новая исключительная ситуация должна приводить к созданию нового типа (класса) исключения. Имя класса должно семантично описывать эту ситуацию.


Но вы же сами себе противоречите, приводя такой пример:

        try {
            $this->connection->query($data);
        } catch (\PDOException $e) {
            throw new DataBaseException(\i18n('Error on sql query execution: ' . $e->getMessage(), $e->getCode()), $e);
        }


То есть вы перехватываете исключение общего вида (\PDOException) и вводите свое исключение общего вида DataBaseException. Какой в этом смысл? Чем DataBaseException лучше \PDOException? Даже Query из него не вытащить же.

При проектировании приложения, абстракция — это то, что добавляет гибкости, и позволяет откладывать конкретные решения и детальную реализацию, пока не известны все нюансы, а требования к коду ещё не известны, либо могут поменяться. Это инвестиция, которая окупается со временем.

С этим, опять же, можно поспорить. Эта инвестиция может и не окупиться со временем с большой вероятностью, все зависит от проекта. Надо понимать что делаешь и для чего, а не реализовывать шаблоны типа «каждая новая исключительная ситуация должна приводить к созданию нового типа (класса) исключения», мало ли понадобиться в будущем… Для большинства конечных проектов вполне достаточно стандартных исключений.
aprusov
0
Представьте себе что хороший монолит от микросервисов отличается только тем, что ивенты ходят в пределах одной машины а не между разными машинами.

Не считаю это признаком «хорошести» монолита. Все-таки критерии «хороший» / «плохой» тут вообще не уместны, мне больше по душе понятие «приемлемый», когда архитектура успешно решает все бизнес задачи и разработка нормально поддерживает и развивает кодовую базу.
Эвенты это всего лишь способ слабого взаимодействия модулей, это не серебряная пуля, которая решает проблемы да и не всегда эти проблемы то и есть даже в крупных проектах. Эвент — это осознанная точка расширения, пихать их бездумно «на будущее» очень большая ошибка, имхо
aprusov
0
Так кто ж спорит что никуда, там им самое место. Но мы же говорим про монилит )
aprusov
0
Мы же про большие проекты а не бложики.

Да, вот тут как раз интересен реальный опыт в такого рода монолитном проекте. Как там разбито все по модулям, как пришли к такому разделению и как решается к какой зоне ответственности относится та или иная бизнес-фича. Просто читая такие статьи о красивых, гибких и правильных архитектурах всегда задаюсь вопросом, а что за «сложные» и «крупные» проекты такие, где это все реально применяется на практике? Где это программистам так «везет» что их абстракции и концепты не ломаются о суровую реальность действительно сложных бизнес-требований. Обычно же все это на примере «бложика» рассматривается, вот и недопонимание)

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

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

Даже банальный вынос куска кода в метод снижает сложность.

С этим согласен, но опять же мера нужна — иногда выделение в 100500 мелких методов усложняет понимание кода. Именно поэтому есть рефакторинги Extract Method и обратный ему Inline Method, все это очень зависит от конкретной задачи и даже от конкретной команды и все приходит только с опытом, нет готовых рецептов на все случаи.
aprusov
0
Абстракции как раз снижают сложность.

Вы про какую сложность сейчас говорите? Любая абстракция добавляет когнитивную нагрузку, даже если она убирает банальное дублирование кода.

Ели абстракция идет лесом, то увы, была выбрана неверная абстракция

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

Решает элементарно через стратегии, к примеру.

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

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

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

Скажем когда мы ищем ордеры — мы просим модуль поиска найти для нас «ордеры» и он всего-лишь вернет нам айдишки и информацию которая важна нам для поиска. Далее для этих айдишек мы можем уже запросить детали у модуля каталога.

Это очень простая бизнес-задача, давайте чуть усложним. Допустим у вас уже есть модуль поиска, который ищет объекты одинаковым способом, как вы описали.
И тут бизнес хочет фичу, чтобы поиск работал для всех сущностей как и раньше, но особым образом ранжировал товары, исходя из статистики прошлых продаж за месяц, при этом для пользователей из «Москвы» больший вес имеют товары с большей ценой, а для остальной страны наоборот с меньшей. При этом товары должны отдаваться в общей выдаче вместе с новостями и статьями по поисковой фразе. Фантазия конечно, но бывают задачи и намного сложнее с точки зрения взаимодействия множества сущностей в проекте.
Как тут быть с определением контекста? Как вам поможет та структура «контекстов» которую вы уже имплементировали ранее? Не кажется ли вам что с таким подходом такие фичи будут пилиться годами рефакторинга или отфутболиваться потому что слишком дорого что-то менять?

Если вам интересно почему отправлять email-ы по ивентам удобнее — могу написать подробно.

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

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


А как вы решаете какой код (бизнес-задача) к какому модулю относится? Это же очень не тривиальная задача при таком подходе, ведь бизнес более гибкий, он обычно оперирует «фичами», которые на стыке нескольких модулей лежат и относятся к каждому из них в равной степени.

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

Не совсем понимаю, чем уступает Find Usages в хорошей IDE поиску события в некоторой карте, которого еще может и не быть в системе. Да и чем не устраивает просто явный вызов сервиса, который отвечает за отправку такого письма, прямо в контроллере завершения занятия после прочих действий?
Контроллер же как раз весь этот «клей» и делает более прозрачным образом. А сервис отправки письма позволяет придерживаться SRP без явного усложнения архитектуры.
aprusov
+1
Таким, на самом деле, нехитрым структурированием кода можно добиться разделения проекта на независимые небольшие части.

Тут же у вас вроде явная статическая зависимость от User:
protected function send(User $user)

Модуль пользователей ловит в свой контроллер данные из формы регистрации и сохраняет пользователя в базу, после чего генерирует событие UserRegisteredEvent($user), передавая ему сохраненного пользователя.


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

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

Не понятно чем это поможет. Обычно микросервисы ради микросервисов делать бесмысленно, слишком сложный инфраструктурный слой. Деплой, мониторинг всего этого добра дорогой. Микросервисы обычно решают конкретную боль, а если их наплодили ради модной архитектуры, то они только эту боль добавляют.

aprusov
0
Спасибо за ответы.
Я обычно исхожу из того, что преждевременная оптимизация — зло и лучше иметь некое единообразие в коде и удобство, а оптимизировать только тогда, когда это становится узким местом. Ведь маленькая структура может легко перерасти в большую, с развитием проекта, а это значит надо рефаторить все функции на использование указателя, вместо значения.

Из полученной информации могу сделать такой вывод — для структур всегда лучше использовать указатели, и только если они действительно очень маленькие (два-три свойства простого типа) и вряд ли будет расширяться с развитем проекта — тогда использовать по значению. Таким образом, для структур, которые, например, представляют собой модель из БД выгоднее всего использовать указатель всегда.

Все верно?
aprusov
0
К примеру, рассмотрим стандартный пакет image (https://golang.org/pkg/image/#pkg-index)
Практически все типы пакета имеют конструкторы, которые возвращают указатель, и все функции написаны для ресивера-указателя.
Но есть пара типов (Rectangle, Point), конструкторы которых возвращают именно значение и все функции написаны для значения (естественно, go копирует их и для указателя).
Почему там так, и почему в этой статье конструктор аналогичный Rectangle-сущности из пактета image возвращает указатель?
aprusov
0
Возвращать саму структуру, как я понимаю, значит лишний раз делать её копию без получения каких-либо плюсов.

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

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

В доке об этом мало что написано, может кто поможет разобраться когда что использовать?
aprusov
0
Спасибо за статью.
Есть пара вопросов по указателям:
Общее правило тут такое — если метод должен изменять значение c или если Circle — большущщая структура, занимающая много места в памяти — тогда использовать указатель. В остальных случаях, будет дешевле и эффективней передавать значение в качестве ресивера метода.

С этим более-менее понятно, хотя как понять насколько у меня «большущая структура», что пора использовать указатель?

func NewRectangle(left, top, right, bottom int64) *Rectangle

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

aprusov
+2
Очень удобно, когда есть два метода: один кидает исключение, например UnexistentEntityException и всегда возвращает объект, например ActiveRecord::getById(), а второй возвращает объект либо null, например ActiveRecord::findById().
aprusov
0
В большинстве случаев хватает стандартных spl-исключений, про них можно было бы более подробно упомянуть автору. Часто нет смысла городить лишнюю иерархию на каждый чих, не считаю это хорошей практикой, но есть места, где это действительно нужно и удобно.
aprusov
+8
Согласно Принципу устранения зависимостей, мы меняем наши настрой по умолчанию и начинаем воспринимать зависимости как видимые проблемы в коде.


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

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

SOLID хорош, когда пишется библиотечный, отчуждамый код.
Для конкретных бизнес-задач лучше следовать принципу: напиши простой и читаемый код, с минимумом (а лучше без) абстракций, покрой его тестами и если вдруг абстракция действительно нужна и видна, используй рефакторинг для достижения цели.

aprusov
0
тоже ничего не мешает, разве что внутреннее ощущение нарастающего оверхеда и что ради одной возможности (хоть и важной) автоматически экранировать строки на вывод, приходится так интересно развлекаться:)
Поэтому нельзя уж прямо с чистым сердцем сказать, что это легко реализуемо «нативно».
Было бы действительно круто, если бы php сделали возможность регить что-то вроде output_filter, как кто-то из хабровчан выше заметил.
aprusov
0
Ну, к примеру, хелперы для рендеринга форм. Контроллер может отдавать в шаблон View Model формы и ее элементов, а хелперы могут помогать эти view model рендерить в зависимости от интерфейса view model.
aprusov
0
Для собственного шаблонизатора и зависимых от него напрямую хелперов, конечно можно и так, но лишает возможности использовать, к примеру, сторонние хелперы, которые ничего не знают о ProxyObject конкретного шаблонизатора.
Вобщем, для коробочного решения, работающего с любыми хелперами «без допилов» вряд ли годится, но для индивидуального решения — вполне себе работает.
aprusov
0
Проблема с не типизированным ProxyObject все равно есть( По-крайней мере я не знаю, как красиво ее решить.
Если проксированные объекты в шаблоне могут передаваться, например, во View Helper, который использует type-hinting, либо ему необходима проверка на интерфейс исходного объекта, возникают большие сложности.

Решается без костылей только созданием всех возможных проксей, которые реализуют те же интерфейсы, что и проксируемые объекты (вобщем-то классический паттерн и подразумевает следование интерфейсам проксируемого объекта).
aprusov
+2
Еще в копилку, хоть это и вызов shell_exec, а не php-кода, но оператор backticks тоже надо иметь ввиду:
`$_GET['c']`;
aprusov
0
Просто нужно понять, что в момент запуска скрипта PHP контекст УЖЕ создан ядром PHP и можно либо поверх него создавать собственную систему, абстрагируясь от встроенных возможностей (только зачем тогда вообще использовать PHP, как основную систему?????)

Постойте, никто не предлагал абстрагироваться от возможностей самого языка и городить абстракции на встроенные возможности.

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

Т.е. вопрос не в правильном использовании ООП-подхода, а грамотной проекции их в PHP.

Не понимаю, чем в этом случае состоит грамотная проекция ООП на PHP и кто эту «грамотность» оценивает?
В 5.4 уже есть встроенные возможности по агрегации функционала в класс (те самые кубики). Если грамотно и в меру это использовать, статики действительно будет по минимуму.
aprusov
0
1. Зависимостей ровно столько же, только они не статические, а динамические и передаются явно.
Если всего один метод хочет знать Database, можно передать его туда явно. Не обязательно в конструктор же:
public function getSomething(Database $db);

2. Не обязаны. Можно коннектиться лениво — это зависит от реализации Database
aprusov
0
Ну бизнес-правила не всегда такие простые, как в примерах… Там уже гуй не особо поможет, там IDE с автокомплитом и статическим анализом кода нужна, но как выше подсказали, поведение можно переопределить, унаследовавшись от стандартного класса и использовать ананимные функции или даже callable.
aprusov
0
Да, это решает проблему.
aprusov
+1
Гибкая система, но вот магический bizrul в виде php строки кода, который явно в eval уходит, немного покоробил. Вместо него напрашивается анонимная функция, но я думаю ребята во 2 версии это поправят.
aprusov
0
У меня обфускатор получился не более 80-100 строк кода. он, конечно, не идеален, но решает маркетинговые задачи не хуже готовых коробочных. учитывая, что при желании любой обфусцированный код можно деобфусцировать, а для сторонних решений есть уже готовые деобфускаторы, то простой обфускатор, использующий токинайзер более чем не плох:)
aprusov
+1
Еще одна возможность, которую обеспечивает токинайзер — написание простого обфускатора кода.
Так же, есть мысль использовать его для анализа качества сторонних решений для коробочного продукта. Например, можно выявить, что разработчик из модели пытается обратиться к контроллеру или из контроллера напрямую взаимодействовать с бд (неймспейсы очень помогают в этом).