Теория планного программирования.

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

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

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

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

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

И в целом, если объектоориентированное программирование крутится вокруг идеи выделения объекта и методов, чего можно делать с объектом. То аспектноориентированое программирование крутиться вокруг выделения идеи аспекта и собирания его из размазанного вида в одно место. Так, что вместо бегания по 100 функциям разных объектов и copy/paste одного  того же куска, мы можем сказать — делать то-то при входе в функции таких-то классов.

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

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

Да, поясню слово вклиниваться. Я имею в виду, то, что мы не пишем никакого кода в определенном месте программы, но тем не менее есть код (на самом деле компилятор), который «знает», что в этом месте надо выполниться.

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

Но, это все так, отклонения от общей темы.

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

Так, вот, возвращаясь к планному программированию. Моя идея следующая — херим все сущности, которые были введены раньше (функции, объекты, аспекты, модули, namespac’ы, templat’ы и т.п.) и вместо всего оставляем совмещая все это получаем коцепцию плана.

Итак, что такое план?

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

Основная идея, это построить их так, чтобы сделать код гораздо более податливым. Если сейчас код находится в классе X, похожий код в классе Y и Z, то начинается целая свистопляска, чтобы его объединить, или хотя бы свести в одно место (как с логированием).

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

Чтобы не углубиться окончательно в теоретические изъяснения, приведу пример. Хотим мы написать адресную  книгу. Как мы это делаем?

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

— В этот application план, мы добавляем UI план, в который добавляем MainWindow план. И например мы вписываем код, который читает файл, показывает табличку с именами на экране, а так же делаем чтобы можно было редактировать и сохранять файл.

— Мы обнаружили, что наш MainWindow план стал слишком жирным, выделяем Storage план, переносим туда весь код по работе с файлом (чтение, запись, временное сохранение измененных записей).

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

— Мы решаем, что нам надо добавить 5 полей. Начинаем их добавлять, и обнаруживаем, что мы нам нужно будет тупо copy-paste работу с полями для того, чтобы их отрисовывать в  MainWindow и сохранять, считывать в Storage. И вот тут наступает самое интересное, мы вводим новый план ContactField в который составляем из куска Storage по работе с полем и куска MainWindow по работе с полем.

Замечу, что ContactFiled содежит не копию кода, а зеркало кода. То есть, редактируя в ContactFiled оно редактируется в MainWindow или Storage. Итак, это первая выгода, что мы теперь можем просматривать код в том виде, который нас действительно интересует — либо с точки зрения UI (смотрим в MainWindow) или Storage или с точки зрения Field. На самом деле, теперь мы можем в этот план включать любые другие места где наше поле используется, передается, храниться и обрабатывается. То есть, всегда есть место где централизованно можно  видеть сущность.

— Движемся дальше. Так как мы знаем, чтона самом деле полей будет много, то план ContactField мы переименовываем в ContactFieldList и указываем, что туда должны актоматически вклиниваться все планы с названием ContactFieldList.ContactField*. Теперь внутри ContactFieldList мы можем делать новые планы, которые будут заниматься сохранием и отрисовкой и которым автоматически будут вклиниваться. Вот тут, наступает вторая выгода. Выгода состоит в возможности работы с планами в докомпиляционное время. То есть, то, что можно указать что все планы с таким-то именем должны вклиниваться. Современные языки предоставляют достаточно небольшое количество докомпиляонных средств.

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

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

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

а) Мощная среда разработки, которая умеет делать много разных отображений кода и работы с этими отображениями.

б) Уменьшение количество сущностей (класс, процедуры, шаблоны, интерфейсы) и сведения их к минимальному количеству.

Надеюсь, кого-нибудь зацепила эта писанина. 🙂 Жду, комментариев, критики и горячих споров.

P.S. Насчет Mix-in и Closure. И то и другое продолжает решать проблему дублирования кода, но все таки не так радикально как предложено у меня 🙂

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


25 комментариев to “Теория планного программирования.”

  1. Конечно избегать дублирования кода это хорошо, да и программисты это любят:) Но вот только это очень часто вызывает каскадные ошибки. Типа подкрутил в одном месте, а упала вся система:)

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

    Также есть еще порог вхождения, не все смогут понять столь высокий уровень абстракции:) Помнится еще Джоэл Спольски жаловался что не все понимают что такое указатели:)

    • Victor Ronin:

      Насчет unitTest’ов согласен. Но собственно проблема подкрутил в одном месте, упало все — общая проблема, а не присущая именной этой методике.

      По поводу абстракции… Ну даже и не знаю. Как я написал, мы уменьшаем количество сущностней, а не увеличиваем. Да и уровень абстракции не выше, чем в объектном программировании.

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

    • Victor Ronin:

      Я только, что прочел о примесях (Mix-ins) и замыканиях (closure), так что количество знаний у меня по этому вопросу очень мало. Так, что поправьте если что.

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

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

      Итого, имеем что Mix-ins сделаны для упрощения наследования/решения проблем с дублированием кода. ЗАмыкания действительно чем-то похожи, но все таки и то и друго не может оперировать с кодов в таких маштабах (собрать куски другого кода из разных классов) и нет поддержки в IDE.

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

        • Victor Ronin:

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

          Не может код класса (который завязан на доступ к членам класса) выполниться в другом окружении.

          А это значит уже тянет за собой наследование. А наследование тянет за собой вопросы множественного наследования и т.п.

  3. Timur:

    Я могу ошибаться, но что-то очень похожее и реализовано MS как WPF. или нет?

    • Victor Ronin:

      Честно говоря я не знаю. Если есть название, как там это называется с удовольствием прочту.

  4. Const:

    Clarion for DOS 3.x, for Windows 🙂
    По крайней мере описанное здесь там очень похоже реализовано системой шаблонов, с последующей генерацией кода.

    • Victor Ronin:

      Э… Пожалуй, насчет этого я пропасую. Ссылки насчет чего-то нового (Mixin, Closures) с удовольствием читал, так как могут пригодиться. Clarion для DOS 3.0…. это звучит малехо устаревшим.

      • Const:

        Ну хотя-бы на IDE взгляните. Вся сила в Clarion как раз в ИДЕ, а не в языке(я бы сказал простой процедурно-орентированый, под Windows — объекты 🙂 ).
        И идеология продукта как раз корнями уходит в DOS версию.
        Но и применение сильно ограничено только для СУБД.
        http://clarion.ru/index.php?option=com_content&task=view&id=27&Itemid=25

        • Victor Ronin:

          Поглядел и почитал.

          Имеем
          а) Шаблоны
          б) Кодогенерацию

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

          Clarion дает автоматизацию решения проблемы дублирования кода.

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

  5. meowth:

    Посмотрите на JetBrains MPS — конструктор для собственных DLS (которые имеют много общего с вашими планами)

  6. Костя:

    Новая спецификация C/C++ будет поддерживать замыкания — отдаленно напоминает план в вашей терминологии. Особенно круто то, что замыкания можно выполнять параллельно. См. технологию GDC от Apple: http://images.apple.com/macosx/technology/docs/GrandCentral_TB_brief_20090608.pdf

    • Victor Ronin:

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

      То, что написано в Apple, чем-то похоже.

  7. Владимир:

    По поводу третьего абзаца стоит заметить, что двумя строками тут уже не обойдешься, а одной запросто 🙂

    • Victor Ronin:

      По поводу логов? Да, можно объект создавать, который на конструктор и деструктор будет логировать. Тем не менее, вставлять его придется таки везде.

  8. centrist:

    по-моему такую штуку можно на перле запрогать. Во всяком случае, без проблем можно пойти в любые модули по маске СontactFieldList::ContactField*, взять там все функции и дописать им в начала и конец логирование.
    Другое дело, что этого, как мне кажется, делать не стоит 🙂
    Все-таки когда очень много неявных завязок — это жуть, в том же перле это становится заметно при увеличении объема проекта…
    Я слишком мало понимаю пока в функциональном программировании, но изучаю его как раз за тем, чтобы получить что-то вродже описанного, но явное и хорошо проверенное на этапе компиляции..

    • Victor Ronin:

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

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

  9. Евгений Лукаш:

    Что-то напомнило такую штуку как multimethods
    В некоторых языках, например haskel, eсть такая штука как полиморфный диспатчинг функций, по всем параметрам (в отличии от мейнстримных сейчас single dispatch (c++,java,c# и пр)), т.е. наиболее подходящими во время выполнения (на заметку — тот же хаскель он компилируемый с типизацией «строжее некуда»).
    И там получается что создаются какието-структуры данных, расширяются существующие, а потом можно например наслаивать и наслаивать функции, с более специфическими наборами параметров. Там традиционная объекная ориентация с данными и привязаными к ним методами какбы считается просто излишне ограничивающей абстракцией.

    • Victor Ronin:

      Замечал в нескольких местах по обсуждению уже.

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

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

      • В модульных системах бывает очень сложно обеспечивать одновременно предсказуемость статической обработки компилятором (в IDE) и гибкость комбинирования модулей во время развертывания/выполнения. Ну я имею ввиду некоторую разницу между
        «примени аспект(или план) ко всему такому что собрал я» и «примени аспект(или план) ко всему такому-вот такому что есть или будет в системе вообще». Придётся просто выбирать предсказуемость или гибкость. Мне более симпатизирует предсказуемость, но как всегда утопический мираж сверхгибкости не дает покоя.

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

        • Victor Ronin:

          Да, согласен полностью — это действительно проблема предсказуемости (визуаного отображения) vs гибкости.

          Скажем так, я за то, чтобы система была предсказуема.

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

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

  10. zahardzhan:

    Еще немного и вы точно додумаетесь до лисп-машины. Посмотрите на OpenGenera. Особенно в исходники — там этих планов тьма.