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

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

Долго думал, в Управление проектами или в Разработку, ну пусть лежит здесь

Главное, что опубликовано вовремя.

Работаю с двумя реализациями IoC, Ни в одной не встречал ситуации «у нас все плохо, но мы не скажем почему», скорее всего по тому, что живых ситуаций потери зависимостей не так много и все они разжевываются новичкам буквально в первые недели работы. Гораздо сложнее объяснить про контекстно зависимость для части классов. Вот тут реально такие варианты решения «проблем» видел, что хоть вешайся. Тут остро стоит вопрос «я новичок и должен показать, что я умею круто программировать», в итоге — свои реализации стандартных контейнеров, обязательная «самая правильная» реализация синглтона (я уж не знаю, возможно это какое-то неформальное соревнование). А размер проекта вообще на сложность не влияет, если он изначально написан модульно.
Вот вы пригласите почитать кого-нибудь со стороны почитать ваш код, и пусть он скажет. :)

А размер проекта вообще на сложность не влияет

Вот это поворт :)
Вот вы пригласите почитать кого-нибудь со стороны почитать ваш код, и пусть он скажет. :)

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


Вот это поворт :)

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


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

и пусть он скажет

Самое забавное, что все (ну ладно, многие) считают, что они пишут для себя. Отсюда и такое как у Вас отношение. Пишите так, чтобы другие сказали, что вы пишете понятно. Исли изначально исходить из таких предпосылок, то и вопросов о читаемости не возникнет.
Вот это поворт

Ну да. Тут главное понять что такое «большой проект» и «сложный проект» — полтора миллиона строк для управления продажами или 12 тысяч строк на ассемблере, которые будут работать на орбите в течении 3х лет. Так вот второе это «сложный», а первое совсем наоборот. Довести любой учетный проект до состояния точки невозврата возможно, но довольно быстро возникает ситуация, когда само управление изменениями становится большей проблемой чем, собственно, внесение изменений. Тут мы вспоминаем про Фаулера и начинаем излагать умные мысли, хотя на самом деле нужно было изначально подумать «а как мы будем изменять», ведь изменение — основа всех учетных систем. И IOC в данном случае как раз путь к решению этой проблемы на уровне архитектуры
Самое забавное, что все (ну ладно, многие) считают, что они пишут для себя. Отсюда и такое как у Вас отношение. Пишите так, чтобы другие сказали, что вы пишете понятно. Исли изначально исходить из таких предпосылок, то и вопросов о читаемости не возникнет.

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

Тут мы вспоминаем про Фаулера и начинаем излагать умные мысли, хотя на самом деле нужно было изначально подумать «а как мы будем изменять», ведь изменение — основа всех учетных систем. И IOC в данном случае как раз путь к решению этой проблемы на уровне архитектуры

Задним умом мы всегда умные. Но, как правило, ближе к запуске проекта ни бюджета, ни времени, ни сил уже нет на то, чтобы переделывать архитектуру.
С точки зрения архитектуры я привел стати авторов, которые утверждали, что IoC вполне может применяться, если, например, может быть несколько стратегий поведения.
Ну не знаю насчет сложности того же Prism, он сейчас в опенсорсе и заметно похудел. Но однажды мне пришлось полностью переписать DAL (заменить Refit на RestSharp) и я благодарил вселенную за то, что люди изобрели IoC, потому что вся процедура заняла буквально час-два. Даже не знаю, сколько пришлось бы ковыряться в противном случае.
Даже не знаю, сколько пришлось бы ковыряться в противном случае.

Replace by RegExp? По времени долно быть также.
Что обычно надо поменять
import package.SomeService

тут все ясно, с RegExp нет проблем

someService.someMethod(...)

тут тоже нет особых проблем

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

под слабой связанности вы тут именно cohesion или coupling имеете ввиду?


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

Интересно каким образом DI/IoC нарушает инкапсуляцию. Клиентский код как не знал о зависимостях используемых объектов так и не знает. Точно так же как объект который мы хотим получить не должен знать ничего о жизненном цикле своих зависимостей.


Ну и IoC опять же способствует тому чтобы скрывать от объектов не интересующие их вещи. Сам принцип Don't call us we call you об этом.


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

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


Малоинформативные отладочные данные

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


Так и IoC/DI идут рука об руку вместе с шаблоном Service Locator который тесно связан с идеей позднего связывания.

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


Чем сложнее код, тем дольше длится погружение в проект новичка.

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


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


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


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


Не используйте таковые шаблоны, если:

Я так и не увидел предлагаемой альтернативы. Более того я так и не понял что плохого вы видите в IoC. Есть подозрение что под IoC вы имеете ввиду контейнер зависимостей а не сам принцип.

+1 Особенно вот про это:

>Так и IoC/DI идут рука об руку вместе с шаблоном Service Locator, который тесно связан с идеей позднего связывания.

Потому что обычно как раз _не_ идут. DI как правило избавляет от надобности в Service Locator совсем.

Поправка: хороший DI. Зачастую под так называемым IoC программисты понимают раскиданные по всему коду вызовы Resolve. Термин Composition Root — так воовсе тарабарщина для многих :-(

Да и многие наоборот считают Service Locator антипаттерном (https://www.manning.com/books/dependency-injection-in-dot-net).
под слабой связанности вы тут именно cohesion или coupling имеете ввиду?

Имею в виду инстанцирование в runtime-е.
Интересно каким образом DI/IoC нарушает инкапсуляцию.

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

Разумеется. Об этом и речь. Других под рукой не было :)
То есть это в целом проблема инстанциирования большого графа зависимостей. нет? Причем тут IoC/DI? Ну и еще есть простой лайфхак. если между кодом который вызывает и кодом который выполняет тонны абстракций, их можно спрятать/выкидывать грепом. Как правило у таких вещей будет вполне себе явный нэймспейс и можно легко фильтровать стэктрейсы

Вполне допускаю, что есть хорошие способы. Вопрос к авторам кода, почему их не использовали. Возможно, на тот момент какая-то книжка была прочитана наполовину. :)
причем тут IoC/DI? Если система спроектирована плохо,

Разумеется. Плохо. С IoC/DI это сделать очень просто. ИМХО.
Я так и не увидел предлагаемой альтернативы. Более того я так и не понял что плохого вы видите в IoC. Есть подозрение что под IoC вы имеете ввиду контейнер зависимостей а не сам принцип.

Наверное, альтернатив может быть много. Тут я не берусь навязывать.

Имею в виду инстанцирование в runtime-е.

простите, а инстанциирование бывает не в рантайме?


Реализация некой логики выносится не понятно куда

так проблема в IoC или в том что:


  • какая-то логика вынесена в конструктор
  • какая-то логика запихнута непонятно куда
  • все очень плохо с coheasion.

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

я плохо понимаю причем тут все таки DI. Откуда взялись анонимные вызовы какие-то?


С IoC/DI это сделать очень просто. ИМХО.

Эм… а с фабриками? Ну мол предположим что для каждой сервисной фигни у вас своя фабрика. Все зависимости для каждого объекта в этом случае будут описываться явно. Или вы предлагаете размазать знания о цикле жизни объектов по клиентскому коду (код который использует зависимости)?


Наверное, альтернатив может быть много. Тут я не берусь навязывать.

какие еще альтернатив… https://en.wikipedia.org/wiki/Inversion_of_control

> простите, а инстанциирование бывает не в рантайме?

Автор имел ввиду coupling под тем.

> Или вы предлагаете размазать знания о цикле жизни объектов по клиентскому коду (код который использует зависимости)?

У меня сейчас оочень большой проект над которым работало много разных людей последние 8 лет. Не смотря на использование Dependency injection, это на практике не остановило размазывание о котором вы описываете. И я бы наверено действительно предпочел бы фабрику, что дало бы анализировать код статически.
Реализация некой логики выносится не понятно куда, и что именно будет делаться в данном классе — поди разберись в многоуровневом стеке анонимных вызовов.

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

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

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

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

Внешний код внешнему коду рознь. Есть клиентский код, а есть вынесенный код. Вынос кода инициализации, связывания не уменьшает уровень абстракции для клиентского кода.

Вынесенный куда?

В инфраструктуру типа DI-контейнера.

От кого и когда этот DI-контейнер узнаёт как инициализировать объект?

От разработчика на этапе билда или администратора на этапе запуска.

То есть от самого дальнего клиента. Абстракция протекла и затопила весь дом.

Не от клиента, от инфраструктуры.

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

Меняется. Не клиент библиотеки должен это окружение готовить в общем случае.

Тот, кто использует. А использует программист.

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

Вы никогда не шарите код между приложениями?

Шарю. А какое это имеет значение?

"Инфраструктуру" тоже шарите?

Конечно, не писать же для каждого проекта с нуля.

Отлично, не поделитесь либой?

Не DI, а вашей либой, которая на DI завязана.

Либы на DI не завязаны. У них есть параметры конструкторов и сеттеры, фабрики и т. п., которые вызывает DI-контейнер. Замена DI или его полное отсутствие никак на либу не влияют.

есть либа, она ничего не знает о DI и т.д. Есть сервис провайдеры/экстеншены/модули под конкретный DI контейнер которые занимаются подготовкой окружения. Часто это отдельный пакет. Клей который все склеивает. Живет он так же снаружи приложения, оно ничего не знает о контейнерах и фреймворках… ну как-то так.

То есть либа у вас — это как мебель из Икеи — вот вам фанера с дырками (классы либы), вот вам пакет шурупелей (провайдеры/экстеншены/модули) — собирайте сами. Абстракцией тут и не пахнет.

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


И все. Никакой фанеры, никаких шурупов. Просто залейте бензин сюда.

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

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

Ненужные — выносит.


Выше же описывалась совсем иная архитектура, "без зависимостей и без внутренних DI-контейнеров"

Цитата:


Либы на DI не завязаны. У них есть параметры конструкторов и сеттеры, фабрики и т. п., которые вызывает DI-контейнер. Замена DI или его полное отсутствие никак на либу не влияют.

"Либа" не завязана на внешний DI. У нее есть фабрика (на которую я вам дал ссылку), которую можно сконфигурить напрямую или завести на внешний DI-контейнер. Внутри у "либы" — сквозной DI, есть у нее контейнер или нет — для потребителя не важно.


И да, если бы внутри не было Dependency Inversion, это все было бы невозможно.

"фабрика, которую можно сконфигурировать" — сервис локатор?

Нет, зачем. Обычный composition root.

НЛО прилетело и опубликовало эту надпись здесь

Вы бы ещё в Гугл послали — уж там-то точно вообще все темы обсосали :-) Но зачем мне это однобокое представление об абстракции как о "абстрактном классе"? Задача абстракций — упрощать работу за счёт скрытия деталей. Любых деталей. Будь то детали кода конкретных методов или детали развесистости внутренних интерфейсов.

Так DI-контейнері именно что скрывают от клиентов класса детали его реализации в части инстанцирования и инициализации, абстрагируют инстанцирование и инициализацию до вызова чего-то вроде container.get('name') или container.getName(). Сам класс может иметь внутренние захардкоженные зависимости, они могут инжектиться контейнером или фабрикой через конструктор, сеттер, свойства или ещё как, контейнер может подставлять не сам класс, на который рассчитывает клиент, а его наследников, клиент может рассчитывать вообще не на конкретный класс, а на абстрактный, а то и интерфейс. Он полностью абстрагирован от деталей инстанцирования и инициализации, просто обращается к контейнеру и получает готовый к употреблению инстанс класса, реализующий нужный ему интерфейс, ничего не зная о его зависимостях.

Вынос чего бы то ни было наружу естесственным образом уменьшает абстракцию

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

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

Приведите законченный пример.


без возможности для их замены или оптимизации — абстракция все равно протекает

Вы точно знаете, что такое "абстракция"?

НЛО прилетело и опубликовало эту надпись здесь
Хотел бы заступиться за Smart Client. На мой взгляд — это очень хороший набор инструментов для разработки приложений. В нем, конечно, есть недостатки, но сама суть — это шедевр. Я просто могу предположить, что либо вы не до конца поняли как устроен Smart Client, либо ваш проект использовал его не совсем правильно. С продуманной архитектурой, которую понимают все разработчики, вопросов поиска классов не возникает. У меня был опыт работы с проектом на Smart Client, который начали писать не особо думая о том, как правильно это делать (Вау! Глобальные события! Давайте их везде использовать!). Но потом, когда все привели в порядок не стало вопросов о том, где писать нужный кусок кода. Все понимали за что отвечает view, presenter, workitem, module и т.п.

На самом деле к DI/IoC надо прийти. Не пихать его бездумно везде, потому что это «модно, стильно, молодежно», а действительно понимать зачем он там. Я его начал использовать, когда в мелком проекте было по 2-3 реализации каждого этапа работы программы. Мне просто надоело комментировать конкретные реализации для запуска программы с определенной конфигурацией. Вот тут и пригодился DI/IoC, который одной строчкой мог переключить набор реализаций на запуске.

Все нужно уметь использовать и понимать, что как и зачем. Я тоже не люблю DI/IoC в проектах где всегда одна реализация, но когда-нибудь или «а вдруг!»… Обычно это никогда.
Если возникает вопрос зачем DI, то о тестах, к сожалению, речи не идет. А так, да — моки в тестах.
Или это система толстый клиент + сервер. Очень удобно все зависимости получать через IoC, а при старте приложения в едином месте указывать, какие реализации запускаются на клиенте, а какие заменены прокси-классами для вызова их на сервере через SOAP/WCF.
Хотел бы заступиться за Smart Client.

Пока у меня был программист, который стоял у истоков создания этого приложения, который понимал всю эту штуку в целом, меня всё устраивало. Проблемы начались, когда я остался один.
На самом деле к DI/IoC надо прийти.

Безусловно. С этим никто не спорит. Но с точки зрения РП, как можно в самом начале оценить, будет разработчик правильно использовать или нет? Я могу поверить, что в этом есть что-то изящное, но простым смертным не всегда доступное. :)
Пока у меня был программист, который стоял у истоков создания этого приложения, который понимал всю эту штуку в целом, меня всё устраивало. Проблемы начались, когда я остался один.

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

Безусловно. С этим никто не спорит. Но с точки зрения РП, как можно в самом начале оценить, будет разработчик правильно использовать или нет? Я могу поверить, что в этом есть что-то изящное, но простым смертным не всегда доступное. :)

Ну, я вот сейчас пишу проект на Smart Client — в самом начале выделил 2 недели на то, чтобы полностью продумать всю структуру проекта. Как view будет общаться с presenter'ом, как система будет реагировать на команды из view и т.п. Получил понятную, удобную и простую картину всего приложения. Нет вопросов, где писать каждый кусок кода.
Да, это личный проект, я могу потратить 2 недели на это(да и в реальном проекте, это тоже не так много). Но теперь, если со мной этот проект будет кто-то писать, то первое что будет сделано — это рассказано об архитектуре, почему так и какие плюсы минусы.
Если программист говорит, что что-то надо использовать, потому что это последнее достижение или все сейчас используют — то это ничего не говорит о понимании. Если он может полностью рассказать о том, как это использовать в рамках проекта, какие плюсы это принесет, какие ограничения есть и как это заменить, если придется переходить на что-то другое, то он понимает как правильно работать.
Ну, так это проблема не Smart Client, а организации работы в команде. Если у вас только один понимает архитектуру проекта, то это закономерно приведет к таким последствиям, вне зависимости от того, какие инструменты, библиотеки и т.п. вы используете.

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

Можно в проекте запретить generics или linq и писать всё в стиле FORTRAN-77 (а то вдруг идиоты стажёры не поймут).
А можно просто делать просто, чтобы было понятно даже стажёру.

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

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


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

> Но с точки зрения РП, как можно в самом начале оценить

РП оценивающий самостоятельно «простым смертным не всегда доступное» вызывает недоумение. Да и вообще непонятно зачем ему это. (Хотя… Сегодня — можно.)

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

Если старый бюджет съеден, а новый будет только после запуска, то запускать придётся самому.
1) Я не спрашивал кто будет лезть в код, когда программист уволится. Я спрашивал «кто отвечает за код в процессе». Если никто не отвечает, то вам и без DI наворотят, вплоть до "#define TRUE FALSE", и спросить не с кого.

2) Ну так Вы и оценивайте сможете ли Вы правильно использовать.Если нет — не нужны статьи про DI, это нефункциональное требование «мне лень в этом разбираться — делаем по-старинке».

3) Так получается, вы и не РП. Ну или «и жнец и кузнец и на дуде игрец», плохо справляющийся со всем (ни в DI разобраться времени не нашли, и бюджет съеден, и код не отревьюен).

Вы, как РП, сами выстроили процесс, где проблемы. Ну или у вас проекты часов на 40, где думать о DI некогда, трясти надо. Но проблема не в самом DI. Выбейте бюджет в 3 раза больше, будет время порефлексировать.
1) Я не спрашивал кто будет лезть в код, когда программист уволится. Я спрашивал «кто отвечает за код в процессе». Если никто не отвечает, то вам и без DI наворотят, вплоть до "#define TRUE FALSE", и спросить не с кого.

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

2) Ну так Вы и оценивайте сможете ли Вы правильно использовать.Если нет — не нужны статьи про DI, это нефункциональное требование «мне лень в этом разбираться — делаем по-старинке».

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

3) Так получается, вы и не РП. Ну или «и жнец и кузнец и на дуде игрец», плохо справляющийся со всем (ни в DI разобраться времени не нашли, и бюджет съеден, и код не отревьюен).

О своих управленческих ошибках я не забыл упомянуть в своей статье. т.ч. на чём вы меня пытаетесь подловить?
Жизнь оказывается несколько сложнее, чем наши идеалистические представления. Полицейские должны ловить преступников, а не копаться в кипах бумаг, чиновники должны исходить с позиций долга перед обществом и ставить интересы социума выше своих личных, врачи должна оказывать квалифицированную помощь бесплатно. Ну должны. Тут вопрос, скорее, философский. Сделать так, чтобы наверняка. Или наваять по науке, а потом сетовать на окружающий мир за непонятый замысел. Мне ближе 1-ый вариант.
> Может, это будет РП, может — программист-стажёр, может — перешедший с других технологий.

А может просто не брать разбираться дилетантов?

> указать, что именно в ней не так сказано

Да вам другие расписали подробно. В статье вообще не про DI.

И в лени я вас не обвинял, вы наверняка чем-то важным по 12 часов в сутки на работе занимаетесь. Просто это не попытки разобраться с DI, а попытки закопать неугодный DI (консерватизм+confirmation bias).

Требование «мне лень в этом разбираться — делаем по-старинке» вообще здоровое, программист должен быть немного ленивым.

> Сделать так, чтобы наверняка. Или наваять по науке, а потом сетовать на окружающий мир за непонятый замысел. Мне ближе 1-ый вариант.

Ну я примерно об этом и говорю. Если бы Вы были президентом — Вы бы запретили программистам использовать DI, а врачам антибиотики потому, что Вам непонятен замысел, а доделывать и долечивать придётся, если что, вам. Это путь в никуда.

Если у архитектора нет времени — надо уволить его и нанять того, у которого есть время на свои обязанности.
Если чиновник ставит свои интересы выше — его сменяют (так или иначе).
Если врач лечит ангину слабительным — его надо уволить, а не писать статьи, что лекарства не надо применять, если «У вас нет основания полагать, что на этапе проектирования Вы предусмотрели все варианты использования продукта».

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

> Тогда может возникнуть вопрос, а кто отвечает за тех, кто отвечает за код.

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

Существует такой критерий оценки проекта как Bus factor.
По посту и комментам сложилось впечатление: что дело было не бобине...

Все нужно уметь использовать и понимать, что как и зачем. Я тоже не люблю DI/IoC в проектах где всегда одна реализация, но когда-нибудь или «а вдруг!»
Как по мне, это кошмар, если часть сервисов подключена напрямую, а часть через DI. Всё должно быть единообразно, чтобы не ломать голову, зачем тут так, а там по-другому.

Я его начал использовать, когда в мелком проекте было по 2-3 реализации каждого этапа работы программы.
Вы придумали своё применение этому механизму. Прочитайте SOLID, там однозначно указано, для чего применяется DI, и это не для выбора стратегии в определенном месте программы.

Пользуйтесь ФП, каррингом и композициями функций и ничего не будете нарушать, и будет вам счастье.

По своим личным ощущениям, я хочу сказать, что отношение разработчика к DI зависит от платформы / реализации этого самого DI. Я абсолютно так же был удивлён как сообщество хорошо отзывается о DI практиках, после активного использования StructureMap в .Net проектах. Много boilerplate кода, регистрация каждой мелочи, странные ошибки и куча ограничений. Но все изменилось когда я начал работать со Spring Framework и его DI — вот теперь я не могу представить крупный проект без DI. А разница в том, что в Spring DI это естественная часть платформы, там все работает через контейнер. А благодаря функциям типа Component Scan отпадает нужда в огромных классах для регистрации компонентов. Все системные компоненты самого Spring можно точно так же включать в зависимости в своём коде. И настройки не требуется вообще — по умолчанию все контроллеры резолвятся через DI и можно без усилий начать строить свою injection hierarchy. Точно так же можно менять компоненты системы, переконфигурируя DI — и Spring подхватит нужные пользовательские бины вместо своих. То же самое в тестировании — можно брать готовое приложение и мокать компоненты.

А вот в .net (по крайней мере до .net core) все DI контейнеры что я видел — монструозны и настолько чужеродны платформе, что создают больше проблем, чем их решают. Надеюсь, это поменяется с выходом .net core, где DI это вроде бы часть платформы.

Spring как недостижимый идеал для дотнетных контейнеров — хорошая первоапрельская шутка.

В .Net MVC очень хорошо интегрированы разные DI. Лично я предпочитаю Autofac.
Но у автора десктоп-приложение, наверняка с множеством OnPropertyChanged, может поэтому там DI «не зашел».
Статья похожа на первоапрельскую шутку!

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

Про сферических коней в вакууме я уже писал выше. Когда вы несколько дней подряд посидите по 12-16ч на работе или 3-4 недели без выходных, я посмотрю на вас, с каким энтузиазмом вы будете писать автотесты.
В-вторых, организация тестовой среды в интеграционном проекте часто бывает делом далеко не тривиальным.
с каким энтузиазмом вы будете писать автотесты.

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


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


Так что все это просто отговорки.

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


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

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

Одним из примеров хорошего DI я считаю реализацию Nucleus в ATG, где в системе существует много компонентов с легкостью их переопределения и создания новых. Хотя платформа конечно не очень популярна из-за ее узконаправленности.

НЛО прилетело и опубликовало эту надпись здесь
С точки зрения теории разработки ПО лично мне гораздо чаще приходилось читать или слышать хвалебные статьи и отзывы об IoC/DI, но, как всегда, критика тоже есть. Можно ознакомиться, например, здесь (англ.), здесь (англ.), тут (хабр), ещё (англ.). В частности в вину ставится нарушение принципа инкапсуляции в ООП.

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

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

"Распределение логики по разным кускам проекта" — это обычная декомпозиция, которая применяется для упрощения понимания и реализации отдельных задач. Плохо проведенная декомпозиция — проблема того, кто проектировал, а никак не DI/IoC


Возможно, мой мозг начал костенеть и стал менее восприимчив к новым идеям (хотя IoC/DI были придуманы, кажется, в начале 90-х), но мне сложно понять, чем стал неугоден принцип инкапсуляции из ООП.

Никакого противоречия между DI и инкапсуляцией нет.

BBB, DDD, NNN – это я намеренно изменил название проектов и пространства имён, которые указывали на наименование компании-субподрядчика. Но там ничего интересного для отладки не было. Dispatcher.Start() – это запуск службы MS Windows, точка входа в программу. StructureMap – это библиотека IoC. Ни единого упоминания какого-либо из бизнесовых методов, т.к. было сделано всё, чтобы исключить контекст из стека вызова.

  1. В любом UI приложении в стеке очень много инфраструктуры и очень мало бизнеса и это не проблема, а особенность. C DI-контейнерами все то же самое — служебные строки в стеке моно спокойно игнорировать: достаточно строк из бизнес-классов и текста ошибки.
  2. DI не заставляет вас использовать конкретный контейнер. Если вам не нравится сообщения конкретного контейнера — замените на удобный вам или вообще обходитесь без него, паттерн позволяет.
  3. Если проблема в параметрах не конструктора, а другого метода — не используйте Method Injection и будет вам счастье.
Так и IoC/DI идут рука об руку вместе с шаблоном Service Locator, который тесно связан с идеей позднего связывания.

Service Locator с точки зрения DI — антипаттерн. DI-контейнер технически легко использовать как Service Locator, но это классическое забивание гвоздей микроскопом.


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

Это цена гибкости настройки контейнера, которую вас никто не заставляет платить. Пример для Autofac:


// Гибкий вариант
builder.RegisterType<A>().As<IA>();

// Жесткий вариант
builder.Register<IA>(c => new A(c.Resolve<IB>(), c.Resolve<IC>()));

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

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

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

У меня складывается впечатление, что автор просто или путает или отождествляет DI/IoC с Service Locator. Надо отделить мухи от котлет. Почему в статье нет пояснения к используемой терминологии?

DI — Dependency Injection, внедрение зависимостей, на самом деле очень банальная вещь: если вы определили конструктор с параметрами, вы уже воспользовались внедрением зависимостей через параметры конструктора. Различают также внедрение зависимостей через метод класса (в котором иногда выделяют две разновидности). Но я бы не рекомендовал пользоваться последним методом без необходимости, т.к. способ внедрения зависимостей через параметры конструктора более нагляден.

IoC — Inversion of Control, наилучшая формулировка, на мой взгляд такая: «Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций». Первая рекомендация вытекающая из такой формулировки это: «Используйте интерфейсы для определения зависимостей». Следование только этому правилу уже позволит построить менее связанную архитектуру, которую очень просто тестировать с помощью модульных тестов и легко изменять реализацию какой-то части, при необходимости.

Очень рекомендую почитать про DI и IoC на англоязычной википедии.

Далее, если у вас очень сложный проект (сотни классов), то вы можете упростить себе жизнь (или усложнить) воспользовавшись Service Locator. Использование Service Locator невозможно без DI и IoC, но не наоборот: если вы используете DI и IoC, то это не означает автоматическое использование Service Locator.
Вы перепутали инверсию зависимостей с инверсией контроля. А автор вообще все в одну кучу смешал.
НЛО прилетело и опубликовало эту надпись здесь
Не используйте таковые шаблоны, если:
Вы не уверены на 100%, что на этапе запуска проекта, его стабилизации, опытной и даже промышленной эксплуатации будут оперативно доступны разработчики этого продукта.

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


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

Это соображение только усиливает предыдущий аргумент.


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

Здесь DI позволяет получить экономию (вплоть до башни экспонент — в зависимости от объема задач) за счет:


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

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


Лучше один раз прочитать Симана, чем оценивать Карузо по перепевам от Рабиновича.

Как показал мой опыт, основная проблема бездумного использования IoC/DI-контейнеров — за ними прячут чрезмерную сложность системы. Видел системы на 5-7 уровней вложенности зависимостей. Видел по 15 интерфейсов в конструкторе. Но сам по себе IoC/DI не добавляет сложности, она рождается из конкретной реализации.
Прятать сложность всегда опасно, надо вместо этого делать просто.
Насколько я помню — в .NET инжекция возможна только через конструктор — из-за чего и требуется такое количество boilerplate кода. В Java же инжекция как правило происходит в загрузчике классов, не требуя ничего от «потребителя» кроме пометки соответствующих членов атрибутом Injectable. Поэтому каждый класс просто определяет, какие интерфейсы ему нужны — а сколько там еще уровней вниз ему совершенно не интересно.
Не, есть и Property Injection и Constructor Injection.
Тут скорее по смыслу повелось как паттерн — в конструкторе получаем строго обязательные зависимости, в свойствах описываем опциональные.
Однако, это не особо меняет ситуацию — 15 свойств с интерфейсами точно такое же опасное дело, точно такой же God-Object, хоть весь и на DI. Только с DI поддерживается ощущение «тут всего то параметр/свойство добавить».

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

Безусловно, согласен со вторым абзац. Но, если эти зависимости сложные, то при добавлении каждой новой хардкодом в классе — волей-неволей задумаешься, правильно ли ты делаешь, ведь её еще надо инициализировать правильно. Когда же имеется возможность лишь добавить свойство с нужным контрактом, то задумываться приходится явно меньше.
Но, если эти зависимости сложные, то при добавлении каждой новой хардкодом в классе — волей-неволей задумаешься, правильно ли ты делаешь, ведь её еще надо инициализировать правильно

Те, кому 15 зависимостей на класс норма, о правильной инициализации не задумываются.
Вдобавок без DI сколь угодно сложная инициализация обычно выглядит гораздо проще чем есть на самом деле: достаточно "просто" вызвать конструктор зависимости с 1-4 параметрами.

Давайте считать — порядочному компоненту нужен логгер, конфигуратор, ввод/вывод, доступ к хранилищу данных (бывает, и не одному). Так и получаются 10-15 зависимостей…

Компоненту. Но не классу.

У вас классы только с одним методом?

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

Ну вот есть конроллер для продуктов — CRUD
— зависимость к хранилищу пользователей — чтобы проверить не заблокирован ли пользователь
— зависимость к хранилищу городов — чтобы проверить что можно сделать доставку по городу
— зависимость к хранилищу продуктов
— зависимость к хранилищу картинок
— зависимость к полнотекстовому поиску
— зависимость к емайл провайдеру
— зависимость к социальным сетям
— зависимость к логгеру

Толстый контроллер с бизнес-логикой.
God object в чистом виде.

Ок, пусть будет не конроллер, пусть будет сервис (макро).
Т.е. вы создаете по классу CreateX, ReadX, UpdateX, DeleteX для каждой сущности?

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

Т.е. если у меня в классе 3 валидатора, то это плохо, если один, но у него внутри 3 валидатора, то это хорошо.
Почему это плохо, чему это мешает?

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

А это не преждевременная абстрактизация? у меня только в одном месте эти валидаторы используются

вам надо его добавить везде

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

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

Т.е. если у меня 3 валидатора то у меня будут 6 композитных валидатора, а если 4, 5 валидатора…

Нет. Композитных валидаторов (объектов, класс все равно один) надо столько, сколько у вас различных вариантов его использования, а не все комбинации из простых.

Ну так в приложении могут понадобиться и все 6 вариантов.

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

Нет смысла делать лишнюю работу заранее.

Ну вот эти 3 валидатора надо использовать только в одном месте, зачем мне композитный?

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

Можно пример, что может поменятся в зависимостях так что править изменения в одном классе сложне чем править в трех?
поменятся в зависимостях

тут не в зависимостях изменения надо рассматривать а в этих самых трех классах.


Короч почитайте про single responsibility principle и cohesion.

тут не в зависимостях изменения надо рассматривать а в этих самых трех классах.

И как эти зависимости мне будут мешать править текушии класс? Можно конкретные примеры а не только эти баззворды?
И как эти зависимости мне будут мешать править текушии класс?

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


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

Если изменение небольшое, то править придется в одном маленьком классе.
Это проще чем править в большом как с точки зрения внесения изменений, так и для оценки аффекта, контроля версий, объединения правок и всего остального.
Если изменение масштабное, то маленькие изменения в трех маленьких классах проще чем большое в одном большом классе.

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

А это не преждевременная абстрактизация?

Нет. Это удобство.

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

У меня валидатор привязан к модели. Переиспользуется модель — переиспользуется и валидатор.

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

Любой метод, применённый бесцельно, лишь увеличивает энтропию.


Огонь может согреть вас холодной ночью, а может спалить весь ваш дом.

Допустим, у вас есть несколько контроллеров: C1, C2, C3.

Они зависят от валидаторов, соответственно C1(a,b), C2(a,b,c), C3©.

Мы добавляем контроллер C4 и валидатор d. Какие классы и интерфейсы в идеале должны существовать?

Слишком мало информации для ответа.


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

> Слишком мало информации для ответа.

Можно додумывать

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

Ну и как именно это организовано?

Т.е. получаем C1, C2, C3, C4 и IValidationService c ValidationService (в котором лежит a,b,c,d)? Или a,b,c,d — отдельные(4 шт) классы-зависимости ValidationService?
Ну и как именно это организовано?

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

А, метаданные, это ок.

Меня заинтриговала именно логика композиции в «А если вы добавляете валидатор в композитный валидатор, то править надо только композитный». Давайте для простоты представим, что Главный Архитектор ненавидит метаданные и запретил. Тогда как поступить?
Давайте для простоты представим, что Главный Архитектор ненавидит метаданные и запретил. Тогда как поступить?

Для начала — слезть с MVC-подобного фреймворка.

Еще раз, вопрос не про выбор фреймворка, а про композицию классов.

Слезть на что?
Давайте для простоты представим, что Главный Архитектор ненавидит метаданные и запретил. Тогда как поступить?

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

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

С чего вдруг? У композитного валидатора на входе типизированный IEnumerable с простыми валидаторами, и возвращает он сводный список результатов валидации.

Как я понял, lair композитным валидатором назвал другое.

Ещё раз, у нас есть контроллер, он должен валидировать. У каждого поля есть свои правила валидации. Т.е. нам нужна связка поля и правила. Их много.

Эти связки не должны лежать в контроллерах. Они не лежат и в EmailFormatValidator. Значит за них отвечает (композитный) валидационный сервис. Тогда он будет весьма жирненьким.
Эти связки не должны лежать в контроллерах.

Почему?

Ещё раз, у нас есть контроллер, он должен валидировать. У каждого поля есть свои правила валидации. Т.е. нам нужна связка поля и правила. Их много.

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


Тогда он будет весьма жирненьким.

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

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

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

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

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

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

> за счет отказа от внешней связи

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

Я не забыл, пожалуйста, читайте внимательнее:


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

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

Без последнего абзаца совершенно непонятно вызываем мы как Check(object) или Check(object, o => o.Name)

Т.е. если нам надо проверить сотню полей на «не пустая строка» — мы должны описать сотню классов атомарных валидаторов?
Без последнего абзаца совершенно непонятно вызываем мы как Check(object) или Check(object, o => o.Name)

Второй вариант делает связь валидатора с полем внешней, что прямо противоречит моему предложению.


Т.е. если нам надо проверить сотню полей на «не пустая строка» — мы должны описать сотню классов атомарных валидаторов?

Зачем это перепроектирование? На пустоту 100 полей спокойно проверит один атомарный валидатор.

Один атомарный валидатор проверяет вообще все сущности в солюшене, или вы их по какому-то принципу делите?

Интерфейс валидатора — обобщенный, параметризованный типом сущности.

А это чем-то помогает понять какие поля надо проверять на «не пустое»?

А это зачем-то надо понимать не из кода валидатора?

Ещё раз, валидатор у вас всё же один, но вы его зачем-то, непонятно зачем, параметризуете. Зачем?

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

> С чего вы взяли, что валидатор у меня должен быть один?

Будет проще, если вы перестанете отвечать вопросом на вопрос. Начать можно с

> Один атомарный валидатор проверяет вообще все сущности в солюшене, или вы их по какому-то принципу делите?

Ну и вообще перестать отвечать «можно выбирать по вкусу» (спасибо, я в курсе).

Пожалуйста, читайте внимательнее.
Ответ на ваш вопрос про атомарные валидаторы уже есть:


Интерфейс валидатора — обобщенный, параметризованный типом сущности.

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

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

Но именно в этом смысл, как именно вы затем реализуете ваш этот валидатор — дело лично ваше. Суть в том что благодаря этому единому интерфейсу уровень связанности будет небольшой.

> Но именно в этом смысл, как именно вы затем реализуете ваш этот валидатор — дело лично ваше.

Смысл чего? Мой вопрос был про вторую стадию, когда «дело лично ваше».

1) Делаем единый интерфейс
2) ???
3) Profit!

> Суть в том что благодаря этому единому интерфейсу уровень связанности будет небольшой.

Раз уж на мой вопрос никто не хочет отвечать… Он предлагает не «единый», а «обобщенный». То есть для каждого типа он на самом деле свой. Уровень связности он даст не меньше любого другого интерфейса, а то и прибывит. Например в DI придётся регистрировать отдельно для каждого наследника в иерархии.
Мой вопрос был про вторую стадию, когда «дело лично ваше».

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


Он предлагает не «единый», а «обобщенный»

а вы можете объяснить разницу в контексте нахождения абстракции для валидатора?


То есть для каждого типа он на самом деле свой.

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


Уровень связности он даст не меньше любого другого интерфейса

Код будет завязан на один интерфейс, а не на 10 реализаций. За счет этого связанность и уменьшается. Модуль который использует валидаторы теперь знает только об одном типе.


Например в DI придётся регистрировать отдельно для каждого наследника в иерархии.

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

> Модуль который использует валидаторы теперь знает только об одном типе.

Давайте вот с этого места поподробнее. Мне приходит абстрактный родитель P (у которого есть наследники A и B) который надо отвалидировать (соответственно валидируются ValidatorA:IValidator<A> и ValidatorB:IValidator<B>). Можете показать как выглядит полиморфизм в коде использующего их модуля?

> DI никак не влияет на уровень связанности

Если у вас есть код регистрации — влияет.
Можете показать как выглядит полиморфизм в коде использующего их модуля?

validator.validate<MyEntity>(entity)

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


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


Если у вас есть код регистрации — влияет.

не влияет, это отдельный модуль.

У меня нет P, A или B. У меня есть клиентский код которому надо провалидировать объект с типом MyEntity и он делигирует эту работу некому валидатору. Этот валидатор затем достанет для нашего типа другой валидатор, который может быть композицией других валидаторов которые будут непосредственно валидировать поля сущности.


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

> У меня нет P, A или B. У меня есть клиентский код которому надо провалидировать объект с типом MyEntity

Рад за Вас! К сожалению, я полиморфизм использую на объектах разных типов, например наследниках.
я полиморфизм использую на объектах разных типов

разные типы — нет полиморфизма. Либо поясните.

Внезапно.

> В языках программирования и теории типов полиморфизмом называется способность функции обрабатывать данные разных типов

https://ru.wikipedia.org/wiki/Полиморфизм_(информатика)
К сожалению, я полиморфизм использую на объектах разных типов, например наследниках.
«К сожалению», потому что понимаете, что этим нарушаете принцип 'L' из SOLID?

Если у вас есть код
void DoSomethng(P entity)
{
    validator.Validate<P>(entity);
}

Значит, этим вы показываете, что свойства наследников, отпочкованных от P, здесь вам не нужны. Валидировать объект надо как P, а не как A или B.

Если всё же разница есть, язык достаточно мощный, чтобы её выразить:
void DoSomethng<T>(T entity) where T : P
{
    validator.Validate<T>(entity);
}


> Значит, этим вы показываете, что свойства наследников, отпочкованных от P, здесь вам не нужны. Валидировать объект надо как P, а не как A или B.

Я понимаю, что предложить прочитать ТЗ — это верх наглости, но…

> Мне приходит абстрактный родитель P

> соответственно валидируются ValidatorA:IValidator<A> и ValidatorB:IValidator<B>

То есть интересующий меня код выглядит примерно так

void DoSomethng(P entity)
{
    validator.Validate<???>(entity);
}


> Валидировать объект надо как P, а не как A или B.

А вот это нарушает L.
А вот это нарушает L.
Может, у меня другое понимание этого принципа. Если бы P имел виртуальный метод валидации, а наследник его перекрывал, то можно было бы безболезненно передавать наследника в метод, потому что метод ничего не знает об отличии A от P:
entity.Validate() — нет проблем с LSP.


У вас же метод *должен* знать об отличии P от A (о чём и говорит "???"), а это нарушение LSP.

Я сейчас не пытаюсь доказать, что SOLID это хорошо и догма, и только по нему надо писать. Но факт есть факт. Метод с
validator.Validate<???>(entity)
нарушает LSP.
Я себя поправлю. Код
void DoSomethng(P entity)
{
    validator.Validate<P>(entity);
}

нарушает LSP (о чём я и написал в своём первом комменте).

Про код с вопросами нельзя ничего сказать, пока не будет понятна методика реализации этих вопросов.
«Нарушает» не совсем точно. Для разговора о LSP надо определить функцию (валидатор) над P. А я определил 2 валидатора — для A и B. И это вполне SOLID валидаторы, которые могут вызываться и из entity.Validate().

> Но факт есть факт. Метод с validator.Validate<???>(entity) нарушает LSP.

Это да. Но дженерик в валидаторе то придумал не я.

То есть реализация паттерна стратегия нарушает LSP?


А я определил 2 валидатора — для A и B. И это вполне SOLID валидаторы, которые могут вызываться и из entity.Validate().

Вот только теперь entity знает о валидации и слегка так нарушает SRP. Да и зачем валидировать сущность если она и так должна быть валидной всегда.

> Вот только теперь entity знает о валидации и слегка так нарушает SRP.

> То есть реализация паттерна стратегия нарушает LSP?

И SRP, видимо.

> Да и зачем валидировать сущность если она и так должна быть валидной всегда.

Глубокая мысль, запишу.
«Нарушает» не совсем точно
Я руководствовался таким принципом:

Если фунция принимает P, она не должна опираться на свойства, не входящие в контракт P

То есть корректно (с т.з. LSP) сделать валидацию невозможно, если принимать только базовый класс, в контракте которого нет метода валидации.
И это вполне SOLID валидаторы, которые могут вызываться и из entity.Validate()
А тут уже нарушается SRP. Entity отдельно, валидация отдельно.
> Если фунция принимает P, она не должна опираться на свойства, не входящие в контракт P

Это LSP или вообще? )

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

> То есть корректно (с т.з. LSP) сделать валидацию невозможно, если принимать только базовый класс, в контракте которого нет метода валидации.

У нас в контракт P как-бы входит кастинг
(entity as IValidatable)?.Validate() ?? true


А с т.з. LSP принципиально, что мы не упоминаем ни A, ни B, ни С, и код будет работать (если ABC сами не нарушат LSP).

А каким определением SRP вы руководствуетесь?
>> А каким определением SRP вы руководствуетесь?

Определения равнозначны.
Если в контракт входит возможный кастинг к IValidatable, то можно валидировать A,B,C, не нарушая SRP. Иначе, как и писал раньше
корректно (с т.з. LSP) сделать валидацию невозможно, если принимать только базовый класс, в контракте которого нет метода способа валидации.

А полиморфизм бывает еще и параметрический. Именно его обобщенные типы и реализуют.

Это замечательно. А с наследованием этот ваш параметрический полиморфизм совместим?

А можно посмотреть? А то я никак не пойму какую же именно строчку кода вы полиморфизмом все называете.

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

То есть параметрический полиморфизм, по-вашему, заключается в том, что я могу интерфейс реализовать несколькими способами? А к исполняемому коду отношения не имеет?

Нет-нет-нет, Дэвид Блейн!
Обобщенным должен быть интерфейс, а не отдельный метод.


public interface IValidator<in T>
{
   IEnumerable<ValidationResult> Validate(T entity);
}

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

Отличное замечание!
Тогда реализуем вот так:
        public IValidator<int> IntValidator { get; set; }

        public IValidator<string> StringValidator { get; set; }

        public void DoSomethng<T>(T entity, IValidator<T> validator)
        {
            validator.Validate(entity);
        }

        public void Caller()
        {
            int x = 10;
            DoSomethng(x, IntValidator);
            string s = "yes";
            DoSomethng(s, StringValidator);
        }
Остался один шаг до DoAnything(lambda, params[])

Но нет, публичное API менять по-прежнему не будем.
Предложите удачный вариант нового API
API ровно одно, public void DoSomethng(P entity). И с этим надо жить.

Что внутри делать — это другой вопрос.

По валидации я знаю 2 не очень плохих варианта — метаданные и метод на сущности. Хороших — не знаю. Дженерик — плохой.

В результате у ваших классов и методов третья стадия ожирения.

Так вы покажите свои, а мы посмотрим у кого жирнее.
То есть практического опыта применения магического интерфейса с дженериком нет, понятно.
public void DoSomethng(P entity).
Что внутри делать — это другой вопрос.
Ну ок, обменялись плохими практиками )))

Не совсем.


Во-первых, (публичный) метод будет один (и он не будет зависеть от сложности модели).


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

Вы имеете в виду веб-контроллер? Это пограничный класс, который всегда создается инфраструктурой и никогда программистом. И позднее связывание контроллеров — врожденная проблема многих веб-фреймворков, DI с IoC тут уже ничего не портят, ибо некуда.


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


  • проверка не заблокирован ли пользователь должна выполняться общим решением по авторизации, а не boilerplate-кодом в каждой операции контроллера, а потому зависимость к хранилищу пользователей — мимо;


  • как вы собрались проверять доставку по городу через хранилище городов? Запрашивая информацию в цикле что ли? Если нужно отдавать ответ быстро — этим может заниматься только хранилище продуктов;


  • для того чтобы сформировать ссылку на картинку обычно не требуется доступ к хранилищу картинок;


  • зачем тут нужны соц. сети и email провайдер — даже не представляю...
как вы собрались проверять доставку по городу через хранилище городов?

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

для того чтобы сформировать ссылку на картинку обычно не требуется доступ к хранилищу картинок

у вас ссылки на картики хранятся в той же таблице что и продукты?

зачем тут нужны соц. сети и email провайдер — даже не представляю

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

А оптимизировать запросы к БД не пробовали?


у вас ссылки на картики хранятся в той же таблице что и продукты?

А зачем ее хранить?

А зачем ее хранить?

А как вы показываете картинки продукта?

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

Ок, в тэги есть ссылка, откуда вы ее берете?

Генерирую в соответствии со схемой

Т.е. у вас у картинки продукта всегда одна и таже ссылка?
Т.е. вы никогда не кэшируете картинки?

А при чем тут количество методов в классе?

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

Обычно разные зависимости у разных методов — это сигнал что что-то не так с архитектурой.

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

Предлагаете по одно классу на метод?
зависимость к хранилищу пользователей — чтобы проверить не заблокирован ли пользователь

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


зависимость к хранилищу городов — чтобы проверить что можно сделать доставку по городу

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


зависимость к емайл провайдеру

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


зависимость к хранилищу картинок

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


зависимость к социальным сетям

А это откуда вылезло при простом добавлении продуктов?


зависимость к логгеру

cross-cutting concerns можно выносить в декораторы или делать аспекты (АОП).


зависимость к полнотекстовому поиску

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


Предлагаете по одно классу на метод?

В этом случае скорее да чем нет.

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

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

к которому у нас тоже зависимость
А все картинки уже залиты.

А связи между картинками и продуктом, или у вас ссылки на картинки хранятся в той же таблице?
А это откуда вылезло при простом добавлении продуктов?

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

ну так ловите эксепшен на уровне выше аля DeliveryNotSupportedForCountry и вуаля.


к которому у нас тоже зависимость

но про зависимости зависимостей мы не должны париться ибо Law of demeter. Инкапсуляция и все такое.


А связи между картинками и продуктом, или у вас ссылки на картинки хранятся в той же таблице?

у меня за этим следит ORM. ну или Table Gateway какой-то мэпит объект со связями на таблички. Это уже как сделаете. Ну и да, на нескольких проектах я тупо храню ссылки на картинки в jsonb.


автоматически шарить с соц. сети

опять же декорация, доменные ивенты.

Зачем вы пишите одно и то же в двух ветках? Ответил вам выше.

высокая связанность

между кем? Я всегда могу вынести любой метод в другой класс
между кем?

у нас есть класс. У него несколько методов. допустим 3. У каждого метода по две зависимости. Итого 6 зависимостей на один класс. Итого — высокая связанность.


С другой стороны, если у вас есть один класс, и там настолько разные штуки что у них разные зависимости, то это должно быть три класса. Итогда с точки зрени coheasion и с точки зрения coupling у нас все будет хорошо.


Как следствие все будет хорошо и с SRP и с ISP. Будет проще находить какой код править. Будет проще менять код.

Итого 6 зависимостей на один класс. Итого — высокая связанность.

А сколько зависимостей должно быть чтобы не было высокой связанности?

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

НЛО прилетело и опубликовало эту надпись здесь

Ну вы смиряйтесь если хотите. А я такое не стесняюсь рефакторить.

Если в методе сервиса надо сделать 4 запроса в 4 таблицы(4 зависимости на репозитории), как тут быть?

Совсем-совсем несвязанные 4 запроса к разным базам?

Да

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

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

Так это совершенно разные сервисы же

Ну кто-то их же вызывает в одном методе, т.е. у него на них зависимости

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


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

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

А как? Есть запрос отобразить гланую страницу
Во-вторых, такая вещь абстрагируется до «компонент первой страницы», и тогда у вас остается одна зависимость — перечисление компонентов первой страницы.

И у этого «компонент первой страницы» 4 зависимости, да?
А как? Есть запрос отобразить гланую страницу

Он не обязан вызывать все сервисы, он может просто ответить, какие компоненты нужны.


И у этого «компонент первой страницы» 4 зависимости, да?

Нет. Погода — компонент, курс валют — компонент, продукты — компонент, и новые сообщения — компонент.

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

Четыре запроса на сервер вы сам придумали. В моем же предложении все происходит на сервере, не покидая его.

Он не обязан вызывать все сервисы, он может просто ответить, какие компоненты нужны.

Ок, он знает какие компоненты нужны, но как он получит информацию если он не будет их вызывать их на сервере?

Говорю же: если у компонент единый интерфейс, вместо n зависимостей вы получаете одну зависимость (на перечисление из n элементов). Для потребителя они все обрабатываются одинаково.

если у компонент единый интерфейс

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

Я не додумал, я сказал, что его надо ввести.


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

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


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

Точно так же эту информацию из контекста может получить и компонент главной страницы.

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

Не больше, чем они знали раньше.


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

Брать дату из БД, простите, неумно. И такой запрос, в любом случае, оптимизируется.

Не больше, чем они знали раньше.

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

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

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


В зависимости от зоны пользователя надо показывать разные даты

Значит, из БД надо брать зону пользователя (а не дату). Это все равно профиль пользователя, который надо загружать один раз на (HTTP-)запрос.

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


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


Во-вторых, у них разная кэшируемость.

В третьих, если один не отработает почему-то, то остальные три отработают

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

Это надо постараться надо так написать, по-моему.

Типичный код:


Promise.all([ getOne(), getTwo(), getThree() ]).then( ... ).catch( ... )

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

… плохо написанное. Не делайте так.

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

могут быть лучше

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

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

Можно кэшировать на сервере
Мне не трудно сделать веб сервис для этого отдельно

Отдельно для чего?


Можно кэшировать на сервере

Это все равно хуже, чем закэшировать на проксе по времени.

Отдельно для чего?

Отдельный веб сервис для календаря, отдельный веб сервис для курса валют, если такие сервисы нужны

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

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

… или не надо, потому что см. выше.


Или если очень надо, то вам уже показали, как это декомпоновать.

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

А как получилось изменить сигнатуру метода, не меняя имплементированный интерфейс?
Тем кто использует DI, а что вы делаете если надо поменять DI контайнер, он у вас тоже в DI?

DI-контейнер — инструмент для облегчения реализации точки сборки (Composition Root), в обычных классах на него ссылок нет.
Как результат для замены DI-контейнера необходимо и достаточно переписать точку сборки с использованием нового инструмента вместо старого.

А как вы инжектите зависимости?

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

Реализации вы регистрируете вручную или аннотоациями?

Обычный класс ничего не знает о точке сборки, включая аннотации, поэтому аннотации в топку — они навязывают классу паразитную зависимость от конкретных вариантов использования.
Вот только аннотации — не единственный способ автоматизации маркировки зависимостей и регистрации реализаций.
Практически все контейнеры автоматически трактуют параметры конструктора как зависимости без необходимости в аннотациях.
Большинство контейнеров (кроме откровенного легаси вроде Spring.NET) позволяют регистрировать зависимости прямо в коде, что означает возможность использования любой автоматизации при построении точки сборки.
На практике чаще всего используются конвенции (Autofac умеет разрешать делегаты, имея только регистрацию возвращаемого типа), сканирование сборок и генерация регистраций из конфигурационных файлов.
Я предпочитаю вариант регистрация в коде, дополненную поддержкой конвенций.
Это позволяет сочетать ясность, краткость и гибкость в коде точки сборки.

Т.е. у одного интерфейса только одна реализация зарегистрирована?

Нет, конечно, единственность реализации не обязательна.

Хм, а откуда тогда контейнер знает какую реализацию надо внедрить?

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

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

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


Очень информативно, согласитесь?

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


Так и IoC/DI идут рука об руку вместе с шаблоном Service Locator

Нет, не идут. Dependency Injection — это прямая противоположность Service Locator. То же самое относится и к IoC — когда вы используете Service Locator, вы не используете IoC, у вас "прямое" управление.


Service Locator, который тесно связан с идеей позднего связывания. При этом при компиляции решения не проверяется, соответствует ли сигнатура методов требованиям в точке вызова.

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


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

Это ошибка. DI (как и IoC) — это общеизвестный паттерн, не привязанный к одному продукту. Более того, я вот когда получаю в наследство код, в котором все зависимости прямые, обычно очень хочу как-нибудь отомстить его автору.


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

Это тоже ошибка. Во-первых, см. выше: DI — общеизвестный паттерн. Во-вторых, DI (в отличие от прямых зависимостей) как раз упрощает исследование архитектуры, потому что показывает все зависимости кода в его контракте.


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

"Говорят", что DI как раз позволяет уменьшить стоимость таких доработок… нет?


И нет, (правильно примененный) DI не нарушает принцип инкапсуляции.

когда вы используете Service Locator, вы не используете IoC, у вас «прямое» управление.

Точно? На MSDN(раздел Related Patterns) написано, что Service Locator является специализированной версией IoC
Я до конца не доверяю википедии(раздел Implementation techniques), но там тоже сказано об этом.
Дак Service Locator является реализацией IoC или нет?

С моей точки зрения — не является, потому что зависящий компонент (потребитель) сам запрашивает (=прямое управление) экземпляр компонента, от которого он зависит (сервиса). При инвертированном управлении он должен получать этот экземпляр извне.


Service Locator и Dependency Injection оба являются методами реализации Dependency Inversion, которую часто приравнивают к Inversion of Control — видимо, на основании общего слова Inversion — но это разные паттерны.

Да, компонент сам запрашивает то, что ему нужно в коде. Но он сам не создаёт конкретных реализаций. Он запрашивает их у локатора по абстракции. Это ли ни IoC? И тоже самое при инъекции в конструктор компонент всё равно прописывает то, что ему нужно только в публичном «контракте». Сути это не поменяло, только зависимости стали прозрачными и явными.

Dependency Inversion

Это Вы про DIP?
Да, компонент сам запрашивает, то что ему нужно в коде. Но он сам не создаёт конкретных реализаций. Он запрашивает их у локатора по абстракции. Это ли ни IoC?

Неоднозначный вопрос. С моей точки зрения, нет, потому что последовательность вызовов (а IoC — это про control flow) — прямая.


И тоже самое при инъекции в конструктор компонент всё равно прописывает то, что ему нужно только в публичном «контракте». Сути это не поменяло, только зависимости стали прозрачными и явными.

Зависит от того, что вы считаете сутью. Для меня "суть" IoC — это "кто кого вызывает".


Это вы про DIP?

Да, я про него.

Ок. Я Вас понял. Спасибо за ответ.
> С моей точки зрения — не является, потому что зависящий компонент (потребитель) сам запрашивает (=прямое управление) экземпляр компонента, от которого он зависит (сервиса).

А если компонент получил в конструктор Lazy<T> — это прямое управление?

"Более прямое". А Func<T> — еще более прямое. Это же не то что бы триггер, это континуум.

IoC же не самоцель, он должен приносить конкретные профиты. Он их или приносит, или нет. ИМХО нет смысла говорить о «более прямом», если профиты остались те же.

А я нигде не говорю, что IoC — самоцель, или что надо делать вот так или вот так. Я просто говорю, что конкретную реализацию/поведение — как мне кажется — лучше называть иначе.

Дак Service Locator является реализацией IoC или нет?

Что такое сферическое IoC в вакууме, особенно в отрыве от DI — не знаю, но любой IoC-контейнер по своей природе является реализацией Service Locator. Другое дело, что использовать его таким образом нужно в очень ограниченном количестве места, желательно — в одном.

но любой IoC-контейнер по своей природе является реализацией Service Locator

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

Название IoC-контейнер лишено всякого смысла, если посмотреть что такое IoC. DI-контейнер как это названо у Симана мне нравится больше. Контейнер можно использовать как Service Locator, но вопрос был в другом
С точки зрения теории разработки ПО лично мне гораздо чаще приходилось читать или слышать хвалебные статьи и отзывы об IoC/DI, но, как всегда, критика тоже есть. Можно ознакомиться, например [...] тут (Хабр)

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

Отличная статья. Паттерны проектирования очень хороши в теории, а на практике довольно часто просто рыдаешь когда их очень грамотно к месту применяют 'гуру' проектирования.

НЛО прилетело и опубликовало эту надпись здесь
чтобы догадаться, куда нужно «воткнуть» обработку нового поля объекта из БД, то


Давно думаю над этой проблемой.

IMHO надо запретить использовать простой if на уровне языка. Вместо этого должна быть конструкция конструкция с семантикой.
if(...){...}else{ошибка}

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

if(...){...}
if(...new...){...new...}
else{ошибка}

Круто, давайте запретим простые проверки.

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

Что такое "новые данные", откуда они взялись?

например, новый тип договора или документа, если это автоматизация предприятия

То есть человек добавил новое значение в словарь и система упала?


И что делать с требованиями "для любого договора верно следующее"?

То есть человек добавил новое значение в словарь и система упала?


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

И что делать с требованиями «для любого договора верно следующее»?

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

Вы себе стоимость поддержки такой системы представляете? В которой нет ни одного обобщения?

В моем текущем проекте порядка 1 Гб исходников на ООП. Постоянно чувствую over-engineering изза того, что предметная область проще ООП. Незнания в предметной области выражаются в виде обобщений, которые зачастую уводят не туда, куда надо.

И вы думаете, что если вы откажетесь от ООП и обобщений, станет лучше?


Так вот нет, не станет. Лучше станет, если изучить предметную область и избегать over-инжиниринга.

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

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


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

Я когда-то интересовался вопросом, — когда можно обобщать, а когда еще рано? Четких критериев не нашел.

В целом должно сойтись несколько условий для того, что бы обощение было удачным:
1) доведенные до предельной простоты базовые понятия.
2) четкая область применения, возможность специализации базовых понятий.
3) возможность усиления функций, интенсификации базовых понятий.

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

Я, извините, не вижу никаких оснований для ваших утверждений.

руки пока не доходят облачить это в нечто понятное для программистов.

идеи растут отсюда:
— биологическая теория эволюции Северцова
— и кибернетическая эволюция Турчина

Спасибо, но нет. Я лучше как-нибудь с простыми проверками.

тогда не обращайте внимания, условия еще не созрели

Вы читали труды Барбары Лисков по абстракции данных? Думаю вам понравится.

Постоянно чувствую over-engineering изза того, что предметная область проще ООП

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


Сложности же возникают когда эти самые объекты:


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

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


Если DbC вас это заставляет делать — отлично. Для кого-то например лучше работает TDD. Кто-то руководствуется каким-то другими методиками. Кто-то как способ заставить себя декомпозицию лучше делать вообще микросервисами балуется. И не стоит забывать о штуках вроде property based testing.


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

Да, это извечная тема — чем модель отличается от явления, чем карта отличается от реальной местности, чем восприятие предмета отличается от самого предмета…
Обобщения вредят.

преждевременные обобщения вредят. А так все это лишь часть процесса декомпозиции, поиск нужных абстракций. Не стоит просто забывать проверять абстракции на актуальность (LSP, Open/Close).


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


По сути суть поиска абстракций в том что бы накорню избавиться от этих самых if-ов во славу open/close.

преждевременные обобщения вредят.

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

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

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации