Несколько аргументов против Dependency Injection и Inversion of Control

    Помнится, во времена .NET 1.1 и 2.0 можно было часто увидеть пророчества майкрософтовских евангелистов, мол, скоро любая домохозяйка сможет создавать сайты и писать программы. Большинство программистов посмеялось, но, как выяснилось, кто-то отнёсся к этому серьёзно. Во всяком случае, это объясняет, почему шаблоны проектирования IoC/DI получили второе дыхание в 2000-х, причём даже внутри самой MS (желаю Вам никогда в жизни не столкнуться с SCSF).


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

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

    Действительно, много ли Вы встречали книг или статей по программированию, где указывалось бы на то, что код всегда содержит ошибки (даже калькулятор невозможно покрыть 100% тестированием) и нужно в него вставлять возможности диагностирования ошибок в продуктивной среде, где Вам никто не даст поставить Studio и отладиться? Что если продукт окажется достойным и найдёт своего пользователя, то он обязательно будет дорабатываться, но делать это, вполне вероятно, будут другие люди, с не известно каким уровнем подготовки? Вот я ни одной не могу вспомнить.

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

    Итак.

    Сложность для понимания


    Где-то читал в своё время – то ли у Брукса, то ли у Листера с ДеМарко, точно не помню, – что языки программирования придуманы не для машин, а для людей. Машине, в конце концов, без разницы, забьёте ли вы руками в файл нолики и единички или сначала напишите текстовые команды, а затем откомпилируете в исполняемый код. Компилятору всё равно, будет ли программист вставлять комментарии в свой код или посчитает, что тот является самодокументированным. Интерпретатор JavaScript одинаково обработает обфусцированный, сжатый скрипт и отформатированный, с человекопонятными названиями переменных и функций.

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

    Если ваша программа состоит из десятка-другого объектов (т.е. это не более 100 файлов с описанием классов и интерфейсов), то воспринять всё это вместе в виде единого целого относительно просто. Мне в своё время довелось сопровождать настольное приложение, созданное на основе Microsoft Smart Client Software Factory (потом ему на смену MS запустила Prism, но уверен, что IoC/DI там так же плотно задействованы), по своему функционалу не такое уж сложное, но состоявшее из пары десятков проектов (в терминах Visual Studio), а это сотни и сотни классов, отвечающих и за DAL, и за логику, и за пользовательский интерфейс, и за событийную модель под ним. Каждый раз, когда на горизонте появлялась задача по добавлению новой фичи, меня начинало слегка колотить изнутри, т.к. всплывала перспектива увлекательно провести несколько дней, чтобы догадаться, куда нужно «воткнуть» обработку нового поля объекта из БД, точнее – по каким классам распиханы зависимости. При слабой связанности классов, поверьте, это не самая тривиальная задача.

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

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


    Вспоминается цитата с башорга:

    #define TRUE FALSE //счастливой отладки, уроды (*)

    (*) фраза была несколько смягчена, дабы не навлекать на Ресурс.

    Смешно, не правда ли? А вот такие шутки штуки я встречал в своём проекте на этапе запуска в ПЭ (и было мне не совсем смешно):

    Stack Trace
    StructureMap.StructureMapException: StructureMap Exception Code: 202
    No Default Instance defined for PluginFamily System.Func`2[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
    at StructureMap.BuildSession.<.ctor>b__0(Type t)
    at StructureMap.Util.Cache`2.get_Item(KEY key)
    at StructureMap.BuildSession.CreateInstance(Type pluginType)
    at StructureMap.Pipeline.Instance.createRawObject(Type pluginType, BuildSession session)
    at StructureMap.Pipeline.Instance.Build(Type pluginType, BuildSession session)
    at StructureMap.Pipeline.ConstructorInstance.Get[T](String propertyName, BuildSession session)
    at lambda_method(Closure, IArguments )
    at StructureMap.Construction.BuilderCompiler.FuncCompiler`1.<>c__DisplayClass2.<CreateBuilder>b__0(IArguments args)
    at StructureMap.Construction.InstanceBuilder.BuildInstance(IArguments args)
    at StructureMap.Pipeline.ConstructorInstance.Build(Type pluginType, BuildSession session, IInstanceBuilder builder)
    at StructureMap.Pipeline.SmartInstance`1.build(Type pluginType, BuildSession session)
    at StructureMap.Pipeline.Instance.createRawObject(Type pluginType, BuildSession session)
    at StructureMap.Pipeline.Instance.Build(Type pluginType, BuildSession session)
    at StructureMap.Pipeline.ObjectBuilder.Resolve(Type pluginType, Instance instance, BuildSession session)
    at StructureMap.BuildSession.CreateInstance(Type pluginType, Instance instance)
    at StructureMap.BuildSession.CreateInstance(Type pluginType)
    at StructureMap.Pipeline.Instance.createRawObject(Type pluginType, BuildSession session)
    at StructureMap.Pipeline.Instance.Build(Type pluginType, BuildSession session)
    at StructureMap.Pipeline.ConstructorInstance.Get[T](String propertyName, BuildSession session)
    at lambda_method(Closure, IArguments )
    at StructureMap.Construction.BuilderCompiler.FuncCompiler`1.<>c__DisplayClass2.<CreateBuilder>b__0(IArguments args)
    at StructureMap.Construction.InstanceBuilder.BuildInstance(IArguments args)
    at StructureMap.Pipeline.ConstructorInstance.Build(Type pluginType, BuildSession session, IInstanceBuilder builder)
    at StructureMap.Pipeline.SmartInstance`1.build(Type pluginType, BuildSession session)
    at StructureMap.Pipeline.Instance.createRawObject(Type pluginType, BuildSession session)
    at StructureMap.Pipeline.Instance.Build(Type pluginType, BuildSession session)
    at StructureMap.Pipeline.ObjectBuilder.Resolve(Type pluginType, Instance instance, BuildSession session)
    at StructureMap.BuildSession.CreateInstance(Type pluginType, Instance instance)
    at StructureMap.BuildSession.CreateInstance(Type pluginType)
    at StructureMap.Pipeline.Instance.createRawObject(Type pluginType, BuildSession session)
    at StructureMap.Pipeline.Instance.Build(Type pluginType, BuildSession session)
    at StructureMap.Pipeline.ConstructorInstance.Get[T](String propertyName, BuildSession session)
    at lambda_method(Closure, IArguments )
    at StructureMap.Construction.BuilderCompiler.FuncCompiler`1.<>c__DisplayClass2.<CreateBuilder>b__0(IArguments args)
    at StructureMap.Construction.InstanceBuilder.BuildInstance(IArguments args)
    at StructureMap.Pipeline.ConstructorInstance.Build(Type pluginType, BuildSession session, IInstanceBuilder builder)
    at StructureMap.Pipeline.SmartInstance`1.build(Type pluginType, BuildSession session)
    at StructureMap.Pipeline.Instance.createRawObject(Type pluginType, BuildSession session)
    at StructureMap.Pipeline.Instance.Build(Type pluginType, BuildSession session)
    at StructureMap.Pipeline.ObjectBuilder.Resolve(Type pluginType, Instance instance, BuildSession session)
    at StructureMap.BuildSession.CreateInstance(Type pluginType, Instance instance)
    at StructureMap.BuildSession.CreateInstance(Type pluginType)
    at StructureMap.Pipeline.Instance.createRawObject(Type pluginType, BuildSession session)
    at StructureMap.Pipeline.Instance.Build(Type pluginType, BuildSession session)
    at StructureMap.Pipeline.ConstructorInstance.Get[T](String propertyName, BuildSession session)
    at lambda_method(Closure, IArguments )
    at StructureMap.Construction.BuilderCompiler.FuncCompiler`1.<>c__DisplayClass2.<CreateBuilder>b__0(IArguments args)
    at StructureMap.Construction.InstanceBuilder.BuildInstance(IArguments args)
    at StructureMap.Pipeline.ConstructorInstance.Build(Type pluginType, BuildSession session, IInstanceBuilder builder)
    at StructureMap.Pipeline.SmartInstance`1.build(Type pluginType, BuildSession session)
    at StructureMap.Pipeline.Instance.createRawObject(Type pluginType, BuildSession session)
    at StructureMap.Pipeline.Instance.Build(Type pluginType, BuildSession session)
    at StructureMap.Pipeline.ObjectBuilder.Resolve(Type pluginType, Instance instance, BuildSession session)
    at StructureMap.BuildSession.CreateInstance(Type pluginType, Instance instance)
    at StructureMap.Container.GetInstance[T](String instanceKey)
    at NNN.BBB.Integration.Uvhd.Dispatcher.Start() in j:\.projects\DDD\trunk\NNN.BBB.UvhdIntegrationService\Dispatcher.cs:line 30

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

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

    Нивелирование достоинств компилируемых языков


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

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

    Во-первых, в данном проекте было всего лишь около 50 классов и методом [научного] тыка удалось относительно быстро установить, что нужно ещё доработать класс загрузки конфигурации.

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

    Повышенные требования к уровню подготовки специалистов


    Сами по себе сложность восприятия кода и трудности отладки не смертельны. Беда в том, что для целей внедрения, сопровождения и развития продукта нужны люди из плоти и крови. И очевидно, чем сложнее продукт, тем выше требования к уровню подготовки специалистов. А тут уже встают проблемы рынка труда и сферы образования, о которых, полагаю, не нужно никому тут рассказывать: грамотных, опытных спецов найти занимает много времени, содержать не дёшево, удержать и того сложнее. Да и на изучение «матчасти» уходят недели и даже месяцы.
    Как следствие из данного аргумента хотел бы выделить ещё 3 уточнения.

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

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

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

    Заключение


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

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

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

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

    Похожие публикации

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

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

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

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

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

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


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

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


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

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

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

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

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

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

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

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

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

                someService.someMethod(...)
                

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

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

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


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

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


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


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

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


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

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


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

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


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

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


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


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


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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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


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

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

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


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

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


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

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

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

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

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

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

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

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

                            +1

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

                              0

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

                              0

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

                                –1

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

                                  0

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

                                    –3

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

                                      0

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

                                        –2

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

                                          0

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

                                            –2

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

                                              0

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

                                                –2

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

                                                  0

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

                                                    –2

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

                                                      0

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

                                                        –2

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

                                                          0

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

                                                            –1

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

                                                                –2

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

                                                                  +2

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

                                                                    +1

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

                                                                      –2

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

                                                                        +2

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


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

                                                                          0

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

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

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


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

                                                                            Цитата:


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

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


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

                                                                              –3

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

                                                                                +1

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

                                                                          –1

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


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

                                                                            –1

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

                                                                              0

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

                                                +1
                                                По своим личным ощущениям, я хочу сказать, что отношение разработчика к 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 это вроде бы часть платформы.
                                                  +1

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

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

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

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

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


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


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

                                                            0

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


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

                                                              +1

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

                                                          0

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

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

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

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

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


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

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

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

                                                                  1. В любом UI приложении в стеке очень много инфраструктуры и очень мало бизнеса и это не проблема, а особенность. C DI-контейнерами все то же самое — служебные строки в стеке моно спокойно игнорировать: достаточно строк из бизнес-классов и текста ошибки.
                                                                  2. DI не заставляет вас использовать конкретный контейнер. Если вам не нравится сообщения конкретного контейнера — замените на удобный вам или вообще обходитесь без него, паттерн позволяет.
                                                                  3. Если проблема в параметрах не конструктора, а другого метода — не используйте Method Injection и будет вам счастье.
                                                                    +3
                                                                    Так и 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>()));
                                                                    

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

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

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

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

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

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

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

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

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

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

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


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

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


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

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


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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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


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

                                                                                                                                  0

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

                                                                                                                                    0

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

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

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

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

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

                                                                                                                          +2

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

                                                                                                                            0

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


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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

                                                                                                                                  –1

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

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

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

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

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

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

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

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

                                                                                                                                            Почему?

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

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


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

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

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

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

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

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

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

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

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

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


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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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


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

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


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

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


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

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


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

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

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

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

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

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

                                                                                                                                                                                      validator.validate<MyEntity>(entity)

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


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


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

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

                                                                                                                                                                                        0
                                                                                                                                                                                        У вас MyEntity — это P, A или B? Или Object?
                                                                                                                                                                                          0

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


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

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

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

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

                                                                                                                                                                                                0
                                                                                                                                                                                                Внезапно.

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

                                                                                                                                                                                                https://ru.wikipedia.org/wiki/Полиморфизм_(информатика)
                                                                                                                                                                                                –1
                                                                                                                                                                                                К сожалению, я полиморфизм использую на объектах разных типов, например наследниках.
                                                                                                                                                                                                «К сожалению», потому что понимаете, что этим нарушаете принцип '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);
                                                                                                                                                                                                }


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

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

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

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

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

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


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

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


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

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

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

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

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

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

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


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

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

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

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

                                                                                                                                                                                                            И SRP, видимо.

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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


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

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

                                                                                                                                                                                                      –1
                                                                                                                                                                                                      Отличное замечание!
                                                                                                                                                                                                      Тогда реализуем вот так:
                                                                                                                                                                                                              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);
                                                                                                                                                                                                              }
                                                                                                                                                                                                      
                                                                                                                                                                                                        0
                                                                                                                                                                                                        Остался один шаг до DoAnything(lambda, params[])

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

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

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

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

                                                                                                                                                                                                                0
                                                                                                                                                                                                                Так вы покажите свои, а мы посмотрим у кого жирнее.
                                                                                                                                                                                                                  0

                                                                                                                                                                                                                  Смотрите на здоровье:
                                                                                                                                                                                                                  https://bitbucket.org/KirillMaurin/fluenthelium

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

                                                                                                                                                          Не совсем.


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


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

                                                                                                                                    0

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


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


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


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


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


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

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

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

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

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

                                                                                                                                      пользователь может поставить галочку опубликовать в соц. сети