АВТОМАТНОЕ ПРОГРАММИРОВАНИЕ С ИСПОЛЬЗОВАНИЕМ ДИНАМИЧЕСКИХ ЯЗЫКОВ ПРОГРАММИРОВАНИЯ
О.Г. Степанов
Научный руководитель - кандидат технических наук Д.Г. Шопырин
В работе рассматривается проблема переноса диаграмм переходов, принятых в SWITCH-технологии, в исполняемый код. Для решения данной проблемы предлагается использование динамических языков программирования, возможности которых позволяют добиться изоморфности диаграммы переходов и соответствующего программного кода. Разработан формальный метод преобразования диаграмм переходов в исполняемый код, а также практическая реализация этого метода в виде библиотеки STROBE на языке Ruby.
Введение
Для проектирования и разработки реактивных систем часто используется SWITCH-технология, также известная как «автоматное программирование» или «программирование с явным выделением состояний» [1]. Одной из основных частей SWITCH-технологии является графический язык, позволяющий описывать поведение систем со сложным поведением в терминах состояний и переходов между ними и связей между этими системами.
При использовании SWITCH-технологии в разработке программного обеспечения важной частью разработки является реализация поведения, описанного на диаграммах переходов, на целевом языке программирования. Для решения этой задачи традиционно используется один из следующих трех подходов.
1. Полностью ручное программирование. Одним из простейших общепринятых методов ручного программирования является следующий: текущее состояние системы хранится в переменной интегрального или перечислимого типа, а основная логика программы сосредоточена внутри одного или нескольких операторов switch, определяющих действия программы в зависимости от текущего состояния [2]. Другим популярным методом является использование паттерна программирования State, впервые описанного в [3]. Несмотря на такие преимущества этой группы методов, как высокая производительность и полный контроль над получаемым кодом, она имеет существенные недостатки - низкая читаемость кода и большая трудоемкость.
2. Автоматическая генерация кода по диаграмме переходов. Обычно при этом подходе генерируется код, аналогичный тому, который получается при использовании ручного программирования. Недостатками этого подхода являются низкая читаемость кода (связанная с тем, что в качестве целевого языка используется императивный язык, например Java, и при переносе теряется информация, специфичная для логики диаграмм переходов), малая степень контроля над получаемым кодом, невозможность ручного изменения получаемого кода и привязанность к конкретному формату входных данных, с использованием которого задается исходная диаграмма переходов (формат файлов Visio, XML [4, 5] или другой).
3. Ручное написание кода с использованием специальной библиотеки. В этом случае происходит перенос диаграммы переходов в вызовы специальной библиотеки, которая по этим инструкциям строит внутреннее представление диаграммы переходов. Затем, согласно этому представлению, происходит исполнение логики описанного автомата. Основным преимуществом подхода является то, что вызовы библиотеки отражают семантику диаграммы переходов (каждый вызов может, например, соответствовать объявлению состояния или перехода), что позволяет создавать читаемый код, который легко поддерживать. Также некоторые библиотеки ориентированы на конкретные виды взаимодействия автоматного и объектно-ориентированного кода, что позволяет более гладко комбинировать эти подходы к програм-
мированию. Основными недостатками являются низкая производительность некоторых из таких систем (существуют системы, основанные на метапрограммирова-нии и статической генерации кода [6, 7], которые позволяют достичь высокой производительности), а также невозможность описать ряд конструкций диаграмм переходов, используя ограниченный синтаксис целевого языка.
В настоящей работе предлагается развитие третьего подхода, расширяя его с точки зрения «качества» и читаемости получаемого кода. Разработанный подход использует динамические языки программирования, т.е. языки, позволяющие менять и дополнять код программы во время выполнения. В работе показано, как свойства динамических языков могут быть использованы для увеличения читаемости кода, строящего модель системы, особенно в части формирования условий переходов.
Для формализации процесса перенесения диаграмм переходов в код была разработана операционная семантика (правила интерпретации) диаграмм переходов SWITCH-технологии. Предложенная семантика позволяет верифицировать правильность воспроизведения спроектированного поведения.
Материал статьи организован следующим образом: в первом разделе дано краткое описание SWITCH-технологии, предложена операционная семантика диаграмм переходов и описана проблема их переноса в исполняемый код. Во втором разделе предложен подход к решению этой проблемы, базирующийся на формальном преобразовании диаграмм переходов в исполняемый код с использованием свойств динамических языков программирования, а также описана библиотека STROBE, реализующая разработанный подход.
1. Операционная семантика диаграмм переходов
Одна из проблем при использовании SWITCH-технологии, возникающая при реализации поведения, описанного на диаграммах состояний, на различных языках программирования - обеспечение корректности переноса логики системы при сохранении исходных обозначений и структуры описания.
Рассмотрим пример использования диаграмм переходов. В работе [8] описана система управления лифтом, основанная на автоматах, в ходе реализации которой был разработан автомат управления кнопкой вызова лифта на нижнем этаже (All). Его диаграмма переходов представлена на рис.
Рис. Диаграмма переходов автомата управления кнопкой вызова лифта
Из диаграммы видно, что этот автомат имеет три состояния: «Готовность» (кнопка на этаже подсвечена, ее можно нажать), «Включена» (кнопка нажата) и «Выключе-
на» (кнопку нажать нельзя). Переход в состояние «Включена» происходит по нажатию кнопки (событие ell), переход в состояние «Готовность» - по событию e4 (разрешение на включение лампы в кнопке) от головного автомата, переход в состояние «Выключена» - по событию e3 (выключение лампы в кнопке) от головного автомата при условии, что лифт находится на нижнем этаже (определяется переменной x6l).
Для обеспечения корректности переноса логики требуется формально описать семантику диаграмм переходов и метод их преобразования в исполняемый код. Это позволит верифицировать получаемый код на соответствие логике исходной диаграммы. В условиях сложного графического синтаксиса диаграмм оптимальным описанием является создание операционной семантики. Ниже предлагается операционная семантика для диаграмм переходов в SWITCH-технологии, аналогичная семантике UML-диаграмм состояний, описанной в работе [9].
Для начала определим основные свойства диаграмм переходов.
1. Диаграмма изображает одно или несколько состояний системы и переходы между ними.
2. Состояния на диаграмме могут быть объединены в группы; группы могут быть вложены друг в друга; состояния внутри группы равноправны.
3. Переходы могут начинаться в состоянии или в группе состояний, заканчиваться только в состоянии (переходы, начинающиеся в группе состояний, называются групповыми переходами); переходы могут начинаться и заканчиваться в одном и том же состоянии.
4. Каждое состояние помечено следующими атрибутами:
a. имя состояния,
b. номер состояния (нумерация начинается с нуля),
c. действия по входу в состояние,
d. вложенные автоматы (возможно с номерами воздействий, с которыми они вызываются).
5. Переход может быть помеченследующими атрибутами:
a. условие перехода,
b. действия на переходе,
c. приоритет перехода.
Обработка события происходит следующим образом: в качестве текущего события выставляется обрабатываемое. Затем перебираются переходы, выходящие из текущего состояния и содержащих его групп в порядке приоритета (сначала рассматриваются переходы с меньшими номерами). Для каждого перехода вычисляется условие, которое является логической формулой. Эта формула может использовать следующие переменные:
1. ei - имеет значение «истина», если текущее событие - ei, и «ложь» иначе;
2. yi - имеет значение равное номеру состояния автомата Ai;
3. xi - значение переменной xi.
Выполняется первый переход (взятый в указанном выше порядке), для которого значение условия истинно. Выполнение перехода состоит из следующих шагов:
1. выполняются действия на переходе (вызываются указанные выходные воздействия в порядке их следования на диаграмме);
2. текущим выставляется состояние, в котором заканчивается переход;
3. если произошла смена состояния (текущее состояние до начала обработки события отличается от состояния, в котором заканчивается переход), вызываются действия по входу в состояние;
4. вызываются вложенные автоматы: если для автомата указан номер события, он вызывается с этим событием; иначе - с событием e0.
Для описанного выше автомата A11 применение семантики дает следующие инструкции:
1. в качестве текущего установить обрабатываемое событие;
2. если текущее состояние - «Готовность» и текущее событие - ell, то осуществляется переход в состояние «Включена», при этом производится выходное воздействие zlll (включить лампу в кнопке);
3. если текущее состояние - «Готовность» или «Включена», текущее событие - e3 и при этом значение переменной x6l - «истина», осуществляется переход в состояние «Выключена», при этом производится выходное воздействие zll0 (выключить лампу в кнопке);
4. если текущее состояние - «Выключена» и текущее событие - e4, производится переход в состояние «Готовность».
2. Реализация автоматных систем на языке Ruby
Для решения задачи переноса диаграмм переходов в исполняемый код предлагается использовать динамические языки программирования. Отличительными свойствами этих языков, позволяющими упростить перенос диаграмм переходов, являются следующие:
1. возможность динамической генерации кода;
2. возможность использования замыканий (совокупностей функций и данных в данном лексическом контексте).
С помощью динамической генерации кода можно создавать индивидуальный для каждой диаграммы переходов код выполнения автомата, что позволит увеличить производительность, а совместно с замыканиями позволяет переносить условия переходов в код на целевом языке практически без изменений.
Для практической реализации предложенного подхода был выбран язык Ruby [10], разработанный Юкихиро Мацумото (Yukihiro Matsumoto). Это интерпретируемый динамический язык программирования, обладающий следующими свойствами:
1. простой синтаксис;
2. объектная ориентированность;
3. поддержка механизма mixin-ов через включение модулей;
4. поддержка замыканий;
5. легкая переносимость (среда исполнения Ruby может быть запущена под большинством современных операционных систем);
6. возможность интеграции с кодом на других языках программирования.
Приведем исполняемый код,, соответствующий приведенному выше примеру, на языке Ruby с использованием библиотеки STROBE, описанной ниже.
# Подключение библиотеки STROBE require 'strobe/automaton' module Elevator
# Декларация класса автомата class A11 < Strobe::Automaton
# Декларация внутренней переменной x61 attr_accessor :x61
# Декларация событий inputs :e3, :e4, :e11
# Декларация выходных воздействий outputs :z110, :z111
# Начало группы состояний begin_group
# Состояние "Готовность" state :ready
# Переход в состояние "Включена" transition :to => :on,
:if => lambda { e11 }
# Состояние "Включена" state :on,
:output actions => :z111
# Групповой переход в состояние "Выключена" group_transition :to => :off,
:if => lambda { e3 && x61 }
end_group
# Состояние "Выключена" state :off,
:output actions => :z110
# Переход в состояние "Включена" transition :to => :ready,
:if => lambda { e4 }
end
end
В этом коде внутри класса A11, объявленного как автоматный путем наследования от библиотечного класса Strobe::Automaton, последовательно определена диаграмма состояний автомата A11. Инструкции inputs, outputs, state, transition и grouptransition объявляют события, выходные воздействия, обычный и групповой переход, соответственно. Инструкции begingroup и end group объединяют все декларированные между ними состояния в группу.
Представленный выше пример был построен с использованием формального метода, позволяющего перенести любую диаграмму переходов в исполняемый код на Ruby. Схемаметода - следующая:
1. для каждой диаграммы создается новый класс с именем, совпадающим с именем автомата (возможно использовать модули чтобы избежать конфликтов имен);
2. внутри класса с помощью специальных конструкций описывается диаграмма переходов (каждое выражение соответствует описанию одной сущности на диаграмме: состоянию, переходу, группе состояний и т.д.);
2.1. при описании состояний им присваиваются идентификаторы, соответствующие их именам на диаграмме;
2.2. при описании переходов ссылки на состояния производятся по именам;
2.3. при описании перехода условие перехода переносится с диаграммы в виде выражения, использующего те же самые переменные, что и исходное выражение на диаграмме;
3. класс автомата связывается с другими подсистемами через подписку на выходные воздействия.
Ключевыми моментами использования языка Ruby являются полностью декларативное описание поведения системы и автоматическая генерация специальных методов, позволяющих переносить диаграмму переходов с минимумом изменений. Покажем это на примере. Допустим, в диаграмме есть переход, условие которого задается формулой e1 v e0 л (x1 v (y2 = 3)). В коде программы он будет представлен в виде выражения el || e0 && (x1 || (y2 == 3)). При вычислении условия перехода это выражение будет вычислено в контексте автоматного класса, что позволит использовать сгенерированные по описанию автомата (списку входных и выходных воздействий, связям с другими автоматами) методы e0, el, xl и y2. Генерируются следующие методы: 1. для каждого входного воздействия - метод с именем воздействия, который возвращает значение «истина», если сейчас автомат обрабатывает соответствующее воздействие, и «ложь» во всех других случаях;
2. для каждой внутренней переменной - метод с именем переменной, который возвращает текущее ее значение;
3. для других автоматов в системе - переменная с префиксом у, возвращающая номер состояния соответствующего автомата.
При попытке практической реализации предложенного подхода возникает ряд проблем:
1. обеспечение возможности вызова методов описания диаграммы переходов непосредственно в теле класса, наряду со стандартными декларациями языка;
2. обеспечение выполнения условий переходов в нужном контексте;
3. поддержка наличия нескольких экземпляров одного и того же автомата и обеспечение связи между автоматами через переменные yi.
4. поддержка интеграции с программами на других языках, а также обеспечение возможности управления физическими устройствами;
5. обеспечение приемлемой производительности получаемого кода.
Автором разработан метод описания автоматов, решающий большинство поставленных проблем. Данный метод реализован в библиотеке STROBE, которая описана ниже.
Разработана библиотека STROBE, позволяющая декларативно задавать диаграммы переходов на языке Ruby. Для программиста эта библиотека предоставляет дополнительный набор инструкций (реализованных в виде методов), позволяющих поэлементно определять диаграммы переходов (в число инструкций входят, например, state для определения состояния и transition для определения перехода). Методы используют технологию именованных параметров (при вызове инструкции значение каждого параметра связывается с параметром явно по имени), что позволяет увеличить читаемость кода (примерами имен параметров, использованных в примере выше, могут являться :to или :output_actions).
Процесс описания автомата при использовании библиотеки STROBE соответствует описанному в предыдущем пункте, а также имеет следующие особенности:
1. внешняя конфигурация автомата (входные и выходные воздействия, связи с другими автоматами) описывается в начале класса;
2. состояния описываются последовательно, в порядке их нумерации на диаграмме переходов;
3. переходы из данного состояния описываются непосредственно после описания самого состояния;
4. связь с другими компонентами осуществляется путем подписки на конкретные выходные воздействия;
5. связь с другими автоматами по переменным состояния (yi) осуществляется путем явного связывания экземпляров автомата и имен переменных в блоке описания внешней конфигурации автомата;
6. для выполнения нескольких экземпляров одного автомата независимо используются система доменов, описанная ниже.
Библиотека позволяет перенести в код на Ruby любую синтаксически верную диаграмму переходов, а также перенести несколько автоматов и связать их. Возможна также интеграция с модулями на других языках программирования, в том числе модулями управления физическими объектами. При этом описание автомата изоморфно диаграмме состояний и понятно без дополнительных инструкций и описаний.
Для обеспечения независимого выполнения нескольких экземпляров автомата используется система доменов - логических областей, к одной из которых может быть приписан экземпляр автомата при создании. Каждый экземпляр автомата имеет уникальный в пределах домена идентификатор, по умолчанию равный имени класса. Та-
ким образом, ссылаться иа другие автоматы можно по имени класса, используя стандартные алгоритмы разрешения ссылок языка.
Производительность решения находится на одном уровне с динамически выполняемыми решениями третьей группы (интерпретирующими внутреннее представление диаграммы переходов). Этот показатель можно улучшить путем полной генерации кода обработки входных воздействий, что невозможно осуществить в рамках языка Ruby, сохранив читаемость кода, в связи с отсутствием доступа к модели кода и невозможностью производить подстановку замыканий в сгенерированный код. Тем не менее, по всем остальным показателям предложенный подход решает проблемы, описанные в конце третьего раздела.
Заключение
В настоящей работе рассматривается проблема переноса диаграмм переходов автоматов, разработанных согласно SWITCH-технологии, в исполняемый код. Рассмотрены основные направления решения этой проблемы, предложен подход, развивающий одно из этих направлений. Разработан формальный метод преобразования диаграмм переходов в исполняемый код, описаны отличительные свойства этого метода - декларативная структура кода, его изоморфность исходной диаграмме, в особенности в области задания условий переходов. Описаны основные проблемы, возникающие при практической реализации разработанного подхода, и предложена конкретная реализация в виде библиотеки STROBE на языке Ruby, решающая большинство этих проблем.
Литература
1. Шалыто A.A. SWITCH-технология. Алгоритмизация и программирование задач ло-гическогоуправления. СПб: Наука, l998. б28 с.
2. Шалыто A.A., Туккель Н.И. Реализация автоматов при программировании событийных систем // Программист, 2002. №4. C. 74-80.
3. Гамма Э., Хелм Р., Джонсон Р., Влиссидес Дж. Приемы объектно-ориентированного проектирования. Паттерны проектирования. СПб: Питер, 200l. Зб8 с.
4. Бондаренко К.А., Шалыто A.A. Разработка XML-формата для описания внешнего вида видеопроигрывателя с использованием конечных автоматов. СПб: СПбГУ ИТМО. 2003. // http://is.ifmo.ru, раздел «Статьи».
5. Gurov V.S., Mazin M.A., Narvsky A.S., Shalyto A.A. UniMod: Method and Tool for Development of Reactive Object-Oriented Programs with Explicit State Emphasis // Proceedings of St. Petersburg IEEE Chapters, 2005. V.2. P. 106-110.
6. Abrahams D., Gurtovoy A. C++ Template Metaprogramming. Addison Wesley, 2004.
7. Шопырин Д.Г., Шалыто A.A. Объектно-ориентированный подход к автоматному программированию // Информационно-управляющие системы, 2003. № 5. С. 29-39.
8. Наумов A.C., Шалыто A.A. Система управления лифтом. // http://is.ifmo.ru, раздел «Статьи».
9. Гуров B.C., Мазин М.А., Шалыто A.A. Операционная семантика UML-диаграмм состояний в программном пакете Unimod /// Тез. докл. Всероссийской научно-метод. конф. «Телематика'2005».
10. Thomas D., Fowler C., Hunt A. Programming Ruby. Second Edition. Pragmatic Bookshelf, 2004.