Предуведомление: Эта статья написана в остром полемическом ключе. Практически всё в ней сильно утрировано, доведено до логического и концептуального абсурда, и, вообще, это не статья, а одна большая развёрнутая реплика, хотя, и по поводу, но совершенно «не в тему». Особо впечатлительные читатели могут спокойно пропустить эту статью и найти более полезное для себя занятие. Но, если, вдруг, у кого-то в душе появится отклик и возникнет идея создать какой-нибудь «старт-ап», то я буду этому очень рад (будет очень приятно, хоть, на малую толику, сдвинуть сознание разработчиков в сторону от всем привычного). Вот почему, я не считаю свою статью первоапрельским розыгрышем, хотя я был бы очень рад каким-нибудь благоприятным образом разыграть столь квалифицированное сообщество.
Представим себе разработчика, который (в далёкие, но «лихие» девяностые) впервые столкнулся с довольно заманчивой возможностью создавать простые HTML-странички. Он думает, что, теперь, всё просто: набираешь текст, вставляешь в него теги (они же: команды) и сохраняешь полученный результат в файле на рабочем сервере, а пользователь получает то, что ему, пользователю, нужно. Разработчик быстро смекает, что будет довольно просто реализовать приложение, которое будет интерпретировать HTML-код, и… создаёт первый браузер. Идея что-то там интерпретировать не нова, здесь важен результат или, точнее, эффект: пользователь запрашивает определённую страницу, получает заранее заготовленный HTML-код, браузер разбирает этот код (в соответствии со своей объектной моделью) и конечный результат показывает пользователю на экране. Очень эффектно, не так ли?
Разработчик не особо утруждает себя вопросами, что, в действительности, нужно пользователю и начинает придумывать всевозможные теги (они же команды): наклонное начертание, полужирное начертание, задание шрифта, заголовок раздела, список и т.д. и т.п. Разработчик вводит возможность создавать списки, вставлять рисунки и… гиперссылки (те самые, ради которых «всё»). Мир, в котором действует разработчик, прост и понятен: у браузера есть только одно окно, и, следовательно, у пользователя есть только одна возможность — загрузить себе новую страницу и увидеть её всё в том же единственном окне браузера. Так в браузере появилась кнопка «Назад» (и сопутствующая ей кнопка «Вперёд», которая предназначена, как бы для отмены отмены), история посещений и возможность сделать закладку на нужной странице.
В реальности, таких разработчиков оказывается не так, уж, и мало, и каждый из них норовит внести свою лепту в создание браузеров. Браузеров становится несколько, и различные фирмы соревнуются друг с другом в том, как лучше всего отображать содержимое документов. На первый план выдвигаются вопросы совместимости, а страничкописателям приходится помнить всё больше вещей о том, как и что отображается (или не отображается) в тех или иных браузерах.
Пока WEB статичен, Вы не можете ничего толкового в нём сделать. Вы можете только выставить свой товар на витрину и красиво его там разложить. Но пользователь будет, только, пассивным наблюдателем. Между тем, возникает желание вести посредством WEB свой бизнес, а для этого нужно где-то иметь программный код, который должен выполняться в ответ на действия пользователя. Обычное (локальное) приложение весь свой программный код «носит» с собой (в исполняемом файле), а в WEB размещение кода оказывается более сложным: код может располагаться на стороне сервера (и тогда сервер должен каким-то образом получать по сети команды от пользователя), а может располагаться на стороне клиента (и тогда этот самый код, который, разумеется, будет тратить процессорное время на компьютере пользователя, должен располагаться на самой странице). Осознание этого факта привело, с одной стороны, к появлению скриптовых языков и встраивания интерпретаторов таких языков в браузеры, а, с другой стороны, к появлению в HTML специальных тегов, предназначенных для создания форм ввода данных от пользователя. Далее, состоялось рождение XML, возникли технологии ASP, JSON и REST…
Я не буду более утомлять читателя перечислением общеизвестных фактов, но, замечу, что в основе бурного роста в любой области лежит именно та простота, с которой сталкиваются разработчики первых версий программных продуктов. Эти счастливые разработчики изначально имеют дело с простыми и обозримыми объектами. Они ещё не знают о том, во что это всё потом выльется. Они не сдержаны никакими рамками каких-либо практических задач. Их горизонт предельно сужен. Но, при этом, значительную роль играет элемент новизны технологии. И этот элемент провоцирует весьма бурное развитие технологий. Одни пытаются заработать на этом много денег. Другие находят в новой области немало интересных задач и добиваются весьма неплохих результатов в их решении. Третьи всё это наблюдают и описывают это буйство жизни на журнальных страницах…
Представим себе другого, гораздо более вдумчивого разработчика. Такой разработчик обязательно задаст себе следующие вопросы:
Такой разработчик не будет торопиться с выводами, и, уж точно, не станет торопиться создавать свой первый браузер. Во-первых, вдумчивый разработчик обязательно подумает над тем, а нельзя ли в WEB использовать обычные «настольные» приложения, и что следовало бы добавить в них, чтобы научить работать их в распределённой среде. Например, что должно делать приложение для работы с базой данных, если сами данные находятся на удалённом сервере? Во-вторых, он обязательно подумает над тем, что должно быть в самой операционной системе, чтобы пользователь мог бы корректно работать с конкретным сайтом. Более того, вопрос разделения кода на «клиентский» и «серверный» станет для вдумчивого разработчика самой настоящей головной болью, и эта боль не прекратится до тех пор, пока разработчик не найдёт решение вопроса, удовлетворительное как в плане производительности, так и в плане безопасности и удобства. Наконец, в третьих, вдумчивый разработчик обязательно подумает о том, как должен быть оптимально организован сам WEB, и что должна представлять из себя операционная система, которая умеет работать с WEB.
Разумеется, вдумчивый разработчик ничего не успеет сделать. Он, вообще, ничего никогда не успеет сделать. Всё, что будет сделано, будет сделано множеством простых разработчиков. И… делается.
Что же мы имеем, в результате? Что имеем, то и имеем.
Имеем слоёный пирог технологий.
Имеем множество весьма различных вариантов и средств реализации.
Имеем множество подходов к дизайну, и, вообще, постоянную смену дизайна сайтов и подходов к
организации хранения данных и к организации взаимодействия.
Имеем хрупкость WEB, когда данная сегодня ссылка уже завтра становится недействительной.
Имеем ситуацию, когда большая часть WEB становится невидимой, потому как вместо статических страничек и полнотекстового поиска, у нас есть всевозможные динамические приложения. При этом, поисковые системы на наши запросы возвращают практически один только информационный мусор. Как, впрочем, и сама инфраструктура WEB, которая, прямо-таки, провоцирует на постоянное дублирование (по большей части, ненужной нам) информации. Я не говорю уже и о том, что мы имеем просто чудовищные проблемы с безопасностью хранения и передачи данных, и, при всём при этом, становится особенно жутко от того, что разработчики совершенно спокойно позволяют себе выпускать небезопасные продукты, прекрасно зная об их небезопасности и требуя за них плату! При этом, постоянное обнаружение уязвимостей (в программных продуктах) стало для всех уже повседневным фоном, а у пользователя с каждым шагом индустрии разработки ПО остаётся всё меньше способов контролировать собственные компьютеры, и эти последние уже совсем перестают быть «персональными»…
Но! Мир мог быть и иным. Почему же он не такой, какой хочется? И как мог бы выглядеть тот мир, который кажется нам лучше? И, самое главное, действительно ли этот другой мир «прекрасен», и мы зря тешим себя иллюзиями о некотором другом «прекрасном мире»?
Начнём с самого начала. Кстати, а где оно, это самое начало? Здесь придётся действовать с различных концов. В-первых, у нас есть сами страницы, то есть то, что непосредственно отображается пользователю. Во-вторых, у нас есть то, что является источником данных для страниц. В-третьих, у нас есть то, что осуществляет формирование и доставку запросов серверу, и то, что осуществляет доставку результатов выполнения запросов клиенту. Это, что называется, с одной стороны. С другой стороны, есть вопрос о то, как это всё должно выглядеть в целом: с точки зрения пользователя, с точки зрения операционной системы, с точки зрения сети. Третий аспект проблемы заключается в том, а что должны (на самом деле) делать разработчики, и как должна быть устроена индустрия разработки ПО. Крайне соблазнительно начать с конца и развести длинную демагогию по поводу разработки ПО, но мы возьмём себя в руки и, всё-таки, начнём с самого начала, а именно: с самих страниц, которые показываются пользователю.
Свёрстанный текст состоит из блоков. Всякая WEB-страничка — это тоже результат вёрстки. Поэтому, произвольная страница в WEB состоит, прежде всего, из блоков. Это означает, что прежде чем отобразить на странице некоторый текст или более сложный объект, Вам необходимо создать под него блок. Когда Вы пишете текст, составляете некоторый документ, Вас не интересуют никакие блоки, но Вы заранее знаете, что каждый элемент Вашего документа будет находиться в некотором блоке. Блок — это часть физического уровня организации документа, Вы же, создавая документ, работаете на логическом уровне, на котором присутствуют конкретные абзацы, разделы, смысловые выделения, ссылки, сноски, формулы, рисунки, графики и прочее, и прочее…
Если браузер получает уже свёрстанный документ, то он получает блоки, и Вам, чтобы создать эти блоки, нужно пропустить исходный код Вашего документа через систему вёрстки. В этом случае, браузер должен получить именно сами блоки и только их, а уже каждый блок сам отвечает за своё содержимое. По сути, каждый блок — это именованный канал для обмена данными. Если браузер ограничить только работой с этим самыми блоками, то браузеры будет легко реализовывать.
Кому-то может показаться такая функция довольно узкой для такого приложения как браузер, однако, по некотором размышлении, можно обнаружить немало задач, которые должен выполнять браузер (помимо, собственно, отображения). Во-первых, необходимо обеспечить первичную обработку сообщений от «мышки» и клавиатуры. У пользователя должна иметься возможность выбора отдельного блока (с переносом фокуса ввода на данный блок) и, например, вызов контекстного меню, связанного с данным блоком. В самом меню могут быть предусмотрены, например, операции сохранения на диске в виде файла (точнее, в виде документа простой или сложной структуры) или распечатки на принтере (с предварительным просмотром или без него.) Во-вторых, необходимо обеспечить возможность управления блоками. Если браузер получает список блоков, а каждый блок имеет собственный тип, то пользователь может просматривать сайт не в виде «весёлых картинок», а в виде структуры: вот — список абзацев, вот — список иллюстраций, вот — список формул, а вот — база данных. А это значит, что у пользователя всегда есть возможность довольно простыми (если не сказать, примитивными) подпрограммами решать огромное разнообразие задач. Например, Вы можете захотеть взять определённый набор страниц некоторого сайта и взять с каждой страницы вполне определённые объекты (блоки) и составить из них единый документ и… выставить его на каком-то другом сайте. В-третьих, необходимо иметь возможность применять фильтры содержимого и самостоятельно выбирать нужное (в данный момент) представление одной и той же информации. Например, если Вы работаете с форумом, то у Вас должен имеется выбор: либо Вы просто читаете форум в виде единого потока реплик (в виде одного связного документа), либо Вы просматриваете все реплики последовательно одно за другим (в хронологическом порядке), либо Вы просматриваете только какую-то одну линию разговора между двумя конкретными участниками (и тогда это будет опять же линейный поток), либо Вы видите сразу весь разговор всех со всеми (и тогда будут использовать отступы или какие-либо другие средства отображения иерархии реплик; здесь, также, может быть удобным разделение окна на две части, где в одной части показываются все исходные реплики и сноски, а в другой части — только ответные реплики: отвечая самим, также, удобно делать сноску или цитировать целый фрагмент, создавая протяжённую сноску). Таким образом, у браузера оказывается множество различных функций, но, при этом, надо понимать, что браузер всегда имеет дело только с блоками, и за решение прикладных задач никак не отвечает.
И тут у нас сразу возникает устойчивое подозрение, что браузер, по своей сути, мало чем отличается от операционной системы. Каждый блок — это, по своей сути, самостоятельное окно, у которого есть своя собственная оконная функция. Если это так, то не должно быть никаких различий между построением пользовательского интерфейса любой произвольно взятой операционной системы и WEB-приложения. Это означает, что за реализацию поведения, на стороне клиента, всегда отвечают одни и те же компоненты пользовательского интерфейса, и, следовательно, для реализации кода, работающего на стороне клиента, всё, что нужно, уже есть в операционной системе. Или должно быть. Если, вдруг, для работы с данным сайтом необходимо нечто принципиально новое, то, в этом последнем случае, Вы, прежде чем, начать работу с сайтом, должны загрузить новое программное обеспечение, но эта загрузка не будет ничем отличаться от любой другой установки любого другого произвольно взятого приложения, которое Вы устанавливаете, например, с диска. Вспомните, как это происходит в TeX’е: когда Вы используете, например, новый шрифт, то, перед началом вёрстки, осуществляются вычисления (при помощи специальной подпрограммы METAFONT), после чего, все Ваши документы с применением нового шрифта будут уже выглядеть корректно. (Пример со шрифтом не следует понимать только буквально. В реальной ситуации, под «шрифтом» можно понимать и программный код, который, как и шрифт, может быть определённым образом упорядочен и распределён по определённым логическим позициям, каждая из которых обладает фиксированной семантикой.)
(Здесь, однако, возникает другой вопрос — а что может быть такого специфического на сайте, чего не может быть уже реализовано в самой операционной системе? — но этот вопрос мы оставим до самого конца статьи. Это очень важный вопрос, и к нему надо ещё хорошенько подготовиться.)
Сказанное Выше призвано показать, что даже для создания простой странички с простым текстом требуются определённые слои, и эти слои непосредственно управляют тем, как Вы взаимодействуете с содержимым документа. Одни блоки являются надстройкой над логической структурой документа. Другие блоки ответственны за доступ к функциям и операциям и должны добавляться конечными компонентами пользовательского интерфейса. Третьи блоки отвечают за оформление. В зависимости от выбранной задачи и конкретных намерений пользователя, будет меняться и состав блоков. Если Вы просто читаете текст, то он один; если Вы хотите иметь возможность получать какие-либо пояснения и справки по поводу …, то он будет другим, наконец, если Вы хотите отредактировать текст, то состав блоков будет уже третьим. Наконец, если Вы имеете дело с динамическим содержимым, то… Не слишком ли много вариантов?
Задумывались ли Вы над тем, что Вы ожидаете получить, устанавливая на свой компьютер ту или иную операционную систему (ОС)? По идее, Вы должны получить «из коробки» полностью готовую для работы систему «под ключ». Вы вправе ожидать, что в ОС есть все необходимые для этого средства. Между тем, в действительности, происходит нечто иное: сначала Вы устанавливаете ОС, а, потом, устанавливаете нужные Вам приложения. Зачем Вы это делаете? Нет, конечно, Вам это приходится делать. Вы это делаете не просто так. Если Вы — писатель, то Вам нужны специализированные средства для подготовки Ваших будущих публикаций. Если Вы — художник или дизайнер, то Вам нужны специальные средства, автоматизирующие рисование или конструирование. Если Вы — разработчик программного обеспечения, Вам нужны компиляторы и среды разработки. Вы все используете ту или иную ОС общего назначения и сами для себя выбираете и устанавливаете нужное Вам программное обеспечение. Но… Вы, только, представьте, что будет, если Вам не надо будет этого делать! Разумеется, я не предлагаю от этого избавиться, это совершенно невозможно. Я предлагаю посмотреть на тот же вопрос под другим углом и… увидеть настоящую проблему.
Начнём наши «отвлечённые» рассуждения с самих приложений. Каждое такое приложение — это монолитная конструкция. Можно сказать: «так исторически сложилось». Да, это так и есть. А, теперь, задайте себе вопрос: что в этом приложении есть такого, чего не может быть в самой ОС? Каждое приложение обычно работает с файлами определённого формата, поэтому основные типовые операции данного приложения — это обработка файлов (соответствующего формата): открытие, загрузка, редактирование и сохранение. Если ОС — это полноценная система, предназначенная для управления операциями, то типовые операциями с файлами должны быть неотъемлемой частью самой ОС. «Устанавливая» нужное нам приложение, мы, фактически, сообщаем ОС специфический способ обработки файлов определённого формата, а ОС предоставляет единый для файлов всех мыслимых форматов пользовательский (а, также, и общий программный) интерфейс для доступа к файлам. По сути, ОС должна самостоятельно создавать в своих недрах некий программный объект (будем условно называть объекты, предназначенные для решения прикладных задач «уровня приложения», программными объектами), который является чем-то вроде разделяемого между ОС и данным приложением ресурса. Программный объект можно ещё назвать документом, но, как это не называй, сущность программного объекта остаётся неизменной: программные объекты создаются как бы «внутри» приложения, но находятся под управлением ОС. Каждое приложение оказывается, таким образом, фабрикой документов, предоставляя ОС описание (спецификацию) формата обрабатываемых документов, а уже ОС, берёт на себя основные функции по созданию пользовательского интерфейса. В свою очередь, ОС предоставляет каждому приложению своё окружение, которое определяется типом самого приложения и тем, какую задачу в данный момент решает пользователь. Такой подход позволяет сравнить ОС с базой данных, где все данные хранятся упорядоченным образом, а доступ к ним осуществляется при помощи некоторого языка запросов. К сожалению, «монолитность» приложений существенным образом препятствует реализации этого подхода.
Представим себе, что приложения не являются монолитными. Что это означает? В обычной ситуации, ОС загружает запускаемое приложение в оперативную память и отдаёт ей управление. Делай, что хочешь! Но, во-первых, каждое приложение — это не «сферический конь в вакууме», это некая сущность, предназначенная для решения определённых задач. В этом смысле, каждое приложение должно «лежать» в ОС на определённой «полке». Если воспринимать ОС как БД, то в этой БД должно быть что-что вроде справочника «Номенклатура», содержащегоя, по сути, полноценный тезаурус или реестр понятий, и тогда, каждое приложение будет привязано к некоторой позиции данного справочника. Во-вторых, каждое приложение следует воспринимать как набор взаимосвязанных и взаимоувязанных компонентов, каждый из которых также должен находиться под управлением ОС. Соответственно, имея определённый пул установленных в ОС компонентов, мы можем собирать новые приложения (новые комбинации компонентов) для решения новых задач. Если программный код хранится в ОС в неких таблицах, то мы можем создавать нужные нам приложения, составляя хорошо структурированные запросы к БД программного кода и получая конечный код в виде упорядоченной выборки инструкций. В-третьих, каждое «монолитное» приложение поставляется «как есть» (в виде исполняемого файла или в виде скомпилированного пакета), и у нас нет никакой возможности извлечь из приложения его исходный код (это то, что Сергей Тарасов в своей книге «Софтостроение изнутри» назвал «безысходным программированием»), если, конечно, разработчики данного приложения не предусмотрели (ВНЕЗАПНО) эту счастливую возможность. Если бы разработка приложений осуществлялась бы строго по компонентной модели, то мы всегда могли бы перейти в интересующем нас приложении в режим «мастера», произвести декомпозицию, перейти на уровень компонентов, извлечь исходный любого нужного нам компонента, внести в него исправления, автоматически сохранить изменения в виде новой версии и использовать новую версию наравне со старой. (Да, это в корне расходится с общепринятой практикой прятать исходный код, создавать специальную защиту от взлома.)
Сказанное выше заставляет нас придти к вполне определённому выводу: ОС должна быть чем-то вроде СУБД, которая управляет разнотипными БД — БД кода, БД приложений и пользовательская БД, содержащая конечные данные, с которыми имеют дело пользователи. Каждое приложение делает определённый вклад в каждую из перечисленных БД и, по сути, само является чем-то вроде БД. Соответственно, установка, функционирование и уничтожение приложения оказываются, таким образом, транзакциями, осуществляемыми в различных БД. И здесь мы подходим к очень важному вопросу: где, здесь, находится код, специфический для данного приложения? Другой вариант того же вопроса: а в каком виде должен поставляться (для ОС) исходный код данного приложения? Не является ли, здесь, оптимальным подход, при применении которого каждое устанавливаемое приложение обязано быть представлено (и поставляется разработчиком) в декларативной форме (и только в ней) так, чтобы компиляция производилась только при установке данного приложения в ОС? Положительный ответ на этот вопрос приводит к нескольким важным последствиям. Во-первых, в ОС могут быть (и должны быть) изначально предусмотрены все основные виды взаимодействия и реализованы типовые задачи. Это означает, что всякий программный код, устанавливаемый с ОС, должен изначально привязываться к заранее заданным типовым задачам. Во-вторых, типовые операции и типовые взаимодействия предполагают довольно формализованную и хорошо автоматизированную схему создания пользовательского интерфейса. А этот значит, что никакому приложению не надо будет брать на себя реализацию, по сути, одних и тех же функций, связанных с открытием всевозможных диалоговых окон. В-третьих, имея готовые декларации и рабочие компоненты, Вы можете создавать новые приложения, поставляя своим сотрудникам или собеседникам новые декларации и новые компоненты. Отчасти, это напоминает приложение «1C», которое ВНЕЗАПНО стало бы полноценной ОС. Действительно, если коротко описать всё, что мы рассказали Выше, то, получается, что ОС как технологическая платформа поддерживает определённые «объекты конфигурации», сами конфигурации описывают модель обработки пользовательских данных, и в каждый момент времени используется (запущена и функционирует) какая-то одна конфигурация, которая определяет облик рабочего стола, доступные на нём функции и т.д. и т.п.
… И тут у нас вопрос: не являются ли эти пресловутые компоненты аналогами тех блоков, о которых мы говорили ранее применительно к WEB-страничкам?
Если Вы пишете какую-нибудь простую программу, Вы оформляете её в виде простой процедуры. Обычно, достаточно один раз внимательно прочитать программный код этой процедуры, чтобы понять, что она делает. Особенно, если писать, придерживаясь определённого (всегда одного и того же) стиля. Вы можете увидеть, что некоторые фрагменты кода имеют самостоятельную значимость, и у Вас может возникнуть соблазн оформить эти фрагменты как самостоятельные процедуры. Создавая новый функциональный блок и давая ему некоторое имя, Вы становитесь заложником однажды принятого решения. Одно дело, если это — Ваш код, тогда Вы можете написать новую процедуру. Другое дело, если это не Ваш код, и тогда Вы можете сделать (по возможности) свою версию этой функции, либо Вам придётся довольствоваться тем, что есть.
До тех пор, пока мы придерживаемся процедурного подхода, всё остаётся в некоторых определённых рамках. Но, как только, Вам необходимо использовать развитый пользовательский интерфейс, всё существенно усложняется. Во-первых, появляются компоненты пользовательского интерфейса. Во-вторых, у самих компонентов имеются события, и нужно где-то обрабатывать эти события. В-третьих, Вы ограничены, по сути, тем способом построения компонентов, который используется в Вашем средстве разработки. Появление компонентов, фактически, приводит к тому, что код «размывается» между компонентами, и это существенным образом затрудняет изучение исходного кода приложения.
Тут нас, однако, поджидает одна большая проблема, о которой иногда говорят, но, как-то, уж, очень вскользь. Вопрос заключается в том, что именно считать компонентом, и где, конкретно, должна описываться логика работы компонента. Если формулировать проблему предельно коротко, то можно сказать и так: кто должен отвечать за обработку сообщения от компонента? На самом деле, это — ключевой вопрос построения любой программной архитектуры! Получается, что для реализации произвольного компонента необходимо иметь, как минимум, три объекта: один объект описывает внешний (пользовательский) интерфейс, и в этом объекте возникают исходные события; второй объект задаёт внутренний (программный) интерфейс и осуществляет первичную обработку событий первого объекта; третий объект выполняет уже конкретные действия. По сути, логика работы компонента реализуется в третьем объекте, который можно воспринимать как контроллер (если использовать терминологию MVC). Попытка реализовать готовый к работе визуальный компонент является ошибкой. Например, можно попытаться реализовать компонент [Календарь] (как это и делается) на основе табличного визуального компонента, но табличное представление — это, всего лишь, представление, а календарь, по своей сути, это набор определённых функций и, в некотором смысле, определённый тип данных. Вы должны не пытаться создавать новый компонент, а соединять (то есть: привязывать) уже существующие компоненты с объектами данных. При таком подходе, Вы, просто, указываете модель данных и внутренний (программный) интерфейс к визуальному компоненту. Здесь работает что-то вроде «бритвы Оккама», которая не позволяет слишком разрастаться дереву визуальных компонентов. Если, однако, предположить, что таким образом можно обращаться, вообще, с любыми компонентами, не обязательно, визуальными, а, скажем так, компонентами, как таковыми, предполагая некий обобщённый компонентный способ программирования, то можно реализовать единую схему построения приложений из компонентов. Таким образом, компонентный подход вынуждает нас всегда создавать некоторую прослойку из специальных интерфейсных объектов, которые осуществляют трансляцию событий, происходящих в одних компонентах в действия, производимые другими компонентами. Связывая компоненты в цепочки, мы, фактически, описываем некоторый фрагмент большой системы, а язык описания этих компонентов вместе со связями между компонентами будет тем самым подлинным языком моделирования, о котором многие думают.
Представим себе некоторый программный код в виде последовательности инструкций. В обычной ситуации, этот код, оформленный в виде процедуры, один раз компилируется в код, который исполняется непосредственно процессором. Если за каждой переменной скрывается некоторый объект, и каждая операция является перегружаемой, то мы можем запустить процедуру, так сказать, на «холостом ходу» и воспроизвести дерево синтаксического разбора, построенного для данной процедуры. Этот означает, что Вы можете ещё до выполнения реальных вычислений с реальными данными, подготовить нужные Вам структуры. Другими словами, у Вас автоматически появляется дополнительный слой программных объектов, которые будут ответственны не только за, собственно, саму обработку данных, а, например, за ведение протокола событий, за преобразования данных из одной формы в другую, за управление пользовательским интерфейсом и за разграничение доступа к тем или иным функциям. При наличии этого промежуточного слоя, Вы можете, имея готовое приложение, сохранить его… в виде исходного кода. Но, что представляется здесь особенно важным, Вы получаете возможность писать все свои программы, придерживаясь строгого процедурного подхода. И, если, например, Вы реализуете некоторый вычислительный алгоритм, требующий ввода каких-либо параметров, то Вы должны это оформить именно как ввод данных, но не описывать каким-либо образом сам ввод: промежуточный слой объектов осуществит необходимую привязку вводимых данных с полями ввода или с диалогом выбора файла. Таким образом, компонентный подход, позволяет многократно использовать один и тот же программный код, при этом, в зависимости от контекста, результат работы данного кода будет различным, а те вычисления, которые будут реально выполняться при обработке конечных (пользовательских) данных, могут отличаться от заранее заданных, поскольку, в результате «промежуточных» вычислений, вместо некоторого указанного в исходном коде обобщённого алгоритма, была вставлена конкретная реализация, определяемая текущей конфигурацией приложения.
Было бы крайне соблазнительным каким-либо подходящим образом «математизировать» программирование и рассмотреть некое подобие скалярного произведения. Смысл аналогии в том, чтобы задать некое пространство и задать в нём операции умножения (взаимодействия двух объектов) и сложения (конкатенации или слияния результатов). Тогда, операция скалярного произведения могла бы описывать довольно широкий круг явлений. А где скалярное произведение, там и процесс ортогонализации Грамма-Шмидта. Здесь нам нужно понятие линейной независимости. Имея систему линейно независимых «векторов», мы можем перейти к ортонормированной системе, а скалярное произведение в ортонормированном базисе описывается предельно просто. Вот к этой простоте и следует стремиться при создании ПО.
Что означает ортогонализация для данных? Дело в том, что любая сущность проявляет себя по-разному в различных обстоятельствах. Это означает, что один и тот же объект реального мира фигурирует в различных ролях в различных взаимодействиях. Следовательно, для каждого такого вида взаимодействия нужно создавать свой программный объект. Но каждый такой объект (или класс объектов) будет обрабатываться при помощи своего отдельного компонента. Разводя разнотипные потоки управления, мы, фактически, предоставляем разработчику возможность писать максимально обобщённый код.
А что означает ортогонализация для операций? Это означает, что Вы всегда можете организовать удобный доступ ко всем операциям, Вы можете создавать списки операций по типу, вести отдельную БД для операций, и, что представляется мне особенно важным, у нас появляется возможность единым образом кодировать операции над объектами и делать этот код частью физического представления программного кода. Вспомните FORTH с его идеей представлять программы как связки рекурсивных функций. Если у Вас есть таблица функций (операций), то… Вы сможете буквально любую задачу решать при помощи построения конечных автоматов (по Шалыто), но эта возможность должна, по идее быть строенной в ОС!
Само сравнение программирования с процессом верстки сложных документов кажется весьма показательным. Обычно предполагается, что, сначала пишется исходный код программы (на каком-либо языке программирования), а, потом, этот код транслируется в код целевой машины. (Есть ещё и важное промежуточное представление в виде т.н. «байт-кода», но речь, сейчас, не об этом.)
Здесь есть две проблемные точки.
С одной стороны, любое сложное ПО должно быть представлено не в виде своего исходного кода, а в некотором особом виде; в этом смысле, исходным кодом всякого приложения должен быть не код на каком-то языке программирования, а чем-то вроде БД. По сути, среда разработки должна быть не простым редактором исходного кода с расширенными функциями, а документально-ориентированной системой, позволяющей вести, например, некое подобие «бухгалтерского учёта». Если воспринимать каждое действие в такой среде как операцию с некоторым «счётом», то «программирование» сложной корпоративной системы будет заключаться в пополнении определённых «счетов» с последующим составлением «отчёта», при этом, различные виды «отчётов» будут отвечать различным задачам (например, документация проектируемой системы также можно считать своеобразным «отчётом»). (Именно к этому, по моему, в итоге, и приходят серьёзные разработчики крупных корпоративных систем. Но этот подход, к сожалению, не становится «общим местом».)
Здесь важно то, что приложение, сначала, всегда находится в состоянии проекта, а это значит, что программный код (на конечном языке программирования) — это лишь конечный результат работы разработчика, и ещё до того, как этот самый программный код (нужный исключительно при реальной эксплуатации продукта) будет полностью готов, у разработчика довольно много работы, и первый релиз сможет появиться только тогда, когда заказчик и исполнитель, сидя за одним и тем же средством разработки, смогут, наконец, «ударить по рукам» и нажать заветную «красную» кнопку (которая может называться, например, «Создать релиз»). Это, опять же, сильно сближает создание ПО и верстку документов: вы пишете «исходный код» и, только, перед печатью, пропускаете созданный «код» через систему вёрстки. Вы, конечно, можете постоянно проверять промежуточные результаты, но иногда бывает, если Вы, например, пишете статью для какого-нибудь сборника, то Вы посылаете в редакцию только «исходный код».
С другой стороны, возникает вопрос о соотношении процедурного и декларативного подходов. Здесь трудно обойтись без практического примера и объяснить суть вопроса «на словах». Если бы мы могли довольно широко использовать всевозможные декларации, то… использовали бы только их, например, для передачи данных по сети. (Не для этого ли создавался в своё время XML?). Получая только декларации, ОС могла бы компилировать по заданной декларации родной для себя код. (И это было бы безопасно!) В этом смысле, ОС можно воспринимать как иерархию языков, а это значит, что в ОС обязательно должны быть встроены средства построения трансляторов, но тогда вопрос будет ставиться не так, как он ставится сегодня — какие выбрать язык программирования и среду разработки, — а противоположным образом: какой язык программирования нужно создать, чтобы оптимальным образом решать задачи определённого вида. Я этого ещё не знаю, но мне кажется, что функциональное программирование способно внести ясность при ответе на этот вопрос, а будущие открытия в области создания какой-нибудь, там, трёхмерной памяти (где одни и те же данные можно будет размещать огромным количеством способов, в зависимости от задачи) решат вопросы быстродействия т.н. «ленивых вычислений», которые представляются крайне перспективными, потому что они концептуально прозрачны и доказательно точны. Вперёд — к безошибочному программированию!
Почему всё происходит так, как происходит?
Для начала, этому есть экономическое объяснение. Время, необходимое для детального продумывания и реализации полного многофункционального и безопасного решения нет. Всё нужно «здесь и сейчас». В результате, в выигрыше оказываются фирмы, которые первыми застолбили себе фронт работ. Если Вы сделаете, однажды, продукт, который, действительно, автоматизирует определённую деятельность, то Вы оставите будущие поколения без хлеба.
«Политика — это концентрированная экономика», и, поэтому, для распространения своего влияния во времени и пространстве нужно всегда поддерживать свою собственную значимость для пользователя. А если практические задачи решаются проще, если есть способ как-то упорядочить их, если есть возможность один раз автоматизировать типовые операции и конструировать «на лету» приложения для решения текущих задач пользователя, то… зачем нужны многочисленные компании, разрабатывающие ПО?
А что является «концентрированной политикой»? Правильно! Наука! А что нам может сообщить наука? Я хочу напомнить заинтересованному читателю, что первоначальная идея гиперссылки заключалась не в том, чтобы сделать возможным переход от одного документа к другому, а в том, чтобы создать неразрывную связь двух документов, чтобы, просматривая один документ, мы могли бы пользоваться одновременно и другим документом. Гиперссылки, по своей сути, нужны для того, чтобы суметь отразить динамический характер изменений всего WEB’а в целом. Это означает, что Вы, открывая один документ, имеете возможность автоматически подгружать информацию, которая относится к интересующему Вас предмету. То, что только недавно сообщалось как некое достижение (JSON и REST), должно было бы изначально стать фундаментом всей технологии распределённого хранения данных. Но для реализации этого информационного «рая» следовало бы понять, что: во-первых, WEB должен быть большой распределённой базой знаний (а это значит, что в основе должны лежать идеи иерархических распределённых семантических баз данных, и где обычные реляционные базы данных занимали бы своё почётное, но, всё-таки, подчинённое «промежуточное» положение); во-вторых, для функционирования этой системы нужно не минимизировать трафик (как это делается обычно), а наладить постоянный обмен данными, сделать его регулярным (а это значит, что будут, в частности, невозможны никакие DDoS-атаки, поскольку будет нельзя «просто так» сделать запрос определённой страницы, запросить можно будет только какую-то конкретную информацию); в-третьих, для пользователей WEB должен представляться как большая Wikipedia, где каждая страница имеет историю правок, всегда можно создать обсуждение или принять участие в редактировании. Одним словом: такой WEB подобен реальному миру, где не может быть никаких «сломанных» ссылок, пользователи имеют дело с документами, у каждого документа есть своя история и т.д. и т.п. Да-да. Мечтать «не вредно», не так ли?
Зачем я написал эту статью?
Во-первых, я решил немного привести в порядок свои собственные мысли и попытаться их связно изложить. Связно изложить не получилось. Виноват. Получилось несколько разрозненных сюжетов и эпизодов. По идее, здесь, в этой статье, напоминающей айсберг, на который наткнулось моё буйное воображение, зарыто несколько заготовок для вполне себе приличных статей, которые можно и, вообще говоря, следовало бы здесь, на Хабрахабре, написать.
Во-вторых, я попытался очертить для себя круг собственных интересов и определил для себя область своих поисков. Мне было бы крайне интересно разрабатывать гибкие программные комплексы, основанные на компонентном подходе (который надо обязательно выработать!), искать нестандартные способы реализации обобщённого программирования и решать увлекательную проблему соотношения процедурного и декларативного начала в программировании.
В-третьих, … а, может быть, всё уже есть? Всё уже изобретено? Всё нужное уже существует? Ну, тогда я буду рад услышать об этом от других и смогу сразу заняться решением реальных задач. И, тогда, поверьте, будет немало поводов написать о том, что у меня получилось на этом пути…
Как было однажды сказано: «не говорите о том, как Вы много и усердно работаете, расскажите, лучше о том, чего Вы добились».
Экспозиция: два типа разработчиков
Представим себе разработчика, который (в далёкие, но «лихие» девяностые) впервые столкнулся с довольно заманчивой возможностью создавать простые HTML-странички. Он думает, что, теперь, всё просто: набираешь текст, вставляешь в него теги (они же: команды) и сохраняешь полученный результат в файле на рабочем сервере, а пользователь получает то, что ему, пользователю, нужно. Разработчик быстро смекает, что будет довольно просто реализовать приложение, которое будет интерпретировать HTML-код, и… создаёт первый браузер. Идея что-то там интерпретировать не нова, здесь важен результат или, точнее, эффект: пользователь запрашивает определённую страницу, получает заранее заготовленный HTML-код, браузер разбирает этот код (в соответствии со своей объектной моделью) и конечный результат показывает пользователю на экране. Очень эффектно, не так ли?
Разработчик не особо утруждает себя вопросами, что, в действительности, нужно пользователю и начинает придумывать всевозможные теги (они же команды): наклонное начертание, полужирное начертание, задание шрифта, заголовок раздела, список и т.д. и т.п. Разработчик вводит возможность создавать списки, вставлять рисунки и… гиперссылки (те самые, ради которых «всё»). Мир, в котором действует разработчик, прост и понятен: у браузера есть только одно окно, и, следовательно, у пользователя есть только одна возможность — загрузить себе новую страницу и увидеть её всё в том же единственном окне браузера. Так в браузере появилась кнопка «Назад» (и сопутствующая ей кнопка «Вперёд», которая предназначена, как бы для отмены отмены), история посещений и возможность сделать закладку на нужной странице.
В реальности, таких разработчиков оказывается не так, уж, и мало, и каждый из них норовит внести свою лепту в создание браузеров. Браузеров становится несколько, и различные фирмы соревнуются друг с другом в том, как лучше всего отображать содержимое документов. На первый план выдвигаются вопросы совместимости, а страничкописателям приходится помнить всё больше вещей о том, как и что отображается (или не отображается) в тех или иных браузерах.
Пока WEB статичен, Вы не можете ничего толкового в нём сделать. Вы можете только выставить свой товар на витрину и красиво его там разложить. Но пользователь будет, только, пассивным наблюдателем. Между тем, возникает желание вести посредством WEB свой бизнес, а для этого нужно где-то иметь программный код, который должен выполняться в ответ на действия пользователя. Обычное (локальное) приложение весь свой программный код «носит» с собой (в исполняемом файле), а в WEB размещение кода оказывается более сложным: код может располагаться на стороне сервера (и тогда сервер должен каким-то образом получать по сети команды от пользователя), а может располагаться на стороне клиента (и тогда этот самый код, который, разумеется, будет тратить процессорное время на компьютере пользователя, должен располагаться на самой странице). Осознание этого факта привело, с одной стороны, к появлению скриптовых языков и встраивания интерпретаторов таких языков в браузеры, а, с другой стороны, к появлению в HTML специальных тегов, предназначенных для создания форм ввода данных от пользователя. Далее, состоялось рождение XML, возникли технологии ASP, JSON и REST…
Я не буду более утомлять читателя перечислением общеизвестных фактов, но, замечу, что в основе бурного роста в любой области лежит именно та простота, с которой сталкиваются разработчики первых версий программных продуктов. Эти счастливые разработчики изначально имеют дело с простыми и обозримыми объектами. Они ещё не знают о том, во что это всё потом выльется. Они не сдержаны никакими рамками каких-либо практических задач. Их горизонт предельно сужен. Но, при этом, значительную роль играет элемент новизны технологии. И этот элемент провоцирует весьма бурное развитие технологий. Одни пытаются заработать на этом много денег. Другие находят в новой области немало интересных задач и добиваются весьма неплохих результатов в их решении. Третьи всё это наблюдают и описывают это буйство жизни на журнальных страницах…
Представим себе другого, гораздо более вдумчивого разработчика. Такой разработчик обязательно задаст себе следующие вопросы:
- Чем принципиально отличаются приложения для WEB от обычных настольных приложений?
- Какова должна быть программная инфраструктура, поддерживающая работу с WEB-приложениями?
- А что, на самом деле, нужно пользователю?
Такой разработчик не будет торопиться с выводами, и, уж точно, не станет торопиться создавать свой первый браузер. Во-первых, вдумчивый разработчик обязательно подумает над тем, а нельзя ли в WEB использовать обычные «настольные» приложения, и что следовало бы добавить в них, чтобы научить работать их в распределённой среде. Например, что должно делать приложение для работы с базой данных, если сами данные находятся на удалённом сервере? Во-вторых, он обязательно подумает над тем, что должно быть в самой операционной системе, чтобы пользователь мог бы корректно работать с конкретным сайтом. Более того, вопрос разделения кода на «клиентский» и «серверный» станет для вдумчивого разработчика самой настоящей головной болью, и эта боль не прекратится до тех пор, пока разработчик не найдёт решение вопроса, удовлетворительное как в плане производительности, так и в плане безопасности и удобства. Наконец, в третьих, вдумчивый разработчик обязательно подумает о том, как должен быть оптимально организован сам WEB, и что должна представлять из себя операционная система, которая умеет работать с WEB.
Разумеется, вдумчивый разработчик ничего не успеет сделать. Он, вообще, ничего никогда не успеет сделать. Всё, что будет сделано, будет сделано множеством простых разработчиков. И… делается.
Что же мы имеем, в результате? Что имеем, то и имеем.
Имеем слоёный пирог технологий.
Имеем множество весьма различных вариантов и средств реализации.
Имеем множество подходов к дизайну, и, вообще, постоянную смену дизайна сайтов и подходов к
организации хранения данных и к организации взаимодействия.
Имеем хрупкость WEB, когда данная сегодня ссылка уже завтра становится недействительной.
Имеем ситуацию, когда большая часть WEB становится невидимой, потому как вместо статических страничек и полнотекстового поиска, у нас есть всевозможные динамические приложения. При этом, поисковые системы на наши запросы возвращают практически один только информационный мусор. Как, впрочем, и сама инфраструктура WEB, которая, прямо-таки, провоцирует на постоянное дублирование (по большей части, ненужной нам) информации. Я не говорю уже и о том, что мы имеем просто чудовищные проблемы с безопасностью хранения и передачи данных, и, при всём при этом, становится особенно жутко от того, что разработчики совершенно спокойно позволяют себе выпускать небезопасные продукты, прекрасно зная об их небезопасности и требуя за них плату! При этом, постоянное обнаружение уязвимостей (в программных продуктах) стало для всех уже повседневным фоном, а у пользователя с каждым шагом индустрии разработки ПО остаётся всё меньше способов контролировать собственные компьютеры, и эти последние уже совсем перестают быть «персональными»…
Но! Мир мог быть и иным. Почему же он не такой, какой хочется? И как мог бы выглядеть тот мир, который кажется нам лучше? И, самое главное, действительно ли этот другой мир «прекрасен», и мы зря тешим себя иллюзиями о некотором другом «прекрасном мире»?
Разработка
Начнём с самого начала. Кстати, а где оно, это самое начало? Здесь придётся действовать с различных концов. В-первых, у нас есть сами страницы, то есть то, что непосредственно отображается пользователю. Во-вторых, у нас есть то, что является источником данных для страниц. В-третьих, у нас есть то, что осуществляет формирование и доставку запросов серверу, и то, что осуществляет доставку результатов выполнения запросов клиенту. Это, что называется, с одной стороны. С другой стороны, есть вопрос о то, как это всё должно выглядеть в целом: с точки зрения пользователя, с точки зрения операционной системы, с точки зрения сети. Третий аспект проблемы заключается в том, а что должны (на самом деле) делать разработчики, и как должна быть устроена индустрия разработки ПО. Крайне соблазнительно начать с конца и развести длинную демагогию по поводу разработки ПО, но мы возьмём себя в руки и, всё-таки, начнём с самого начала, а именно: с самих страниц, которые показываются пользователю.
Эпизод №1: TeX для WEB'а
Свёрстанный текст состоит из блоков. Всякая WEB-страничка — это тоже результат вёрстки. Поэтому, произвольная страница в WEB состоит, прежде всего, из блоков. Это означает, что прежде чем отобразить на странице некоторый текст или более сложный объект, Вам необходимо создать под него блок. Когда Вы пишете текст, составляете некоторый документ, Вас не интересуют никакие блоки, но Вы заранее знаете, что каждый элемент Вашего документа будет находиться в некотором блоке. Блок — это часть физического уровня организации документа, Вы же, создавая документ, работаете на логическом уровне, на котором присутствуют конкретные абзацы, разделы, смысловые выделения, ссылки, сноски, формулы, рисунки, графики и прочее, и прочее…
Если браузер получает уже свёрстанный документ, то он получает блоки, и Вам, чтобы создать эти блоки, нужно пропустить исходный код Вашего документа через систему вёрстки. В этом случае, браузер должен получить именно сами блоки и только их, а уже каждый блок сам отвечает за своё содержимое. По сути, каждый блок — это именованный канал для обмена данными. Если браузер ограничить только работой с этим самыми блоками, то браузеры будет легко реализовывать.
Кому-то может показаться такая функция довольно узкой для такого приложения как браузер, однако, по некотором размышлении, можно обнаружить немало задач, которые должен выполнять браузер (помимо, собственно, отображения). Во-первых, необходимо обеспечить первичную обработку сообщений от «мышки» и клавиатуры. У пользователя должна иметься возможность выбора отдельного блока (с переносом фокуса ввода на данный блок) и, например, вызов контекстного меню, связанного с данным блоком. В самом меню могут быть предусмотрены, например, операции сохранения на диске в виде файла (точнее, в виде документа простой или сложной структуры) или распечатки на принтере (с предварительным просмотром или без него.) Во-вторых, необходимо обеспечить возможность управления блоками. Если браузер получает список блоков, а каждый блок имеет собственный тип, то пользователь может просматривать сайт не в виде «весёлых картинок», а в виде структуры: вот — список абзацев, вот — список иллюстраций, вот — список формул, а вот — база данных. А это значит, что у пользователя всегда есть возможность довольно простыми (если не сказать, примитивными) подпрограммами решать огромное разнообразие задач. Например, Вы можете захотеть взять определённый набор страниц некоторого сайта и взять с каждой страницы вполне определённые объекты (блоки) и составить из них единый документ и… выставить его на каком-то другом сайте. В-третьих, необходимо иметь возможность применять фильтры содержимого и самостоятельно выбирать нужное (в данный момент) представление одной и той же информации. Например, если Вы работаете с форумом, то у Вас должен имеется выбор: либо Вы просто читаете форум в виде единого потока реплик (в виде одного связного документа), либо Вы просматриваете все реплики последовательно одно за другим (в хронологическом порядке), либо Вы просматриваете только какую-то одну линию разговора между двумя конкретными участниками (и тогда это будет опять же линейный поток), либо Вы видите сразу весь разговор всех со всеми (и тогда будут использовать отступы или какие-либо другие средства отображения иерархии реплик; здесь, также, может быть удобным разделение окна на две части, где в одной части показываются все исходные реплики и сноски, а в другой части — только ответные реплики: отвечая самим, также, удобно делать сноску или цитировать целый фрагмент, создавая протяжённую сноску). Таким образом, у браузера оказывается множество различных функций, но, при этом, надо понимать, что браузер всегда имеет дело только с блоками, и за решение прикладных задач никак не отвечает.
И тут у нас сразу возникает устойчивое подозрение, что браузер, по своей сути, мало чем отличается от операционной системы. Каждый блок — это, по своей сути, самостоятельное окно, у которого есть своя собственная оконная функция. Если это так, то не должно быть никаких различий между построением пользовательского интерфейса любой произвольно взятой операционной системы и WEB-приложения. Это означает, что за реализацию поведения, на стороне клиента, всегда отвечают одни и те же компоненты пользовательского интерфейса, и, следовательно, для реализации кода, работающего на стороне клиента, всё, что нужно, уже есть в операционной системе. Или должно быть. Если, вдруг, для работы с данным сайтом необходимо нечто принципиально новое, то, в этом последнем случае, Вы, прежде чем, начать работу с сайтом, должны загрузить новое программное обеспечение, но эта загрузка не будет ничем отличаться от любой другой установки любого другого произвольно взятого приложения, которое Вы устанавливаете, например, с диска. Вспомните, как это происходит в TeX’е: когда Вы используете, например, новый шрифт, то, перед началом вёрстки, осуществляются вычисления (при помощи специальной подпрограммы METAFONT), после чего, все Ваши документы с применением нового шрифта будут уже выглядеть корректно. (Пример со шрифтом не следует понимать только буквально. В реальной ситуации, под «шрифтом» можно понимать и программный код, который, как и шрифт, может быть определённым образом упорядочен и распределён по определённым логическим позициям, каждая из которых обладает фиксированной семантикой.)
(Здесь, однако, возникает другой вопрос — а что может быть такого специфического на сайте, чего не может быть уже реализовано в самой операционной системе? — но этот вопрос мы оставим до самого конца статьи. Это очень важный вопрос, и к нему надо ещё хорошенько подготовиться.)
Сказанное Выше призвано показать, что даже для создания простой странички с простым текстом требуются определённые слои, и эти слои непосредственно управляют тем, как Вы взаимодействуете с содержимым документа. Одни блоки являются надстройкой над логической структурой документа. Другие блоки ответственны за доступ к функциям и операциям и должны добавляться конечными компонентами пользовательского интерфейса. Третьи блоки отвечают за оформление. В зависимости от выбранной задачи и конкретных намерений пользователя, будет меняться и состав блоков. Если Вы просто читаете текст, то он один; если Вы хотите иметь возможность получать какие-либо пояснения и справки по поводу …, то он будет другим, наконец, если Вы хотите отредактировать текст, то состав блоков будет уже третьим. Наконец, если Вы имеете дело с динамическим содержимым, то… Не слишком ли много вариантов?
Первое лирическое отступление (длинное)
Задумывались ли Вы над тем, что Вы ожидаете получить, устанавливая на свой компьютер ту или иную операционную систему (ОС)? По идее, Вы должны получить «из коробки» полностью готовую для работы систему «под ключ». Вы вправе ожидать, что в ОС есть все необходимые для этого средства. Между тем, в действительности, происходит нечто иное: сначала Вы устанавливаете ОС, а, потом, устанавливаете нужные Вам приложения. Зачем Вы это делаете? Нет, конечно, Вам это приходится делать. Вы это делаете не просто так. Если Вы — писатель, то Вам нужны специализированные средства для подготовки Ваших будущих публикаций. Если Вы — художник или дизайнер, то Вам нужны специальные средства, автоматизирующие рисование или конструирование. Если Вы — разработчик программного обеспечения, Вам нужны компиляторы и среды разработки. Вы все используете ту или иную ОС общего назначения и сами для себя выбираете и устанавливаете нужное Вам программное обеспечение. Но… Вы, только, представьте, что будет, если Вам не надо будет этого делать! Разумеется, я не предлагаю от этого избавиться, это совершенно невозможно. Я предлагаю посмотреть на тот же вопрос под другим углом и… увидеть настоящую проблему.
Начнём наши «отвлечённые» рассуждения с самих приложений. Каждое такое приложение — это монолитная конструкция. Можно сказать: «так исторически сложилось». Да, это так и есть. А, теперь, задайте себе вопрос: что в этом приложении есть такого, чего не может быть в самой ОС? Каждое приложение обычно работает с файлами определённого формата, поэтому основные типовые операции данного приложения — это обработка файлов (соответствующего формата): открытие, загрузка, редактирование и сохранение. Если ОС — это полноценная система, предназначенная для управления операциями, то типовые операциями с файлами должны быть неотъемлемой частью самой ОС. «Устанавливая» нужное нам приложение, мы, фактически, сообщаем ОС специфический способ обработки файлов определённого формата, а ОС предоставляет единый для файлов всех мыслимых форматов пользовательский (а, также, и общий программный) интерфейс для доступа к файлам. По сути, ОС должна самостоятельно создавать в своих недрах некий программный объект (будем условно называть объекты, предназначенные для решения прикладных задач «уровня приложения», программными объектами), который является чем-то вроде разделяемого между ОС и данным приложением ресурса. Программный объект можно ещё назвать документом, но, как это не называй, сущность программного объекта остаётся неизменной: программные объекты создаются как бы «внутри» приложения, но находятся под управлением ОС. Каждое приложение оказывается, таким образом, фабрикой документов, предоставляя ОС описание (спецификацию) формата обрабатываемых документов, а уже ОС, берёт на себя основные функции по созданию пользовательского интерфейса. В свою очередь, ОС предоставляет каждому приложению своё окружение, которое определяется типом самого приложения и тем, какую задачу в данный момент решает пользователь. Такой подход позволяет сравнить ОС с базой данных, где все данные хранятся упорядоченным образом, а доступ к ним осуществляется при помощи некоторого языка запросов. К сожалению, «монолитность» приложений существенным образом препятствует реализации этого подхода.
Представим себе, что приложения не являются монолитными. Что это означает? В обычной ситуации, ОС загружает запускаемое приложение в оперативную память и отдаёт ей управление. Делай, что хочешь! Но, во-первых, каждое приложение — это не «сферический конь в вакууме», это некая сущность, предназначенная для решения определённых задач. В этом смысле, каждое приложение должно «лежать» в ОС на определённой «полке». Если воспринимать ОС как БД, то в этой БД должно быть что-что вроде справочника «Номенклатура», содержащегоя, по сути, полноценный тезаурус или реестр понятий, и тогда, каждое приложение будет привязано к некоторой позиции данного справочника. Во-вторых, каждое приложение следует воспринимать как набор взаимосвязанных и взаимоувязанных компонентов, каждый из которых также должен находиться под управлением ОС. Соответственно, имея определённый пул установленных в ОС компонентов, мы можем собирать новые приложения (новые комбинации компонентов) для решения новых задач. Если программный код хранится в ОС в неких таблицах, то мы можем создавать нужные нам приложения, составляя хорошо структурированные запросы к БД программного кода и получая конечный код в виде упорядоченной выборки инструкций. В-третьих, каждое «монолитное» приложение поставляется «как есть» (в виде исполняемого файла или в виде скомпилированного пакета), и у нас нет никакой возможности извлечь из приложения его исходный код (это то, что Сергей Тарасов в своей книге «Софтостроение изнутри» назвал «безысходным программированием»), если, конечно, разработчики данного приложения не предусмотрели (ВНЕЗАПНО) эту счастливую возможность. Если бы разработка приложений осуществлялась бы строго по компонентной модели, то мы всегда могли бы перейти в интересующем нас приложении в режим «мастера», произвести декомпозицию, перейти на уровень компонентов, извлечь исходный любого нужного нам компонента, внести в него исправления, автоматически сохранить изменения в виде новой версии и использовать новую версию наравне со старой. (Да, это в корне расходится с общепринятой практикой прятать исходный код, создавать специальную защиту от взлома.)
Сказанное выше заставляет нас придти к вполне определённому выводу: ОС должна быть чем-то вроде СУБД, которая управляет разнотипными БД — БД кода, БД приложений и пользовательская БД, содержащая конечные данные, с которыми имеют дело пользователи. Каждое приложение делает определённый вклад в каждую из перечисленных БД и, по сути, само является чем-то вроде БД. Соответственно, установка, функционирование и уничтожение приложения оказываются, таким образом, транзакциями, осуществляемыми в различных БД. И здесь мы подходим к очень важному вопросу: где, здесь, находится код, специфический для данного приложения? Другой вариант того же вопроса: а в каком виде должен поставляться (для ОС) исходный код данного приложения? Не является ли, здесь, оптимальным подход, при применении которого каждое устанавливаемое приложение обязано быть представлено (и поставляется разработчиком) в декларативной форме (и только в ней) так, чтобы компиляция производилась только при установке данного приложения в ОС? Положительный ответ на этот вопрос приводит к нескольким важным последствиям. Во-первых, в ОС могут быть (и должны быть) изначально предусмотрены все основные виды взаимодействия и реализованы типовые задачи. Это означает, что всякий программный код, устанавливаемый с ОС, должен изначально привязываться к заранее заданным типовым задачам. Во-вторых, типовые операции и типовые взаимодействия предполагают довольно формализованную и хорошо автоматизированную схему создания пользовательского интерфейса. А этот значит, что никакому приложению не надо будет брать на себя реализацию, по сути, одних и тех же функций, связанных с открытием всевозможных диалоговых окон. В-третьих, имея готовые декларации и рабочие компоненты, Вы можете создавать новые приложения, поставляя своим сотрудникам или собеседникам новые декларации и новые компоненты. Отчасти, это напоминает приложение «1C», которое ВНЕЗАПНО стало бы полноценной ОС. Действительно, если коротко описать всё, что мы рассказали Выше, то, получается, что ОС как технологическая платформа поддерживает определённые «объекты конфигурации», сами конфигурации описывают модель обработки пользовательских данных, и в каждый момент времени используется (запущена и функционирует) какая-то одна конфигурация, которая определяет облик рабочего стола, доступные на нём функции и т.д. и т.п.
… И тут у нас вопрос: не являются ли эти пресловутые компоненты аналогами тех блоков, о которых мы говорили ранее применительно к WEB-страничкам?
Эпизод №2: компонентный подход
Если Вы пишете какую-нибудь простую программу, Вы оформляете её в виде простой процедуры. Обычно, достаточно один раз внимательно прочитать программный код этой процедуры, чтобы понять, что она делает. Особенно, если писать, придерживаясь определённого (всегда одного и того же) стиля. Вы можете увидеть, что некоторые фрагменты кода имеют самостоятельную значимость, и у Вас может возникнуть соблазн оформить эти фрагменты как самостоятельные процедуры. Создавая новый функциональный блок и давая ему некоторое имя, Вы становитесь заложником однажды принятого решения. Одно дело, если это — Ваш код, тогда Вы можете написать новую процедуру. Другое дело, если это не Ваш код, и тогда Вы можете сделать (по возможности) свою версию этой функции, либо Вам придётся довольствоваться тем, что есть.
До тех пор, пока мы придерживаемся процедурного подхода, всё остаётся в некоторых определённых рамках. Но, как только, Вам необходимо использовать развитый пользовательский интерфейс, всё существенно усложняется. Во-первых, появляются компоненты пользовательского интерфейса. Во-вторых, у самих компонентов имеются события, и нужно где-то обрабатывать эти события. В-третьих, Вы ограничены, по сути, тем способом построения компонентов, который используется в Вашем средстве разработки. Появление компонентов, фактически, приводит к тому, что код «размывается» между компонентами, и это существенным образом затрудняет изучение исходного кода приложения.
Тут нас, однако, поджидает одна большая проблема, о которой иногда говорят, но, как-то, уж, очень вскользь. Вопрос заключается в том, что именно считать компонентом, и где, конкретно, должна описываться логика работы компонента. Если формулировать проблему предельно коротко, то можно сказать и так: кто должен отвечать за обработку сообщения от компонента? На самом деле, это — ключевой вопрос построения любой программной архитектуры! Получается, что для реализации произвольного компонента необходимо иметь, как минимум, три объекта: один объект описывает внешний (пользовательский) интерфейс, и в этом объекте возникают исходные события; второй объект задаёт внутренний (программный) интерфейс и осуществляет первичную обработку событий первого объекта; третий объект выполняет уже конкретные действия. По сути, логика работы компонента реализуется в третьем объекте, который можно воспринимать как контроллер (если использовать терминологию MVC). Попытка реализовать готовый к работе визуальный компонент является ошибкой. Например, можно попытаться реализовать компонент [Календарь] (как это и делается) на основе табличного визуального компонента, но табличное представление — это, всего лишь, представление, а календарь, по своей сути, это набор определённых функций и, в некотором смысле, определённый тип данных. Вы должны не пытаться создавать новый компонент, а соединять (то есть: привязывать) уже существующие компоненты с объектами данных. При таком подходе, Вы, просто, указываете модель данных и внутренний (программный) интерфейс к визуальному компоненту. Здесь работает что-то вроде «бритвы Оккама», которая не позволяет слишком разрастаться дереву визуальных компонентов. Если, однако, предположить, что таким образом можно обращаться, вообще, с любыми компонентами, не обязательно, визуальными, а, скажем так, компонентами, как таковыми, предполагая некий обобщённый компонентный способ программирования, то можно реализовать единую схему построения приложений из компонентов. Таким образом, компонентный подход вынуждает нас всегда создавать некоторую прослойку из специальных интерфейсных объектов, которые осуществляют трансляцию событий, происходящих в одних компонентах в действия, производимые другими компонентами. Связывая компоненты в цепочки, мы, фактически, описываем некоторый фрагмент большой системы, а язык описания этих компонентов вместе со связями между компонентами будет тем самым подлинным языком моделирования, о котором многие думают.
Представим себе некоторый программный код в виде последовательности инструкций. В обычной ситуации, этот код, оформленный в виде процедуры, один раз компилируется в код, который исполняется непосредственно процессором. Если за каждой переменной скрывается некоторый объект, и каждая операция является перегружаемой, то мы можем запустить процедуру, так сказать, на «холостом ходу» и воспроизвести дерево синтаксического разбора, построенного для данной процедуры. Этот означает, что Вы можете ещё до выполнения реальных вычислений с реальными данными, подготовить нужные Вам структуры. Другими словами, у Вас автоматически появляется дополнительный слой программных объектов, которые будут ответственны не только за, собственно, саму обработку данных, а, например, за ведение протокола событий, за преобразования данных из одной формы в другую, за управление пользовательским интерфейсом и за разграничение доступа к тем или иным функциям. При наличии этого промежуточного слоя, Вы можете, имея готовое приложение, сохранить его… в виде исходного кода. Но, что представляется здесь особенно важным, Вы получаете возможность писать все свои программы, придерживаясь строгого процедурного подхода. И, если, например, Вы реализуете некоторый вычислительный алгоритм, требующий ввода каких-либо параметров, то Вы должны это оформить именно как ввод данных, но не описывать каким-либо образом сам ввод: промежуточный слой объектов осуществит необходимую привязку вводимых данных с полями ввода или с диалогом выбора файла. Таким образом, компонентный подход, позволяет многократно использовать один и тот же программный код, при этом, в зависимости от контекста, результат работы данного кода будет различным, а те вычисления, которые будут реально выполняться при обработке конечных (пользовательских) данных, могут отличаться от заранее заданных, поскольку, в результате «промежуточных» вычислений, вместо некоторого указанного в исходном коде обобщённого алгоритма, была вставлена конкретная реализация, определяемая текущей конфигурацией приложения.
Второе лирическое отступление (не такое длинное)
Было бы крайне соблазнительным каким-либо подходящим образом «математизировать» программирование и рассмотреть некое подобие скалярного произведения. Смысл аналогии в том, чтобы задать некое пространство и задать в нём операции умножения (взаимодействия двух объектов) и сложения (конкатенации или слияния результатов). Тогда, операция скалярного произведения могла бы описывать довольно широкий круг явлений. А где скалярное произведение, там и процесс ортогонализации Грамма-Шмидта. Здесь нам нужно понятие линейной независимости. Имея систему линейно независимых «векторов», мы можем перейти к ортонормированной системе, а скалярное произведение в ортонормированном базисе описывается предельно просто. Вот к этой простоте и следует стремиться при создании ПО.
Что означает ортогонализация для данных? Дело в том, что любая сущность проявляет себя по-разному в различных обстоятельствах. Это означает, что один и тот же объект реального мира фигурирует в различных ролях в различных взаимодействиях. Следовательно, для каждого такого вида взаимодействия нужно создавать свой программный объект. Но каждый такой объект (или класс объектов) будет обрабатываться при помощи своего отдельного компонента. Разводя разнотипные потоки управления, мы, фактически, предоставляем разработчику возможность писать максимально обобщённый код.
А что означает ортогонализация для операций? Это означает, что Вы всегда можете организовать удобный доступ ко всем операциям, Вы можете создавать списки операций по типу, вести отдельную БД для операций, и, что представляется мне особенно важным, у нас появляется возможность единым образом кодировать операции над объектами и делать этот код частью физического представления программного кода. Вспомните FORTH с его идеей представлять программы как связки рекурсивных функций. Если у Вас есть таблица функций (операций), то… Вы сможете буквально любую задачу решать при помощи построения конечных автоматов (по Шалыто), но эта возможность должна, по идее быть строенной в ОС!
Эпизод №3: иерархия языков программирования
Само сравнение программирования с процессом верстки сложных документов кажется весьма показательным. Обычно предполагается, что, сначала пишется исходный код программы (на каком-либо языке программирования), а, потом, этот код транслируется в код целевой машины. (Есть ещё и важное промежуточное представление в виде т.н. «байт-кода», но речь, сейчас, не об этом.)
Здесь есть две проблемные точки.
С одной стороны, любое сложное ПО должно быть представлено не в виде своего исходного кода, а в некотором особом виде; в этом смысле, исходным кодом всякого приложения должен быть не код на каком-то языке программирования, а чем-то вроде БД. По сути, среда разработки должна быть не простым редактором исходного кода с расширенными функциями, а документально-ориентированной системой, позволяющей вести, например, некое подобие «бухгалтерского учёта». Если воспринимать каждое действие в такой среде как операцию с некоторым «счётом», то «программирование» сложной корпоративной системы будет заключаться в пополнении определённых «счетов» с последующим составлением «отчёта», при этом, различные виды «отчётов» будут отвечать различным задачам (например, документация проектируемой системы также можно считать своеобразным «отчётом»). (Именно к этому, по моему, в итоге, и приходят серьёзные разработчики крупных корпоративных систем. Но этот подход, к сожалению, не становится «общим местом».)
Здесь важно то, что приложение, сначала, всегда находится в состоянии проекта, а это значит, что программный код (на конечном языке программирования) — это лишь конечный результат работы разработчика, и ещё до того, как этот самый программный код (нужный исключительно при реальной эксплуатации продукта) будет полностью готов, у разработчика довольно много работы, и первый релиз сможет появиться только тогда, когда заказчик и исполнитель, сидя за одним и тем же средством разработки, смогут, наконец, «ударить по рукам» и нажать заветную «красную» кнопку (которая может называться, например, «Создать релиз»). Это, опять же, сильно сближает создание ПО и верстку документов: вы пишете «исходный код» и, только, перед печатью, пропускаете созданный «код» через систему вёрстки. Вы, конечно, можете постоянно проверять промежуточные результаты, но иногда бывает, если Вы, например, пишете статью для какого-нибудь сборника, то Вы посылаете в редакцию только «исходный код».
С другой стороны, возникает вопрос о соотношении процедурного и декларативного подходов. Здесь трудно обойтись без практического примера и объяснить суть вопроса «на словах». Если бы мы могли довольно широко использовать всевозможные декларации, то… использовали бы только их, например, для передачи данных по сети. (Не для этого ли создавался в своё время XML?). Получая только декларации, ОС могла бы компилировать по заданной декларации родной для себя код. (И это было бы безопасно!) В этом смысле, ОС можно воспринимать как иерархию языков, а это значит, что в ОС обязательно должны быть встроены средства построения трансляторов, но тогда вопрос будет ставиться не так, как он ставится сегодня — какие выбрать язык программирования и среду разработки, — а противоположным образом: какой язык программирования нужно создать, чтобы оптимальным образом решать задачи определённого вида. Я этого ещё не знаю, но мне кажется, что функциональное программирование способно внести ясность при ответе на этот вопрос, а будущие открытия в области создания какой-нибудь, там, трёхмерной памяти (где одни и те же данные можно будет размещать огромным количеством способов, в зависимости от задачи) решат вопросы быстродействия т.н. «ленивых вычислений», которые представляются крайне перспективными, потому что они концептуально прозрачны и доказательно точны. Вперёд — к безошибочному программированию!
Реприза: в чём причина происходящего?
Почему всё происходит так, как происходит?
Для начала, этому есть экономическое объяснение. Время, необходимое для детального продумывания и реализации полного многофункционального и безопасного решения нет. Всё нужно «здесь и сейчас». В результате, в выигрыше оказываются фирмы, которые первыми застолбили себе фронт работ. Если Вы сделаете, однажды, продукт, который, действительно, автоматизирует определённую деятельность, то Вы оставите будущие поколения без хлеба.
«Политика — это концентрированная экономика», и, поэтому, для распространения своего влияния во времени и пространстве нужно всегда поддерживать свою собственную значимость для пользователя. А если практические задачи решаются проще, если есть способ как-то упорядочить их, если есть возможность один раз автоматизировать типовые операции и конструировать «на лету» приложения для решения текущих задач пользователя, то… зачем нужны многочисленные компании, разрабатывающие ПО?
А что является «концентрированной политикой»? Правильно! Наука! А что нам может сообщить наука? Я хочу напомнить заинтересованному читателю, что первоначальная идея гиперссылки заключалась не в том, чтобы сделать возможным переход от одного документа к другому, а в том, чтобы создать неразрывную связь двух документов, чтобы, просматривая один документ, мы могли бы пользоваться одновременно и другим документом. Гиперссылки, по своей сути, нужны для того, чтобы суметь отразить динамический характер изменений всего WEB’а в целом. Это означает, что Вы, открывая один документ, имеете возможность автоматически подгружать информацию, которая относится к интересующему Вас предмету. То, что только недавно сообщалось как некое достижение (JSON и REST), должно было бы изначально стать фундаментом всей технологии распределённого хранения данных. Но для реализации этого информационного «рая» следовало бы понять, что: во-первых, WEB должен быть большой распределённой базой знаний (а это значит, что в основе должны лежать идеи иерархических распределённых семантических баз данных, и где обычные реляционные базы данных занимали бы своё почётное, но, всё-таки, подчинённое «промежуточное» положение); во-вторых, для функционирования этой системы нужно не минимизировать трафик (как это делается обычно), а наладить постоянный обмен данными, сделать его регулярным (а это значит, что будут, в частности, невозможны никакие DDoS-атаки, поскольку будет нельзя «просто так» сделать запрос определённой страницы, запросить можно будет только какую-то конкретную информацию); в-третьих, для пользователей WEB должен представляться как большая Wikipedia, где каждая страница имеет историю правок, всегда можно создать обсуждение или принять участие в редактировании. Одним словом: такой WEB подобен реальному миру, где не может быть никаких «сломанных» ссылок, пользователи имеют дело с документами, у каждого документа есть своя история и т.д. и т.п. Да-да. Мечтать «не вредно», не так ли?
Кода: всё, только, ещё начинается
Зачем я написал эту статью?
Во-первых, я решил немного привести в порядок свои собственные мысли и попытаться их связно изложить. Связно изложить не получилось. Виноват. Получилось несколько разрозненных сюжетов и эпизодов. По идее, здесь, в этой статье, напоминающей айсберг, на который наткнулось моё буйное воображение, зарыто несколько заготовок для вполне себе приличных статей, которые можно и, вообще говоря, следовало бы здесь, на Хабрахабре, написать.
Во-вторых, я попытался очертить для себя круг собственных интересов и определил для себя область своих поисков. Мне было бы крайне интересно разрабатывать гибкие программные комплексы, основанные на компонентном подходе (который надо обязательно выработать!), искать нестандартные способы реализации обобщённого программирования и решать увлекательную проблему соотношения процедурного и декларативного начала в программировании.
В-третьих, … а, может быть, всё уже есть? Всё уже изобретено? Всё нужное уже существует? Ну, тогда я буду рад услышать об этом от других и смогу сразу заняться решением реальных задач. И, тогда, поверьте, будет немало поводов написать о том, что у меня получилось на этом пути…
Как было однажды сказано: «не говорите о том, как Вы много и усердно работаете, расскажите, лучше о том, чего Вы добились».