Pull to refresh

Comments 55

Придерживайтесь одного источника истины

Централизация, одним словом

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

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

А можете назвать хоть один случай, где была бы важна "чистота архитектуры"?

Вообще, я не продвигал ее использование в своем комментарии, а говорил о том, как важно именно индивидуально подстраивать реализацию под ТЗ, если говорить по простому

Но если вам интересно, то можете прочесть одноименную книгу дяди Боба (или хотя бы рецензию), в ней он подробно излагает смысл. Чистая архитектура - лишь идея! В самой книге рассматривается множество проблематик и подходов к их решению, удовлетворяющие понятию "Чистой архитектуры", которую как раз-таки каждый, по непонятной мне причине, пытается описать чем-то конкретным

Чистая архитектура - лишь идея!

Поддерживаю. Это прекрасная идея и вектор для размышлений о том, как понизить количество чертей в час, а не чудесное руководство из трëх пунктов, которое всë решит.

Книга учит размышлениям, но не решениям. Решения — индивидуальны.

У меня есть некая программа и я ищу как фрилансер аналогичные заказы. В каждом новом заказе я беру предыдущий код и дорабатываю его для нового клиента.

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

Вот тут не соглашусь в корне, увы. Описанная ситуация далеко не так страшна, как кажется. Одиночный фриланс с многими заказчиками (а не поддержанием одного проекта 10 лет) прощает адский говнокод. Ибо достаточно просто копировать фрагменты со старых заказов и каждый раз один раз доводить франкенштейна до стабильного состояния в момент сдачи.

Где реально нужна качественная архитектура, так это B2B продукт с кастомизацией под каждого клиента. Ибо надо найти идеальный баланс, чтобы одновременно достаточно гибко удовлетворить каждого, но при этом, все ещё сохранить возможность одновременно исправить баг сразу у всех, если он в общем функционале. Ну и да, работа с разными клиентами по разным версиям API сюда же, внедрение новых фич, которые дублируют кастомные наработки для некоторых клиентов... Много ловушек, в которые попадают практически все, ибо в первые годы любого такого продукта без говнокода не выжить, а понять момент, когда точки масштабирования стабилизировались, и переписать заново не у всех выходит

А теперь представьте, что один и тот же код за 3 года был продан 500 клиентам и у каждого из них свои доработки

Хотите вставит кусок кода отмотав 100 клиентов назад без чистой архитектуры? :)

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

И даже не сильнейший, а наиболее адаптивный, если мы смотрим на плюс-минус длительный период :)

Правильно отметил автор в пункте 2 про DRY. Расширю мысль -- программирование как инженерная задача столь сложна, что она не сводится к набору примитивных аббревиатур и принципов. Пытаемся убрать дублирующий код, т к заметили сходства в двух алгоритмах, а потом нас просят что то изменить в первом, но не трогать второй, и сходства уже меньше, а проблем от объединения кода в один класс или метод все больше. Так же SPR из SOLID, на который некоторые молятся -- и доходят в этом до абсурда, превращая 50 рационально разработанных классов в 500 микро-классов. Да, каждый из этих 500 классов конечно стал проще, но в целом каша из 500 микро-классов стала сложнее чем 50 нормальных. А корень всей этой проблемы в одном -- программирование это сложно, опыт нужно набирать годами, и все равно каждый раз думать головой -- где нужно разбить на два класса, где нужно два объединить в один, а где оставить как есть. Многие же люди, особенно которые недавно "вкатились в айти", думать не хотят, хотят следовать простым принципам, находят их в разных книжках, и потом пытаются найденным молотком забить все, как будто кругом только гвозди.

 проблем от объединения кода в один класс или метод все больше

Есть ещё принцип "работает - не трогай".

Не нужно набирать опыт годами, чтобы принять решение "объединять/не объединять".

Просто ответьте на один вопрос: могут ли у этих двух классов (функций, модулей, структур данных и т.д.) изменяться требования независимо друг от друга? Если ответ "да", то не объединяем, копипаста обязательна. Будущий Вы будет Вам очень благодарен.

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

И снова непонимание ООП, SOLID, DRY. Война против абстракций будет вечна, потому что это требует особых усилий. Проблема не в том, что сделали абстракцию, а в том, как Вы сами отметили, что кто-то зачем-то в абстракцию что-то добавил или изменил. Не надо подменять понятия и суть проблемы. Допустим, это сделали не Вы, тогда кто пропустил на код ревью такое вмешательство в абстракцию? Думаю, вопрос в квалификации. Прекратите воевать с ООП и SOLID.

По пункту № 2. Буква С в СОЛИД как раз и говорит о том что не надо связывать код разных "похожих" модулей. Потому что потом они начнут отличаться. Это же говорил и Мартин Фаулер в книге рефакторинг. Так что мысль старая и хорошая. Согласен что ООП и СОЛИД ни как не противоречит указанным пунктам в статье.

нащёт моков согласен. только что на работу не взяли из-за того что я был настроен не настолько оптимистично о необходимости их использования, предлагая вместо этого другие решения

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

да войны особо нет никакой, это СВО :D:D:D

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

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

Поддержка этой кучи да, не очень, но тут какая альтернатива в плане покрытия множества сценариев?

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

Да вообще-то нет, вполне себе чёткая.

Юнит-тест покрывает одну единицу кода - класс, например. Логика всего, что лежит за его пределами (за исключением совсем уж тривиальных вещей), эту единицу кода не волнует от слова совсем и на её внутреннюю логику не влияет. Её волнуют лишь входы/выходы этого запредельного. Соответственно, запредельное мокируется.

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

По поводу хранения инфы о балансе.

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

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

Не осилил DRY — "придумал" PRY!

Вы серьёзно?

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

У "реальной имплементации" должен быть свой набор тестов, который проверяет, что она соответствует своему интерфейсу

А мне статья не понравилась, я почти ничего не понял. Примеров нет, все крайне в общем виде.

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

По поводу DRY. Очень часто сталкивался с тем, что изначально невинные абстракции потом разрастались до каких-то монстров с таким ветвлением, что уследить за ним могли не только лишь все.

Мне нравится делить приложение на очень слабо связанные слои (или даже пакеты), внутри которых и применять этот принцип по схеме "больше двух повторений - повод задуматься". Однако, если какая-то "общая" на первый взгляд функциональность появляется в разных пакетах, то я буду 100500 раз думать перед тем, как выносить это всё в абстракцию. Иначе это может стать большой головной болью.

"Кто-то с кем-то сделал что-то — ой-ой-ой-ой!
Неизвестно, где-когда…" — так что всё это мура!

Абстракция "с таким ветвлением" — уже не абстракция. А вот дублирование кода головную боль приносит гарантировано. Естественно, если там есть чему болеть :)

Так что давайте примеры в студию, а то какие-то страшилки для самых маленьких получаются.

class Category {
  int $id;
  ...
}

class Product {
  int $id;
  ...
}

Нужно ли выносить int $id в абстракцию BaseEntity? Нет.

class CategoryService {
  public function create(CreateCategoryDto $dto) {
    $category = new Category();
    
    $category->name = $dto->name;
    $category->parent_id = $dto->parent_id;
    
    $this->entityManager->save($category);
  }
}

class ProductService {
  public function create(CreateProductDto $dto) {
    $product = new Product();
    
    $product->name = $dto->name;
    $product->category_id = $dto->category_id;
    
    $this->entityManager->save($product);
  }
}

Нужно ли делать BaseCrudService, который будет вызывать new и save, с абстрактным методом loadDataToEntity($entity, $dto), который переопределяется в наследниках? Нет.

Нужно ли выносить int $id в абстракцию BaseEntity? Нет.

Здесь нет дублирования функциональности. Её (функциональности) здесь в принципе нет.

Нужно ли делать BaseCrudService, который будет вызывать new и save, с абстрактным методом loadDataToEntity($entity, $dto), который переопределяется в наследниках? Нет.

Вы серьёзно?! У вас два раза написан тривиальный код, который практически ничего не делает, и вы всерьёз рассматриваете вариант по созданию базового класса, в котором будет вызываться конструктор? И из-за того, что этот …мммм, как бы помягче… не самый удачный вариант действительно оказывается …не самым удачным, делаете вывод, что DRY это плохо, а дублирование это хорошо? Я вас правильно понял?

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

Её (функциональности) здесь в принципе нет.

Зато поля общие есть. И я встречал случаи, когда их действительно пытаются вынести в общий класс.

и вы всерьёз рассматриваете вариант по созданию базового класса

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

Я вас правильно понял?

Из-за "ммм", многоточий и иносказаний я не понимаю, что именно вы поняли.

в котором будет вызываться конструктор?

Я ничего не говорил про конструктор.

делаете вывод, что DRY это плохо, а дублирование это хорошо?

Нет. Вы потеряли нить обсуждения.
Я делаю вывод, что в этих примерах абстракцию делать не надо.

У вас два раза написан тривиальный код

Неважно, насколько он тривиальный, важно, что обе функции могут (и скорее всего будут) меняться независимо друг от друга. Сегодня он тривиальный, завтра в ProductService появится 20 полей с проверками, уведомление менеджера по email по определенным условиям, и отправка сообщений в другую систему, а в CategoryService так и останется 2 поля.

Нет. Вы потеряли нить обсуждения.

(…)

Неважно, насколько он тривиальный,

Нет, видимо это вы не поняли, о каких примерах шла речь. Или сделали вид — не знаю.

Вы спросили про примеры, когда не надо делать абстракцию, я привел примеры.

Можно было еще привести пример метода тепа Calculator >> twoPlusTwo return 2 + 2. — тоже пример кода, в котором "не надо делать абстракцию" (на самом деле, даже в нём надо).

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

Абстракцию делать надо. Просто абстракция — это не вынос родительского класса.

обе функции могут (и скорее всего будут) меняться независимо друг от друга

Любые две функции, пока они две разные "функции", будут меняться независимо. И именно поэтому, надо выявлять куски кода (не обязательно "функции"), которые связаны между собой и искать способ избавляться от этого дублирования.

Сегодня он тривиальный, завтра (…)

YAGNI.

У нас с вами здесь спора не получится. Я подозреваю, что мы придерживаемся принципиально разных взглядов на разработку и даже вкладываем разный смысл в одни и те же термины. Предлагаю просто не тратить ни ваше, ни моё время.

видимо это вы не поняли, о каких примерах шла речь

Это написано прямым текстом в первом комментария этой ветки - "изначально невинные абстракции потом разрастались до каких-то монстров с таким ветвлением". Я привел как раз такой пример, который изначально выглядит невинно.

Просто абстракция — это не вынос родительского класса.

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

Родительский класс это наиболее частый способ "доабстрагирования".

YAGNI

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

Можно было еще привести пример метода тепа

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

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

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

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

Предлагаю просто не тратить ни ваше, ни моё время.

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

Хорошо, уговорили, потрачу ещё какую-то часть своего бесценного времени в надежде, что это кому-нибудь чем-нибудь поможет.

Это написано прямым текстом в первом комментария этой ветки

Читаем внимательно и не выдёргиваем из контекста. Комментарий начинается словами:

По поводу DRY.

Про что разговор? Про DRY!

Так что

Это еще одно подтверждение, что вы потеряли нить обсуждения.

— верно, но только относительно вас. Ну, почти верно — так как вы нить обсуждения, похоже, и не находили. Без обид, по факту.

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

(…)

Покажите вашу версию этого кода с избавлением от дублирования,

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

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

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

Чтобы точно судить, есть ли дублирование в той системе, надо смотреть контекст: как используются, как работают и на сколько связаны между собой Категория и Продукт. И, если дублирование всё же обнаружится, то способ избавления от него всё равно должен будет подбираться по тому же контексту… 

…Но если я начну углубляться в эту тему, придётся говорить о плохих формулировках принципа DRY (например, о каких "повторениях информации различного рода" идёт речь и до каких приделов предлагается это повторение "снижать" в статье на Wikipeda?), проводить исследования о реальных истоках и основаниях этого принципа, о диффузии и инверсии смысла… — это очень долго и в данном контексте не имеет большого смысла.

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

Тут всё зависит от того, как много у вас классов объектов, которые нужно уметь создавать из соответствующей DTO-шки и сохранять. И на сколько сильно повторение этой "процедуры" влияет на деятельность разработчиков.

Если у вас таких объектов не много (например, как в вашем примере, ровно два), то я бы так делать не стал, а записал бы в "тех.долг".

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

Для этого я бы создал объект, который знает, как создать и сохранить правильный объект из DTО. И, кстати, это была бы единственная функциональность этого объекта — гарантия, что он не разрастётся до пресловутого "монстра".

Разумеется, я бы не стал создавать базовый класс для упомянутых в вашем сервисов и выносить эту функциональность туда. Как именно такой объект был бы реализовано зависит от используемого языка и его объектной модели. В Java с её якобы "строгой типизацией" пришлось бы подумать и поизвращаться (и это ещё один аргумент отложить избавление от такого дублирования до того момента, когда связанные с ним неудобства начнут перевешивать расходы на избавление от него …но, обращу внимение, отложить, а не вообще забить на это дублирование, или, тем более, утверждать, что это нормально и даже хорошо). А, например, в Smalltalk-е и Ruby (по второму не уверен, это, скорее, гипотеза) всё было просто и органично. Как это сделать в PHP (вы же на нём свои примеры привели?) — без понятия, никогда не имел с ним дела.

Кстати, по поводу решения о том, нужно ли избавляться от дублирования немедленно или лучше отложить, есть известная (сейчас не помню, где оно изложено — при необходимости найду) эвристика: если ты первый раз заметил дублирование (то есть, обнаружил два сходных по смыслу "куска кода"), то запомни этот факт. если обнаружил тот же дублирование ещё раз (есть три похожих по смыслу куска) — уже есть смысл повнимательнее исследовать и решить, есть ли там дублирование. Для себя я к нему добавил такой пункт: если я в какой-то момент осознаю, что уже не могу легко и быстро вспомнить/найти все места, где просходило такое дублирвоание, то выношу задачу по избавлению от него наверх своего бэклога. На практике неплохо работает.

Я не знаю, зачем вы начали спорить.

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

DRY предлагает некоторый критерий для обнаружение "плохого" кода. Но вообще, формулировки этого принципе мне не нравятся — они переусложнены, запутывают и скрывают настоящий смысл. Но, как я уже сказал, это отдельная большая тема. Избавлять от разного рода "плохого" дублирования (в идеале — от любого) необходимо. Объяснить, как именно, находить дублирование и оценивать его — сложно. Но это не значит, что не нужно вообще. Просто понимание придёт с практикой.

(Раз уж упомянул и его тоже затронули.) YAGNI представляет собой гипотезу, что не надо пытаться предугадывать (фантазировать про) возможные будущие проблемы, а делать только то, что нужно здесь и сейчас.

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

Но понять и освоить нужно правильно. И исходное высказывание про DRY, и приведённые вами "примеры" является как раз иллюстрацией неправильного понимания идеи и, соответственно, неадекватной её критики.

Например, ваши примеры и высказывания говорят о том, что вы не очень хорошо понимаете, о каком дублировании идёт речь. Я вбросил словосочетание "кусок кода". Вы незамедлительно за него ухватились, ведь ваши примеры — это тоже куски кода. Но тогда тогда и любая буква где-то в коде — это тоже "кусок кода", не так ли? И, следуя DRY, нужно избавляться от всех повторов этой буквы? Судя по вашим примерам и высказываниям , вы должны ответить положительно. А по-моему — нет.

Основываясь на такой доведенной до абсурда трактовке DRY, можно сделать вывод, что это принцип вообще плохой. Где-то в комментариях уже предлагали заменить его на PRY. Но это лишь логическая ошибка. Либо намеренно используемый полимеческий манипулятивный приём.

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

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

DRY предлагает некую эвристику, помогающую определить, что, возможно, следует почистить зубы. А YAGNI говорит, что не стоит переживать, что из-за чистки зубов вы попадёте под автомобиль.

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

Разве это не очевидно? ;)

- По поводу DRY.
- Про что разговор? Про DRY!

Правильно. Теперь откройте текст статьи и прочитайте, что там написано по поводу DRY.

А потому что пример неудачный — он иллюстарирует ситуацию, не попадающую под этот принцип.

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

Повторяющаяся последовательность символов в вашем коде — это ещё не дублирование

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

Просто по приведённому фрагменту об этом невозможно судить.

Вот в том и смысл этого примера, что можно. В них содержится столько информации, сколько есть на старте проекта.

есть известная эвристика
если обнаружил тот же дублирование ещё раз (есть три похожих по смыслу куска) — уже есть смысл повнимательнее исследовать

Это написано в первом комментарии, на который вы отвечали: "больше двух повторений - повод задуматься".
Что еще раз говорит о том, что вы не следите за дискуссией и спорите о чем-то своем.

Судя по вашим примерам и высказываниям , вы должны ответить положительно.

Нет, не должен. Это тоже говорит о том, что вы спорите о чем-то своем.

Как это сделать в PHP — без понятия, никогда не имел с ним дела.

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

YAGNI представляет собой гипотезу, что не надо пытаться предугадывать (фантазировать про) возможные будущие проблемы

YAGNI
"a principle which arose from extreme programming that states a programmer should not add functionality until deemed necessary."

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

Для этого я бы создал объект, который знает, как создать и сохранить правильный объект из DTО.

Понятно, вместо 20 строк кода вы решили написать много текста. Видимо кода будет еще больше. Это хорошо показывает цену вашего подхода и правильность утверждения про монстра.

Правильно. Теперь откройте текст статьи и прочитайте, что там написано по поводу DRY.

Теперь откройте мой комментарий и прочитайте, что там написано. Соотнесите. Подумайте. Попробуйте понять. Я ещё долго могу продолжать, но, может, всё-таки откажемся от императивного стиля? ;)

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

Неправильно. Теперь откройте текст статьи и комментариев и прочитайте, что там написано.

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

У вас наблюдается удивительная "сверх"-способность видеть и понимать то, что не было написано, и не понимать то, что написано :)

Без особой надежды на понимание постараюсь еще раз — максимально просто и кратко…

========================= Начало пояснения

В статье предлагается следующая логика (это не цитата, просто других способов выделить текст не нахожу):

У меня не получается применять принцип DRY, следовательно он плохой. Его использовать не надо, это даже почти грехом объявляется :) И предлагается заменить на PRY.

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

А вот с комментарием, на который я ответил, я вообще не спорил. Где вы это вообще обнаружили? …А, ну да — сверх-способности же ;) Простите.

Я всего лишь попросил пример, когда появившиеся в результате адекватного применения DRY …

…изначально невинные абстракции потом разрастались до каких-то монстров с таким ветвлением, что уследить за ним могли не только лишь все.

В ответ на это вы привели абсолютно неадекватные примеры, на что я вам и указал. И тут понеслось… :D

Адекватным примером был бы:

  • Кусок функциональности, к которому имеет смысл применить DRY

  • Результат этого рефакторинга — "невинная абстракция"

  • Какой из этого в итоге получился монстр, и его связь с предыдущим рефакторингом

========================= Конец пояснения

А с чем я действительно готов спорить по поводу DRY, я подробно объяснил ранее. Но вы это проигнорировали, как это модно было говорить, "чуть более, чем полностью".

Вот в том и смысл этого примера, что можно. В них содержится столько информации, сколько есть на старте проекта.

Если действительно так, то это печально. Остаётся только пожалеть вас и всех, кто связан с подобными проектами. Но я всё же подозреваю, что вы утрируете. …Но этим, в общем-то, и ограничивается связь приведённого вами ужасающего факта с дискуссией по поводу DRY.

Это написано в первом комментарии, на который вы отвечали: "больше двух повторений - повод задуматься".

Да, согласен. Занесло :) Но если вы так и не поняли, то этот момент у меня возражений как раз не вызывал.

Что еще раз говорит о том, что вы не следите за дискуссией и спорите о чем-то своем.

У вас какие-то проблемы с логикой? То, что я повторил и более подробно раскрыл мысль, вскользь упомянутую выше и не имеющую прямого отношения к обсуждаемой проблеме, говорит именно об этом? Другие варианты исключены?

Нет, не должен.

Если вы не отвечаете нет, то это говорит о том, что ваши "примеры" неадекватны. Что я с самого начала до вас и пытался донести — об этом выше.

Остаётся только понять, вы эти привели по недоразумению или намеренно, для троллинга? Ну, чтобы завязать спор на пустом месте и всеми неправдами пытаться в нём победить. Ок, вы победили. На этом завершим? Я ниже только ещё отвечу на некоторые интересные моменты. Но можем считать, что на результат это не влияет — вы блистательно победили!

Это тоже говорит о том, что вы спорите о чем-то своем.

Это тоже говорит о том, что вы так и не поняли или не захотели понять смысл моего комментария.

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

В моей системе на Smalltalk-е DTO скорее всего не было бы в принципе. Но если вам так хочется кода, пожалуйста:

EntityService >> createFrom: aDto
model = mapper createModelFrom: aDto.
entityManager save: model.
^ model

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

Вы предлагаете не добавлять новую функциональность, а не устранять дублирование — это рефакторинг, так как на существующую функциональность это действие как раз влиять не должно.

А делать это, точнее не делать, вы предлагаете из каких-то смутных фантазий о том, что когда-то в будущем это может привести к проблемам. Вот здесь и возникает You Are Not Gonna Need It — решать какие-то смутные будущие проблемы.

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

Понятно, вместо 20 строк кода вы решили написать много текста.

Не, зачем 20? 4-х достаточно. Но Вам это сильно помогло? Возможно, для вас это станет сюрпризом, но смысл кода не всегда ясен из самого кода. Иногда нужен контекст. А это будет значительно больше 20 строк.

Видимо кода будет еще больше. Это хорошо показывает цену вашего подхода и правильность утверждения про монстра.

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

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

Теперь откройте мой комментарий и прочитайте, что там написано.

Я читал. Выглядит так, что вы считаете, что собеседники считают, что DRY применять вообще никогда не надо, и пытаетесь доказать, что надо.

В статье предлагается следующая логика
У меня не получается применять принцип DRY, следовательно он плохой

Нет. Именно это я объяснял в предыдущем комментарии.

может, всё-таки откажемся от императивного стиля?

Его начали вы фразой "Читаем внимательно и не выдёргиваем из контекста".
Отказывайтесь, я совершенно не против, если вы начнете объяснять свою точку зрения без иносказаний.

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

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

А вот с комментарием, на который я ответил, я вообще не спорил.

Я не сказал, что вы спорите с первым комментарием. Вы спорите в ответе на мои комментарии, а я говорю то же самое, что написано в первом, при этом вы тоже говорите то, что написано в первом.

В ответ на это вы привели абсолютно неадекватные примеры, на что я вам и указал.

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

Адекватным примером был бы:
Кусок функциональности, к которому имеет смысл применить DRY

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

Какой из этого в итоге получился монстр

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

Остаётся только понять, вы эти привели по недоразумению или намеренно, для троллинга?

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

Но если вам так хочется кода, пожалуйста:

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

Код вызова этого сервиса конкретно для Category и Product тоже надо привести, потому что запросы из браузера идут конкретно для них. Мои сервисы напрямую пробрасываются в API-контроллеры, класс DTO явно задан в методе контроллера, код там совершенно стандартный, поэтому я его не привел. Могу привести, если интересно.

А я вам покажу, как этот маппер будет выглядеть.

Mapper >> createModelFrom: aDto
    aDto switch
      case [isMemberOf: CreateCategoryDto]: then: [
        model = Category new.
        model at:#parent_id = dto at:#parent_id.
        model at:#name = dto at:#name.
      ]
      case [isMemberOf: CreateProductDto]: then: [
        model = Product new.
        model at:#category_id = dto at:#category_id.
        model at:#name = dto at:#name.
      ]      
   ^ model

Вот вам и упомянутое ветвление.

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

Потом появляется DTO компании, где телефоны приходят в виде одной строки, а сохранять их надо по отдельности в таблицу phones, а не в поле сущности Company, потом DTO изображения к товару с файлом, который надо сохранять в таблицу файлов и загружать в файловое хранилище прежде чем писать id файла в сущность ProductImage, и таких вариантов много. Судя по тому, что вы считаете это фантазиями, вы никогда такие приложения не писали.

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

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

Полезного спора как с самого начала не получалось, так до сих пор, к сожалению не намечается.

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

Вы просили примеры, я их привел. Если вам непонятен их контекст, можно было задать вопросы. Если все понятно, но вы с ними не согласны, надо было написать свой вариант с вынесением абстракций. Полностью всю функциональность, а не сферический код в вакууме. И сказать "Вот я вынес, нет никакого монстра - А как надо будет поменять, если у бизнеса появятся такие-то требования? - Вот так - А в моем варианте вот так". Тогда бы каждый мог сравнить как было и как стало.

Это полезно и конструктивно. А делать умный вид с высказываниями "Если действительно так, то это печально" это неконструктивно. Печально, что приходится объяснять ИТ-специалистам такие банальные вещи.

Я не слился ;) Отвечу позже (когда будет время) и, скорее всего, не здесь — в рамках комментариев уже стало совсем тесно. Ссылку, разумеется, сюда скину.

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

Извините, конечно :) но я буду делать то, что интересно и нужно мне. То, что вас интересует, "меня интересует меньше. Интересует. Но меньше." (с)

У меня нет цели убедить лично вас в чём-то или победить в это "споре". За 25 лет в программировании я принял участие в разработке довольно большого количества проектов. По крайней мере, достаточное для того, чтобы сформировать своё понимание, много раз уточнить/скорректировать и проверить его. Данная дискуссия навела меня на некоторые мысли и даже, в конечном счёте, на очередные корректировки. Ими я и собираюсь поделиться. Но удовлетворение ваших запросов — это ваше собственное дело.

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

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

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

Обсуждать пример имеет смысл в некотором контексте. А контекст довольно большой и продолжает расти.

А если без контекста… Ну, вот я ваш код заменю на такой:

Mapper >> createModelFrom: aDto
^ (self modelClassBy
Dto: aDto) from: aDto.

Вы же опять скажете, что всю сложность "замёл под ковёр"? Хотя, по факту, это и есть абстракция через распределение ответственности между объектами. Там придётся подтягивать ещё и обсуждение "анемичные модели (по факту процедурное программирование) против ООП (это я вам в самом начале писал, вот оно — вылезает потихоньку).

Вся наша перепалка уже давно свелась к вопросам о том, что понимать под "дублированием" и любое ли "дублирование" можно устранить, упростив при этом систему", а так же"что хотел сказать автор?".

Первые два просто не помещаются в формат комментариев.

А по последнему — это про

Выглядит так, что вы считаете, что собеседники считают, что DRY применять вообще никогда не надо, и пытаетесь доказать, что надо.

Если не вдаваться в детали, примерно так и есть.

Автор статьи, даже если хотел сказать что-то другое, явно и однозначно предложил отказаться от DRY. Других вариантов трактовки смысла написанного под заголовком (которого, в принципе уже достаточно) "Да, пожалуйста, повторяйтесь" я при всём желании найти не могу. Хотя честно пытался (ещё до того, как меня таки угораздило втянуться в эту перепалку). Но получилось сгенерировать лишь две версии такого “совмещения”:

  1. В одних случаях не повторяемся, в других повторяемся. В этой идее, кстати (как я раньше уже писал) в нет ничего нового, она изначально заложена в идее “устраняем дублирование”, так как хочешь-не хочешь, но сначала придётся определиться, что понимать под дублирование. Очевидно же, — и про это я тоже уже писал — что, например, устранять дублирование отдельных символов в коде не имеет смысла. Но у автора статьи про это нет ни слова — не то, что про определение понятия “дублирование” или критерий применимости DRY, даже про саму возможность такого выбора. То ест, здесь просто предлагается заменить DRY на PRY. Я где-то здесь ошибаюсь?

  2. Двоемыслие. Этот вариант вряд ли заслуживает подробного рассмотрения — не так ли?

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

Почему же я приравниваю изложенную в том комментарии точку зрения к призыву отказа от DRY? По той же причине — отсутствия чёткого критерия применимости этого принципа. Конкретно, что предлагает автор комментария:

Мне нравится делить приложение на очень слабо связанные слои (или даже пакеты) /…/ Однако, если какая-то "общая" на первый взгляд функциональность появляется в разных пакетах, то я буду 100500 раз думать перед тем, как выносить это всё в абстракцию. Иначе это может стать большой головной болью.

То есть, предлагается устранять дублирование только внутри отдельного “пакета”, который кто-то когда-то по каким-то причинам решил выделить. Это, конечно, не такой категорический отказ, какой предлагается в статье, но тем не менее именно отказ, так как “100500” представляет собой практически синоним слова “бесконечность”, В купе с неясными критериями деления по пакетам такой подход делают критерий применимости DRY абсолютно размытым и, по сути, даёт зеленый свет к тому же самому отказу от DRY по принципу "Не знаешь, как устранить дублирование? Просто забей!"

Кроме того, в этом комментарии содержится серьезный логический изъян (или когнитивное искажение) — на что я тоже ранее уже указывал (как же часто мне приходится повторять то, что я ранее всё это уже излагал!):

По поводу DRY. Очень часто сталкивался с тем, что изначально невинные абстракции потом разрастались до каких-то монстров с таким ветвлением, что уследить за ним могли не только лишь все.

Между "устранением дублирования" и "превращением абстракции в монстра" здесь нет причинно-следственной связи. Устранение DRY — это рефакторинг, который должен упрощать систему, так дублирование — это как раз усложнение.

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

Возможно, и эти авторы, и вы пытались сказать что-то другое? Возможно. Но в таком случае это просто не удалось сделать. Разумеется, с моей — субъективной и не претендующей на окончательную истинность — точки зрения.

P.S.: Да, опять получилось много букв (и это только мЕньшая часть из уже написанных) и мало кода. Но это не потому, что, (как вам явно хочется думать) я не умею его писать или редко это делаю или пишу "игрушечный" код. Это не так. Просто вы сами задали такие условия "игры" …что — уже надоело, но я ещё раз это отмечу — я уже не раз здесь заявлял.

Вся наша перепалка уже давно свелась к вопросам о том

Нет, это вы пытаетесь уйти от ответа и перевести разговор с изначальной на эту тему.

Ну, вот я ваш код заменю на такой
Хотя, по факту, это и есть абстракция

Вы похоже так и не поняли, о чем идет речь, и не хотите слушать собеседника, когда он это объясняет. Это и делает дискуссию бессмысленной.

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

На остальное отвечать не буду, это оффтоп и уход от ответа. Покажите код полностью, в том числе то, что у вас там переносит значения из DTO в модель, тогда я вам ткну пальцем где и при каких требованиях там будет сложность. Не хотите, как хотите. И да, "(self modelClassByDto) from" это тоже ветвление.

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

Гениально! Так вы собираетесь заставлять меня писать код до каких пор? До кода виртуалной машины? Или инструкций процессора? Может до схемы на платах компьютера и устройства процессра? Ведь на каждом этапе будет отсылка на что-то сложное, очень часто даже гораздо более сложное, чем исходная задача. Вы действительно не понимаете, что такое абстракция? Не понимаете разницу между essential complexity и accidental complexity? Не понимаете, что первую устранить можно, только изменив постановку задачи, и с ней приходится жить? Что эту сложность побороть можно (только?) методом "разделяй и властвуй"? Что суть ОО-дизайна состоит как в распределении сложности между объектами и затем их комбинации?

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

Mapper >> modelClassByDto: aDto
^ modelClassByDto at: aDto class.

Mapper class >> newWithMapping: classToClassDictionary
^ serlf new
modelClassByDto: classToClassDictionary

Вот инициализация, а то ж вы не поверите:

Applicaiton >> initialize
self mapper: Mapper newWithMapping: (
Dictionary newFrom: {
CategoryDto -> Category.
ProductDto -> Dto.
}
).

Как пройтись переменным экземпляра DTO и передать их значение в конструктор модели, расписывать, надеюсь, не нужно?

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

В очередной раз объяснять вам, что это всё в принципе нереалистично, наверное, бесполезно? Уверен, что да. Но вдруг это еще кто-то будет читать. Оставлю как примечание на такой случай.

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

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

Вы действительно не понимаете, что такое абстракция?

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

- Покажите код полностью, в том числе то, что у вас там переносит значения из DTO в модель
- Как пройтись переменным экземпляра DTO и передать их значение в конструктор модели, расписывать, надеюсь, не нужно?

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

Так вы собираетесь заставлять меня писать код до каких пор?

Я это повторил уже раз 10 - до тех пор, пока не приведете пример полностью. У меня в примере 2 класса, которые реализуют функциональность "Создание категории" и "Создание товара" по входным данным. Такой код есть в любой админке веб-приложения, и не только в админке. Там есть код переноса значений из входных данных в модели. Если у вас нет этого кода для товара и категории, значит ваш пример приведен не полностью. Если у вас нет вызова этого кода, значит ваш пример приведен не полностью. Возьмите код моего примера, переведите на свой язык, сделайте рефакторинг как считаете нужным, приведите весь код, который получился. Странно, что приходится это объяснять специалисту в области программирования.

Я уже едва сдерживаюсь, но ладно, пока есть силы, продолжим эту игру :D

Category class >> from: categoryDto
^ self new
name: categoryDto name.
parentId: categoryDto parentId.

Product class >> from: productDto
^ self new
name: productDto name;
categoryId: productDto categoryId.


Вам именно этого не хватало для полного счастья?

Или нужно добавить, что если, например, делать раз за разом одно и то же (например, клепать админки), и он заметит, что создание админок — это на …(подставьте нужное значение)% и есть дублирование, и решит это дублирование вынести в отдельный фреймворк, то в нём появится примерно такой код:

DtoReader >> create: aModelClass from: aDto
^
self initialize: aModelClass new from: aDto.

DtoReader >> initialize: anObject from: aDto
valuesByName := aDto ivarWithValues.
anObject ivarNames do: [:ivarName |
valuesByName
ifKeyPresent: ivarName
doWithValue: [:value |
anObject at: ivarName put: value ].
].


…А Category, Product и все другие классы, в которых будет требоваться функциональность чтения из DTO, будут в этом фрейворке регистрироваться. И этот фрейворк будет в нужных точках с помощью DtoReader-а создавать их экземпляры? (Да, это был вопрос.)

Код этого фреймворка я приводить по понятным (тем, кто захочет понять) и даже очевидным причинам. Тем более, что при написании такого фрейворка в нормальной объектной среде в принципе не возникнет никакой необходимости в DTO.

…:Будет 11-е повторение? Если будет, вы уж уточните тех.задание. А то я спрашиваю-спрашиваю, реализацию какой именно функциональности и в каких именно рамках вы так жаждете увидеть, а вы всё игнорируете и игнорируете. Всё повторяете одно и то же, и повторяете. Я, вроде бы понимаю, а вы говорите, что нет. И опять повторяете то же самое.

(Безотносительно данной перепалки и не в упрёк…)

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

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

Если же это делается не намеренно, то, проанализируйте — вдруг это подтвердиться, и вы захотите это исправить.

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

Опять какой-то уход от изначальной темы. Я не спрашивал ни про какой фреймворк, мне он не нужен.

в нормальной объектной среде в принципе не возникнет никакой необходимости в DTO

Похоже вы не понимаете, зачем нужны DTO. От объектной среды это не зависит.

то в нём появится примерно такой код:

Ага, потом приходит бизнес и говорит "При создании компании во входных данных будет приходить поле phones, фронтенд там будет присылать массив телефонов, надо сохранять их в отдельную таблицу company_phones". И весь ваш фреймворк идет на …(подставьте нужное значение), потому что такого поля в классе Company нет.

Потом приходит еще раз и говорит "При создании товара надо в поле user_id указывать пользователя, который его создал". И весь ваш фреймворк идет на …(подставьте нужное значение), потому что текущий пользователь не приходит в DTO, а берется из сессии.

Product class >> from: productDto

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

Код
Applicaiton >> initialize
    self mapper: Mapper newWithMapping: (
        Dictionary newFrom: {
          CategoryDto -> Category.
          ProductDto -> Dto. 
        }
    ). 


Mapper class >> newWithMapping: classToClassDictionary
 ^ self new
     modelClassByDto: classToClassDictionary

Mapper >> modelClassByDto: aDto
 ^ modelClassByDto at: aDto class.

Mapper >> createModelFrom: aDto
 ^ (self modelClassByDto: aDto) from: aDto.

    
EntityService >> createFrom: aDto
    model = mapper createModelFrom: aDto.
    entityManager save: model.
    ^ model

    
Category class >> from: categoryDto
  ^ self new
      name: categoryDto name.
      parentId: categoryDto parentId.

Product class >> from: productDto
  ^ self new
        name: productDto name;
        categoryId: productDto categoryId.

Теперь надо добавить user_id в класс Product. Он берется из сессии, а не из ProductDto.

Hidden text

Я делаю так.

class ProductService {
  public funciton __construct(
+   private SessionManager $sessionManager,
  ) {}
  
  public function create(CreateProductDto $dto) {
    ...
+   $product->user_id = $this->sessionManager->getCurrentUser()->id;
  }
}

SessionManager автоматически пробрасывается через DI, и в тестах легко заменяется на мок, настроенный для конкретно этого теста.

Что делаете вы? Правильно, глобальную переменную в статическом методе Product:from. Ну или статическую для какого-то класса, что примерно то же самое, разница только в префиксе.

Product class >> from: productDto
  ^ self new
      name: productDto name;
      categoryId: productDto categoryId;
+     userId: gSessionManager getCurrentUser id.

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

Или второй аргумент currentUser для методов from во всех классах моделей, даже где он не нужен.

Mapper >> createModelFrom: aDto
+   ^ (self modelClassByDto: aDto) from: aDto  currentUser: (self sessionManager getCurrentUser).

Вот и появилось усложенение.

Или поле user_id в aDto с логикой его заполнения в контроллере. Код уж приводить не буду.

Теперь после сохранения товара в базу надо отправлять его через Кафку в другую систему.

Hidden text

Я делаю так.

class ProductService {
  public funciton __construct(
    private SessionManager $sessionManager,
    private SessionManager $htmlPurifier,
+   private KafkaProducer $kafkaProducer,
  ) {}
  
  public function create(CreateProductDto $dto) {
    ...
    $entityManager->save($product);
    
+   $kafkaProducer->send('productTopic', $product->toArray());
  }
}

Что делаете вы? Видимо еще один маппер с ветвлением, общий для всех моделей и методов EntityService, который будет решать, что отправлять, а что нет.

EntityService >> createFrom: aDto
    model = mapper createModelFrom: aDto.
    entityManager save: model.
+   kafkaMapper onCreate model: model.
    ^ model

+KafkaMapper >> model
+  model switch
+      case [isMemberOf: Product]: then: [
+        self kafkaProducer send topic: 'productTopic' data: model toArray: 
+      ]
+      case [...]: ...
+   .

В результате другой программист не может сразу понять, какие действия происходят при создании товара, ему приходится лазить по системе и всё изучать.

Теперь надо сделать загрузку изображений к товару.

Hidden text

Я делаю так. Существующий код остается без изменений.

class ProductImageService {
  public function create(Product $product, CreateProductImageDto $dto) {
    $file = $this->awsFileUploder->uploadFile($dto->filePath);
    
    $productImage = new ProductImage();
    $productImage->product_id = $product->id;
    $productImage->file_id = $file->id;
    $productImage->sort_pos = $product->getLastImage()->sort_pos + 1;
    
    $this->entityManager->save($productImage);
  }
}

Что делаете вы? Переписываете Mapper и EntityService, потому что они не учитывают зависимость от другой модели, и все методы from. Глобальные переменные тоже присутствуют.

+Mapper >> createModelFrom: aDto parentModel
+   ^ (self modelClassByDto: aDto) from: aDto parentModel: parentModel

+EntityService >> createFrom: aDto parentModel
+   model = mapper createModelFrom: aDto parentModel: parentModel.
    
    entityManager save: model.
    kafkaMapper onCreate model: model.
    ^ model
    
+ Category class >> from: productDto notUsedParentModel

+ Product class >> from: productDto notUsedParentModel

ProductImage class >> from: productDto product
  file = gAwsFileUploader uploadFile path: (productDto filePath)
  
  ^ self new
      product_id: product id;
      file_id: file id;
      sort_pos: product getLastImage sort_pos + 1;

Или 2 набора методов createFrom/createModelFrom с одним и двумя аргументами и копипастой логики. Код уж приводить не буду.

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

И это мы еще не начинали про сортировку изображений, где в DTO приходит массив ProductImage::id в нужном порядке, про бизнес-валидацию с возвратом сообщений пользователю, про фильтры списка сущностей, где в DTO 2 поля createdFrom/createdTo, а в модели одно createdAt, и т.д. Ваши универсальные сервисы там никак не помогут, и у вас все равно будет 2 сервиса - универсальный и специфичный для сущности.

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

Может быть вы такие приложения не пишете, и для вас такие требования это фантазии.

Пишу. И если бы вы только захотели, вы бы поняли, что “фантазиями” я называю преждевременные и необоснованные предположения о том, как система может развиваться в будущем. А не то, что было удобно увидеть вам.

У меня они встречаются в каждом приложении.

…И вы в каждом приложении дублируете их реализацию. Понимаю. Это делают (почти) все.

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

Неверное утверждение, основанное на неверных посылках.

Надеюсь, вы не рассчитывали, что я буду вникать в перипетии вашей задачи? Уверен, что нет. Почти уверен, что это нужно лишь для того, чтобы в очередной раз сказать, что я "ухожу от темы". Что, кстати, тоже является ложным утверждением.

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

К тому же смысла в таких разбирательствах относительно изначально поднятой темы (sic! не темы решения ваших конкретных примеров-упражений, а темы про DRY) я не вижу.

Во-первых, как вы верно заметили, подобное происходит буквально в каждом проекте. И при этом есть люди, которые утверждают, что дублирование устранять необходимо. Значит, для них это работает.

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

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

В общем, этот "мой" (если бы это был действительно мой, я бы считал, что жизнь прожита не зря; но я, к сожалению не автор, а всего лишь пользователь) метод как раз обеспечивает и простоту внесения изменений в код (как раз за счёт устранения дублирования), и упрощает понимание (за счёт абстрагирования).

Очередной "уход от темы" завершён, на этом разрешите откланяться.

И если бы вы только захотели, вы бы поняли, что “фантазиями” я называю преждевременные и необоснованные предположения о том, как система Ю может развиваться в будущем.

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

Мне, очевидно, некогда разбираться в деталях ваших задач.

В таком случае я не понимаю, зачем вы развели весь этот флуд. Нельзя что-то объяснить собеседнику, если вы не хотите его слушать, и говорите о чем-то своем.
Вы сами спросили примеры того, что люди подразумевают усложнением абстракций, а теперь отказываетесь их разбирать. Зачем спрашивали тогда. Это риторический вопрос, ответ на него вряд ли у вас есть.

И вы в каждом приложении дублируете их реализацию. Понимаю.

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

В этом нет ничего страшного или плохого.

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

на любое предложенное решение конкретной задачи вы будете отвечать накидыванием новых задач по типу "а если пришёл менеджер/бизнес/кто-то и докинул требовний"

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

"мой" метод как раз обеспечивает и простоту внесения изменений в код (как раз за счёт устранения дублирования), и упрощает понимание (за счёт абстрагирования)

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

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

Очередное выдёргивание, переворачивание, повторение одних тех же уже множество раз опровергнутых тезиов. На этом всё. Удачи вам в вашем нелёгком труде, который вы успешно делаете ещё более тяжёлым.

Sign up to leave a comment.