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


Эта статья во многом вдохновлена докладом Павла Силина на РИТ 2017, однако здесь много моего собственного опыта и размышлений. Примеры будут на React + TypeScript, однако подход не привязан к какой-либо технологии.




Как не надо делать


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


ShowModalWindow(header: string, content: JSX.Element): Promise<ModalWindowResult>;

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


ShowModalWindow(header: string, content: JSX.Element, buttons?: string[]): Promise<ModalWindowResult>;

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


ShowModalWindow(header: string, content: JSX.Element, buttons?: string[], showOverlay?: boolean): Promise<ModalWindowResult>;

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


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


В моем примере была функция, но это может быть что угодно — реакт-компонент с огромными props, jquery-плагин со множеством опций, базовый класс с кучей наследников и переопределяемых методов, ASP.NET Rasor хелпер, со множеством параметров, scss mixin и т.д. Наступить на эти грабли можно в любой технологии и в самых разных видах.


Заменяй и властвуй


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


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


  1. S (single responsibility) — делать повторно используемые компоненты очень маленькими и простыми, с минимумом ответственности;
  2. O (open-closed) — никогда, ни при каких обстоятельствах не модифицируем код компонентов, которые часто используются;
  3. L (Liskov substitution) — любой компонент может быть заменен другим так, что все остальные компоненты не должны заметить подмены;
  4. I (interface segregation) — вместо написания "обобщенных" компонентов на все случаи жизни пишем простые конкретные реализации;
  5. D (dependency inversion) — решение о том, какой из компонентов будет использован в каждом случае, должен принимать вызывающий код.

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


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


Давайте отрефакторим наше модальное окно в соответствии с этими принципами. Нам нужно сделать модальное окно примерно следующего вида:



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


  1. Позиционирование окна — располагает что-либо по центру экрана;
  2. Затемнение фона — создает полупрозрачный div на весь экран;
  3. Коробка окна — определяет размеры и заливку внутри окна;
  4. Коробка заголовка — добавляет отступы для заголовка и рисует разделительную линию;
  5. Заголовок — опередяет стилизацию текста заголовка (в основном размер шрифта);
  6. Кнопка закрытия (крестик);
  7. Коробка содержимого — добавляет отступы для содержимого окна;
  8. Коробка кнопок диалога — добавляет отступы и позиционирует кнопки в правую часть;
  9. Кнопка — просто обычная кнопка, никак не связанная с диалогом.

Получается что-то вроде этого:


     <ModalBackdrop onClick={() => this.setState({ dialogOpen: false })} />
         <ModalDialog open={this.state.dialogOpen} >
             <ModalDialogBox>
                 <ModalDialogHeaderBox>
                     <ModalDialogCloseButton onClick={() => this.setState({ dialogOpen: false })} />
                     <ModalDialogHeader>Dialog header</ModalDialogHeader>
                 </ModalDialogHeaderBox>
                 <ModalDialogContent>Some content</ModalDialogContent>
                 <ModalDialogButtonPanel>
                     <Button onClick={() => this.setState({ dialogOpen: false })} key="cancel">
                         {resources.Navigator_ButtonClose}
                     </Button>
                     <Button disabled={!this.state.directoryDialogSelectedValue}
                         onClick={this.onDirectoryDialogSelectButtonClick} key="ok">
                         {resources.Navigator_ButtonSelect}
                     </Button>
                 </ModalDialogButtonPanel>
             </ModalDialogBox>
         </ModalDialog>
     </ModalBackdrop>

Каждый из этих компонентов, как правило, добавляет один div и несколько css-правил. Например, ModalDialogContent выглядит так:


    // JS
    export const ModalDialogContent = (props: IModalDialogContentProps) => {
        return (
            <div className="modal-dialog-content-helper">{props.children}</div>
        );
    }
    // CSS
    .modal-dialog-content-helper {
        padding: 0 15px 20px 15px;
    }

Если в будущем мне понадобится сделать модальное окно с другими отступами, то я просто заменю ModalDialogContent на обычный div, и задам свои собственные отступы. Если мне понадобится убрать затемнение, я просто уберу ModalBackdrop. Такая гибкость достигается за счет соблюдения всех принципов SOLID: компоненты простые и конкретные (S, I), ничего друг о друге не знают (D), поэтому проще их заменить (L), чем добавлять какие-то опции (O).


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


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


   <CommonModalDialog header="Header text" 
          isOpen={this.state.open} onClose={this.onClose}>
       Modal content
   </CommonModalDialog>

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


Назад в реальность


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


  1. Дороговизна. Проектирование и разработка всех этих маленьких компонентов требует много времени и сил. Мало того, что просто приходится много писать служебного кода, документации и тестов, так нужно еще и спроектировать эти компоненты таким образом, чтобы они ничего друг о друге не знали, но при этом корректно между собой взаимодействовали. Это очень сложно, и с точки зрения бизнеса — стоит много денег (время разработчика — деньги).
  2. Дробление сущностей. В примере выше, вместо одного модального окна, у нас получилось крошечных 9 компонентов. Соответственно, логика работы окна оказалась размазана по всем этим составляющим. В данном случае это не критично т.к. особой логики у окна нет, но для компонентов приложения это может иметь серьезные последствия.

Рассмотрим подробнее на примере меню пользователя вконтакте.



Можно начать разбивать его на кучу независимых маленьких компонентов, отдельно будет иконка пользователя, отдельно имя, отдельно менюшка… Мы потратим кучу сил на то, чтобы организовать взаимодействие между этими независимыми компонентами. Но что в итоге мы получим? Это меню существует в единственном числе, и только в таком виде. У этого меню есть некоторая логика — своя единая модель данных (информация о пользователе), определенный состав меню (набор действий), поведение (по щелчку открывается меню). Все это логика конкретного приложения, которая определяется бизнес-задачами и диктуется предметной областью. Размазывая эту логику по многим местам, не только создаем себе лишние трудности, но и усложняем поддержку и сопровождение нашего сайта. Другому программисту будет трудно найти место, где вешается обработчик на событие клика, который открывает менюшку, потому что он будет (безусловно по SOLID) запрятан где-нибудь в глубинах нашей архитектуры.


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


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


Заключение


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


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

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


  1. vasIvas
    24.08.2017 10:43
    -1

    Что-то полный бред. Клик, это бизнес логика, которая диктуется предметной областью?
    Такое ощущение, что кто-то только вчера открыл для себя новые слова, но ещё не успел постичь их смысл…


    1. PFight77 Автор
      24.08.2017 11:47

      Вы о чем конкретно? Опишите подробнее свою мысль.


      1. VolCh
        24.08.2017 12:49
        +1

        В общем случае, разработка меню или модального окна для конкретного сайта/веб-приложения не имеет никакого отношения к бизнес-логике, это чистая логика представления, логика UI. Бизнес-логикой она становится, если ваша задача разработать UI-фреймворк.


        1. PFight77 Автор
          24.08.2017 12:53

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


          1. vasIvas
            24.08.2017 12:58
            +1

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

            А это что?

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

            У КОМПОНЕНТА ModalDialogBox две ответственности, размеры и цвет… предметная область… Вы о чем? Вы даже не представляете какую безбожную ерунду Вы написали.

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

            На каждый пчих своя коробка… какая коробка, Вы грузчик? Разве это переиспользование?

            Если судить по себе, то складывается впечатление, что автор занимается программированием не больше полугода. И как венец архитектурного гения и накопленного личного опыта, та-дам! buttons?: string[]


            1. PFight77 Автор
              24.08.2017 13:40

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


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


            1. PFight77 Автор
              24.08.2017 13:48

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


              1. vasIvas
                24.08.2017 14:13
                -2

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

                И Вы пытаетесь случаем не пытаетесь улизнуть от ответа на непонимание основ ооп и dry + solid, о которых собственно и статья? Вы понимаете что статья не просто неверна, она ещё и вред принесет и таких как Вы будет еще больше, а работать с ними потом мне? Прежде чем учить, выучитесь сами. пол года программирования css + html не дает право писать такую ень.

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


                1. PFight77 Автор
                  24.08.2017 14:50

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


                  1. vasIvas
                    24.08.2017 15:31

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


                    1. AndreyRubankov
                      27.08.2017 13:19
                      +1

                      Судя по треду, дело в вашем недопонимании.

                      Автор в целом пишет правильные вещи, но местами могут быть проблемы с терминологией, – это нормально. Тут главное не терминология, а сама идея.

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

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


  1. vasIvas
    24.08.2017 11:52
    -3

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

    Вы о чем конкретно? Опишите подробнее свою мысль.

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


  1. NimElennar
    24.08.2017 12:50

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

    Для решения данной проблемы обычно используется паттерн «фабрика». Однако он может входить в противоречие с принципом Interface segregation.


  1. NimElennar
    24.08.2017 15:01

    Эта статья во многом вдохновлена докладом Павла Силина на РИТ 2017

    Бегло глянув на доклад, часть претензий по непониманию принципов SOLID и вообще ООП можно предъявить его автору. Основной посыл докладчика — в javascript интерфейсов нет, наследование есть, однако использовать его он не рекомендует (дословно цитируя — «моя практика показала, что использование наследования в React-компонентах это приводит к больше проблемам, чем к каким-то профитам»), но про SOLID он читал, и поэтому будет пытаться как-то связать прочитанное с темой доклада. Ведущий front-end разработчик…


    1. PFight77 Автор
      24.08.2017 15:04

      Я выше уже писал, это статья не о SOLID. Я, как и Павел, просто используем идеи из SOLID. Или Вы считаете, что если у меня функция вместо класса, то принципы SOLID никак нельзя применить?


      1. NimElennar
        24.08.2017 15:39

        Если из SOLID выбросить 2 или 3 принципа (за «ненужностью» или из-за того, что в языке javascript чего-то нет), это будет уже не SOLID. Зачем тогда вообще упоминать эти 5 принципов?

        Не скажу, что я гуру в терминологии, но расшифровка SOLID и в докладе и в тексте Вашей статьи, плохо соответствует тому, как лично я их понимаю.

        S — компоненты (классы) не обязательно должны быть маленькими и простыми, но должны иметь узкую специализацию. Условно, если есть класс «персона» (или как правильнее перевести то, что в английском вкладывается в слово person), у него может быть хоть 10000 методов и миллион строк кода, но если они связаны исключительно со свойствами абстрактной персоны, то принцип single responsibility де-юре не нарушается.

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

        L — компонент может быть заменён не просто на другой, а на такой, который реализует те же самые методы с той же самой логикой/поведением (но помимо этого может реализовывать ещё какие-то новые методы, которых не было в старом компоненте).

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

        D — не являюсь специалистом по javascript и front-end-у, но dependency injection в javascript якобы возможно, например, об этом писалось в habrahabr.ru/post/232851


        1. PFight77 Автор
          24.08.2017 15:54

          Спасибо, можете еще добавить, что из этого не соответствует представленному в статье описанию? Не обязательно маленькие, здесь я согласен. Но в случае компонентов в веб они чаще всего маленькие. Если они большие, то скорее всего там слишком много ответственности. В букве О я делаю чуть более жесткие требования — не рекомендую вообще менять, даже рефакторить. D — разве суть DI не в том, чтобы зависимости резолвились из вне? Или все что не Composition Root это не DI?


          1. NimElennar
            24.08.2017 16:39
            +1

            Я повторюсь, что single responsibility несёт в себе несколько иной смысл, чем просто размер компонента. Пусть у нас будет даже маленький компонент, даже крохотный, у которого будет только одно свойство, допустим строка value, и пара методов — get/set. То, как оно будет визуализироваться в html — это уже должен быть другой компонент. Нельзя будет добавить в первый компонент метод showAsHtml, не нарушив при этом принцип S.

            Принцип open/closed, условно, пусть у меня будет в коде цикл for(i=0; i <= myArray.length — 1; i++), почему я не могу поменять его на for(i=0; i < myArray.length; i++)? Что это нарушит в логике/поведении программы? Ничего. Если быть осторожным (и правильно покрыть код unit-тестами), то рефакторинг вполне допустим и не нарушает принцип O.

            Принципы L и D я в предложенном решении не вижу.

            Принцип interface segregation, возможно, соблюдён для мелких «деталек LEGO», но в самом решении конструирования диалогового окна из кубиков принцип I вообще не соблюдается. Это несоблюдение, само по себе. не хорошо и не плохо, даже самые полезные принципы и паттерны могут не подходить для решения каких-то конкретных, специфичных проблем, и в этом нет ничего зазорного.

            Как я уже писал выше, конструирование диалогового окна из деталек я бы вынес в отдельный factory-класс, в котором был бы набор facade-методов вроде dialogWithTextAndOkButton(text, okButtonCallback), dialogWithTextAndOkButtonAndXClose(text, okButtonCallback, closeCallback) или что-то подобное.


            1. PFight77 Автор
              24.08.2017 16:59

              Что это нарушит в логике/поведении программы?

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


              single responsibility несёт в себе несколько иной смысл

              Полностью с Вами согласен, но все-же статью править не буду. Именно из-за специфики веба, размер здесь особенно важен.


              я бы вынес в отдельный factory-класс

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


              но в самом решении конструирования диалогового окна из кубиков принцип I вообще не соблюдается.

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


              Принципы L и D я в предложенном решении не вижу

              L: Вы же сами пишите — "компонент может быть заменен на любой другой" — именно это и фигурирует в названии статьи "заменяй и властвуй". D: вызывающий код выбирает, из каких компонентов он собирает окно. Вызывающий код является внешним для компонентов LEGO, то есть зависимости резолвятся из вне. Что это если не DI?


              1. NimElennar
                24.08.2017 17:40
                +1

                Изменение вёрстки — это изменение поведения, я изначально сказал, что рефакторинг его не должен менять.

                Слишком общее решение может быть не удобно для использования и может увеличивать количество ошибок. Легче один раз написать и отладить helper (в общепринятой терминологии facade), чем помнить как, с какими параметрами и в какой последовательности нужно выполнить 5-10 простых, казалось бы, операций. То есть супер-гибкий метод может быть доступен, но, скорее всего, в 90% случаев будет достаточно пяти вариантов диалогового окна, и лучше иметь эти 5 вариантов в виде пяти простых и отлаженных методов.

                Я не стал бы переводить interface segregation как разделение интерфейса, это скорее изоляция вариантов использования интерфейса, разделение его на несколько частей с более узкой специализацией. Условно, вот есть у Вас это гибко-настраиваемое диалоговое окно. И есть в приложении 10 вызовов этого окна, где нужен лишь заголовок, input-поле и кнопка «ок». А также один единственный вызов этого диалогового окна, где, допустим, будет заголовок, под ним строка для поиска с кнопкой «искать», внизу слева древовидный справочник для выбора значения, справа при click-е на значении в дереве будет показываться какой-то текст, и внизу кнопка «ок». Принцип interface segregation советует разделить эти два варианта на разные классы/компоненты, так как по отдельности ими будет легче пользоваться.

                Дело не в «из каких компонентов он собирает окно», а в том, что DI-код сборки, условно, встретив Some content, может динамически (то есть в runtime) выбрать один из нескольких вариантов отрисовки этого компонента, причём он сам решает исходя из каких-то параметров или контекста, какой именно вариант выбрать. А может быть и только один вариант такого компонента, или вообще не одного, мало ли при сборке проекта что-то потерялось.


                1. NimElennar
                  24.08.2017 17:45

                  Прошу прощения, <ModalDialogContent>Some content</ModalDialogContent> заменилось на просто Some content.


                1. PFight77 Автор
                  24.08.2017 18:02

                  Изменение вёрстки — это изменение поведения

                  Компоненты, о которых я говорю, в основном только версткой и занимаются. Бывают компоненты с логикой, там рефакторинг и unit-тесты возможны, да.


                  Легче один раз написать и отладить helper (в общепринятой терминологии facade), чем помнить как, с какими параметрами и в какой последовательности нужно выполнить 5-10 простых, казалось бы, операций.

                  Разумно, соглашусь.


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

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


                  может динамически (то есть в runtime) выбрать один из нескольких вариантов

                  Когда резолвить зависимости — runtime или compile-time это уже не так принципиально, и зависит от потребностей в каждом случае. Среди задач которые мне встречались, динамическое сопоставление зависимостей привнесло бы только лишнюю сложность и не дало никакой выгоды. Можно, конечно, для каждого LEGO придумать фабрику и т.д., но это уже будет over-проектирование. Для фронтенда я такого не встречал.


                  1. NimElennar
                    24.08.2017 18:31

                    Сложно объяснить это на примере javascript. Условно, в Java у меня вместо относительно (пример искусственный, на самом деле методов может быть не 4, а, скажем, 100) переусложнённого варианта

                    interface ModalDialogWindow {
                    void setupInputFieldVersion();
                    void showInputFieldVersion();
                    void setupTreeDirectoryVersion();
                    void showTreeDirectoryVersion();
                    }

                    был бы
                    interface ModalDialogWindow {
                    void show();
                    }
                    interface InputFieldModalDialogWindow extends ModalDialogWindow {
                    void setupInputField();
                    void show();
                    }
                    interface TreeDirectoryModalDialogWindow extends ModalDialogWindow {
                    void setupTreeDirectory();
                    void show();
                    }
                    то есть я разделил бы общий случай на более специализированные.

                    Про DI также сложно объяснять пишущим на javascript. На Java у меня будет, допустим,

                    interface A {}
                    class B implements A {}
                    class C implements A {}

                    В коде у меня будет заведено поле типа A (экземпляр interface или abstract class создать невозможно, можно создавать только экземпляры не-абстрактных class-ов), и мне кто-то (DI-контейнер) подставит в это поле экземпляр либо типа B либо типа C, он сам по какой-то (известной, не случайной) логике решит, какой именно, B или C подставлять, не я в коде прямо впишу создание экземпляра конкретного класса

                    A a = new B();

                    или по какому-то условию буду создавать экземпляр

                    A a;
                    if(someVar == 'B') { a = new B(); }
                    else if(someVar == 'C') {a = new C(); }

                    а DI-контейнер за меня это сделает.
                    Затем я, допустим, добавлю

                    class D implements A {}

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

                    else if(someVar == 'D') { a = new D(); }

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


                    1. PFight77 Автор
                      24.08.2017 19:05

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


                      Про DI, я понимаю о чем Вы, я даже делал свою либу для TypeScript. Seeman пишет, что лучший DI это через конструктор. Он еще не очень любит DI-контейнеры, и в CompositionRoot предпочитает кидать зависимости в конструктор вручную (Poor Man's DI). В этом случае весь DI сводится к тому, что вы создаете объекты и передаете их параметрами в конструктор другим объектам.


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


                      <ModalDialog>
                           <ModalDialogContent></ModalDialogContent>
                      </ModalDialog>

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


                      К сожалению, настоящий CompositionRoot здесь затруднительно сделать, и вообще не нужно. Вместо этого, какждый компонент приложения выступает в качестве мини-composition-root, и собирает, допустим, модальное окно для своих нужд. Для фронтенда такой гибкости в управлении зависимости обычно достаточно.


                      1. NimElennar
                        24.08.2017 19:34

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

                        Как именно сделано DI это менее важная вещь, чем то, что в поле или в параметр у нас могут попадать interface либо «родительский» class, у которого могут быть несколько «детей», имеющих «докрученную», более специализированную логику. Возвращаясь к примеру, у меня будет тип ModalDialogWindow с методом show(), а уж какое конкретное окно будет создано, InputField или TreeDirectory это будет разрулено в runtime контейнером DI.

                        Я не могу сказать, насколько DI важно или реализуемо в front-end разработке, так как больше специализируюсь на back-end и БД. Всё, что я хотел сказать — то, что описано в статье — не содержит DI.


        1. AndreyRubankov
          27.08.2017 13:45
          +1

          D — не являюсь специалистом по javascript и front-end-у, но dependency injection...
          Классическая ошибка.
          D из SOLID – это Dependency Inversion, а не Dependency Injection.

          Dependency Injection говорит, что должен существовать некий DI контейнер, в который будет производить инстанциирование объектов и внедрение зависимостей (это уже развитие идеи и конкретная имплементация принципа D из SOLID).

          Dependency Inversion гласит лишь о том, что зависимость будет передана из вне. Точка.
          Это может быть передача параметром в конструктор / сеттер, а может быть билдер, а может быть фабрика, это может быть DI контейнер, ну или на крайний случай ServiceLocator.

          Применимо к статье: Создается компонент и ему через проперти передается конкретная реализация (инстанс) компонента, который должен будет отобразиться; или функция отрисовки компонента (provider).

          <MyComponent header={this._renderHeader()} body={this._renderBodyProvider} />


          1. NimElennar
            28.08.2017 00:29

            DI-контейнер это типичный подход к реализации принципа, однако нигде не говорится, что injection реализуется исключительно DI-контейнером. Любой внешний код, создающий экземпляр объекта и передающий его в другой объект (через конструктор, setter или присваиванием значения полю) является injector-ом, то есть реализует dependency injection.


          1. NimElennar
            28.08.2017 02:24

            Перечитал оригинал, мы оба не правы. Там совсем про другое написано, условно про три уровня:

            class MyApp {
            void doSomething(A a) {
            a.someMethod();
            }
            }

            interface A {
            someMethod();
            }

            class AImpl implements A {
            someMethod() {}
            }

            На том, как именно будет создан объект 'a', который будет передан в метод doSomething класса MyApp, в статье внимание не заостряется.


        1. pavelsilin
          28.08.2017 00:30

          Прежде чем предъявлять кому либо претензии по непониманию принципов SOLID и ООП, стоит самому их изучить.
          1) если ваш класс будет иметь 10000 методов или миллион строк, он 100% нарушит SRP. Небольшой размер компонента или класса это следствие от соблюдения SRP и Interface segregation.
          2) Добавление методов с новой логикой — обязательно нарушит Open Closed, рефакторинг также нарушает данный принцип. Нужны новые методы — сделай наследника, нужен рефакторинг — создай новый класс, нужна расширяемость — сделай api позволяющее расширять или менять поведение. Написанный и протестированный класс больше ни когда не должен трогаться.
          3) Принцип Лискова, как раз про то что те кто работаю с классом A должны корректно работать и с классом B (наследником от A) и логика класса B может отличаться от класса A и у вас явное не понимание данного принципа.
          4) Interface Segregation совсем не про разные версии и поведение одного компонента, а про соответствие одновременно нескольким разным интерфейсам. Не стоит делать class A implements Foo, Bar, Baz {}, следствием данного принципа будет как раз раздутое и плохое апи класса или компонента.
          5) Dependency Inversion != Dependency injection, принцип совсем не про dependency injection и он как раз сильно завязан на интерфейсах, в javascript нету типизации и интерфейсов и dependency injection не поможет соблюдать dependency inversion, как в типизированных языках.

          И да статья не про SOLID, как и мой доклад. А о принципах по мотивам SOLID.


          1. NimElennar
            28.08.2017 01:40
            +1

            1) Нигде в оригинале не говорится о размере или количестве методов. Если у класса есть 10000 свойств (полей) и getter-ы/setter-ы либо вычисления для всех этих 10000 свойств, и больше никаких других методов (скажем, вывод этих свойств в файл или показ на экране), то принцип SR не нарушен.

            2) Добавление новых методов не нарушает OC. В оригинале, помимо прочего, говорится:
            «It should be clear that no significant program can be 100% closed.… Since closure cannot be complete, it must be strategic. That is, the designer must choose the kinds of changes against which to close his design.»
            В статье про L упоминается «The primary mechanisms behind the Open-Closed principle are abstraction and polymorphism», в случае если у меня класс implements SomeInterface, добавление в него новых методов или изменение старых методов, которые не перечислены в SomeInterface, не нарушает OC.

            Рефакторинг существующего кода — возможно является нарушением де-юре, однако если этот рефакторинг никоим образом не меняет логику/поведение (на чём я заострил внимание), то де-факто он не привносит проблем, с которыми борется OC («When a single change to a program results in a cascade of changes to dependent modules»).

            3) Оригинал: «What is wanted here is something like the following substitution property: If for each object o1 of type S there is an object o2 of type T such that for all
            programs P defined in terms of T, the behavior of P is unchanged when o1 is
            substituted for o2 then S is a subtype of T.»
            Если логика/поведение одних и тех же методов в двух классах будет разной, это изменит поведение программы, а значит нарушит L.

            4) Соответствие одновременно нескольким интерфейсам — допустимая вещь для Interface Segregation, главное, чтобы эти интерфейсы «правильно» группировали методы по логике/применению. Признаю, я неточно выразился, не упомянув интерфейсы (или «abstract base classes» как в оригинале).

            Как раз class A implements Foo, Bar, Baz {} это будет нормальный IS, при условии, что части программы, которые используют класс A об этом не знают (DI), а обращаются к нему как к реализации либо интерфейса Foo, либо интерфейса Bar, либо интерфейса Baz.

            5) Ну, пускай это несколько разные вещи. Как я написал в одном из ответов: «Как именно сделано DI это менее важная вещь, чем то, что в поле или в параметр у нас могут попадать interface либо «родительский» class, у которого могут быть несколько «детей», имеющих «докрученную», более специализированную логику. „


    1. pavelsilin
      27.08.2017 23:21

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


  1. copal
    24.08.2017 16:43
    -1

    Этой статья заслужила первое место в моем рейтинге отменной ереси. Браво!


  1. Nerlin
    24.08.2017 20:24
    +2

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


    1. PFight77 Автор
      24.08.2017 20:28

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


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


      1. NimElennar
        24.08.2017 20:58

        Весь вопрос — насколько часто понадобятся эти сложности и понадобятся ли вообще. Обычно программистам советуют придерживаться принципа YAGNI


        1. PFight77 Автор
          24.08.2017 21:01

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


      1. Nerlin
        24.08.2017 21:14

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

        <Modal.YesNoDialog ... />
        


        Просто местами это выглядит как будто следование слепой вере. В статье много говорится о SOLID, но при этом из всего SOLID мне здесь видится лишь два принципа — SRP и может ISP. Дело в том, что в React как таковом остальные принципы поддержать сложно. LSP — это явно не про то, что один компонент можно заменить другим компонентом, это о том, что при замене некоторого объекта-исполнителя на его наследника мы не получим неожиданного поведения, которого нет в родителе. Здесь нет наследования, в примерах статьи, убрав один компонент, мы избавимся от добавления определенного поведения; заменив компонент на другой мы можем изменить поведение в корне — мы можем заменить затемняющий компонент на любой другой, при этом контракты нашего кода ничего на это не скажут. Это не про LSP вовсе, а максимум о каких-нибудь поведенческих шаблонах проектирования или вообще о паттерне Компоновщик.

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

        Про DI уже писали выше, в примерах зависимости внутрь компонентов никак не поступают вовсе, потому что их собственно особо и нет, это все Presentational Components.

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


        1. PFight77 Автор
          24.08.2017 21:42

          Тут ведь еще какой момент, если мы в одном месте подменим Backdrop на FadeBackdrop, то будет ли везде работать также как работало раньше? К сожалению, в веб без тестирования трудно это утверждать. Где-то кто-то может переопределить стили, и с FadeBackdrop там верстка посыпется. Поэтому делать такого рода гибкость во фронтенде я не вижу смысла. Лучше руками поправить везде и точно знать, где именно ты поправил и где нужно протестить.


          DI я понимаю здесь именно в том смысле, что каждый LEGO кусочек не должен предполагать с кем именно он будет взаимодействовать. Очень легко попасть в эту ловушку — пишешь Menu, и подразумеваешь, что внутри будут MenuItem (специальные компоненты). Завязываешь стили внутри, так что кроме MenuItem ничего внутри быть не может. Подход заключается именно в том, что Menu должен быть максимально agnostic относительно своих итемов, чтобы туда можно было положить SuperMenuItem. Это сложно, но это окупается с лихвой. Ну и тем более, Menu сам явно не должен создавать MenuItem, а передавать эту обязанность вызыввающему коду.


          Конечно, все принципы здесь применены очень в неявном виде. Здесь скорее дух этих принципов, чем они сами. Например, Seemann пишет, что в общем случае DI is a passing an argument. Именно это я и делаю, передаю дочерние компоненты как параметры (children или аттрибуты).


          1. Nerlin
            24.08.2017 22:01
            +2

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

            <Modal
               closeButton={null}
               backdrop={true}
               title={"Мое модальное окно"}   
            >
               Добро пожаловать!
            </Modal>
            


            Здесь например, в случае отсутствия title в Modal можно не рендерить заголовок, closeButton можно заменить с null на реальную кнопку с нужной версткой или же вообще сделать его как backdrop типа boolean.

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


            1. PFight77 Автор
              24.08.2017 22:07

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


              Все это так важно для фронтенда именно потому, что здесь даже малейшее изменение font-size может сломать всю верстку самым непредсказуемым образом.


              1. Nerlin
                24.08.2017 22:31
                +1

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

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

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


                1. PFight77 Автор
                  24.08.2017 22:47
                  -1

                  Ну а что если придется менять не Modal, а конкретную эту самую составную часть

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


                  попробовать решения аля CSSModules,

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


                  правило трех

                  Да, что-то в этом роде. Если код используется в 3+ местах, то стоит подумать над созданием переиспользуемого компонента пускаясь во все тяжкие SOLID (это довольно дорого).


                  1. Nerlin
                    24.08.2017 22:57
                    +1

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

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


                    1. PFight77 Автор
                      24.08.2017 23:26

                      Спасибо за конструктивный диалог, дополнил статью.


  1. Nahrimet
    24.08.2017 20:32

    Ну да ладно, добавляем еще параметр:


    прямо аларм о том, что начал прорисовываться антипаттерн telescoping constructor


  1. otec_pigidiy
    25.08.2017 15:16

    Сам недавно писал такое окно на js. Пол года где-то писал. Реализовал все возможные инпуты и поведения формы. Как-то никаких проблем не заметил. есть конструктор Form, задаются параметры, где Form([массив инпутов], функция-обработчик, {объект — свойства формы}). Всё! Придумывай сколько угодно инпутов по единому базовому шаблону с неограниченной возможностью кастомизации параметров и вешай сколько угодно свойств и методов на саму форму. При чем вся моя философия повторного использования кода сводилась к следующему: видишь кусок кода повторяющийся больше двух раз — выноси в функцию. Я не пытаюсь что-то опровергнуть, но я не вижу большой пользы от всей этой конструкции под названием SOLID. По крайней мере статья этого не раскрывает.


    1. PFight77 Автор
      25.08.2017 15:18

      Если в каком-то случае Вам нужна была новая фича от Form, которой не было предусмотрено. Что в этом случае Вы делали?


      1. otec_pigidiy
        25.08.2017 16:02

        Например, понадобилось, чтобы форма была встраиваемая в основной контент. Просто в конструктор передаешь третьим параметром: {parent: selector}, то есть родительскую ноду, в которую надо встроить форму и затем рендеришь в нее, а не в popup. Как-то так. Если нужно убрать кнопки, оставив крестик добавляешь что-то типа {parent: selector, controls: false}. Если нужна какая-то особая кнопка то используешь инпут типа html — просто контейнер для гипертекста и в него вставляешь нужные кнопки, размещая их как надо при помощи стилей. Если нужна кастомная стилизация, то добавляешь {parent: selector, controls: false, class: 'myCustomClass'}, затем добавляешь этот класс к форме $form.addClass(class). Думаю смысл понятен.


        1. PFight77 Автор
          25.08.2017 16:37

          добавляешь что-то типа {parent: selector, controls: false}

          Это описано в секции статьи "Как делать не надо":


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

          Описанный подход решает проблему следующим образом:


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


          1. otec_pigidiy
            25.08.2017 17:05

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

            new Form(inputs, function(results){
             send(results);
            });
            

            После:
            new Form(inputs, function(results){
             send(results);
            }, {showControls: false});
            

            Напишите свой пример До и После.


            1. PFight77 Автор
              25.08.2017 17:41
              +1

              До (будем считать, что использованы некоторые обертки):


              <Form onSend={this.onSend)} isOpen={this.state.open}>
                 <FormHeader>My form</FormHeader>
                 <FormCloseButton onClick={this.onClose()} />
                     {inputs}
                 <StandardFormButtonPanel />
              </Form>

              После:


              <Form onSend={this.onSend)} isOpen={this.state.open}>
                 <FormHeader>My form</FormHeader>
                     {inputs}
                 <StandardFormButtonPanel />
              </Form>

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


              1. otec_pigidiy
                25.08.2017 18:29

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

                var formHTML = showControls ? 
                 '<form><controls></controls></form>' :
                 '<form></form>' 
                $('#form').html(formHTML);
                

                То есть в моем случае я просто добавляю кейс на уровне if или switch. Вы же городите новый модуль. При чем он городится точно так же по мере возникновения необходимости, а не заранее, ведь изначально неизвестно нужно ли будет его скрывать или нет.
                В итоге мы имеем:
                function Form(inputs, handler, options){
                 var inputsHTML = getInputsHTML(inputs);
                 var formHTML = showControls ? 
                  '<Form onSend={this.onSend)} isOpen={this.state.open}>
                   <FormHeader>My form</FormHeader>
                   <FormCloseButton onClick={this.onClose()} />
                       {inputsHTML }
                   <StandardFormButtonPanel />
                  </Form>' :
                  '<Form onSend={this.onSend)} isOpen={this.state.open}>
                   <FormHeader>My form</FormHeader>
                       {inputsHTML  }
                   <StandardFormButtonPanel />
                  </Form>'  
                  $(body).html(formHTML);
                }
                
                new Form(inputs, handler, {showControls:false})
                


                1. PFight77 Автор
                  25.08.2017 18:42

                  В вашей реализации нарушены буквы O и D из SOLID:


                  O (open-closed) — никогда, ни при каких обстоятельствах не модифицируем код компонентов, которые часто используются;
                  D (dependency inversion) — решение о том, какой из компонентов будет использован в каждом случае, должен принимать вызывающий код.

                  Из-за того, что нарушена D пришлось нарушить S. Решение о рендеринге FormCloseButton принимает Form, а не вызывающий код. Поэтому, когда нам понадобилось изменить это поведение, нам пришлось менять Form. А изменение повторно используемого компонента это зло.


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


                  По поводу скрытия кнопки в моем варианте все проще:


                  <Form onSend={this.onSend)} isOpen={this.state.open}>
                     <FormHeader>My form</FormHeader>
                     {this.state.showCloseButton && <FormCloseButton onClick={this.onClose()} />}
                         {inputs}
                     <StandardFormButtonPanel />
                  </Form>


                  1. otec_pigidiy
                    25.08.2017 20:53

                    Погодите меня штрафовать за нарушения. Я пока в целом не понял, чем я так провинился и чем ваш подход лучше. В итоге, так и не ясно от куда взялось this.state.showCloseButton. Кто и через что этот state туда засунул? В этом же ключевой момент. И для чистоты эксперимента давайте может чистый js и html использовать (не считая jquery). А то я может какой-то ключевой момент упускаю.


                    1. PFight77 Автор
                      25.08.2017 22:44

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


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


                      var html = 
                          Form({onSend: this.onSend, isOpen: this.state.open}, [
                              FormHeader({}, [ 
                                  RawText("MyForm") ]),
                              this.state.showCloseButton && FormCloseButton({onClick: this.onClose}),
                              StandardFormButtonPanel()
                         ]);

                      Здесь функция Form ничего не знает о дочерних компонентах. Она может выглядеть примерно так:


                      function Form(options, children) {
                          return `<form onsubmit=${options.onSubmit}>${children.join("")}</form>`;
                      }

                      Это конечно все скорее псевдокод, но идея думаю ясна.


                      1. otec_pigidiy
                        26.08.2017 00:32

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


                        1. PFight77 Автор
                          26.08.2017 08:52

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


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


                          1. Вынести все взаимодействие в вызывающий код. То есть, тот кто собирает форму подписывается на onSubmit, проверяет там значения полей, и если нужно подсветить какое-то поле, передает ему параметр invalid=true, или добавляет новые компоненты, которые рисуют выделение и показывают сообщение. Обычно, именно этот подход и используется. Допустим, выше по клику на FormCloseButton по умолчанию ничего не происходит. Нужно самим подписаться на клик, и самим закрыть форму (передать isOpen в Form).
                          2. Сделать обертку, которая будет генерировать некоторые стандартные формы, и добавлять к ним стандартную валидацию, как описано в 1. Этот подход опасен тем, что мы начнем менять эту обертку. Если обертка часто используется, то она не должна меняться.
                          3. Предъявить какие-то требования к дочерним компонентам. Form может потребовать, чтобы дочерние компоненты были не произвольными, а реализовывали определенный интерфейс (имели функцию validate, например). Тогда форма сможет пробежаться по children и выполнить валидацию.
                          4. Использовать общий контекст. Мы создаем некоторый объект (контекст), который передаем в Form, и во все дочерние компоненты-инпуты. Каждый компонент умеет работать с контекстом (это часть его интерфейса). В контексте есть некоторый механизм, через который обеспечивается взаимодействие. В первом приближении это архитектура Flux.


                          1. otec_pigidiy
                            26.08.2017 23:04

                            Я использую пункт 3. Но тогда рушится вся концепция SOLID. Ведь форма начинает знать о свойствах и методах инпутов.
                            И раз уж в этом подходе «одна из самых сложных тем», где я проблемы даже не встретил, то я просто создал отдельный базовый класс Input, от которого отнаследовал остальные инпуты, откастомизировав их, а сама Form использовала их прототипные методы вроде getValue(), getHTML(), blink() и т.д. Хочешь добавить свой метод, например, onBlur(), не вопрос, передавай его в параметр инпута, типа new Form([{droplist:{onBlur: blurHandler}}], handler, opts). Я считаю в этом как раз наоборот вся красота и простота подхода, а не его недостаток. Да и собственно в статье эти недостатки описаны как-то пространно, вроде «это „попахивает“», «грозит появлением регрессий». Я даже не понимаю о чем речь. Как будто меня пугают привидениями.


                            1. PFight77 Автор
                              27.08.2017 23:41
                              +1

                              Смотрите, когда мы просто передаем любой компонент — мы уже задаем некоторый интерфейс, и родитель что-то о своих детях знает. В React он знает что дети — react-компоненты (они имеют функцию render и др.). Если Form потребует чуть более специфичный интерфейс для детей (не только функция render, но и еще функция validate), то концепции в целом это не нарушит. Просто создаст некоторые ограничения по использованию Form.


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


                              ShowDialog(title, content);

                              Потом мы начали работать над новым требованием, и нам понадобился диалог без затемнения фона. Мы добавили необязательный параметр showOverlay, и использвали его в фиче Б так:


                              ShowDialog(title, content, false);

                              Но по невнимательности в коде ShowDialog эту опцию реализовали следующим образом:


                               ShowDialog(title: string, content: string, showOverlay?: boolean) {
                                  // ...
                                  if (showOverlay) {
                                      // render overlay
                                  }
                              }

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


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


                              1. otec_pigidiy
                                28.08.2017 11:31

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

                                <container>
                                 <form>
                                  <inputs></inputs>
                                  <controls></controls>
                                 </form>
                                </container>
                                .
                                Дальше у него возникает необходимость убрать оверлей:
                                
                                var formHTML =  ' <form>  <inputs></inputs>  <controls></controls> </form>';
                                var  fullHTML = overlay ?
                                 '<container>' + formHTML + '</container>' :
                                 formHTML;
                                

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

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


                                1. PFight77 Автор
                                  28.08.2017 11:41

                                  Вы путаете повторно используемые компоненты и компоненты приложения. form, container — это повторно используемые, а formHTML — это код компонента приложения, он не используется повторно. Он реализует конкретную фичу в единственном экземпляре. Вот это условие overlay ? нужно только в том случае если Вам нужно динамически менять поведение (показывать/не показывать) overlay. Обычно такой динамики не нужно, и если компоненту не нужен overlay, то он просто его не использует.


                                  p.s. SOLID как раз об этом. Но если у вас много динамической логики (какие-то настройки), то тут да, где-то эти if-ы должны появиться.


                                  1. otec_pigidiy
                                    28.08.2017 14:51

                                    Хорошо, вот пример моей проблемы:
                                    Конструктор

                                    Input({
                                     inputMode:true,
                                     checkMode:true,
                                     multiSelect:true
                                    })
                                    

                                    Внутренняя реализация:
                                    function Input (opts) {
                                     if(inputMode){
                                      if(checkMode){
                                       if(multiSelect) {/*case 1*/}else{/*case 2*/}
                                      } else {
                                       if(multiSelect) {/*case 3*/}else{/*case 4*/}
                                      }
                                     } else {
                                      if(checkMode){
                                       if(multiSelect) {/*case 5*/}else{/*case 6*/}
                                      } else {
                                       if(multiSelect) {/*case 7*/}else{/*case 8*/}
                                      }
                                     }
                                    }
                                    

                                    Можно ли кадринально изменить этот код с помощью SOLID?


                                    1. PFight77 Автор
                                      28.08.2017 15:18

                                      Могу предположить, что вариантов не 8. checkMode очевидно не имеет смысла для input type="text", также как и multiSelect. Опишите, пожалуйста, подробнее задачу (что значат эти опции). Нужно ли динамически (на основе каких-то данных) выбирать соответствующую опцию, или нужный вариант известен на этапе написания кода?


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


                                      1. otec_pigidiy
                                        28.08.2017 15:40

                                        Пусть инпут будет droplist, multiSelect — множественный выбор с отображением выбранного свреху над дроплистом, checkMode — чекбокс напротив каждого пункта дроплиста, inputMode — в поле дроплиста можно вводить своей значение, а из пунктов находится наиболее подходящий. Пусть кейсов даже будет немного меньше. Все равно легче не становится.


                                    1. AndreyRubankov
                                      28.08.2017 15:50

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

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

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


                                      1. PFight77 Автор
                                        28.08.2017 16:13

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


                                      1. otec_pigidiy
                                        28.08.2017 18:13

                                        Но проблема то никуда не денется. Ну будет у нас вместо одного droplist(opts), восемь конфигураций типа droplistCheck(), droplistMS(), droplistInput() и так далее, всего 8 компонентов.


                                        1. PFight77 Автор
                                          28.08.2017 18:16

                                          Можно сделать droplist(opts) обертку в дополнение к этим 8 компонентам, если есть острая на то необходимость (он будет просто создавать droplistCheck, droplistMS и т.д.). Только этот droplist не должен меняться.


                                          Ну а в целом да, будет 8 компонентов. Бойлерплейта в этом подходе много, это факт. Именно поэтому его нужно использовать только там, где это нужно.


                                          1. otec_pigidiy
                                            28.08.2017 18:50

                                            То есть с появлением нового режима, мне нужно городить droplist2(opts)? Лишь бы не трогать старый? Какой-то оверкилл. И почему сейчас «Можно сделать droplist(opts)», а раньше (в статье) это было нельзя?


                                            1. PFight77 Автор
                                              28.08.2017 18:53

                                              В статье говорится про компоненты-обертки, это именно оно. И да, городить новый droplist2, чтобы не трогать. Ну, на самом деле выбор, конечно, всегда есть. Просто изменение droplist это боль — нужно проверять что ничего не ломаешь. Ты выбираешь — словить эту попа-боль, или просто сделать другую обертку. Вообщем, жизнь боль в любом случае: ))


                                              1. AndreyRubankov
                                                28.08.2017 19:01

                                                Не совсем так.

                                                Если нужно добавить новое поведение – нужно создать новый компонент.

                                                Если нужно изменить поведение только в определенных местах – нужно создать новый компонент (с новым поведением).

                                                Если нужно исправить / изменить поведение текущего везде – нужно просто взять и изменить текущий компонент. Новый компонент создавать нету необходимости.


                                                1. PFight77 Автор
                                                  28.08.2017 20:38

                                                  Да, все верно.


                                        1. AndreyRubankov
                                          28.08.2017 18:54

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

                                          Лично для меня в приведенном примере основная проблема в том, что у него высокая цикломатическая сложность. Этот компонент крайне тяжело отлаживать, еще сложнее тестировать и невероятно сложно поддерживать.
                                          – это некий «God Component», который может принимать любую форму в зависимости от входных параметров.

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

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


                                          1. VolCh
                                            28.08.2017 20:03

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

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


                                            1. AndreyRubankov
                                              28.08.2017 20:11
                                              +1

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

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


                                          1. otec_pigidiy
                                            29.08.2017 10:24

                                            «это некий «God Component»,» Прямо в точку. Это именно то, что и предполагалось сделать. Эдакое параметрическое программирование: вводишь конфигурацию — получаешь нужный компонент.
                                            Повторюсь у меня нет проблем с отладкой, тестированием (тесты отсутствуют), развитием и поддержкой. Меня смущают макароны из if-else конструкций, которые выглядят не эстетично. Хотелось что-то более феншуйское.
                                            Все принципы SOLID похожи на библейские заповеди, как хочешь так и трактуй. В итоге начинаются холивары, вроде, как надо: element.render() или render(element)?


                                            1. AndreyRubankov
                                              29.08.2017 11:46

                                              Если вам нужно сделать «God Component», то SOLID вам точно не поможет. SOLID это набор принципов о том, как избежать God Objects / God Components и писать код, который можно будет легко поддерживать и тестировать.

                                              НО, в целом, можно пойти следующим путем:
                                              1. вы заводите все те же 8 компонентов.
                                              2. вы заводите GodComponent, который будет по входящим параметрам выбирать один из нужных 8.
                                              3. вы пишите функцию-маппер (своего рода hash-function) от входящих параметров, которая на выходе возвращает Класс нужного компонента.
                                              4. вы инстанциируете компонент с параметрами от GodComponent.

                                              const COMPONENTS = {
                                                'imput_check_multi': InputCheckMultiComponent,
                                                'imput_ncheck_multi': InputNotCheckMultiComponent,
                                                'imput_ncheck_single': InputNotCheckSingleComponent,
                                              ...
                                              }
                                              
                                              function getComponent(params) {
                                                 const name = [];
                                                 name.push(params.input ? 'input' : 'static');
                                                 name.push(params.check ? 'check' : 'ncheck');
                                                 name.push(params.multi ? 'multi' : 'single');
                                              ...
                                                 return COMPONENTS[name.join('_')];
                                              }
                                              


                                              или вместо Класса компонента возвращать Функцию, которая будет инстанциировать компонент – это уже как вам больше нравится.


                                              1. otec_pigidiy
                                                29.08.2017 12:32

                                                И ситуация становится еще хуже. Теперь, вместо того, чтобы просто лазить по простыне if-elseов в одном месте, я буду лазить по 8-ми кускам кода, к которым отсылает новый god-component.
                                                Честно говоря, мне кажется, что я уже использую SOLID подход, но мы как-то не то обсуждаем. Например chekbox() в checkMode или input() в inputMode это такие же компоненты, как и droplist(), он их использует внутри себя по своему усмотрению. И в этом есть смысл: повторное использование, дочерний элемент ничего не знает о родителе, ну и так далее. Но в статье это как-то на столько извращено и перековеркано, что рациональное зерно утеряно. Остались только радостные возгласы о том, как стало легче жить и светлое будущее не за горами.


                                                1. AndreyRubankov
                                                  29.08.2017 12:51

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

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

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

                                                  1 js файл на 500+ строк кода, который содержит в себе кучу логики и условного рендера – это худшее, что может быть для поддержки. Это прям как открыть какой-то проект на php 4.x, когда все писали в одной куче.

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


                                                  1. otec_pigidiy
                                                    29.08.2017 13:51

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


    1. copal
      26.08.2017 12:01

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


  1. noodles
    25.08.2017 20:33

    Позже понадобилось показывать окна без «крестика» в правом верхнем углу, без заголовка либо с другим заголовком, с другими отступами от краев окна, какие-то окна нужно было закрывать по щелчку во вне, а какие-то нет…

    Ну так ноги у проблему растут из проектирования дизайна как я понимаю. Если нет общей дизайн-системы/style-гайда/брендбука/etc, то нормального переиспользуемого компонента не будет. Проще, быстрее, дешевле создать новый компонент под сегодняшнее настроене дизайнера, даже если в нём будет 70% копи-пасты, чем городить универсального монстра.
    И думаю то что не предугадал дополнительные состояния, поведения, кнопки — это не проблема разработчика. Разработчик не ванга. Он не должен предугадывать, он должен просто реализовать в коде то что уже(!) продумали специально обученные люди.
    Это я сейчас в контексте вёрстки, если что)


    1. pavelsilin
      28.08.2017 00:43

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


    1. VolCh
      28.08.2017 11:23

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


  1. pavelsilin
    28.08.2017 01:04

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


    1. PFight77 Автор
      28.08.2017 11:46

      Круто, рад это слышать от автора того самого доклада!