Статья 1. Почему я не преподаю SOLID


Если вы разговариваете с кем-то, кому небезразлично качество кода, уже достаточно скоро в разговоре всплывёт SOLID — аббревиатура, помогающая разработчикам запомнить пять важных принципов объектно-ориентированного программирования:

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

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

Сегодня SOLID остается для меня важным, но я больше не пытаюсь сделать мой код SOLID. Я редко упоминаю его, когда говорю про дизайн. И тем более я не учу пользоваться им разработчиков, которым хочется почерпнуть хорошие дизайнерские методы проектирования. Он больше не находится у меня под рукой в моем «ящике для инструментов». Он лежит в пыльной коробке на чердаке. Я храню его, потому что он важен, но редко им пользуюсь.

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

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

Основная проблема в том, что SOLID концентрируется на зависимостях. Каждый из принципов Открытости / Закрытости, Разделения интерфейса и Инверсии зависимостей ведет к использованию большого количества зависимостей от абстракций (интерфейса или абстрактного класса C#/Java). Принцип открытости / закрытости использует абстракции для простоты расширения. Принцип разделения интерфейса способствует созданию более клиенто-ориентированных абстракций. Принцип инверсии зависимостей говорит, что зависимости должны быть от абстракции, а не от конкретной реализации.

В результате все это приводит к тому, что разработчики начинают создавать интерфейсы где попало. Они загромождают свой код интерфейсами типа IFooer, IDoer, IMooer, IPooer.

Навигация превращается в кошмар. Во время code-review часто непонятно, какая именно часть кода заработает. Но это нормально. Это же SOLID. Это великолепный дизайн!

Чтобы помочь управиться с этим безумием, мы внедряем IoC-контейнер. А еще Mock-фреймвок для тестов. Если и раньше все было не слишком понятно, теперь становится непонятно окончательно. Теперь вы в самом прямом смысле не можете найти вызов конструктора в коде. Пытаетесь разобраться во всем этом? Удачи! Но это ничего. Потому что это же SOLID. Это великолепный дизайн!

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

Статья 2. За рамками SOLID: «Принцип устранения зависимостей»



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

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

Мы получаем какие-то данные через зависимость? Передайте сами данные, но уберите ПолучательДанных.
Мы передаем что-то через зависимость? Рассмотрите модель с событиями и подпиской на них, вместо передачи интерфейса. Или же используйте Data Transfer Object, описывающий состояние зависимости и реагируйте на его изменение (как, например, в MVVM).

Объекты-значения

В большинстве проектов оказывается слишком много зависимостей, потому что они слабо используют объекты-значения для описания своих концепций. Следующие несколько простых подсказок помогут создать ясный и понятный код с умеренным количеством зависимостей и абстракций (подсмотрено в статье «Take on the 4 Rules of Simple Design» J.B. Rainsberger):

  • Избавьтесь от Одержимости примитивами
  • Придумывая названия, используй имена существительные (и не используй отглагольные существительные, которые имеют окончание “er”)
  • Избавляйтесь от дублирования


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

Тестопригодность в качестве эталона

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

  • Уровень 0: статическая функция без побочных эффектов
  • Уровень 1: Класс с неизменяемым состоянием. Например, Объекты-Значения, заменяющие примитивы вроде EmailAddress или PhoneNumber
  • Уровень 2: класс с изменяемым состоянием, взаимодействующий только с простыми зависимостями, такими как Объект-Значение из Уровня 1.
  • Уровень 3: класс с изменяемым состоянием, взаимодействующий с другими изменяемыми классами


Уровень 0 тестировать элементарно. Просто отправляйте различные входные данные в функцию и проверяйте правильность результата.

Уровень 1 не слишком отличается от Уровня 0, кроме того, что у вас есть еще несколько методов, которыми его можно протестировать и еще несколько конфигураций, которые тоже надо проверить. Уровень 1 хорош, потому что он заставляет вас инкапсулировать ваши концепции в Объекты-Значения.

Уровень 2 сложнее, чем Уровень 1, так как вам придется думать о внутреннем состоянии и тестировать разные случаи, в которых оно меняется. Но иногда вам нужен Уровень 2 из-за своих преимуществ.

Уровень 3 тестировать сложнее всего. Придется либо использовать Mock-объекты, либо тестировать несколько модулей одновременно.

Я хочу сделать тестирование как можно более простым, поэтому я стараюсь использовать как можно более низкий уровень, который удовлетворяет моим требованиям: в основном я использую код Уровня 0 и Уровня 1. Иногда Уровня 2 и редко Уровня 3. Мой код становится очень функциональным, однако пользуется преимуществом объектно-ориентированности при создании Объектов-Значений для группировки связанной функциональности.

Возвращаясь обратно к SOLID

Допустим, мы применили Принцип устранения зависимостей. Давайте проанализируем насколько SOLID стал этот код:

  • Принцип единственной обязанности: Ну, да! Массовое использование Объектов-Значения. Необыкновенно высокое сцепление.
  • Принцип открытости /закрытости: Да, но по-другому. Открытость в том, что мы можем как угодно комбинировать Объекты-Значения, а не в том, чтобы везде внедрять зависимости от абстракций.
  • Принцип подстановки Барбары Лисков: он нам не важен. Мы вообще почти не используем механизм наследования.
  • Принцип разделения интерфейса: опять же, не важен. Мы почти не используем интерфейсы.
  • Принцип инверсии зависимостей: опять же по большей части не нужен, так как мы устранили большую часть зависимостей. Оставшиеся зависимости являются Объектами-Значениями, которые стоит рассматривать как часть системы типов, а также возможно небольшое количество интерфейсов, чтобы общаться с внешним миром.


Все дело в Принципе единой обязанности

Применяя Принцип устранения зависимостей, вы фокусируетесь исключительно на Принципе единой обязанности. И вы получаете гибкость Принципа открытости / закрытости, который ведет к простоте адаптации под бизнес-требования. А еще вы можете забыть про все сложности, который несут с собой принципы подстановки Лисков, Разделения интерфейсов и Инверсии зависимостей. Мы выигрываем по всем статьям!

Комментарии (101)


  1. SVVer
    21.06.2015 11:27
    +19

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

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

    Мы же понимаем, что устранить все зависимости в системе не удастся. Классический пример — приложение, работающее с данными через Репозиторий. Зависимость обрабатывающего кода от репозитория все-равно останется. Вопрос только в том, где она в итоге будет: вы либо передадите ее туда, где нужно получить данные, либо вынесете в некий мега-менеджер, который сначала получит требуемые данные из репозитория, а потом вызовет класс, и так для каждого класса; в итоге вы просто получите что-то типа GodObject.

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

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


    1. KAndy
      21.06.2015 13:00

      в итоге вы просто получите что-то типа GodObject.

      Нет, вы получите объект который отвечает только за одну вещь — создание объекта.
      Обычно эго называют IOC или DI контейнер


      1. SVVer
        21.06.2015 13:29
        +2

        Предположим, вы имеете два класса — A и B, которым требуются данные из репозитория. Причем, каждый из классов получает данные с использованием разных критериев поиска. Если вы вынесете логику получения данных за эти классы, то там вы будете вынуждены знать, как A и B формируются критерии поиска. А если классов не два, а десять? А если больше?
        IoC-контейнер, в отличие от приведенного примера, не знает об особенностях тех классов, в которые он внедряет зависимости. Он лишь знает, что и куда ему надо внедрить (например, что если в конструкторе порождаемого класса есть параметр типа IServ1).


        1. RouR
          22.06.2015 11:14

          Репозитарий возвращает IQueryable, сами фильтры внутри классов А и В.


          1. RouR
            22.06.2015 11:20

            Другой способ — классы А и В из своих фильтров формируют Specification, который в общей форме умеет обрабатывать репозиторий.


            1. SVVer
              22.06.2015 11:45

              И дальше два варианта: либо вы инъектируете в A и B репозиторий, передадите ему спецификацию и получите данные, либо какой-то внешний код вызовет A.GetSpecification(...) и B.GetSpecification(...), после чего запросит у репозитория данные и подсунет их через A.SetData(...) и B.Set(...). Во втором варианте, имхо, код будет запутаннее, т.е. получится ровно то, против чего возражает автор статьи.


          1. SVVer
            22.06.2015 11:39

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


    1. xoposhiy Автор
      21.06.2015 13:07
      +4

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

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


      1. SVVer
        21.06.2015 13:34
        +1

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

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


      1. MacIn
        21.06.2015 15:40
        +2

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

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


        1. Flammar
          21.06.2015 20:39

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


          1. MacIn
            21.06.2015 23:35
            +3

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


            1. Flammar
              22.06.2015 02:07
              +2

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

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


              1. ATOMOHOD
                22.06.2015 11:31
                +2

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


                1. Flammar
                  27.06.2015 22:18

                  Ценное замечание. Подобные мысли я читал у МакКонелла в «Совершенном коде» про выбор способа обработки ошибок. Способов несколько — от игнорирования ошибки до «обрушивания» программы, каждый — лучший для определённой ситуации, выбор определяется тем, где и зачем надо обрабатывать ошибку.

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


  1. Lol4t0
    21.06.2015 12:35
    +16

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

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

    А вообще SOLID — здравая концепция. Просто если любой принцип возвести в абсолют и довести до абсурда, то получится ерунда


    1. InWake
      21.06.2015 12:56
      +3

      Или даже ерунда не получается, продукт не доживает до релиза…


  1. aprusov
    21.06.2015 12:36
    +8

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


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

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

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


    1. SVVer
      21.06.2015 12:47
      +1

      В solid нет такого принципа и нет там настроя на то, что зависимости — это проблема.
      Автор просто вводит свой принцип. Тут в тексте тоже есть подзаголовок, хотя я при первом прочтении тоже не заметил.
      За рамками SOLID: «Принцип устранения зависимостей»
      В оригинале — это вообще вторая часть поста.


  1. mird
    21.06.2015 12:38
    +9

    Я бы сказал, что автор этой статьи плохо понимает SOLID и когда применял SOLID страдал от overarchitecture. Ну и по пунктам:
    Dependency Inversion это не «используй интерфейсы». Это о том, что зависимости должны передаваться снаружи, а не создаваться внутри потребителя этой зависимости. И «Передайте сами данные, но уберите получатель данных» — это тоже инверсия зависимостей. Более того, «Передайте сами данные, но уберите получатель данных» это еще и принцип единственности ответственности. Потому что тот класс или метод, который данные обрабатывает, не должен отвечать и за получение данных


    1. areht
      21.06.2015 18:52
      +1

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

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

      Впрочем, Dependency Inversion вообще то не (только) про это:
      A. High-level modules should not depend on low-level modules. Both should depend on abstractions.
      B. Abstractions should not depend on details. Details should depend on abstractions.

      Если тупо передавать зависимости снаружи — инверсии не происходит. Это, скорее, про другой DI — Dependency Injection.

      > Если бы меня попросили описать все эти проблемы одним словом, я бы выбрал слово «непонятный».

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


  1. SVVer
    21.06.2015 12:56
    +5

    Вопрос к автору перевода: если вы добавили собственные трактовки принципов из SOLID (в оригинале их нет), то не стоит ли их пояснить? Например, Single Responsibility — это действительно «делай модули меньше»? Например, если модуль большой и несет более одной ответственности, то да «делай его меньше», а конкретнее — дроби. Но если модуль и так несет одну ответственность и является «high cohesive», то его дробление может привести к получению нескольких сильно связанных модулей (highly coupled), ответственность по которым размазана.


    1. xoposhiy Автор
      21.06.2015 22:40
      +1

      Оригинальные объяснения принципов — по многостраничной статье на каждый. Естественно, все попытки передать смысл в трех словах будут грубым упрощением. Впрочем, если вы придумаете способ выразить SRP в трех словах лучше, чем «делай модули меньше», то я с удовольствием в будущем буду пользоваться вашим вариантом! :)


      1. BlessMaster
        21.06.2015 23:04

        «Принцип единственной обязанности», если верить Википедии
        «Проще», не гарантирует «единственность» обязанности


      1. SamDark
        22.06.2015 00:01
        +3

        «Модуль должен делать что-то одно»?


      1. SVVer
        22.06.2015 08:58
        +1

        А зачем дополнительно передавать смысл в трех словах, если само определение состоит из трех слов и наиболее точно выражает смысл?


      1. AlexanderByndyu
        09.07.2015 06:09

        В оригинале SRP поястяется как: «не должно быть больше одной причины для изменения». Здесь нет про больше или меньше, зато критерий вполне конкретный.


  1. InWake
    21.06.2015 13:07

    Можно увидеть что нибудь живое? Какой нибудь проект в исходниках, класс или что нибудь реальное, что можно попробовать и ощутить. Я повернут на идеальном коде, Фоулере и инверсии управления (которая реально работает, когда приходиться менять функционал, который касается конкретной реализации и не затрагивает абстракцию).


  1. deniskreshikhin
    21.06.2015 13:09
    +3

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

    С таким же успехом можно говорить что «дикобразы» это в некотором роде тоже «ежи» (если кто не знает — первые грызуны, а вторые клыкастые хищники).


  1. lair
    21.06.2015 13:28

    Уровень 0: статистическая функция без побочных эффектов
    Уровень 2: класс с изменяемым состоянием, взаимодействующий только с простыми зависимостями, такими как Объект-Значение из Уровня 1.

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

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

    В этом месте мне становится интересно, как же автор компонует свои чистые функции, и проверяет, что их композиция тоже работает.


    1. VladVR
      21.06.2015 23:51

      Тоже, кстати, споткнулся на нулевом уровне при прочтении. Подумал, что может перевод некорректный. Но нет, у автора тоже написано «без побочных эффектов», а не «без состояния».


  1. kosmos89
    21.06.2015 13:44

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


    1. xoposhiy Автор
      21.06.2015 22:46
      +1

      а также возможно небольшое количество интерфейсов, чтобы общаться с внешним миром

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


      1. kosmos89
        22.06.2015 00:54

        У всех разная реальность.


  1. KvanTTT
    21.06.2015 13:44
    +2

    В результате все это приводит к тому, что разработчики начинают создавать интерфейсы где попало. Они загромождают свой код интерфейсами типа IFooer, IDoer, IMooer, IPooer.

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

    А вообще придерживаться принципов SOLID вместе с KISS и YAGNI — хорошая практика.


  1. dnabyte
    21.06.2015 13:57
    +4

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

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


  1. Ktulhy
    21.06.2015 14:45
    +1

    Придумывая названия, используй имена существительные (и не используй отглагольные существительные, которые имеют окончание “er”)

    Скажите это разработчикам Java с их StringBuilder, Notifier и прочими.


    1. VladVR
      21.06.2015 15:09
      +13

      Ну да, вместо DataLoader я назову DataLoadingService, бинго, обманул. Как будто это научит писать код лучше.


      1. xoposhiy Автор
        21.06.2015 22:36
        +1

        Согласно идеям автора, назвать стоило бы Data и сделать у этого класса метод Load. То есть классы по возможности должны описывать настоящие сущности. Идея в общем не нова, и автор статьи не сам ее придумал. То же DDD вокруг этого строится.


        1. lair
          21.06.2015 23:13
          +2

          И что бы делал метод Load в классе Data?


          1. SamDark
            22.06.2015 00:05

            Загружал бы данные :)


            1. lair
              22.06.2015 00:07

              Куда?


              1. SamDark
                22.06.2015 00:43

                Туда же, куда DataLoader и DataLoadingService :)


                1. lair
                  22.06.2015 00:44

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


                  1. SamDark
                    22.06.2015 00:54

                    Именно.


                1. Flammar
                  22.06.2015 00:51

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


                  1. SamDark
                    22.06.2015 00:54

                    Скорее всего.


            1. Flammar
              22.06.2015 00:41

              И откуда?


        1. VladVR
          21.06.2015 23:41

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


          1. Flammar
            22.06.2015 01:01

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


        1. Flammar
          22.06.2015 00:41

          Вариация на тему ActiveRecord? В применении к Java отброшено ещё в начале 2000-х как тупиковый путь. Если вы в рантайме можете поменять метод Load у класса Data на другой алгоритм — тогда это ещё может быть хорошей идеей. Если нет — то нет.


    1. Flammar
      22.06.2015 00:35

      Подозреваю, большая часть интефейсов и классов стандартной библиотеки Java с именами в виде отглагольных существительных с суффиксом “er” является продуктом workaround'а не-функциональности языка. В языке Java, чтобы передать куда-нибудь функцию в качестве значения аргумента, нужно прописать её сигнатуру в интерфейсе и обернуть её тело в экземпляр ad-hoc класса реализующего этот интерфейс. Много телодвижений, но технология работает, к тому же в наличии типобезопасность и статическая типизация «из коробки», поэтому некоторую громоздкость прощают и считают неизбежной платой за компромисс.


  1. tangro
    21.06.2015 15:45
    +7

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


  1. solver
    21.06.2015 17:05
    +7

    Есть клевая старая поговорка «Заставь дурака богу молиться — он лоб разобьет».
    Статья как раз об этом. Когда выключая голову, начинают тупо следовать каким-то догмам.
    А потом, когда хлебнут проблем, так же тупо от них отказываются.

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

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


  1. silentz
    21.06.2015 22:14
    +1

    Согласен с автором в том плане что «стало непонятно». Сейчас работаю в проекте где везде сплошной DI/IoC и MVP. Это жесть. Чтобы создать одну вьюху нужно создать 10 классов/интерфейсов и внести изменения (забайндидь эти классы) ещё в 5 файлах.


  1. Nagg
    22.06.2015 00:40
    +5

    SOLID — отличная вещь, он позволяет обозвать любой код говнкодом ибо невозможно написать серьезный проект 100% следуя SOLID. Где-нибудь да найдется классик, у которого будет больше одной ответственности к примеру :-).


    1. Flammar
      22.06.2015 00:48
      +4

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


      1. Nagg
        22.06.2015 00:51
        +2

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


        1. Flammar
          22.06.2015 01:02
          +2

          И любые попытки следовать принципу DRY — тоже.


  1. EngineerSpock
    22.06.2015 10:35
    +1

    1. Какая же всё-таки у человека в голове каша. Просто адская каша. Путает coupling (связанность) с cohesion (связность) (или перевод неправильный).
    2. Есть подозрение, ИМХО, что человек страдает от синдрома ненависти к чужому коду, поскольку категоричен.
    3. Fully SOLID design — это оксюморон и так говорит сам дядя Боб. Но автору пофиг, автор сам за других решил о том кто как и о чём думает, не удосуживаясь поковыряться в подкастах от дяди Боба того же, например, и сделал свои глупые выводы.
    4. Автор не придумал абсолютно ничего нового, но зато всё неправильно понял.


  1. ganqqwerty
    22.06.2015 12:06
    +2

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


  1. andreycha
    28.06.2015 17:04

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

    Вот поэтому я и предпочитаю обходить стороной все IoC-контейнеры.


    1. lair
      28.06.2015 18:02

      Вы при этом используете изолированное тестирование в вашем приложении или нет?


      1. solver
        28.06.2015 20:15
        +1

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


        1. DjoNIK
          28.06.2015 22:40
          +2

          Простите, но о каких «таких людях» Вы говорите? Или у Вас уже готова оценка профессионализма по одному предложению?


          1. solver
            28.06.2015 23:36
            +1

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


        1. andreycha
          29.06.2015 00:01

          Спасибо за характеристику. Скопипащу себе в резюме.


      1. andreycha
        29.06.2015 00:02

        Что вы имеете в виду под изолированным тестированием?


        1. lair
          29.06.2015 00:10

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


          1. andreycha
            29.06.2015 00:15

            Да, использую.


            1. lair
              29.06.2015 00:16

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


              1. andreycha
                29.06.2015 01:06

                Использую мок-фреймворк. Зависимости, как правило, создаю тут же в тесте.


                1. lair
                  29.06.2015 01:22

                  А как вы их передаете в тестируемый элемент?


                  1. DragonFire
                    29.06.2015 09:23
                    +1

                    Очевидно, что ручным вызовом конструктора =) IoC не является обязательным компонентом для юнит тестирования =)))


                    1. lair
                      29.06.2015 11:31
                      +1

                      Если вы имеете в виду, что зависимости передаются в class under test как параметры конструктора, то это и есть IoC, паттерн Dependency Injection, стратегия Constructor Injection. Так что хотя юнит-тестирование теоретически и возможно без IoC, конкретно ваш сценарий это никак не доказывает.


                      1. DragonFire
                        29.06.2015 12:43

                        предпочитаю обходить стороной все IoC-контейнеры

                        не утруждают себя разбираться как работают IoC-контейнеры

                        Мне казалось мы обсуждаем ioc-контейнеры, а не указываем оппонентам на разницу между IoC и IoC-контейнерами. Видимо был не прав.


                        1. lair
                          29.06.2015 12:49
                          +1

                          Я не знаю, что вы обсуждаете, а я задал andreycha вполне конкретные вопросы и хочу услышать вполне конкретные ответы. Фразу «IoC не является обязательным компонентом для юнит тестирования» зачем-то вы ввернули.


                      1. kosmos89
                        29.06.2015 13:21

                        >это и есть IoC
                        нет, ручная инжекция — это не IoC. IoC подразумевает, что КТО-ТО другой (фреймворк, контейнер, etc) возьмет на себя управление. IoC не является синонимом DI, он просто там может использоваться для удобства.


                        1. lair
                          29.06.2015 13:26
                          +2

                          IoC подразумевает, что КТО-ТО другой (фреймворк, контейнер, etc) возьмет на себя управление

                          Вы, конечно, можете привести цитату, подтверждающую ваше утверждение?

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


                          1. kosmos89
                            29.06.2015 15:03
                            +1

                            >inversion of control (IoC) describes a design in which custom-written portions of a computer program receive the flow of control from a generic, reusable library
                            lmgtfy.com/?q=inversion+of+control


                            1. lair
                              29.06.2015 15:19

                              Используемое вами применение термина (я согласен, что оно исторически более верное) плохо применимо к IoC-контейнерам. Как пишет Фаулер, термин Dependency Injection в этой ситуации более точно отражает суть происходящего.

                              Но спасибо за исправление.


                              1. kosmos89
                                29.06.2015 15:33

                                Ну это кто-то другой начал использовать IoC как синоним DI, а я же как раз за правильное использование. А если имелся в виду DI, то там все верно — от того, кто инжектит принцип DIP не зависит. И это не мешает юнит-тестированию.


                  1. andreycha
                    29.06.2015 14:40
                    +1

                    Как-то так:

                    var dependency = Mock.Generate<IDependency>();
                    // установка ожиданий вызова/желаемого поведения, если необходимо
                    var component = new ComponentUnderTest(dependency);
                    


                    1. lair
                      29.06.2015 15:12

                      Очень хорошо. А в продуктивном выполнении?


                      1. andreycha
                        29.06.2015 18:41
                        +1

                        А в продуктивном выполнении?

                        Простите, не понял.


                        1. lair
                          29.06.2015 18:56

                          Когда вы запускаете этот же класс в реальном окружении, как вы инициализируете его зависимости?


                          1. andreycha
                            29.06.2015 19:03

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


                            1. lair
                              29.06.2015 20:20

                              А что вы имеете в виду под «посреди обычного кода»? Можете привести пример?


                              1. andreycha
                                29.06.2015 21:13

                                Я имел в виду, что этот код не находится в явно обособленном месте, типа ComponenFactory.CreateComponent().

                                А вы к чему клоните? Наверное уже можно и рассказать :).

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


                                1. lair
                                  29.06.2015 21:19

                                  Я клоню к тому, что:

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


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


                                  1. solver
                                    29.06.2015 21:26

                                    Ну тут дело даже не в адекватных контейнерах, а в адекватных разработчиках.
                                    Например излюбленный многими Java разработчиками прием «Инъекция приватных полей», способствует увеличению хаоса. А потом люди рассказывают как контейнер многократно усложнил им жизнь…


                                    1. lair
                                      29.06.2015 21:27

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


                                  1. mird
                                    29.06.2015 22:01

                                    ручное вбрасывание (известное как Pure DI)


                                    Это называется «Poor man DI» — DI бедняка.


                                    1. lair
                                      29.06.2015 22:02

                                      1. andreycha
                                        29.06.2015 23:09

                                        Ничего себе, даже Марк Симан не использует контейнеры. Наигрался :).


                                        1. lair
                                          30.06.2015 00:04

                                          Это, скажем так, неоднозначное утверждение.