Lex Kravetski (lex_kravetski) wrote,
Lex Kravetski
lex_kravetski

Categories:

Концепция «Авто-всё»: полёт мысли разработчиков интерфейса

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

Я делал интерфейсы на Билдере, на MFC, на WinAPI, на Swing, на SWT/RCP и даже на самопальной двумерной библиотеке, содержавшей всего три команды: нарисовать линию, вывести текст и нарисовать прямоугольник с текстурой. Ну и псевдо-интерфейсы а ля «командная строка» тоже имели место быть. Где-то бывало проще, где-то тяжелее, однако в общем и целом постоянно обнаруживалась необходимость в тысячах ненужных по сути строк кода – кем-то уже написанных или тех, которые предполагалось написать мне, но всё равно по сути не нужных.

При разработке интерфейсов программисты до сих пор пишут мегабайты одинакового кода. Того, который уже много раз написан другими. Объединение уже написанного в библиотеки всё более и более высокого уровня не исправляет положение – всё равно на уровне выше библиотеки пишутся мегабайты ненужного.

Тут наверно следует разобраться с тем, что я понимаю под словом «не нужный». Ведь как ни крути, а код интерфейса – нужный код. Без него вроде как ничего не заработает. И оно во многом верно – писать ненужный код нужно. 

Так вот, «не нужным» я называю тот код, который содержит избыточную информацию в заметных масштабах. Или даже в масштабах шокирующих. Самый простой пример такового – программирование копи-пастом. Похожий на нужный фрагмент кода засылается в буфер обмена, вставляется в нужное место и потом слегка модифицируется. Вместо того, чтобы оформить его в некоторый более общий объект или метод. Ненужность такого кода заключена в утверждении «оно уже написано». Его не надо писать снова, пусть даже через буфер. На него надо просто неким образом сослаться.

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

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

Итак, посмотрим.

 

Типа старт: бумажки с дырочками

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

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

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

2x2 + 3x – 10 = 0

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

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

Что там греха таить, таким образом появились вообще все идеи.

Действительно, если у нас есть некоторый алгоритм расчёта чего-то там, то нам нет нужды каждый раз программировать этот алгоритм – достаточно запрограммировать его один раз, а потом только подставлять новые параметры. Но как подставлять-то? Понятное дело, тоже на перфокартах. Например, вот в этой стопке у нас лежит программа, реализующая наш мега-алгоритм. Мы её сейчас внедрим в мега-машину, а следом за ними внедрим и вот ту стопку, в которой описываются нужные нам в данный момент параметры.

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

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

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

 

Командная строка

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

Первый и наиболее интуитивный способ ведения оного – калька с диалогов между людьми. Компьютер задаёт вопрос и ждёт на него ответа. Человек отвечает, компьютер на ответ как-то реагирует и задаёт следующий вопрос. И так вплоть до получения результата.

Процесс такого общения – линеен и детерминирован. Порядок вопросов жёстко задан, формат ответов строго определён. Шаг в сторону считается побегом. Но уже есть куча новых проблем: проверить правильность введённого, дать возможность исправить ошибки ввода, сформулировать вопросы. Раньше всем этим занимался тот, кто писал всю программу, причём, занимался прямо на этапе её написания. Теперь от компьютера требовалось вести диалог не только с программистом, но и с оператором.

Я загадал число от 1 до 10. Попробуйте угадать.
> 5
Больше
> 7u
Вы ввели какую-то хрень. Попробуйте ещё раз.
> 7
Меньше
> 6
Угадали. Хотите ещё раз?

Ну, не исключено, хочу... Только не могу. Для «угадай число» такая схема ещё подходит, но для более сложных действий уже как-то не оно. Хотя практика показала, что в рамках этой схемы можно организовать довольно много всего – работу с файлами, запуск программ, получение результатов и вывод их на печать. Более того, всем этим вполне можно пользоваться.

Многие даже от подобной схемы так впёрлись, что до сих пор считают графические интерфейсы порождением сатаны.

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

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

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

Что вы хотите сделать с этим файлом? Сохранить? Удалить? Напечатать? Скопировать? Защитить паролем? Открыть к нему публичный доступ? Редактировать? Передать в какое-то приложение? Или, быть может, вы хотите прочитать инструкцию? Написать письмо разработчикам? Создать новый файл? Изменить цвет фона? Выйти?

В общем, так жить нельзя. Текст удобно вводить в двух измерениях, а не через командную строку. Редактировать его в двух измерениях, а не командой «поменять 25-й символ в 47-й строке». Печатать по команде, а не после ответа на вопрос. И так далее.

Отсюда вытекает следующий логичный шаг – интерфейс как интерактивная, нелинейная среда. 

 

Интерактивность и двумерное представление

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

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

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

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

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

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

Однако для программиста сиё доставило изрядное количество головной боли. Ведь все эти кнопочки надо нарисовать. Всё это двумерное перемещение надо запрограммировать. Всю эту интерактивность надо чем-то обеспечить. А ну как пользователь нажмёт не то и не тогда? Тоже ведь проблема.

В общем, интерфейс для задачи стал проблемой, сравнимой по сложности с самой задачей. А то и превосходящей её по сложности.

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

 

Графический интерактивный интерфейс на основе Операционной Системы

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

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

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

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

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

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

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

 

Оконные библиотеки и визуальные редакторы

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

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

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

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

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

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

 

Документ, вид, модель, логика

Данные – это некоторая содержательная часть того, что обрабатывает пользователь. Вид – это их локальное отображение на экране. Данные отдельно, вид – отдельно. Обработкой данных заведует логика обработки данных. Видом заведует логика управления видом. Как это всё увязать?

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

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

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

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

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

Ещё бо́льшая обуза – предусматривать реакцию интерфейса на растягивание окна. Просто выравнивание по левому краю не подходит – глазу хочется равномерного распределения элементов. А подписка на событие «окно меняет размер» и перемещение контролей вручную – рутина. И её тоже надо бы изжить.

 

Автоматизация размещения элементов интерфейса

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

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

Жаль, не вся. Остаётся ещё одна довольно объёмная часть – логика интерфейса. То есть, всевозможные обновления элементов при смене значений в других, проверка корректности данных, вызов методов модели при валидном изменении состояния интерфейса, отмена изменений и прочее и прочее. Это, как ни странно, тоже рутина. С первого взгляда кажется, что нельзя угадать, что должно произойти при выборе, например, элемента списка. Или при вводе текста в текстовое поле. Но это только на первый взгляд. На самом же деле в подавляющем большинстве случаев происходит примерно одно и то же: некоторые закономерные манипуляции с интерфейсом и вызов некоторого метода модели для изменения её состояния. И вот именно на это всё сейчас тратится наибольшее количество времени.

Немало времени также тратится на создание собственно окна. Вроде бы уже необходимый минимум остался, вроде бы одной командой всё делается, но команда не одна. Создать окно. Создать метод размещения элементов. Создать каждый элемент. Заполнить каждый элемент. Подписаться на все события. Это немало. Это сотни строк кода. И так для каждого диалога. И одно ведь и то же. Как бы это убрать?

 

Автоматическое создание диалогов и связывание данных

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

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

 

Универсальность

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

 

Желание

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

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

 

Всё про авто-всё

 

Читайте в следующих выпусках:

«Он связывал нас не спрашивая» – Пострадавшие от Авто-маньяка делятся впечатлениями с редакцией

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

Конверсия: благо или преступление? – Это смотря с какой стороны посмотреть!

«Свет мой зеркальце, скажи» – Вся правда об отражениях

Tags: авто-всё, программирование, философия
Subscribe
  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your reply will be screened

    Your IP address will be recorded 

  • 46 comments