========================================================= Жесткая проверка доступности событий в состояниях агентов ========================================================= :Author: Евгений Охотников :Contact: eao197 at intervale dot ru; eao197 at yahoo dot com :Version: 0.1 :Date: 2008.03.11 .. _SObjectizer: http://sobjectizer.sourceforge.net .. contents:: **Оглавление** Мотивация ========== Одной из ключевых особенностей SObjectizer является то, что если событие не разрешено к обработке в текущем состоянии агента, то это событие просто игнорируется и никаких действий не происходит. Например, пусть есть агент, который обладает двумя состояниями: ``st_closed`` и ``st_open``. В состоянии ``st_closed`` агент обрабатывает события ``evt_open``, ``evt_timeout``, а остальные события игнорирует. В состоянии ``st_open`` обрабатываются события ``evt_disconnect``, ``evt_read`` и ``evt_write``, событие ``evt_timeout`` игнорируется, а события ``evt_open`` не должно быть в принципе. В SObjectizer 4 такой агент описывается следующим образом: :: SOL4_CLASS_START( a_my_agent ) SOL4_EVENT( evt_open ) SOL4_EVENT( evt_disconnect ) SOL4_EVENT( evt_timeout ) SOL4_EVENT( evt_read ) SOL4_EVENT( evt_write ) SOL4_STATE_START( st_closed ) SOL4_STATE_EVENT( evt_open ) SOL4_STATE_EVENT( evt_timeout ) SOL4_STATE_FINISH() SOL4_STATE_START( st_open ) SOL4_STATE_EVENT( evt_disconnect ) SOL4_STATE_EVENT( evt_read ) SOL4_STATE_EVENT( evt_write ) SOL4_STATE_FINISH() SOL4_CLASS_FINISH() Т.е. при описании состояния программист должен перечислить все разрешенные в данном состоянии события. Такой подход основывается на предположении, что программист понимает, что он делает. И главное достоинство этого подхода в том, что здесь требуется минимальный набор описаний для состояния агента. К сожалению, в подобном подходе к описанию состояний нельзя проконтролировать корректность сделанных пользователем описаний. А программисты имеют свойство ошибаться. Можно просто забыть описать событие в списке разрешенных событий состояния, что особенно плохо при сопровождении ранее написанного кем-то агента, когда агент расширяется новыми сообщениями, событиями или состояниями. Так же можно не сообразить, что какое-то событие может инициироваться в некотором состоянии агента. Описываемое здесь предложение состоит в том, чтобы заставить программиста явно указывать, что должно происходить со *всеми* событиями в *каждом* из состояний агентов. Существует надежда на то, что подобный жесткий подход поможет решению подобных проблем за счет того, что: - при необходимости явно вписывать имя события в каждое состояние сложнее забыть добавить нужное событие в нужное состояние; - при расширении агента новыми событиями/состояниями сам SObjectizer будет следить за тем, чтобы новое событие было упомянуто в каждом из состояний, а каждое новое состояние определяло поведение всех событий агентов. Для того, чтобы эти предположения оправдались необходимо делать описание класса агента, которое затем обрабатывается каким-то транслятором. Этот транслятор следит за тем, чтобы все события были определены в каждом из состояний. Если это условие нарушается, то транслятор отклоняет описание класса агента. Возможные подходы к организации жесткого контроля ================================================= В описываемых ниже вариантах используется описание класса агента в виде Ruby-скрипта (вообще-то конкретный формат описания не имеет значения, Ruby выбран как наиболее удобный для автора вариант). Вариант 1: перечисление разрешенных и запрещенных событий ---------------------------------------------------------- Это самый простой вариант, в котором программиста заставляют перечислять все события в каждом из состояний, с указанием того, разрешено ли событие к обработке или нет. Например, для обсуждаемого здесь примера описание класса агента могло бы иметь вид: :: agent_class :a_my_agent do |c| ... c.state :st_closed do |st| # Перечисление разрешенных событий. st.enabled :evt_open, :evt_timeout # Перечисление запрещенных событий. st.disabled :evt_disconnect, :evt_read, :evt_write end c.state :st_open do |st| st.enabled :evt_disconnect, :evt_read, :evt_write st.disabled :evt_open, :evt_timeout end end Практически, этот вариант является ничем иным, как простым расширением использующегося сейчас в SObjectizer 4 подхода. Вариант 2: на первом месте инциденты событий -------------------------------------------- Данный вариант основывается на том, что событие является всего лишь следствием возникновения сообщения-инцидента. И что в первую очередь при разработке агента нужно сосредоточиться на том, какие сообщения в каком из состояний обрабатываются. Т.е., нужно описывать, какие сообщения в каком состоянии к какому событию приводят. Описание обсуждаемого здесь класса агента может выглядеть следующим образом: :: agent_class :a_my_agent do |c| c.msg_slot :msg_connected c.msg_slot :msg_disconnected c.msg_slot :msg_timeout c.msg_slot :msg_write c.msg_slot :msg_read c.state :st_closed do |st| st.on :msg_connected => :evt_open st.on :msg_disconnected => UNEXPECTED st.on :msg_timeout => :evt_timeout st.on :msg_read => IGNORE st.on :msg_write => IGNORE end c.state :st_open do |st| st.on :msg_connected => UNEXPECTED st.on :msg_disconnected => :evt_disconnect st.on :msg_timeout => IGNORE st.on :msg_read => :evt_read st.on :msg_write => :evt_write end end Данное описание говорит о том, что: - агент заинтересован в получении пяти разных сообщений (``msg_connected``, ``msg_disconnected``, ``msg_timeout``, ``msg_write``, ``msg_read``); - в состоянии ``st_closed`` сообщение ``msg_connected`` должно приводить к срабатыванию события ``evt_open``, а сообщения ``msg_timeout`` к событию ``evt_timeout``. Сообщения ``msg_write`` и ``msg_read`` в этом состоянии просто игнорируются; - в состоянии ``st_open`` сообщение ``msg_disconnected`` должно приводить к срабатыванию события ``evt_disconnect``, ``msg_read`` -- к событию ``evt_read``, а ``msg_write`` -- к ``evt_write``. Сообщение ``msg_timeout`` игнорируется; - сообщения ``msg_disconnected`` в состоянии ``st_closed`` и ``msg_connected`` в состоянии ``st_open`` вообще не должны отсылаться агенту. При таком подходе возможно достижение еще одного побочного эффекта: в описании класса агента не нужно отдельно описывать события агента и типы их инцидентов. Эту информацию можно вывести исходя из описаний состояний агента, т.к. в них будут перечисленны все события, которыми владеет агент. Слоты сообщений ``````````````` Описание: :: agent_class :a_my_agent do |c| c.msg_slot :msg_connected ... end говорит о том, что описывается не само сообщение, а т.н. слот сообщения. Слот -- это аналог переменной, которая во время работы агента будет содержать имя реального сообщения. Понятие слота введено из-за того, что не все сообщения могут быть известны для агента в момент трансляции описания его класса. Например, подписка агента (т.е. имена сообщений-инцидентов) определяется непосредственно во время работы по каким-то алгоритмам или параметрам конфигурации. В этом случае слот должен быть определен во время работы агента. При этом агент определяет значение своего слота, а SObjectizer автоматически обеспечивает подписку заинтересованных в этом слоте событий. Т.е. в таком варианте программист должен определять слоты сообщений, а не подписывать события агента, как в текущей версии SObjectizer 4. Если же значение слота сообщения известно еще до запуска агента (например, агент подписывается только на собственные сообщения), то этот факт можно отразить непосредственно в описании слота. Например: :: agent_class :a_my_agent do |c| c.msg_slot :msg_connected, own_message c.msg_slot :msg_disconnected, own_message( :fields => { :reason => STRING } ) ... end Такое описание говорит о том, что слот ``msg_connected`` будет связан с сообщением ``msg_connected``, которым владеет сам агент типа ``a_my_agent``. Сообщение ``msg_connected`` не имеет доступных для SOP полей. А вот сообщение ``msg_disconnected`` того же агента, связанное со слотом ``msg_disconnected``, имеет строковое поле ``reason``, которое доступно для SOP. Т.е. эти описания эквивалентны описаниями в SObjectizer 4: :: SOL4_CLASS_START( a_my_agent ) SOL4_MSG_START( msg_connected, a_my_agent::msg_connected ) SOL4_MSG_FINISH() SOL4_MSG_START( msg_disconnected, a_my_agent::msg_disconnected ) SOL4_MSG_FIELD( reason ) SOL4_MSG_FINISH() ... SOL4_CLASS_FINISH() Примеры использования предложенных подходов для реального большого агента ========================================================================= Для демонстрации описанных выше вариантов решения проблемы был взят один из самых больших агентов, которые приходилось разрабатывать автору. Для него были сделаны описания в соотвествии с каждым из предложенных вариантов. `Исходное описание агента `_ в SObjectizer 4 (244 строки). `Описание агента с помощью первого варианта `_ (318 строк). `Описание агента с помощью второго варианта `_ (246 строк). По мнению автора, описание с помощью второго варианта более информативно, чем два других описания, посколько оно позволяет легко определить: - в каких сообщениях заинтересован агент; - как каждое из этих сообщений обрабатывается в каждом из состояний. Но при этом само описание по объему практически равно первоначальному описанию с помощью нотации SObjectizer 4. Предварительное заключение ========================== По замыслу, необходимость строго перечисления состояний/сообщений в каждом из событий агента должно играть такую же роль, как контроль *pattern matching* в функциональных языках программирования со стороны компилятора: компилятор в состоянии указать программисту о том, что не все варианты сопоставления с образцом программист определил. А во время исполнения программы стандартная библиотека порождает исключение, если в pattern matching-выражении ни один из вариантов не сработал. В сумме эти два свойства pattern matching-а положительно сказываются на качестве программы. Предложенные варианты является лишь первыми шагами в направлении решения проблемы забытых/непредусмотренных событий в состояниях агентов. Возможно, не самыми удачными шагами. Например, не очень понятно, во что выльется подобный подход для больших агентов и для наследования агентов. Тем не менее, он может стать прообразом более удачного решения, которое и будет воплощено в одной из следующих версий SObjectizer. .. vim:ts=2:sw=2:sts=2:expandtab:tw=78:ft=rst: