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


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


Гениальной, совершенно, частью Delphi была VCL — тот самый набор флажочков и кнопочек для окошек Windows, которые вдруг стало так легко и просто перетаскивать друг на друга, и создавать красивые и необычные Windows приложения.


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


Ничего из того что мы знаем теперь, я имею в виду. MVC, например — Delphi, считайте, состояла из одного V, дальше все оставалось на откуп разработчику. Прямо из OnClick’а дернуть запрос в базу? Да пожалуйста. В OnChange открывать какой-нибудь файлик и пытаться писать в него, а если файлика на месте не оказалось тихо крашится с чем-нибудь вроде Memory Access Violation — на каждом шагу (впрочем, это к Delphi как платформе отношения не имеет).


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


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


Итак, ключевая мысль: представление отделяем от данных, виджет отдельно, вся работа с информацией в нем — отдельно. Насколько отдельно? Скажем, на кнопке все равно есть надпись, а иногда и иконка. В текстовое поле эту надпись может вбить сам пользователь, а иногда у текстового поля есть еще одна надпись, поясняющая пользователю что именно он вбивает. Общее правило: виджет занимается отображением, минимум (SIC!) логики, та что может в нем присутствовать должна быть связана только с отображением и больше ни с чем. Скажем, если кнопка не готова растягиваться вместе с надписью на ней — надпись должна или как-то обрезаться (например, показывать многоточие и полную версию в ToolTip’е), либо выдавать ошибку при попытке установить значение больше, чем на нее влезет (правда это идея так себе, такая ошибка вылезет скорее всего уже во время выполнения программы, в самый неподходящий момент).


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


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


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


Ну а дальше Controller, или Presenter, обрабатывает полученное и выдает результат. Зовите его как вам привычней, только не суйте в него никаких визуальных элементов.


В чем профит? Перечисляю в порядке от самого очевидного к самому, на мой взгляд, существенному.


Во первых — переиспользуемость (reusability). Довольно много времени бывает тратится на то чтобы понять, что поле для ввода почтового адреса, для ввода фамилии, и для ввода суммы — это, на самом деле, одно и то же поле. Один и тот же виджет, с одним и тем же набором возможностей, сводящимся к дерганию события ввода текста или обновлению состояния. И только логика работы с текстом внутри него может отличаться. Пример “как не надо” из жизни: сделать поле для ввода денежной суммы (до двух знаков после запятой), а потом долго возиться, переделывая его так, чтобы иногда можно было вводить в то же поле и количество (до 4 знаков после запятой). Сломать в процессе все места где вводится сумма в приложении, и аврально чинить их посреди ночи. Это при том, что они и по логике своей работы отличия имели в третьем знаке после запятой. О том как поля для ввода почтовых адресов превращаются в поля для ввода адресов географических с помощью наследования и слова божьего можно писать поэмы.


Во вторых: тестируемость (testability). Протестировать можно и UI. Почти любой UI. Но это будут дорогие, в смысле затрат времени и ресурсов на разработку тестов, и на выполнение тестов, и их вряд ли станут запускать при каждой пересборке, ну а когда тесты грохнуться на релизе — это уже будет форс-мажор. А ведь UI-компоненты самые рискованные в плане поломок, ломаются они, как правило, чаще всего, явнее всего, и болезненней всего. Зато код, от самих виджетов оторванный, реально покрыть модульными тестами (unit tests). А модульные тесты гораздо дешевле — и писать легче, и можно запускать буквально после любых изменений, и всегда быть уверенным что ничего не сломалось.


И в третьих, самых важных, на мой взгляд. Рисуя кусок UI вместе с логикой единым куском крайне тяжело победить лень и вдуматься поглубже в то как он работает. Проработать сценарии кроме идеально-позитивного, когда пользователь ввел ровно то что от него ждали, сделал ровно то что требовалось, и получил ровно то что хотел. Того самого, сакраментально программистского “у меня все работает”.


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


Подводя итог. Вы можете писать на Delph, или приложение под iOS, мучать многострадальный web сколько-то-там-0, или клепать какую-нибудь микроконтроллерную дичь на Питоне, если ваш код перерос формулу “одна формочка, один запрос, один результат” — я вам настоятельно рекомендую решительно отделить виджеты от какой-либо логики их работы, и тогда есть шанс что мух в ваших котлетах будет поменьше. И не смотря на первоначальную совершенную неочевидность плюсов такого решения, чем дальше вы будете работать с ним, тем больше для вас этих плюсов появится.

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


  1. Lure_of_Chaos
    23.07.2018 11:49

    Итак, ключевая мысль: представление отделяем от данных, виджет отдельно, вся работа с информацией в нем — отдельно. Насколько отдельно?

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


    1. ta6aku Автор
      24.07.2018 11:36

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