Предисловие В очередном томе «Трудов Института» представлены статьи, посвященные различным аспектам системного программирования: организации компиляторов и систем программирования, защите программных средств от различного рода атак, тестированию программного обеспечения на основе формальных спецификаций, автоматизации создания программного обеспечения на основе формализованных описаний аппаратуры, модельноориентированной разработке программных систем, интеллектуальному анализу данных. В статье А. Белеванцева, М. Кувыркова, Д. Мельника «Использование параллелизма на уровне команд в компиляторе для Intel Itanium» описывается выполненная авторами разработка и реализация алгоритма эффективной генерации команд раннего выполнения (speculative execution). Технология раннего выполнения – это одна из особенностей EPIC (Explicitly Parallel Instruction Computing), заключающаяся в возможности опережающего выполнения команд, которые используют данные из памяти, что помогает “скрывать” задержки чтения данных и лучше переупорядочивать поток команд. В статье описывается предложенный авторами алгоритм генерации инструкций раннего выполнения, а также приводятся методы, используемые авторами для улучшения эффективности раннего выполнения на основании данных анализа указателей. В статье П. Довгалюка «Разреженная модель базовых блоков для оптимизации потоков команд» предлагается модель для описания потоков команд в базовых блоках. Модель ориентирована на задачи оптимизации потоков команд по скорости их исполнения. Подобные модели применяются с целью получения кратчайшего по времени расписания команд, поступающих на конвейер процессора. Статья С. Гайсаряна и К Долговой «Разработка системной поддержки вызова программ, реализованных на языке Fortran, из среды Java посвящена исследованию возможности вызова программ, реализованных на языке Fortran 95, из среды Java. Чтобы среды могли обмениваться данными, должно иметься отображение данных одной среды на данные другой. В статье представлено описание отображения данных языка Fortran на данные языка Java и обратно. Также описывается способ эффективной передачи данных из среды Java в среду Fortran и обратно. Помимо этого, в статье рассматривается метод организации вызова подпрограмм, реализованных на языке Fortran из окружения Java. В статье П. Бойко «Метод виртуального процессора в защите программного обеспечения» рассматривается метод защиты программного обеспечения от изучения с помощью переноса защищаемого кода в виртуальную среду 5
исполнения. Проводится анализ эффективности, а так же анализ недостатков метода. Предлагается вариант реализации, позволяющий снизить себестоимость разработки. Статья В. Несова и О. Маликова «Использование информации о линейных зависимостях для обнаружения уязвимостей в исходном коде программ» посвящена некоторым аспектам реализации метода обнаружения потенциальных уязвимостей в программе на основе статического потоковочувствительного анализа потоков данных. Серьезной проблемой этого метода является большое количество ложных предупреждений. Часто ложные предупреждения вызваны недостаточной точностью определяемой информации об атрибуте – значение некоторой переменной (объекта программы). Самым простым подходом определения информации о целочисленных значениях является анализ на основе интервальных оценок. Предлагаемый авторами подход состоит в поддержании системы линейных неравенств, выполняющихся для числовых атрибутов в данной точке программы. В статье В. Владимирова «Критерии полноты тестового покрытия в генетических алгоритмах генерации тестов». В статье описывается применение генетических алгоритмов для автоматической генерации тестов. Проводится анализ некоторых широко распространённых критериев полноты на предмет их применимости для построения тестов с помощью генетических алгоритмов. Строятся оценочные функции, соответствующие этим критериям. Статья С. Грошева «Применение технологии UniTesK для тестирования систем с различной конфигурацией активных потоков управления» посвящена особенностям тестирования различных конфигураций активных потоков с помощью технологии тестирования UniTesK. В статье исследуются возможности использования технологии тестирования UniTesK для построения тестов с различной конфигурацией потоков управления, систематизируется опыт тестирования различных конфигураций активных потоков, разрабатываются подходы для случаев, на которые технология UniTesK в существующем на данный момент виде не рассчитана. В статье А. Демакова, С. Зеленова и С. Зеленовой «Генерация тестовых данных сложной структуры с учетом контекстных ограничений» представлена технология автоматической генерации тестовых данных сложной структуры, обеспечивающая возможность тонкой настройки процесса генерации тестовых данных и оптимизации этого процесса под особенности функциональности конкретного тестируемого приложения. Подход основан на использовании формального описания данных сложной структуры. В статье В. Мутилина «Паттерны проектирования тестовых сценариев» рассматриваются вопросы использования типовых решений (паттернов проектирования) для построения тестовых программ, основанных на обобщенных моделях тестируемых систем в форме неявно заданных конечных автоматов. 6
В статье С. Зеленова и Д. Силакова «Автоматическая генерация тестовых данных для оптимизаторов графических моделей» предлагается метод GraphOTK автоматической генерации тестовых данных для тестирования оптимизирующих трансляторов графических моделей. Это метод позволяет решить проблему автоматической генерации тестовых данных, а также за счет параметризации генератора позволяет варьировать количественные и качественные характеристики получаемых тестовых данных. В статье В. Рубанова и А. Михеева «Интегрированная среда описания системы команд встраиваемых процессоров» рассматривается интегрированная среда MetaDSP для описания системы команд встраиваемых процессоров. Такое описание включает спецификацию синтаксиса и поведения команд процессора и позволяет автоматически настроить набор кросс-инструментария разработки (ассемблер, дисассемблер, симулятор, отладчик), а также сгенерировать документацию прикладного программиста для целевого процессора. Эти возможности позволяют использовать MetaDSP и соответствующий настраиваемый кросс-инструментарий на этапе дизайна аппаратуры для прототипирования встраиваемых процессоров. В статье Ю. Фонина «Использование языков описания процессоров высокого уровня для генерации платформо-зависимых частей операционной системы» анализируются языки описания архитектур процессора, рассматриваются основные элементы микропроцессора, а также выделяются базовые элементы архитектуры процессора, необходимые для генерации ядра операционной системы, их параметры и свойства. В статье Е. Волковой и А. Страбыкина «Анализ и трансформации исполняемых UML моделей» рассматриваются конечные автоматы языка UML, предлагается подход к анализу исполняемых моделей UML. На основании выборки моделей, использованных в промышленных проектах, исследуются их количественные свойства и демонстрируется актуальность трансформации моделей. Выделяются образцы, часто используемые при построении автоматов. Предлагаются новые трансформации, улучшающие структуру модели, описывается процесс их применения к реальной системе. В статье А. Волкова «Использование ролей в сценариях взаимодействия» рассматривается возможность систематического использования понятия роли в сценариях взаимодействия и исследуются средства, позволяющие строить общее поведение для объектов моделируемой системы по их поведению в различных ролях. Для представления сценариев используется модель взаимодействий UML (UML Interactions). В качестве абстрактных моделей для описания общего поведения объектов рассматриваются автоматные модели и модели, основанные на сетях Петри, в нотации UML (машины состояний и активности соответственно). Целью статьи Г. Маракаевой «Применение методов выявления закономерностей для классификации химических соединений» является постановка задачи классификации неизвестных химических соединений. С 7
помощью классификации выявляются признаки, характеризующие группу, к которой принадлежит тот или иной объект. Это делается посредством анализа уже классифицированных объектов и формулирования некоторого набора правил. Целью различных алгоритмов классификации, обзор которых приводится в статье, является построение классификационной модели, которая будет предсказывать класс для заданного примера на основании имеющихся значений атрибутов. Наконец, завершающая сборник статья В.В. Кулямина, В.А. Омельченко и О.Л. Петренко «Формирование профессиональных компетенций современного разработчика ПО» посвящена обоснованию эффективности применения активных методов обучения передовым технологиям разработки программного обеспечения, в частности, формальным методам. Предоставлено несколько примеров таких методов, которые подталкивают студентов к активному мышлению и использованию получаемых при обучении знаний в практической деятельности. Замечу, что наряду с изданием ежегодного сборника «Трудов Института» ИСП РАН регулярно издает препринты, в которых публикуются более объемные работы сотрудников Института. В частности, одновременно с настоящим сборников публикуются препринты А. Болдакова и М. Гринева «Расширение языка XQuery функциональными update-выражениями» и П. Плешачкова «SXTM: Высокопроизводительный менеджер управления XMLтранзакциями». Эти работы, как и многие исследования, результаты которых публикуются в сборнике, поддержаны грантами РФФИ, Министерства науки и образования и президиума РАН. Член-корреспондент РАН
8
В.П. Иванников
Использование параллелизма на уровне команд в компиляторе для Intel Itanium А. Белеванцев, М. Кувырков, Д. Мельник
1. Введение Современные микропроцессоры обладают достаточными ресурсами для выполнения нескольких инструкций за один такт. Для того, чтобы достичь хорошей производительности на таких процессорах, нужно уметь находить в программе инструкции, которые могут выполняться независимо. Обычно эту задачу называют обнаружением параллелизма на уровне команд (ILP, instruction level parallelism). Суперскалярные процессоры решают задачу нахождения ILP динамически в процессе выполнения программы. Программа для этих процессоров не содержит сведений о том, какие инструкции могут выполняться независимо, и вся нагрузка по нахождению оптимального “плана” выполнения ложится на аппаратуру. Чем больше команд процессор может выдать за такт, тем сложнее в реальном времени обнаружить оптимальную последовательность выполнения команд. Для того, чтобы преодолеть эти ограничения, фирмой Intel была предложена архитектура EPIC (Explicitly Parallel Instruction Computing [1]) с очень длинным командным словом, реализованная в процессорах семейства Itanium. Идея EPIC состоит в том, чтобы переложить задачу по поиску ILP на компилятор. EPIC-программа содержит явные указания на то, какие инструкции можно выполнять параллельно, а EPIC-процессор в точности следует тому плану выполнения, который задает программа. При этом его основной задачей становится обеспечение работы конвейера, и необходимость в аппаратной реализации сложной логики поиска независимых инструкций отпадает. С другой стороны, отдавая компилятору задачу определения наилучшего плана выполнения программы, архитектура должна предоставить компилятору широкие возможности по управлению ходом выполнения программы. EPIC дает возможность компилятору (помимо явного указания на независимость определенных команд) предсказывать ветвления, влиять на работу кэша, избавляться от коротких ветвлений, выполнять команды с опережением и некоторые другие. Кроме того, предоставляется значительный объем ресурсов 9
– большой регистровый файл и много параллельно работающих функциональных устройств. Из перечисленного списка особенностей архитектуры видно, что создание оптимизирующего компилятора для EPIC является принципиально иной задачей, чем оптимизация под традиционные последовательные архитектуры. Компилятор для EPIC должен уметь выразить как можно больше параллелизма на уровне команд, создавая эффективный план выполнения программы с учетом всех возможностей архитектуры, и упаковать эти команды в длинные слова. При этом задачи, возникающие при использовании свойств архитектуры, ранее в компиляторах не ставились, и методы их решения до сих пор в полной мере не разработаны. Целью нашей работы является разработка и реализация алгоритма эффективной генерации команд раннего выполнения (speculative execution). Технология раннего выполнения – это одна из особенностей EPIC, заключающаяся в возможности опережающего выполнения команд, использующих данные из памяти, что помогает “скрывать” задержки чтения данных и лучше переупорядочивать поток команд. В данной статье мы описываем предлагаемый нами алгоритм генерации инструкций раннего выполнения, а также приводим методы, которые используются нами для улучшения эффективности раннего выполнения на основании данных анализа указателей. Кроме того, мы обсуждаем результаты тестирования реализации алгоритма для компилятора GCC [2] на пакете SPEC CPU 2000.
2. Реализация раннего выполнения на Intel Itanium В этом разделе мы описываем технику раннего выполнения, а также ее реализацию в процессорах Itanium. Далее под инструкцией (или командой) мы понимаем одну операцию, выполняемую процессором. Процессоры семейства Intel Itanium [3] являются реализацией архитектуры EPIC с очень длинным командным словом (VLIW – Very Long Instruction Word). Каждое такое командное слово представляет собой пакет инструкций, который включает в себя 3 инструкции, и задает шаблон, который указывает процессору, на каком функциональном устройстве следует выполнять каждую из инструкций. Itanium 2 имеет более 20 функциональных устройств, которые могут работать параллельно. Пакеты инструкций объединяются в группы инструкций (далее – просто группы), каждая из которых может быть выполнена за 1 такт работы процессора (всего до двух групп за 1 такт). Группы разделяются между собой стоп-битами, которые являются частью кода шаблона; в текущей реализации в одной группе может быть до двух пакетов. Задача компилятора по выявлению ILP состоит в явном указании шаблонов пакетов и границ групп инструкций (с помощью стоп-битов). Шаблоны указывают процессору, куда 10
требуется распределить инструкции, а группы явно указывают, какие инструкции можно выполнять независимо. Для того, чтобы эффективно использовать параллелизм на уровне команд, имеющийся в программе, необходимо иметь как можно большую свободу перемещения инструкций между группами с тем, чтобы максимально использовать все функциональные устройства, доступные во время выполнения каждой группы. Возможности по перемещению инструкций компилятором ограничиваются зависимостями инструкций по данным и по управлению. Тем не менее, часто бывает, что точно определить наличие зависимости в момент компиляции нельзя, но можно с большой вероятностью утверждать, что зависимости нет. При обычном планировании компилятор в таких случаях обязан предполагать наличие зависимости, чтобы сохранить корректность программы. Архитектура EPIC позволяет компилятору игнорировать такие зависимости, поддерживая технику раннего выполнения (speculative execution). Компилятор может выдать инструкции раньше, чем позволило бы наличие зависимости, но должен сгенерировать код восстановления (recovery code), который обеспечит корректное выполнение программы, если зависимость окажется реальной. В случае отсутствия зависимости использование раннего выполнения позволяет скрыть задержки операций загрузки из памяти и уменьшить время выполнения программы. До:
После:
/* Процессор простаивает */
/* Загрузить раньше */
...
До:
После:
...
/* Выдать загрузку раньше */
/* Процессор простаивает */
store(st_addr, data) load(ld_addr, target)
if (a>b) { load(ld_addr, target)
/* Если ld_addr и st_addr различны, можно работать с данными, иначе перейти на код восстановления */
use(target)
acheck(target, recovery) use(target)
Рис. 2. Пример раннего выполнения (устраняется зависимость по данным)
if (a>b) { /* Проверить, было ли исключение */
/* Дождаться окончания загрузки */
scheck(target, recovery) /* Если нет, сразу работать с данными */
use(target)
aload(ld_addr, target) store(st_addr, data)
/* Дождаться окончания загрузки */
...
sload(ld_addr, target)
}
могут быть перемещены. Перед использованием результатов перемещенных команд должна быть выполнена проверочная инструкция, определяющая, действительно ли была зависимость. Если результат проверки положителен, то проверочная инструкция выполняет переход на код восстановления (см. примеры на рисунках 1 и 2).
use(target) }
Рис. 1. Пример раннего выполнения (устраняется зависимость по управлению) Существует два вида раннего выполнения: один направлен на устранение зависимостей по данным (data speculation), другой – зависимостей по управлению (control speculation). Первый состоит в перемещении операции загрузки из ячейки памяти выше операции записи в некоторую ячейку, адрес которой может пересекаться с адресом загрузки. Второй состоит в перемещении операции загрузки из памяти выше операции ветвления. Инструкции, в которых используется результат операции загрузки, также 11
В системе команд Itanium раннее выполнение поддерживается двумя группами инструкций, для преодоления зависимостей по данным и по управлению. К первой группе относятся инструкции ld.a (расширенная команда загрузки), а также ld.c и chk.a (проверка выполненной ранней загрузки), ко второй – соответственно ld.s и chk.s. При устранении зависимости по данным необходимо убедиться, что адреса ячеек памяти, указатели на которые создают эту зависимость, не совпадают в момент выполнения программы. Для этого инструкция ld.a, выполняя раннюю загрузку операнда из памяти, сохраняет фактический адрес ячейки памяти, из которой загружалось значение, в специальной таблице адресов – ALAT (Advanced Load Address Table – таблица адресов ранней загрузки). Эта таблица индексируется по номеру регистра, в который выполнялась загрузка. Операции записи по адресу, перекрывающемуся со значением одной из ячеек в таблице ALAT, а также выполнение ранней загрузки в тот же регистр удаляет предыдущее значение соответствующей ячейки. При выполнении инструкции проверки ранней загрузки ld.c с тем же номером регистра назначения, в таблице адресов ранней загрузки выполняется поиск значения с индексом, равным номеру этого регистра. Если такого значения не найдено, то считается, что была выполнена конфликтующая 12
операция записи. Следовательно, раннее выполнение не удалось, и инструкция загрузки выполняется еще раз. В случае команды chk.a происходит переход на ранее сгенерированный компилятором код восстановления. При устранении зависимости по управлению достаточно гарантировать, что в случае возникновения исключительной ситуации при выполнении ранней загрузки она будет возбуждена в корректном месте программы. Для этого инструкция ld.s, выполняя раннюю загрузку в некоторый регистр, при возникновении исключительной ситуации устанавливает для него специальный флаг NaT (Not a Thing), который свидетельствует о наличии отложенного исключения. Этот флаг может быть установлен для любого регистра общего назначения. Если этот флаг был установлен для какого-либо регистра, то он также будет установлен для всех регистров, значения которых были получены с помощью вычислений, использовавших значение первого регистра. Если впоследствии проверочная инструкция chk.s обнаруживает, что для ее операнда регистра установлен флаг NaT, то она передает управление на код восстановления, который должен устранить последствия неудачной попытки раннего выполнения. Примеры ассемблерного кода с использованием раннего выполнения приведены на рис. 3. Программа с инструкциями Исходная программа раннего выполнения ld8.a r18=[r19];; adds r15=r16,r14 adds r15=r16,r14 st8 r14=[r14] nop.i nop.i st8 r14=[r14] ld8 r18=[r19];; ld8.c.clr r18=[r19] st4 r15=[r33] nop.i;; nop.i ld8 r14=[r18];; ld8 r14=[r18];; st4 [r15]=r33 а) преодоление зависимостей по данным Исходная программа
Программа с инструкциями раннего выполнения adds r14=1,r8 mov r1=r42 ld4.s r15=[r33] adds r14=1,r8;; mov r1=r42;; cmp4.ltu p6, r14 cmp4.ltu p6,r14 (p6) br.cond bd0 (p6) br.cond bf0 ld4 r14=[r33];; chk.s.m r15,b40;; add r14=r14,r8 add r15=r15,r8 б) преодоление зависимостей по управлению
Избыточная генерация инструкций раннего выполнения зачастую может не только не дать выигрыша в производительности, но и наоборот, привести к ухудшению производительности программы, т.к. в случае неудачного раннего выполнения необходимо будет выполнять загрузку из памяти повторно, или выполнять дополнительный код восстановления. Использование информации, полученной с помощью анализа указателей, во многих случаях дает возможность оценить целесообразность генерации инструкций раннего выполнения, что позволяет генерировать такие инструкции только в тех местах, где это действительно необходимо.
3. Алгоритм генерации инструкций раннего выполнения Инструкции раннего выполнения порождаются компилятором в процессе планирования. В этом разделе описывается, как нужно изменить планировщик, чтобы он мог поддерживать раннее выполнение. Предполагается, что планировщик может оперировать регионами из нескольких базовых блоков (иначе не появляется зависимостей по управлению и раннего выполнения для них). При описании того, как меняется собственно алгоритм планирования, мы предполагаем, что планировщик принадлежит к классу алгоритмов списочного планирования (list scheduling [4]). Для моделирования раннего выполнения в планировщике инструкций введем понятие блока раннего выполнения. Некоторые инструкции могут порождать начало таких блоков (например, загрузки из памяти). Затем могут следовать несколько инструкций, использующих результат загрузки (первой инструкции блока). Блок завершается специальными инструкциями проверки (такими как ld.c и chk.s). Таким образом, блок раннего выполнения формируется из операций ранней загрузки и проверки, и может включать несколько использований результата ранней загрузки. Алгоритм поддержки раннего выполнения в планировщике имеет своей целью корректное создание и наполнение блоков раннего выполнения наравне с планированием обычных инструкций. Для этого необходимо решить следующие подзадачи: расширение структур данных планировщика, хранящих сведения об инструкциях и зависимостях, данными раннего выполнения; инициализация новых структур данных; планирование инструкций раннего выполнения; поддержка планирования машинно-зависимой частью компилятора. Далее мы подробно описываем решение каждой из этих задач.
Рис. 3. Примеры ассемблерного кода с командами раннего выполнения 13
14
3.1. Расширение и инициализация структур данных
3.2. Планирование инструкций раннего выполнения
Для отражения свойств инструкций и зависимостей, связанных с ранним выполнением, в структуры данных планировщика, представляющие инструкции и зависимости между ними, включены флаги раннего выполнения (см. таблицу 1). Наличие флага раннего выполнения для зависимости означает, что для преодоления данной зависимости можно использовать раннее выполнение. Аналогично, такой флаг для инструкции означает, что она может быть запланирована альтернативным способом – с использованием раннего выполнения. Также специальными флагами помечаются инструкции, которые более предпочтительны для раннего выполнения, либо, наоборот, не должны в нем участвовать.
Планировщиком семейства list scheduling непосредственно для планирования используется список готовых инструкций. Инструкция может быть помещена в этот список при начале планирования, либо после того, как запланирована предыдущая инструкция и удовлетворены зависимости по данным. Из списка выбирается (обычно руководствуясь набором эвристик) наилучшая для планирования в данный момент инструкция, которая выдается на планирование и удаляется из списка, а зависимые от нее инструкции добавляются в список. Процесс повторяется до тех пор, пока не будут запланированы все инструкции. С точки зрения планировщика, все истинные зависимости по памяти могут быть потенциально устранены с помощью раннего выполнения по данным. Дополнительное ограничение накладывает архитектура машины, которая может поддерживать раннюю выдачу лишь некоторых инструкций (в Itanium это команды загрузки из памяти). За проверку этого ограничения отвечает машинно-зависимая часть компилятора. Аналогично, любая инструкция из последующих базовых блоков может быть выполнена в планируемом блоке с помощью раннего выполнения по управлению, если это поддерживается целевой машиной. Это ограничение проверяется при каждом перемещении инструкции в список для планирования и далее в этом разделе не упоминается.
Флаг
Зависимость может быть устранена с помощью:
Инструкция может быть:
BE_IN_DATA BE_IN_CONTROL
ранняя загрузка с помощью ld.a ранняя загрузка с помощью ld.s использование результата ранней загрузки
FINISH_DATA
–
использование результата ранней загрузки ld.c
FINISH_CONTROL
–
chk.s
HARD_DEP
невозможно устранить
WEAK_DEP
ранняя загрузка предпочтительна
ранняя загрузка не может быть использована ранняя загрузка предпочтительна
BEGIN_DATA BEGIN_CONTROL
ld.a ld.s
3.2.1. Помещение в список планирования.
Таблица 1. Флаги раннего выполнения Инициализация флагов происходит перед началом планирования, когда работает анализ зависимостей по данным. Все, так называемые, истинные зависимости по памяти (команда, читающая данные из памяти, зависит от ранее выполняемой команды записи этих данных) помечаются анализом зависимостей флагом BEGIN_DATA. Некоторые зависимости при этом помечаются флагом HARD_DEP (например, зависимости, возникающие при волотильных обращениях в память). Флаг зависимости по управлению BEGIN_CONTROL, BE_IN- и FINISH- флаги, а также флаги раннего выполнения для инструкций устанавливаются в процессе планирования при анализе их зависимостей.
15
Рассмотрим, как изменяется процесс добавления инструкций в список готовых к планированию. Пусть инструкция находится в том же базовом блоке, который сейчас планируется. Тогда она может быть добавлена в список, если у нее нет зависимостей, либо все ее зависимости помечены флагами BEGIN_DATA либо BE_IN_DATA. Такая инструкция при помещении в список также помечается одним из этих флагов. Если же инструкция находится в другом базовом блоке, то возможны три случая. В первом случае у инструкции нет зависимостей по данным, и она не может возбудить исключение – перемещение такой инструкции не нарушает корректность программы, и она сразу помещается в список. Во втором случае инструкция может создать исключение, но не имеет зависимостей. Эта инструкция может быть помещена в список для раннего выполнения по управлению, если вероятность выполнения ее базового блока относительно текущего высока, и в этом случае она помечается флагом BEGIN_CONTROL. В третьем случае у инструкции есть зависимости по данным. Если все такие зависимости помечены флагами BEGIN_DATA либо BE_IN_DATA, тогда, аналогично двум предыдущим случаям, в зависимости от того, может или нет инструкция возбудить исключение, она может быть помещена в список с флагом (либо без флага) BEGIN_CONTROL и с флагами, соответствующими флагам ее зависимостей. Если же зависимости инструкции устранить 16
невозможно (флаг HARD_DEP), то она не может быть помещена в список планирования.
3.2.2. Сортировка списка планирования. После формирования списка готовых инструкций он сортируется в соответствии с эвристиками планировщика. Затем из списка выбирается инструкция, планирование которой позволит выдать наибольшее число инструкций в текущем цикле. Для определения приоритетов среди инструкций раннего выполнения при планировании используются следующие эвристики: обычная инструкция всегда предпочитается инструкции раннего выполнения; инструкция раннего выполнения по данным предпочитается инструкциям раннего выполнения по управлению; если обе инструкции раннего выполнения устраняют зависимости по данным, то вычисляется оценка того, какие из зависимостей наименее вероятны. Эта оценка является суммой оценок вероятностей каждой зависимости инструкции. Вероятность зависимости в свою очередь является степенью “слабости” зависимости (понятие слабых зависимостей подробнее рассматривается в следующем разделе); если обе инструкции являются инструкциями раннего выполнения по управлению, то предпочитается инструкция с наибольшей вероятностью выполнения.
3.2.3. Выдача инструкций раннего выполнения Выдача инструкций раннего выполнения происходит следующим образом. Инструкция, помеченная одним из флагов BEGIN_* (загрузка из памяти в случае Itanium) разбивается на две части: инструкцию раннего выполнения и инструкцию проверки. Все зависимости исходной инструкции (как прямые, так и обратные), переносятся на инструкцию проверки, а также добавляется зависимость между инструкциями раннего выполнения и проверки. При этом обратные зависимости с флагом HARD_DEP изменяются на зависимости BE_IN_*. Инструкция раннего выполнения планируется на текущем цикле, а инструкция проверки помечается как последняя инструкция блока раннего выполнения (FINISH_*), и планируется позже, как обычная инструкция. Кроме того, при планировании инструкций, создающих новый блок раннего выполнения, также создается новый блок, который будет содержать код восстановления, и в него помещается копия спланированной инструкции. Для данной копии создается зависимость от инструкции проверки типа HARD_DEP, которая обеспечивает планирование кода восстановления после проверочной инструкции. При выдаче инструкции, содержащейся внутри блока раннего выполнения (помеченной флагами BE_IN_*), аналогично ее зависимости также изменяют 17
свой тип с BEGIN_* на BE_IN_*, и копия инструкции помещается в уже созданный блок восстановления. При этом BE_IN_* зависимости инструкции, которые были устранены при планировании, перемещаются на копию инструкции, чтобы указать планировщику зависимость копии инструкции от уже содержащихся в блоке восстановления инструкций. При выдаче инструкции проверки, завершающей блок раннего выполнения, соответствующий блок восстановления закрывается и добавляется к текущему региону для последующего планирования аналогично обычным базовым блокам. Если блок выполнения состоит из одной инструкции, то возможна ситуация, когда задачу восстановления выполнит сама инструкция проверки (в случае Itanium это возможно для ld.c). Тогда блок восстановления уничтожается.
3.3. Машинно-зависимая поддержка раннего выполнения При разработке алгоритма раннего выполнения мы ориентировались на мультиплатформенный компилятор, подобный GCC. Для этого машиннозависимые части поддержки раннего выполнения были выделены отдельно. Если необходимо реализовать поддержку для определенной платформы, то модуль компилятора, реализующий кодогенерацию для этой платформы, должен предоставить следующие возможности (реализованные в виде процедур): запрос о том, какие типы раннего выполнения (в терминах введенных нами флагов) поддерживает архитектура; запрос о том, поддерживает ли архитектура раннее выполнение данной инструкции; запрос на преобразование инструкции, подготавливаемой к раннему выполнению определенного типа, к виду (во внутреннем представлении), который она примет при раннем выполнении. Для Itanium, например, при передаче инструкции загрузки из памяти в качестве параметра необходимо вернуть инструкцию во внутреннем представлении, соответствующую ld.s или ld.a; запрос на создание инструкции проверки для данного типа раннего выполнения; запрос на то, нужен ли блок восстановления для данной инструкции данного типа раннего выполнения, или же можно обойтись инструкцией проверки; запрос на расширение структур данных при создании новых инструкций (нового базового блока). Кроме того, для всех типов инструкций раннего выполнения необходимо задать их вид в ассемблере целевой машины для кодогенератора, а также время выполнения (латентность) и занимаемые функциональные устройства для того, 18
чтобы планировщик мог оценивать состояние конвейера целевой машины в процессе планирования.
4. Реализация алгоритма раннего выполнения в компиляторе GCC
3.4. Использование анализа указателей для эффективности раннего выполнения
Данный подход был реализован в компиляторе GCC на основе серии 4.х (в настоящий момент еще не вышедшей). Логика планирования инструкций раннего выполнения и расширение структур данных планировщика были реализованы так, как описано в разделе 3. В кодогенераторе GCC для процессоров Itanium были описаны ассемблерные формы инструкций раннего выполнения. Кроме того, был исправлен ряд недочетов и сделано несколько улучшений планировщика, не связанных непосредственно с основным алгоритмом: структуры данных планировщика не были рассчитаны на то, что в процессе планирования могут появиться новые инструкции. Нами был предусмотрен ряд процедур, осуществляющих расширение этих структур на лету; планировщик не поддерживал граф потока управления в консистентном состоянии, что не позволяло добавлять блоки восстановления к планируемым регионам; планировщик также не поддерживал консистентность информации о времени жизни регистров, так как дальнейшим оптимизациям она не была нужна. Это мешало обрабатывать инструкции раннего выполнения во время второго запуска планировщика; приоритет инструкций на стыках базовых блоков вычислялся неправильно, что не являлось проблемой до появления команд раннего выполнения. После реализации алгоритма оказалось, что при переходе к планированию следующего базового блока из-за неверного вычисления приоритета в длинное командное слово, не полностью заполненное инструкциями из предыдущего базового блока, могли попасть новые инструкции раннего выполнения, причем корректность программы нарушалась; планировщик не сохранял все типы зависимостей, а только сильнейшую зависимость между двумя инструкциями. Это может помешать корректно выдать команду раннего выполнения. Рассмотрим следующий пример:
улучшения
Данные анализа указателей могут значительно повысить эффективность планирования инструкций с поддержкой раннего выполнения, указывая планировщику, в каких случаях генерация инструкций раннего выполнения является наиболее эффективной. На основе данных анализа указателей с помощью приведенных ниже эвристик оценивается степень истинной зависимости между инструкциями. Будем говорить, что между двумя инструкциями существует слабая зависимость, если между ними с помощью консервативного статического анализа диагностируется истинная зависимость по данным, но фактически вероятность возникновения такой зависимости мала. Аналогично, будем считать, что сильной зависимостью между двумя инструкциями является такая истинная зависимость по данным, которая с достаточно большой вероятностью имеет место и во время выполнения программы. Для определения степени зависимости вычисляется эвристическая оценка, показывающая вероятность ее существования с точки зрения статического анализа. Используются следующие эвристики: два указателя, ссылающиеся на ячейки, степень зависимости которых определяется, имеют непересекающиеся множества значений, на которые они могут указывать (множества points-to), но вследствие консервативности анализа для одного из указателей известно, что он мог ссылаться по неопределенному адресу; один из указателей является прямой ссылкой, а другой – непрямой. Эта эвристика используется при ссылках на поля структуры (s.a или p->b); указатели являются различными параметрами одной функции; указатели имеют различные базовые значения, т.е. значения, относительно которых выполнялись операции над указателями, и эти базовые значения являются различными параметрами функций. Другими словами, указатели p и q имеют вид p = arg1 + offset1, q = arg2 + offset2, где arg1 и arg2 – различные аргументы функции. Аналогично, зависимость является сильной, и мы не должны пытаться «разорвать» зависимость по данным, если множества points-to соответствующих указателей пересекаются. В этом случае, как было установлено экспериментально, высока вероятность того, что раннее выполнение окажется неудачным.
19
<точка планирования> add r3 = r3, r4 st [r6] = r4 ld r4 = [r5] Загрузка в регистр r4 не может быть перемещена для раннего выполнения в текущую точку планирования, поскольку такое перемещение нарушит обратную зависимость (anti-dependence) 20
между инструкциями ld и add. Между тем, эта зависимость может быть опущена из-за наличия прямой (истинной) зависимости у инструкции ld. Мы исправили анализ зависимостей так, чтобы сохранялись все типы зависимостей; было улучшено формирование регионов планирования. За счет нескольких дополнительных итераций (в 95% достаточно двух, а в 99% – трех итераций) по графу потока управления стало возможным формировать бóльшие регионы, что позволяет иметь лучший выбор при раннем планировании.
Результаты тестирования показывают, что раннее выполнение наиболее полезно для вычислительных задач, где применение этой техники может дать большее ускорение (до 20% и выше). Для целочисленных задач применение техники должно быть более консервативным, и стандартного анализа указателей (компилятора GCC) может не хватать. Вообще говоря, чем более консервативны настройки раннего выполнения, тем меньше ускорение для отдельных тестов, но при этом и меньше тестов, показывающих худшие результаты.
6. Заключение
5. Экспериментальные результаты Мы провели тестирование раннего выполнения на наборе тестов SPEC 2000 [5]. Использовались серверы HP rx1600 с двумя процессорами Intel Itanium 2 1.8 ГГЦ и 2 ГБ оперативной памяти. В таблице 2 приведены результаты тестирования для пакета SPEC FP c уровнем оптимизации –O3. Для сравнения приведены также данные ускорений, получаемые при включении отдельных оптимизаций. При уровне оптимизации –O3, помимо раннего выполнения, работает также и широкий набор стандартных оптимизаций компилятора GCC. По данным Только Все Оптимианализ и по вместе зация-O3 управлению указателей
Тесты
Только по данным
Только по управлению
168.wupwise
0,71%
1,43%
1,43%
1,66%
0,95%
-0,47%
171.swim
-0,30%
-0,30%
0,30%
-0,44%
-0,15%
-0,15%
172.mgrid
0,00%
0,00%
0,30%
4,79%
5,09%
-4,84%
173.applu
0,24%
0,00%
1,18%
-0,71%
-0,24%
0,95%
177.mesa
-1,09%
0,00%
2,89%
0,14%
0,82%
1,73%
178.galgel
2,51%
-5,75%
2,51%
-5,92%
-3,41%
8,76%
179.art
1,05%
0,06%
-0,17%
-0,06%
0,58%
-0,23%
183.equake
-0,90%
-0,23%
-1,13%
-0,23%
-0,45%
-2,24%
187.facerec
0,19%
0,00%
-1,12%
-3,16%
-2,99%
2,87%
188.ammp
18,84%
0,15%
18,84%
-1,52%
16,87% 20,68%
189.lucas
0,12%
-0,12%
-0,36%
-0,12%
0,00%
0,00%
191.fma3d
0,73%
-0,36%
0,36%
0,73%
2,93%
-0,72%
200.sixtrack
2,43%
0,00%
2,08%
-1,04%
1,04%
3,16%
301.apsi SPEC FP 2000
1,12%
-0,67%
0,45%
-4,45%
-3,13%
5,57%
1,71%
-0,57%
1,89%
-0,76%
1,14%
2,46%
В этой статье мы описали проблемы, возникающие при компиляции для архитектур с явно выраженным параллелизмом на уровне команд, на примере задачи поддержки раннего выполнения для Intel Itanium. Разработанный нами алгоритм реализован в компиляторе GCC и протестирован на пакете SPEC CPU 2000. Алгоритм показывает ускорение примерно в 2,5% на пакете вычислительных программ SPEC FP 2000, причем отдельное ускорение достигает 20% (для теста ammp). В наших дальнейших планах тонкая настройка алгоритма раннего выполнения, а также тестирование этого алгоритма с улучшенным анализом указателей, разработанным нами в рамках предыдущих исследований. Мы планируем включить нашу реализацию алгоритма раннего выполнения в компилятор GCC версии 4.2. Литература 1. EPIC Technology Whitepaper. http://www.intel.com/pressroom/kits/events/enterprise_server/ EPIC_white_paper.pdf 2. GNU Compiler Collection. http://gcc.gnu.org. 3. Intel(R) Itanium(R) Architecture Software Developer's Manual. http://www.intel.com/design/ itanium/manuals/iiasdmanual.htm 4. S. Muchnick. Advanced compiler design and implementation. Morgan Kaufmann, 3rd ed., 1997. 5. SPEC CPU benchmark. http://www.spec.org/cpu2000/
Таблица 2. Результаты тестирования на SPEC FP 2000 21
22
1 mov a, b
Разреженная модель базовых блоков для оптимизации потоков команд П.М. Довгалюк E-mail:
[email protected] Аннотация. Предлагаемая модель предназначается для описания потоков команд в базовых блоках. Данная модель ориентирована на задачи оптимизации потоков команд по скорости их исполнения. Подобные модели применяются с целью получения кратчайшего по времени расписания команд, поступающих на конвейер процессора.
1. Анализ существующих математических моделей вычислительных процессов в базовых блоках Существует ряд моделей вычислительных процессов в базовых блоках. Наиболее распространенные из них используют для представления базового блока направленные ациклические графы [3], [4], [5]. Во всех распространенных графовых моделях базовых блоков множество вершин соответствует множеству команд, а наличие дуги между двумя вершинами соответствует наличию зависимости между соответствующими командами (дуга (v, u) показывает, что команда v должна быть выполнена раньше команды u). Для того чтобы задать протяженность задержки между командами, в наиболее популярной модели, описанной в [3] и [5], используются числовые пометки ребер графа, соответствующие продолжительностям задержек — D((v, u)). На Рис. 1 и Рис. 2 представлен пример содержимого базового блока и его традиционное представление с помощью графа. mov a, b add c, 1 mul a, c mov d, c mul a, d Рис. 1. Пример содержимого базового блока
23
1 1
add c, 1
1 2
2
mul a, c
1
2
1
1
mov d, c
mul a, d
Рис. 2. Традиционное представление базового блока в виде графа Корректным расписанием S для систем с одним конвейером называется функция S : V N v, u E S u S v Dv, u . Таким образом, S(v) – позиция вершины v в результирующем расписании. В каждой позиции расписания может находиться либо одна инструкция, либо специальная команда NOP, которая не выполняет никаких действий. mov a, b add c, 1 mul a, c nop mov d, c mul a, d Рис. 3. Пример корректного расписания для базового блока Существует множество моделей, построенных на основе описанной выше, отличающихся различными атрибутами вершин и дуг, в зависимости от особенностей архитектуры целевых машин. В некоторых распространенных архитектурах, например Intel i860 [2], зависимости между командами могут быть ограничены по времени сверху. То есть вторая (зависящая) инструкция должна быть выполнена ровно через определенное количество тактов после первой, иначе результат выполнения первой команды будет утерян. Хотя такие виды зависимостей и описываются существующими моделями [1], [5], но эффективных алгоритмов построения расписания, создающих корректное расписание всегда, когда это возможно, для них не существует. Это объясняется тем, что такие зависимости вводятся в 24
модель с помощью специального атрибута связей. Данное расширение модели не позволяет эффективно использовать алгоритмы оптимизации, пригодные для моделей без этого атрибута [4], [5]. Эти алгоритмы в процессе работы могут заходить в тупик, генерируя некорректное расписание. Также ни в одной из наиболее распространенных моделей не учитывается тот факт, что в большинстве архитектур различные команды занимают разное количество тактов конвейера. Например, для RISC-процессоров, где все команды кодируются одним машинным словом, некоторые команды, оперирующие большими константами, могут кодироваться двумя словами. Кроме того, в традиционных моделях базовых блоков не учитываются команды перехода, имеющие неустранимые задержки. Такие задержки допустимо заполнять полезными командами, если это не приводит к конфликтам по данным. Так как такое ограничение плохо вписывается в существующие модели, то для решения этой задачи используются специальные алгоритмы [3]. Таким образом, необходимо построить модель базовых блоков, позволяющую оптимизировать вычислительный процесс и в тех случаях, когда существуют жесткие ограничения сверху на продолжительность задержки между командами, а также, если команды кодируются неодинаковым количеством слов. Кроме того, новая модель должна позволять учитывать зависимости между командами из смежных базовых блоков для конвейерной оптимизации команд перехода.
2. Разреженная модель вычислительных процессов в базовых блоках Традиционная графовая модель базовых блоков использует в качестве узлов отдельные команды целевой машины, из которых состоит базовый блок [5]. Такая модель не отражает загруженности конвейера непроизводительными вычислениями и не позволяет оперировать командами, размер которых больше одного машинного слова. Поэтому предлагается видоизменить модель базовых блоков следующим образом: в качестве узлов использовать операции, выполняемые конвейером за один такт. Такими операциями могут быть – выборка кода команды, либо непроизводительная задержка, в течение которой на конвейер не поступает новых команд. Связывать же эти операции в граф предлагается с помощью связей двух видов: задающих относительный или абсолютный порядок операций, поступающих на конвейер. Добавление узлов-задержек между командами делает граф более разреженным, что и послужило источником названия модели. Разреженную модель базовых блоков можно математически описать с помощью следующего ациклического графа: G V ; E ; s; e , где 25
V – множество узлов, соответствующих конвейерным операциям, формирующим базовый блок;
E V V – множество связей, определяющих порядок поступления узлов-операций (команд и задержек) на конвейер процессора;
s V – стартовый (корневой) узел;
e V – последний узел в любом корректном расписании, построенном на основе данного графа. Узлы в таком графе должны быть помечены соответственно их назначению – являются ли они выборками кода команды из памяти, либо непроизводительными задержками. Для решения поставленных задач необходимо ввести два вида связей между вершинами. Введем следующие определения: Определение 1: Связь называется «жесткой», если две операции, которые она соединяет должны поступать на конвейер строго друг за другом (между ними не должно быть других операций). Обозначим подмножество жестких связей как H. Определение 2: Связь называется «гибкой», если она задает лишь относительный порядок поступления операций на конвейер (между ними на конвейер могут поступать другие операции). Множество задержек введено для моделирования минимального времени между инструкциями, которое традиционно [3] представляется в виде числовой пометки дуги. В предлагаемой модели паузы между инструкциями заполняются с помощью непроизводительных операций. Формальное описание графа приведенное ниже недостаточно точно описывает модель. Для того чтобы решать задачи оптимизации потока команд с помощью данной модели, граф должен удовлетворять следующим условиям:
26
в графе существует только одна корневая вершина – s; в графе существует только один лист – e; граф является слабо связным; в графе не существует циклов, так как не могут существовать циклические зависимости по данным между инструкциями в одном базовом блоке.
1 mov a, b
1
mul a, c
1
1
1
Предлагаемая модель отличается от традиционной специальными видами узлов и связей. В отличие от традиционных моделей, в разреженной модели в качестве узлов используются однотактовые операции конвейера целевой машины. Данные операции объединяются в граф с помощью ребер двух видов – для задания относительного и абсолютного порядка операций. Разряженная модель позволяет применять единый подход при оптимизации потока команд в базовых блоках при наличии команд из нескольких машинных слов, инструкций перехода с неустранимыми задержками, а также команд с ограниченным временем жизни результата их выполнения. Эта ее особенность дает возможность оптимизировать потоки команд в базовых блоках в рамках одного универсального алгоритма, что невозможно в традиционных моделях.
add c, 1
mov d, c
Литература
mul a, d
Рис. 4. Представление базового блока с помощью разреженной модели
2.1. Моделирование машины
особенностей
архитектуры
1. Beaty, S. List scheduling: Alone, with foresight, and with lookahead. In Conference on Massively Parallel Computing Systems: the Challenges of General-Purpose and SpecialPurpose Computing (Ischia, Italy, May 1994) 2. Intel. i860 64-bit microprocessor programmer’s reference manual, 1990. 3. S. Muchnick. Advanced compiler design and implementation, 1997 4. Philip Schielke. Issues in Instruction Scheduling. Rice University, Department of Computer Science. Ph. D. Thesis Proposal 5. Bjorn De Sutter. General-Purpose Architecture Instruction Scheduling Techniques. ELIS Technical Report DG 98-09, November 1998
целевой
Такая особенность целевой машины, как инструкции, состоящие из нескольких машинных слов, может быть описана с помощью нескольких последовательных узлов-операций, соединенных жесткими связями. Команды, продолжительность задержки между которыми строго фиксирована (т.е. время жизни результата выполнения первой из команд ограничено), предлагается моделировать с помощью последовательности, состоящей из двух узлов-операций и нескольких узлов-задержек между ними. Данные узлы соединяются жесткими связями. Аналогичным образом могут описываться команды переходов с неустранимыми задержками, только в этом случае вместо второй операции должен использоваться последний узел графа.
3. Выводы В статье рассмотрен традиционный способ представления базовых блоков с помощью графовой модели. На основе анализа её недостатков введена разреженная модель базовых блоков. 27
28
использованием методики.
JNI,
которая
показала
эффективность
предложенной
2. Отличия языков C и Fortran
Разработка системной поддержки вызова программ, реализованных на языке Fortran, из среды Java. С.С. Гайсарян, К.Н. Долгова Аннотация. Статья посвящена исследованию возможности вызова программ, реализованных на языке Fortran 95, из среды Java. Для того чтобы среды могли обмениваться данными, должно быть отображение данных одной среды на данные другой. В статье представлено описание отображения данных языка Fortran на данные языка Java и обратно. Также описан способ эффективной передачи данных из среды Java в среду Fortran и обратно. Он заключается в том, что память, выделенная средой Fortran для размещения общих блоков и массивов, отождествляется с прямыми буферами среды Java. То есть прямые буферы среды Java размещаются по тем же адресам памяти, по которым размещены общие блоки и массивы языка Fortran. Помимо этого, в статье описан метод организации вызова подпрограмм, реализованных на языке Fortran из окружения Java, заключающийся в передаче параметров через прямые буферы окружения Java.
1. Введение Имеется достаточно большое количество программ, реализованных на языке Fortan и не потерявших ценность. В настоящее время широкую популярность получила среда программирования Java, обеспечивающая переносимость программ. Следовательно, возникает потребность иметь возможность вызывать подпрограммы, реализованные на языках Fortan, из Java-программ. Для вызова подпрограмм, реализованных на языке С из Java-программ есть JNI, который доступен начиная с версии JDK 1.2. Аналогичного интерфейса для вызова Fortran-подпрограмм нет. Предложенная работа повящена разработке методики вызова Fortran-подпрограмм из Java-среды. В настоящей работе рассмотрены основные отличия языков С и Fortran, препятствующих использованию методике аналогичной JNI для вызова Fortran-подпрограмм из Java-программ. Построено отображение данных языка Fortran на данные Java и обратно. Предложена методика реализации общей области памяти для Java- и Fortran-сред через прямые буферы пакета java.nio. В последнем разделе описана прототипная реализация, выполненная с 29
У языков программирования C и Fortran, существует ряд различий, из-за которых нельзя перенести организацию JNI для языка С на организацию подобного интерфейса для языка Fortran. (1) В стандарте языка С напрямую не указан размер примитивных типов [1]. Выбор наилучшего для данной архитектуры размера типов оставлен на рассмотрение разработчиков компилятора. В стандарте языка Fortran для каждого примитивного типа данных строго задан их размер. Это позволяет установить взаимно однозначное соответствие между типами языка Java и типами языка Fortran, не используя промежуточных типов, как это реализовано в JNI. (2) Среда Fortran размещает данные в статической области памяти программы. К данным есть доступ только по ссылке, и нет возможности получить адрес памяти, где они расположены. Среда Fortran не поддерживает динамически создаваемых объектов данных. Среда C, во-первых, располагает данные программы в стеке, в куче и в статической области памяти программы, во-вторых, определена операция взятия адреса, позволяющие получить доступ не только к значениям данных, но и к адресам памяти, где они расположены. Соответственно, для передачи данных из Java-среды в C-среду JNI достаточно указать адрес области памяти, где данные хранятся. Передачу данных из Java-среды в Fortran-среду нельзя выполнить аналогично тому, как это сделано в JNI. (3) Все параметры в языке Fortran передаются только по ссылке, потому что в нем не определено понятие адреса переменной. В языке С параметры передаются только по значению, однако есть возможность передавать в качестве параметра функции указатели на ту область памяти, где хранится переменная. Соответственно, передачу данных из среды Java в среду Fortran и обратно нельзя выполнить аналогично тому, как это сделано в JNI. (4) В многомерных массивах языка С данные располагаются по строкам, тогда как в многомерных массивах языка Fortran данные располагаются по столбцам. В языке Fortran есть возможность непосредственно работать с частями массива – вырезками и сечениями. В языке С такой возможности нет. Следовательно, методика передачи массивов, реализованная в JNI, не может быть применена для среды Fortran. (5) В языке Fortran есть общие блоки COMMON. Эти блоки можно размечать по-разному в каждой подпрограмме. Так, например, в одной подпрограмме может быть объявлен массив типа complex размера 100, 30
расположенный в общем блоке /A/, а в другой подпрограмме на этой же памяти, то есть в том же общем блоке /A/ может быть объявлен массив типа real размера 200. Оба массива будут размещаться в памяти, начиная с одного и того же виртуального. Данные, которые в нем расположены – одни и те же, однако тип данных разный. В языке С аналогичная возможность может быть реализована посредством использования объявления union. Однако передача данных, расположенных в COMMON блоках с целью эффективности должна выполняться по схеме, отличной от той, которая реализована в JNI. Однако передача данных, расположенных в COMMON блоках с целью эффективности должна выполняться по схеме, отличной от той, которая реализована в JNI для передачи данных, объявленных в union. Учитывая то, что в реализации связывания подпрограмм, написанных на языке Fortran, с Java окружением должна быть сделана эффективная передача данных между Fortran-подпрограммами и основным Java-модулем, а так же принимая во внимание отличия языков С и Fortran, можно сделать вывод о том, что организация связывания между виртуальной машиной Java и подпрограммами, реализованными на языке Fortran, должна осуществляться по несколько иной схеме, нежели связывание C-методов и виртуальной машины Java.
3. Размещение данных в среде Fortran Программа, написанная на языке Fortran, допускает использование следующих видов программных единиц: стандартных функций, подпрограмм FUNCTION, подпрограмм SUBROUTINE, операторов – функций, подпрограмм, написанных на других языках программирования, и подпрограмм BLOCK DATA [2]. Формальные параметры языка Fortran передаются обычно по ссылке, за исключением тех случаев, когда параметр не модифицируется в подпрограмме [3]. В языке Fortran имеются средства, позволяющие использовать одну и ту же область памяти для хранения данных, общих для двух или более программных модулей выполняемой программы. Таким средством является общий блок [3]. Значения объектов из общего блока доступны всем программным единицам, в которых этот блок описан [2]. Каждый общий блок обязательно занимает в памяти непрерывный участок. Если некоторый программный модуль содержит несколько объявлений COMMON с одним и тем же именем, то все они рассматриваются как одно объявление и располагаются в памяти непрерывно и последовательно. Для объявления массивов в языке Fortran существуют специальные предложения спецификации: объявление размерности DIMENSION [3]. Так же массивы могут быть расположены в общих блоках. Массивы, полученные объявлением DIMENSION, представляют собой локальные данные той подпрограммы, внутри которой они описаны. 31
При вызове подпрограмм, реализованных на языке Fortran, из Java-окружения необходимо передавать данные из Java-среды в Fortran-среду. Также может возникнуть необходимость передавать данные из среды Fortran в среду Java, если вызываемая программная единица из среды Fortran – FUNCTION – возвращает значение. Java-среда передает все параметры только по значению, в среде Java нет методов работы с указателями, а все данные Java-программы расположены в куче. Любая подпрограмма, реализованная на языке Fortran, может получать данные извне либо как параметры, либо через общие блоки, которые в ней описаны. Если Fortran-подпрограмма получает данные для обработки через общие блоки, то вызывающая Java-программа должна иметь доступ на запись и чтение к той памяти, в которой эти общие блоки расположены. Такой доступ Java-программе возможно обеспечить, если на памяти, где располагается общий блок, разместить Java-объект. В качестве такого Java-объекта может быть взят прямой буфер класса Buffer, методы работы с которым доступны через пакет java.nio. Для того чтобы получить такой буфер, нужно из Fortran среды передать адрес начала общего блока и его размер. Дальше достаточно создать прямой буфер байтов, адрес начала которого будет совпадать с адресом начала общего блока, а размер будет такой же, как у соответствующего общего блока. Если Fortran-подпрограмма получает данные для обработки через формальные параметры, то для передачи таких параметров из Java-окружения необходимо выделить прямой буфер в Java-окружении, на который будут помещены передаваемые параметры. После того, как передаваемые параметры будут расположены на буфере, Fortran-подпрограмме нужно передавать только адрес этого буфера и смещение в нем, по которому расположен соответствующий параметр. Возврат данных из функций языка Fortran осуществляется по значению. Для передачи возвращаемого значения функцией языка Fortran в Java-окружения нужно это значение располагать в той области памяти, которая доступна и Java-окружению, и среде Fortran. Такой областью памяти с точки зрения среды Java может выступать прямой буфер. На нем необходимо выделить место для значения, возвращаемого функцией среды Fortran, и передать смещение в буфере Fortran-функции как параметр. А Fortran-функция запишет по полученному адресу возвращаемое значение.
4. Отображение типов данных языка Java в типы данных языка Fortran Основные типы языка Java и соответствующие им типы языка Fortran представлены в таблице 1. Данные для таблиц взяты из литературы [4] и [2].
32
Тип данных языка Java Int Short Long Byte Float Double Char Boolean
Требуемый объем памяти 4 байт 2 байт 8 байт 1 байт 4 байт 8 байт 2 байт 1 байт
Тип данных языка Fortran INTEGER INTEGER*2 INTEGER*8 CHARACTER REAL DOUBLE PRECISION CHARACTER LOGICAL*1
Таблица 1. Отображение примитивных типов языка Java в типы языка Fortran. Массив языка Java можно отобразить на такое представление данных языка Fortran как массив. Отображение массива языка Java на массив языка Fortran можно сделать через прямой буфер, средства работы с которым предоставлены в пакете «java.nio». Тип данных языка Требуемый объем Тип данных языка Fortran памяти Java INTEGER*2 2 байт short INTEGER 4 байт int INTEGER*4 4 байт int REAL 4 байт float REAL*4 4 байт float DOUBLE PRECISION 8 байт double REAL*8 8 байт double REAL*16 16 байт double double COMPLEX 8 байт float float COMPLEX*8 8 байт float float COMPLEX*16 16 байт double double COMPLEX*32 32 байт double double double double LOGICAL*1 1 байт byte LOGICAL 4 байт int LOGICAL*4 4 байт int CHARACTER 1 байт byte CHARACTER*L L байт string Таблица 2. Отображение примитивных типов языка Fortran в типы языка Java.
Основные типы языка Fortran и соответствующие им типы языка Java представлены в таблице 2. Данные для таблиц взяты из литературы [2] и [4] Для отображения данных, определенных в общем блоке, в окружении Java следует использовать прямой байт буфер. Такое отображение легко организовать, потому что общий блок представляет собой некоторую область памяти, хранящую неоднородные данные. Прямой байт буфер, доступный в Java-окружении также представляет собой область памяти, которая может хранить неоднородные данные. Для каждого как именованного, так и неименованного общего блока можно использовать по одному буферу. В языке Fortran массивом называется упорядоченная последовательность данных, занимающая непрерывную область памяти, к которой можно обращаться по имени [2]. Массивы характеризуются типом значений их элементов и граничными парами – диапазоном индексов по каждому измерению. Несмотря на то, что Fortran массивы могут быть как одномерными, так и многомерными, в памяти они располагаются как одномерный массив. Причем элементы многомерного массива располагаются в памяти таким образом, что значение первого индексного выражения возрастает быстрее второго, значение второго – быстрее третьего и т. д. [2]. Следовательно, приведенный индекс многомерного массива можно рассчитать по ниже приведенной формуле, а именно: пусть имеется многомерный массив arr[N, M, K], тогда приведенный индекс элемента arr[i, j, k] рассчитывается следующим образом: (i - 1) ( j - 1) * N (k - 1) * N * M . Массив языка Fortran следует отображать на прямой байт буфер, доступный в Java среде. Такое представление выгодно, потому что многомерный Fortranмассив в памяти располагается как одномерный массив. Для получения данных из прямого буфера соответствующих элементу многомерного Fortran-массива в Java окружении реализуется специальный класс. Так как многомерный массив не может иметь больше 7 измерений [5], то всегда можно автоматически получить данные из прямого буфера, соответствующие элементу многомерного Fortran-массива в Java окружении. При этом следует обойтись без транспонирования самого Fortran-массива. Для ссылки на элемент массива задается индексированная переменная; на массив в целом ссылаются по его имени. Начиная со стандарта Fortran 90, в языке есть возможность непосредственно работать с частями массива – вырезками и сечениями. Вырезка из массива представляет собой подмассив вида <имя_массива>(< нижняя граница – верхняя граница),
Данные в Fortran-программах могут быть представлены в виде констант или имен переменных (или идентификаторов). 33
34
Элементы вырезки из массива могут быть взяты с шагом, отличным от единицы. В этом случае вырезка по соответствующему измерению задается уже не граничной парой, а триплетом. <имя_массива>(< нижняя граница – верхняя граница, шаг >,…) Если по какому-то измерению опущены обе границы, то говорят о сечении массива. Вырезку из массива можно также задать с помощью векторного индекса[5]. Для отображения вырезки или сечения массива на объекты Java среды также можно использовать байт буфер. Такое отображение можно выполнить следующим образом. Весь массив отображается на буфер, а дальше в Javaсреде организуется специальный класс, содержащий методы получения и записи элементов вырезки и сечения массива посредством пересчета с учетом шага. Таким образом, любой массив языка Fortran можно отобразить на прямой байтовый буфер языка Java. Если массив размещен на общем блоке, он автоматически отобразится в Java окружение при отображении общего блока. Если массив определен посредством использования оператора DIMENSION, то для него надо создать прямой буфер, расположенный на том участке памяти, который компилятор языка Fortran выделил для хранения данного массива. Что касается вырезки и сечения массивов, то это представление данных можно отобразить через указатели на соответствующие элементы.
5. Вызов Fortran-подпрограмм из Java-среды При вызове Fortran-подпрограмм из Java-среды необходимо учитывать особенности чтения данных в Java- и Fortran-средах. Java-машина читает байты, в которые записано одно число, слева направо (прямое чтение), а в C- и Fortran- – программах порядок байт в записи чисел зависит от архитектуры. То есть, на некоторых платформах используется чтение справа налево (так называемое, инвертированное чтение). Следовательно, на некоторых платформах для корректной работы, данные, записанные Fortran-подпрограммой, нужно подвергнуть дополнительному преобразованию в формат языка Java, чтобы Java-программа прочитала их корректно. И наоборот, данные, записанные Java-программой, тоже надо подвергать обратному преобразованию в формат языка Fortran, чтобы подпрограмма, реализованная на языке Fortran, смогла прочитать именно то, что было помещено в Java-коде. В выше упомянутом преобразование предполагается менять местами соответствующие записи в ячейках. Такое преобразование необходимо осуществлять каждый раз, когда обработка данных, расположенных в памяти, общей и для Java-окружения, и для среды Fortran, передается от Java-машины Fortran-среде и обратно. 35
Такое преобразование предполагается целесообразным выполнять при каждой записи виртуальной машиной Java данных в общую память и при каждом считывании данных из общей памяти Java-машиной. Следовательно, среда Fortran всегда будет обрабатывать данные, записанные в формате языка Fortran, а Java-машина всегда будет работать с данными, записанными в формате языка Java.
6. Описание практической части Прототипная реализация выполнена посредством связывания вызова подпрограммы, реализованной на языке Fortran, из Java-программы через язык С (JNI). В настоящее время окружение Java не предоставляет возможности вызывать напрямую подпрограммы, реализованные на языке Fortran. Реализация выполнена для GNU компилятора Fortran (g77), GNU компилятора С (gcc) версии 3.3.4 и JDK версии 1.4.2_03. Компилятор g77 основан на стандарте ANSI Fortran 77, но он включает в себя многие особенности, определенные в стандартах Fotran 90 и Fortan 95 [6]. JDK версии 1.4.2_03 содержит пакет java.nio, который предоставляет возможность использования новых средств ввода-вывода таких как прямые буферы и JNI (Java Native Interface). Как уже отмечалось в пункте 2, прежде чем выполнить вызов подпрограммы, реализованной на языке Fortran, из Java-среды, необходимо выделить область памяти, которая была бы доступна как из Java-окружения, так и из среды Fortran. Для этого нужно: 1. На языке Fortran реализовать подпрограмму. В этой подпрограмме должны быть объявлены все общие блоки, которые будут использоваться для обмена данными Fortran-среды с Java-окружением. 2. На языке С должен быть реализован модуль, который через разделяемую библиотеку посредством JNI будет вызываться из Javaсреды. Модуль должен содержать функцию, которая вызывается из среды Fortran. Данной функции, в качестве параметров по ссылке, из Fortran-среды передается адрес первого, адрес последнего элемента и размер в байтах последнего элемента общего блока. По полученным данным вычисляются и сохраняются начало и размер общего блока. Такая функция вызывается для каждого общего блока. Некоторая функция вычисляет и сохраняет размер общего блока, а так же сохраняет адрес начала общего блока. Теперь во встроенном модуле, реализованном на языке С, хранятся адреса и размеры всех общих блоков, которые определены в подпрограмме, реализованной на языке Fortran. Следовательно, запросив по указанному адресу прямой буфер нужного размера, будет получено размещение нового байт-буфера Javaсреды на том же участке памяти, что и соответствующий ему общий блок. 36
3. На языке Java реализуется класс, который содержит метод инициализации и метод получения прямого байт-буфера. Метод инициализации вызывает встроенный метод инициализации, реализованный на языке С в описанном в пункте 2 модуле. Встроенный метод инициализации вызывает Fortran-подпрограмму, описанную в пункте 1. Метод получения прямого байт-буфера вызывает встроенный С-метод, который заказывает в оперативной памяти прямой буфер нужного размера, начиная с указанного адреса. Дальше полученный прямой байт-буфер уже сам пользователь может представлять как буфер тех данных, которые ему нужны. Байт-буферы расположены непосредственно в том же участке памяти, что соответствующие им общие блоки, следовательно, все данные, которые записываются в прямой буфер в Java-коде, автоматически становятся доступными из общего блока в коде, реализованном на языке Fortran. И наоборот: все, что помещено в общий блок в Fortran-подпрограмме, автоматически становится доступно из прямого буфера в Java-программе. Такое расположение данных полностью решает поставленную в пункте 1 задачу о совместном размещении данных Java-окружения и среды Fortran на одном участке памяти. Чтобы выполнить вызов Fortran-подпрограммы из Java-среды нужно сделать: 1. В Java-среде нужно расположить параметры для передачи в среду Fortran на прямом буфере. Этот прямой буфер передается в качестве параметра вспомогательным С-функциям, которые описаны в пункте 2. Так же, в качестве параметра передается смещение в буфере, по которому расположены передаваемые параметры. 2. На языке С реализовать встраиваемый через JNI в Java-окружение модуль. В этом модуле реализуются вспомогательные функции для каждой вызываемой Fortran-подпрограммы из Java окружения. Каждая такая вспомогательная функция вызывается из Java-программы. Одним из ее действий является непосредственный вызов Fortran-подпрограммы. Также вспомогательная функция выполняет передачу параметров из Java-окружения в среду Fortran, как это описано в пункте 2. То есть вспомогательная функция получает адрес буфера, вычисляет адреса параметров, зная смещения их расположения в буфере, и передает вычисленные адреса Fortran-подпрограмме. 3. На языке Java реализуется класс, который занимается записью и чтением данных из общей для Fortran-среды и Java-оболочки памяти. При этом при записи выполняется преобразование данных из формата языка Java в формат языка Fortran, а при чтении выполняется преобразование данных из формата языка Fortran в формат языка Java, как это описано в пункте 2.
37
7. Накладные расходы В предложенной реализации накладные расходы возникают при вызове метода инициализации прямых Java-буферов, но эти накладные расходы возникают только один раз за все время работы программы, поэтому время, которое на них тратится, не существенно влияет на общую производительность программного продукта. Накладные расходы возникают при преобразовании данных из формата языка Java в формат языка Fortran. Однако полное преобразование данных из одного формата в другой есть необходимость выполнять только дважды за работу всего приложения: в начале, после инициализации, и в конце, перед тем, как вывести окончательный результат работы приложения. Следовательно, эти накладные расходы тоже считаются разовыми и не существенно влияют на время выполнения программного продукта. Однако возникают еще накладные расходы, когда данные обрабатываются не только в Fortran-подпрограммах, но и в основной программе, написанной на языке Java. В этом случае при каждом переключении есть необходимость преобразовать данные из одного формата в другой. Но, как правило, объем данных обрабатываемый сразу и в Java-коде и в коде, реализованном на языке Fortran, не очень велик. Следовательно, не следует преобразовать сразу все данные, которые рассчитываются в приложении, а нужно преобразовать только тот их фрагмент, который нужен для обработки. Такой подход позволит сократить накладные расходы на преобразование данных. Именно эти накладные расходы следует учитывать при оценке времени работы программного приложения.
8. Пример Чтобы убедиться в корректности работы реализации была взята программа расчета динамики взрыва сверхновой звезды, реализованная на языке Fortran. [7]. Основная функция main, которая управляет расчетами, была переписана на язык Java. Остальные подпрограммы оставлены на языке Fortran. Результаты работы исходной программы, реализованной только на языке Fortran, и программы, основная часть которой реализована на языке Java, а подпрограммы выполнены на языке Fortran, одинаковые. Для сравнения времени работы полученного приложения, реализованного на языке Java с использованием Fortran-подпрограмм, было произведено сравнение с точно таким же приложением, но реализованным целиком на языке Fortran и на языке Java. Приложение можно представить в виде следующей схемы, представленной на Рис. 1.
38
только на языке Java, данные из файла записываются в массивы языка Java, а в приложении, реализованном на языках Java+Fortran, - в прямые буферы.
Инициализация данных
Инициализация данных
счет
Счет
300000
70000 60000
250000
50000
200000
Fortran 40000
запись данных в файлы
Java + Fortran
30000
Fortran Java + Fortran
150000
Java
Java
100000
20000 50000
10000
Рис.1. Схема приложения.
1
1
В приложении, реализованном на языках Java+Fortran, инициализация данных и запись данных в файлы выполняется в Java-окружении, а счет выполняется в Fortran-среде. Для сравнения времени выполнения были выполнены замеры, как скорости работы всего программного приложения, так и отдельных его частей, в соответствии с Рис. 1. Замеры проводились на персональном компьютере. Размер оперативной памяти 512 MB, частота процессора 1700 MHz. Характеристики кэш-памяти процессора следующие: CPU L1 Cache: 64K (64 byte/line), CPU L2 Cache: 526K (64 byte/line) Сравнение времени работы представлено в таблице 3 и на Рис. 2. Fortran Java + Fortran Java
0
0
полное приложение (ms)
инициализация (ms)
счет (ms)
запись (ms)
261559 266223 337225
42826 43540 69874
218450 221623 265727
283 1060 1624
а)
б) Счет
Запись данных в файл
1800
300000
1600 250000
1400
200000
1200
Fortran
Fortran
1000
Java + Fortran
150000
Java + Fortran
800
Java 100000
Java
600 400
50000
200 0
0
1
в)
1
г) Рис. 2. Время выполнения.
Таблица 3. Сравнительная производительность. Как видно в таблице 4 и на Рис. 2 (а) реализация приложения на языках Java+Fortran не значительно проигрывает по времени выполнения приложению, реализованному только на языке Fortran. Это достигается за счет того, что в приложении, реализованном на Java+Fortran, вычисления полностью выполняют в Fortran-среде. Однако приложение, реализованное на языках Java+Fortran, значительно быстрее работает, нежели приложение, реализованное на языке Java. Как видно на Рис. 2 (б), 2(в), 2(г) в приложении, реализованном только на языке Java, не только вычисление занимает больше времени, нежели в приложении, реализованном на языках Java+Fortran, но и инициализация и запись данных в файл. Потеря времени происходит за счет того, что большая часть инициируемых данных берется из файла. В приложении, реализованном 39
9. Некоторые ограничения реализации приложения пользователя Fortran-подпрограммы, которые вызываются из Java-среды, не должны содержать символа «подчеркивание» в своем имени. В противном случае разделяемая библиотека не сможет сопоставить реализованные в ней методы с теми, которые вызываются. Это вызовет падение работы всего приложения. Компилятор GNU g77, разрешает использование переменных без их явного описания. Однако если явно не определить тип переменной в подпрограмме, которая вызывается из Java-окружения, вероятен случай, что виртуальная Javaмашина получит внешний сигнал. Этот сигнал, номер которого 11, сообщает виртуальной машине о некорректном обращении к памяти за пределами ее работы. Аналогичная ситуация может возникнуть и с функциями. При 40
описании функций стандартом предусмотрено описывать явно тип возвращаемого значения. Однако если этого не сделать, то компилятор сам подберет соответствующий тип, исходя из типа возвращаемого выражения. Если такую функцию вызывать из программы, реализованной только на языке Fortran, то все стабильно будет работать. Но, как только объектный модуль с такой функцией участвует в формировании разделяемой библиотеки и подобного рода функция вызывается подпрограммой, которая в свою очередь вызывается виртуальной машиной Java, выполнение основной программы прекращается по причине получения виртуальной Java-машиной сигнала номер 11. Данные ограничения в дальнейшем развитии работы будут сняты посредством автоматического добавления в код Fortran-программы недостающих описаний.
10. Заключение Разработана организация взаимодействия среды Java и подпрограмм, реализованных на языке Fortran. Была выполнена прототипная реализация. Прототипная реализация показала, что описанная методика вызова подпрограмм, реализованных на языке Fortran, из окружения Java вызывается с минимальными накладными расходами, а, следовательно, эффективна. Дальнейшее развитие предполагает разработку методики рефакторинга Fortran-программ с целью преобразования их в такой вид, какой было бы удобно автоматически транслировать на язык Java. Литература 1. 2. 3. 4.
Б.Керниган, Д.Ритчи. Язык программирования Си. Санкт-Петербург, 2001 Фортран 77 ЕС ЭВМ. Справочное издание. Москва «Финансы и статистика», 1989 Фортран. Программированное учебное пособие. Киев «Вища школа», 1980 У.Савитч. Язык Java. Курс программирования. Москва – Санкт-Петербург – Киев «Вильямс», 2002 5. Ю.И. Рыжиков. Современный фортран. Санкт-Петербург «Корона принт», 2004 6. Артур Гриффитс. GCC. Полное руководство. Москва – Санкт-Петербург – Киев DiaSoft, 2004 7. С. Д. Устюгов, В. М. Чечеткин. Взрыв сверхновой при крупномасштабной конвективной неустойчивости вращающейся протонейтронной звезды. // Астрономический журнал, 1999, том 76, №11, с. 816-824.
41
Метод виртуального процессора в защите программного обеспечения П.В. Бойко (
[email protected]) Аннотация. В статье рассматривается метод защиты программного обеспечения от изучения с помощью переноса защищаемого кода в виртуальную среду исполнения. Проводится анализ эффективности, а так же недостатков метода. Предлагается вариант реализации, позволяющий снизить себестоимость разработки.
1. Введение Как известно, идеального способа защиты программного обеспечения (ПО) не существует, в связи с этим, разработчики защитных систем не стремятся лишить потенциального взломщика самой возможности нейтрализации защиты, но стараются максимально усложнить этот процесс. Защита может решать одну или комплекс из множества задач, таких как защита от копирования, нелегального использования, модификации и др., но какая бы конечная цель ни стояла перед таким продуктом, разработчикам каждого из них прежде всего необходимо решить общую для всех проблему – качественной защиты от изучения. Какие бы ни применялись алгоритмы защиты ПО, их стойкость к обратной инженерии определяет стойкость всей системы защиты в целом. Сегодня на рынке существует большое количество коммерческих защит, однако многие из них, в т.ч. до сих пор популярные, давно взломаны. Зачастую их подводит именно слабая защищенность от изучения. После анализа взломщиком алгоритмов работы защиты, серийные ключи генерируются, аппаратные – успешно эмулируются. Ситуацию могла бы исправить разработка эффективного метода защиты ПО от изучения, применяя который к алгоритмам других защит, позволила бы качественно поднять их уровень.
2. Методы защиты ПО от изучения Рассмотрим, какие методы существуют для защиты ПО от изучения: - запутывание – искусственное усложнение кода, с целью затруднить его читабельность и отладку (перемешивание кода, внедрение ложных процедур, передача лишних параметров в процедуры и т.п.); 43
- мутация – при каждом запуске создаются таблицы соответствия операций, сами операции заменяются на синонимы; - компрессия, шифрование – изначально программа упаковывается / шифруется, и производит обратный процесс по мере выполнения; - симуляция процессоров – создается виртуальный процессор; защищаемая программа компилируется под него, и выполняется на целевой машине с помощью симулятора. Существуют и другие методы, а так же их комбинации и разновидности, однако, нетрудно заметить, что все они основаны на одной простой идее: избыточности. В самом деле, что такое запутывание, как не избыточное кодирование программы? Лишние переходы, лишние параметры, лишние инструкции – ключевое слово метода «лишние». То же касается любого из перечисленных методов, и, вероятно, было бы естественным объединить все эти методы в одну группу «Методов избыточного кодирования». Чем же так хороша избыточность, ведь интуитивно понятно, что она увеличивает размер программы и снижает скорость ее работы? Дело в том, что во всех этих разновидностях защиты используется понимание «человеческого фактора» человеку тем сложнее понять логику какого-либо процесса, чем больше ресурсов этот процесс использует. Например, функциональность одной простой инструкции загрузки константы на регистр может быть «размазана» на десятки, а то и сотни инструкций, и проследить связь всех используемых ресурсов (регистров, памяти и др.) в этой последовательности человеку довольно сложно. Метод шифрования с этой точки зрения не является чем-то особенным – так же, как и в других методах, для выполнения простой инструкции (или группы) требуется избыточная последовательность команд – в данном случае это операции расшифровки, плюс операции расшифрованного кода. Однако то, что автоматически «запутано» или усложнено, может быть так же автоматически приведено в первоначальное состояние – разработчики механизмов запутывания обычно параллельно разрабатывают и «распутыватели», а методы мутации и шифрования и вовсе подразумевают содержание обратного механизма в защищенном коде. Особняком в этой группе методов стоит лишь метод симуляции виртуального процессора, который, во-первых, приводит к высокой и неснижаемой степени запутанности результирующего кода, а, во-вторых (при определенном подходе к реализации), защищенный код не содержит в явном виде методов восстановления оригинального кода. Рассмотрим этот метод подробнее.
3. Виртуальный процессор в защите ПО Суть метода такова: некоторые функции, модули, или программа целиком, компилируются под некий виртуальный процессор, с неизвестной потенциальному взломщику системой команд и архитектурой. Выполнение обеспечивает встраиваемый в результирующий код симулятор. Таким образом, 44
задача реинжиниринга защищенных фрагментов сводится к изучению архитектуры симулятора, симулируемого им процессора, созданию дизассемблера для последнего, и, наконец, анализу дизассемблированого кода. Задача эта нетривиальна даже для специалиста, имеющего хорошие знания и опыт в работе с архитектурой целевой машины. Взломщик же не имеет доступа ни к описанию архитектуры виртуального процессора, ни к информации по организации используемого симулятора. Стоимость взлома существенно возрастает. Почему же, учитывая высокую теоретическую эффективность, данный метод до сих пор не используется повсеместно? Видимо по двум основным причинам. Во-первых, метод имеет особенности, что сужает области его потенциального применения – об этом будет сказано ниже. Во-вторых, и, возможно, это более серьезная причина, сложность (а следовательно и стоимость) реализации метода весьма высока. Если же учесть принципиальную возможность утечки информации о только что созданной системе, которая моментально приведет к ее неэффективности и обесцениванию, становится понятно, почему фирмы-производители защитного ПО не спешат реализовывать этот метод. Стоит, однако, отметить, что с теми или иными вариациями и ограничениями данный метод все же реализован в таких новейших продуктах как StarForce3, NeoGuard, VMProtect и др. Видимо таких продуктов будет становиться все больше и больше, а существующие будут развиваться, т.к. появляющиеся реализации подтверждают высокую эффективность метода, хоть и имеют пока слабые стороны.
3.1. Реализация метода Одним из недостатков метода является высокая стоимость его реализации, однако она может быть заметно снижена. В основе системы защиты, реализующей данный метод, мог бы лежать компилятор с языка высокого уровня. Необходимо машинно-зависимая фаза в любом компиляторе всего одна – кодогенерация, от зависимостей в других фазах, как правило, можно избавиться. Если же компилятор изначально разрабатывается как мультиплатформенный, в нем, как правило, максимально упрощен процесс перенастройки на другую целевую платформу. Например, это может быть достигнуто автоматической генерацией кодогенератора по специальному описанию целевой машины. В этом случае разработчикам для смены платформы достаточно лишь изменить это описание. Но даже если собственного компилятора нет, можно воспользоваться свободнораспространяемыми с открытым кодом, например, GCC. А чтобы максимально упростить для пользователя работу с описываемой системой защиты, ее можно снабдить механизмами встраивания в популярные среды разработки, такие как MSVC. В этом случае схема работы такого комплекса могла бы выглядеть так, как изображено на Рис. 1. 45
Рис. 1. Схема защиты ПО Соответственно, функционирование защищенного таким способом образом продукта происходило бы по схеме на Рис. 2. Специфика использования компилятора налагает ряд особых требований к виртуальному процессору, тем не менее все они могут быть легко реализованы. Требований немного – нужно лишь обеспечить возможность доступа к внешней, относительно виртуальной машины, памяти, а так же возможность вызова внешних функций – это необходимо для взаимодействия защищенного и незащищенного кода. В остальном архитектура виртуального процессора может быть совершенно произвольной, и чем запутаннее и оригинальней она будет, тем более высокий уровень защиты будет достигнут. Сам компилятор, кроме изменения кодогенерационной фазы, нужно доработать для приобретения им возможностей: 46
- различать обращения к внутренней и внешней памяти относительно виртуального процессора (в том числе и вызовы функций); - создавать для каждой защищаемой функции так называемую оболочку, выполняющуюся на реальном процессоре, с вызовом защищенной функции через симулятор.
Начало
Незащищенные модули
Вызовы защищенных функций
Симулятор виртуального процессора
Рис. 3. Схема вызова защищенной функции
Конец Защищенные модули (псевдокод)
Рис. 2. Схема работы защищенного программного продукта Последнюю возможность следует описать подробнее. Как было отражено в схеме на Рис. 2, функционал защищенных функций будет реализован через вызов симулятора, с указанием, какую из защищенных функций нужно интерпретировать. Однако, до этого, необходимо выполнить специальный код, подготавливающий для защищенной функции параметры - "переместить" их с реальных регистров и памяти на виртуальные, способом, соответствующим архитектуре виртуального процессора. Всем этим будут заниматься специальные функции, сгенерированные нашим компилятором - "оболочки". Оболочки, в свою очередь, будут использовать специальные функции симулятора для доступа к виртуальным регистрам и памяти. Характерно, что наш компилятор будет генерировать оболочки на языке высокого уровня, которые, в свою очередь, будут компилироваться стандартным компилятором, использующимся пользователем для сборки незащищенной части своего проекта. Итак, вызов защищенной функции из незащищенного модуля может выглядеть так, как изображено на Рис. 3. 47
Конечно, конкретная реализация метода виртуального процессора может быть несколько отличной от описываемой, как и схема работы защищенного им продукта. Тем не менее, описанный вариант вполне жизнеспособен, и, кроме того, относительно прост. Сердце защищенного продукта – симулятор. Он будет включаться в любую сборку защищенного продукта. Однако не будем подробно рассматривать его реализацию, т.к. специальных требований к нему практически не предъявляется – он должен лишь симулировать архитектуру нашего виртуального процессора, включая операции доступа к внешней памяти. Стоит, однако, отметить, что с учетом специфики его применения, необходимо максимально автоматизировать процесс перенастройки симулятора на новые виртуальные архитектуры. Недостатки же метода – следствие его достоинств: - скорость работы перенесенного в виртуальную среду кода в разы (ориентировочно в 10-50, в зависимости от архитектуры виртуального процессора и симулятора) ниже, чем кода оригинального; - объем защищенной программы, как правило, будет несколько выше, чем незащищенной. Впрочем, последний недостаток несущественен, т.к. размер увеличится незначительно, а в некоторых случаях может даже снижаться. Первый же недостаток принципиален, и налагает некоторые очевидные ограничения на использование метода.
48
4. Заключение Рассмотренный метод защиты ПО весьма эффективен, учитывая то, что затраты на его разработку можно существенно сократить. Однако, особенности метода не позволяют рекомендовать его для защиты программ полностью. Так, метод не может применяться для защиты функций, критичных ко времени выполнения, а так же функций, замедление работы которых может заметно снизить эффективность использования программы пользователем. Тем не менее, аккуратное применение данного метода позволяет добиться очень высокого уровня защиты от изучения. В связи с этим, основной областью его применения видится повышение стойкости к изучению отдельных алгоритмов других систем защиты ПО. Кроме того, метод применим для защиты нересурсоемких алгоритмов ноу-хау, а так же для сокрытия содержания в защищаемой программе некоторых специальных данных, например, сведений об авторстве.
49
Использование информации о линейных зависимостях для обнаружения уязвимостей в исходном коде программ В.С. Несов, О.Р. Маликов
1. Введение Одним из методов обнаружения потенциальных уязвимостей в программе является статический потоково-чувствительный data-flow анализ. В ходе проведения такого анализа для каждой точки программы собирается информация о различных атрибутах объектов программы, которая затем проверяется на выполнение условий корректности операций с памятью и использования библиотечных функций в различных точках программы. Серьезной проблемой такого метода обнаружения уязвимостей является большое количество ложных предупреждений. Часто ложные предупреждения вызваны недостаточной точностью определяемой информации об атрибуте значение некоторой переменной (объекта программы). Самым простым подходом определения информации о целочисленных значениях является анализ на основе интервальных оценок. Каждому целочисленному атрибуту объектов программы в данной точке программы сопоставляется числовой интервал значений, при этом зависимости между атрибутами не учитываются. Например, если атрибуту x сопоставляется интервал возможных значений [a,b], атрибуту y – интервал [c,d], то результату операции сумма z=x+y сопоставляется интервал [a+c,b+d]. Такая модель хорошо работает для не связанных друг с другом атрибутов.
2. Анализ на основе линейных зависимостей Многие условия корректности операций в программе представляют собой линейные соотношения (равенства либо неравенства) между значениями числовых атрибутов объектов программы. Например, при обращении к массиву проверяется, лежит ли индекс, по которому происходит обращение, в пределах массива. В случае если и длина массива, и значение индекса в широких пределах произвольны, ответить на вопрос о возможности выхода за 51
пределы массива при отсутствии информации о зависимости между индексом и длиной массива невозможно. Подход, предлагаемый нами к использованию в рамках разрабатываемой системы обнаружения уязвимостей, состоит в поддержании системы линейных неравенств, выполняющихся для числовых атрибутов в данной точке программы. При потоково-чувствительном анализе потока данных в каждой точке программы (контексте) хранится информация о большом количестве числовых атрибутов. Так как одной из задач разрабатываемой системы являлась возможность анализа программ промышленного масштаба, были применены различные методы, ограничивающие вычислительные издержки, возникающие при учете линейных зависимостей. Если при анализе требуется определить соотношение между значениями атрибутов (проверить некоторое равенство или неравенство), то переносом всех атрибутов в одну часть соотношения задача сводится к определению множества возможных значений атрибута, равного полученному в этой части выражению. Например, при проверке выхода за пределы массива требуется проверить, меньше ли индекс в массиве x размера массива l, x
3. Многогранное множество Многогранным называется множество, задаваемое системой линейных неравенств. Каждое многогранное множество может быть представлено двумя способами: в виде системы неравенств, либо в виде множества образующих вершин и лучей (геометрическое представление). В геометрическом представлении многогранное множество равно сумме выпуклого замыкания множества вершин и конического замыкания множества лучей. На рисунке {a,b} – множество вершин, {q,s} – множество лучей. Наличие обоих представлений и возможность перехода между ними позволяет производить различные преобразования многогранных множеств при помощи простых алгоритмов. Например, пересечение многогранных множеств задается объединением систем неравенств. Выпуклая оболочка объединения 52
многогранных множеств задается объединением соответствующих множеств геометрического представления.
Для многогранного множества вводятся две оценки сложности: количество атрибутов и количество существенных ограничений неравенствами. Предельное количество атрибутов влияет на степень вершин ГЛС. Чрезмерное повышение этого порога приводит к тому, что при обходе в ширину в ГЛС быстро собирается большое количество посторонних линейных связей (поднимая вторую оценку сложности), до того как в строящееся многогранное множество включается достаточное количество связей, важных для описываемого атрибута. Эксперименты на тестовом наборе программ показали, что оптимальным значением является порог порядка 15 атрибутов. Количество существенных ограничений неравенствами определяет вычислительную сложность операций над многогранным множеством. Для тестового набора программ порог, приводящий к удвоению времени анализа программ при учете линейных связей по сравнению с анализом без учета линейных связей, составляет порядка 20 ограничений. Будем далее называть операцию выделения многогранного множества из ГЛС для данного атрибута замыканием атрибута.
4.1. Выделение системы линейных связей из многогранного множества Для перехода между представлениями многогранного множества используется алгоритм Черниковой с оптимизацией Ле Вержа (H. Le Verge) [1]. Сложность различных операций над многогранными множествами не позволяет применять их к многогранному множеству, описываемому всей системой линейных связей контекста. Поэтому для применения операций, требующих представления в виде многогранного множества, необходимо выделять подсистему линейных связей.
4. Граф линейных связей Введем для дальнейшего рассмотрения ориентированный граф линейных связей (ГЛС). Вершинам этого графа сопоставлены атрибуты, ребро (u,v) присутствует в графе, когда в систему линейных связей атрибута u входит атрибут v. Структура ГЛС существенно зависит от метода выделения системы линейных связей из многогранного множества, полученного в качестве результата преобразования. Этот вопрос будет раскрыт далее. За счет структуры ГЛС для выделения приемлемого многогранного множества, описывающего данный атрибут, достаточно собрать систему из линейных связей вершин ГЛС, встречающихся при обходе ГЛС в ширину, начиная с описываемого атрибута. Добавление линейных связей прекращается, как только одна из оценок сложности получаемого многогранного множества превышает соответствующее пороговое значение. 53
После выполнения преобразования многогранного множества необходимо сохранить результат в атрибуте. Многогранное множество часто получается в результате замыкания атрибута и содержит слишком большое количество линейных связей. Так как при всех операциях каждый раз модифицируется только небольшое количество атрибутов, то, вообще говоря, изменяются только линейные связи, содержащие эти атрибуты. Поэтому из многогранного множества извлекаются только линейные связи, включающие в себя данные атрибуты. Так как системы линейных связей хранятся отдельно для различных атрибутов, часть полученных линейных связей может оказаться следствием связей, получаемых из окружающих вершин ГЛС. Сохранение таких связей излишне, так как при замыкании линейные связи окружающих вершин и так будут учтены. Поэтому такие связи исключаются.
5. Пример В таблице приведен код примера и значения атрибутов при проведении итераций статического анализа. Для каждой строки кода для данной итерации приведены ограничения, справедливые на выходе из инструкций строки, либо, в случае ветвления, ограничения на входе и выходе из инструкции. Например, в приведенном примере контекст на входе в инструкции строки 4 получается в результате объединения контекстов на выходе из инструкций строк 3 и 6.
54
{
В примере имя атрибута s.len ссылается на атрибут длина строки s. Выписываются не все ограничения, а лишь важные для окончательных выводов. Как видно, в результате анализа данного примера определяются оценки значений атрибутов, позволяющие исключить ошибки переполнения массивов. При анализе на основе интервальных оценок такие ошибки исключены не были бы. код
итерация 1
ограничения итерация 3 (расширение)
итерация 2
i=0 s1.len[0,1023] i=0
i[0,1]
i[0,+)
i=0, i<s1.len
i[0,1], i<s1.len
i[0,1022], i<s1.len
– || – – || – i[0,1023], i<=s1.len i[0,1022], i<s1.len
(s1.len-i-1) [0,1022]
– || –
– || –
– || –
while(i<strlen(s1)){
s2[strlen(s1)-i-1]=s1[i]; i++;}
– || – – || –
– || – – || –
i=1
i[1,2]
i=0, i>=s1.len
i[0,1], i>=s1.len
i[1,1023], i>=s1.len i[0,+), i>=s1.len
s2.len=0
s2.len[0,1], s2.len<=i
s2.len[0,+), s2.len<=i
s2[i]=0;
} return 0;
} В данном случае при вызове функции всегда len<=buf.size, при изменении len и buf на одну и ту же величину соотношение не нарушается. Так как возвращаемое значение функции write n<=len, так же сохраняется условие len>=0 (уточняемое условием цикла до len>0). За счет выполнения этих соотношений вызов функции write происходит корректно.
итерация 4
char s1[1024], s2[1024]; unsigned int i=0; fgets(s1,1024,stdin);
n = write(socket, buf, len); if (n <= 0) return -1; len -= n; buf += n;
6. Результаты
– || – i[0,1023], i=s1.len s2.len [0,1023], s2.len<=s1.len
5.1. Примеры устраненных ложных предупреждений bftpd, mystring.c, строка 16: void cutto(char *str, int len) { memmove(str, str + len, strlen(str) - len + 1); } При корректных вызовах функции переполнения буфера не возникает. Требуемое условие в данном случае str.size>str.len-len, где str.size и str.len – соответственно атрибуты массива str, отражающие его длину и длину хранящейся в нем строки. popclient, socket.c, строка 112: int SockWrite(socket,buf,len) int socket; char *buf; int len; { int n; while (len) 55
На тестовом наборе из 7 программ с открытым исходным кодом были получены следующие результаты. В таблице 1 указано количество истинных и ложных предупреждений, выдающихся системой обнаружения уязвимостей до и после использования учета линейных зависимостей между атрибутами. Количество Количество ложных Название Общее количество истинных предупреждений программы предупреждений предупреждений при анализе на основе интервалов bftpd 54 20 34 lhttpd 22 5 17 muh 47 12 35 pgp4pine 45 16 29 popclient 34 7 27 sharutils 49 11 38 troll-ftpd 47 2 45
Количество ложных предупреждений, устраненных при использовании линейных зависимостей 7 0 2 3 12 7 2
Таблица 1. Результаты тестирования Литература 1. H. Le Verge. A note on Chernikova’s Algorithm. July 27, 1994. 2. P. Cousot and N. Halbwachs. Automatic discovery of linear restraints among variables of a program. In 5th ACM Symposium on Principles of Programming Languages, POPL’78, Tucon (Arizona), January 1978. 3. N. Halbwacht, Y.E. Proy, and P. Roumanoff. Verification of real-time systems using linear relation analysis. Formal Methods in System Design, 11(2):157-185, 1997. 4. Бирюков С.И. Оптимизация. Элементы теории. Численные методы. МЗ-Пресс, 2003.
56
Критерии полноты тестового покрытия в генетических алгоритмах генерации тестов1
2. Основные понятия 2.1. Генетические алгоритмы
М.А. Владимиров
[email protected] Аннотация. В статье описывается применение генетических алгоритмов для автоматической генерации тестов. Проводится анализ некоторых широко распространённых критериев полноты на предмет их применимости для построения тестов с помощью генетических алгоритмов. Строятся оценочные функции, соответствующие этим критериям.
1. Введение При разработке и сопровождении программного обеспечения, значительная часть усилий тратится на поиск и устранение ошибок. Самым распространённым методом поиска ошибок является тестирование, то есть процесс выполнения программ с целью обнаружения ошибок [1]. Здесь слово «программа» понимается в широком смысле, как любая запись алгоритма. В частности, программами являются отдельные процедуры, функции, классы и т.д. Процесс тестирования включает выполнение некоторого набора тестов и анализ полученных результатов. Тест — это последовательность обращений к тестируемой программе. Результатом выполнения теста является решение (вердикт) о том, отработала ли программа корректно или некорректно. Основной характеристикой тестового набора, определяющей качество тестирования, является класс возможных ошибок в программе, которые данный тестовый набор способен обнаружить. Для количественной оценки качества тестирования используются различные метрики тестового покрытия [9]. Для качественного тестирования необходимо построить полный тестовый набор, то есть набор, удовлетворяющий некоторому критерию полноты. Зачастую критерий полноты для тестового набора определяют через пороговое значение метрики тестового покрытия. Построение полного тестового набора для больших систем вручную может быть крайне трудоёмкой задачей. Автоматизация этого процесса позволяет 1
существенно снизить затраты на тестирование. Существуют различные подходы к решению задачи автоматической генерации тестов: [3,4,6]. Один из них основан на применении генетических алгоритмов [8]. Этот подход во многих случаях даёт хорошие результаты. К сожалению, его эффективность существенно зависит от используемого критерия полноты. Цель данной статьи — проанализировать некоторые широко распространённые критерии полноты тестового набора на их применимость при использовании генетических алгоритмов для генерации тестов.
Генетические алгоритмы — это метод решения задач оптимизации. В методе используются идеи, почёрпнутые из эволюционной биологии: наследование признаков, мутация, естественный отбор и кроссовер [7]. Определяется множество кандидатов, среди которых ищется решение задачи. Кандидаты представляются в виде списков, деревьев или иных структур данных. Общая схема генетического алгоритма выглядит следующим образом: создать начальный набор кандидатов; оценить качество каждого кандидата в текущем наборе; выбрать пары наиболее качественных кандидатов для воспроизводства; применить оператор кроссовера; применить оператор мутации; если не выполнено условие останова, перейти к шагу 2. Начальный набор кандидатов, как правило, формируется случайным образом. На множестве кандидатов определяется оценочная функция, задающая качество кандидата, то есть то, насколько он близок к верному решению. При выборе кандидатов для воспроизводства более качественные кандидаты имеют больше шансов. По двум выбранным кандидатам предыдущего поколения оператор кроссовера строит кандидата следующего поколения. Оператор мутации вносит малые случайные изменения кандидатов. Алгоритм завершается, когда выполняется условие останова. Часто используются следующие условия останова: достигается заданное количество поколений; найдено верное решение; за заданное количество итераций максимальное качество кандидатов в популяции не улучшилось; различные комбинации предыдущих условий. Генетические алгоритмы позволяют решать задачи, для которых не применимы традиционные методы оптимизации. Одной из областей применения генетических алгоритмов является автоматическая генерация тестов для программного обеспечения.
Работа поддержана грантом РФФИ (05-01-999). 57
58
2.2. Критерии полноты тестового покрытия Для тестирования программного обеспечения требуется создать репрезентативный набор тестов, то есть набор, охватывающий все возможные сценарии работы системы. Для оценки репрезентативности тестовых наборов используются различные критерии полноты тестового покрытия.
тестовый набор убивает всех мутантов из заданного набора. Заметим, что все критерии, приведённые в качестве примеров, соответствуют ранее изложенной схеме.
2.3. Метрики тестового покрытия Со многими критериями полноты тестового покрытия можно связать соответствующую метрику тестового покрытия. Метрика тестового покрытия — это функция вида M P R . Значение этой функции M ( S ) имеет смысл числовой оценки того, насколько хорошо тестовый
Пусть P — множество программных систем, T — множество тестов, а — множество тестовых наборов, то есть множество всех конечных подмножеств множества T . Тогда задача генерации тестов может быть сформулирована следующим образом: для заданной тестируемой системы S P построить тестовый набор , удовлетворяющий заданному критерию полноты тестового покрытия F P {• } , то есть такой набор , для
набор покрывает тестируемую систему S . Сам критерий при этом можно записать в виде M ( S ) S , где S — это минимальное пороговое
которого F ( S ) • .
значение метрики M для тестируемой системы S .
Многие критерии полноты тестового покрытия, имеющие практическое применение, строятся по следующей схеме: для тестируемой системы S
В частности, для критерия полноты тестового покрытия виде (1), можно ввести следующую метрику:
критерий F
определяет множество элементов тестового покрытия
Элементом тестового покрытия можно считать некоторый класс событий, которые могут произойти в ходе работы тестируемой программной системы. По появлению в процессе исполнения программы элементов тестового покрытия и различных их комбинаций можно судить о полноте или качестве проверки, которую выполняет данный тестовый набор. Например, элементами тестового покрытия могут быть исполняемые строки исходного кода (соответствующие событиям их исполнения); рёбра графа потока управления; пути в графе потока управления; логические выражения, встречающиеся в исходном коде и т.п. Кроме того, критерий F определяет логическую
f Q T {• } , которая принимает значение f (q t ) • , q покрывается тестом t . Тестовый набор для системы S удовлетворяет критерию полноты тестового покрытия F , F если каждый элемент тестового покрытия из множества QS покрывается хотя бы одним тестом из тестового набора . Иными словами: F ( S ) q QSF t f (q t ) • (1) функцию
M F ( S ) {q QSF t f (q t ) • }
QSF .
F S
если элемент тестового покрытия
Приведём несколько примеров часто упоминаемых критериев полноты тестового покрытия: каждый оператор в исходном коде выполняется хотя бы один раз; каждая ветвь графа потока управления выполняется хотя бы один раз; каждый путь графа потока управления выполняется хотя бы один раз; каждое логическое выражение хотя бы один раз вычисляется со значением «истина» и хотя бы один раз – со значением «ложь»; 59
F , представимого в (2)
Сам критерий при этом примет вид:
{q QSF t f (q t ) • } QSF
(3)
В некоторых случаях, когда не удаётся построить тестовый набор, удовлетворяющий такому критерию полноты тестового покрытия, можно использовать ослабленный критерий:
{q QSF t f (q t ) • } QSF
(4)
Параметр (01] указывает, какая доля элементов тестового покрытия должна быть покрыта тестовым набором. Приведём несколько примеров часто упоминаемых метрик тестового покрытия: количество покрытых (выполненных хотя бы один раз) операторов в исходном коде; количество покрытых ветвей графа потока управления; количество покрытых путей графа потока управления; количество распознанных мутантов (версий тестируемой системы с искусственно привнесёнными ошибками). Все эти метрики могут быть представлены в виде (2). Подробное описание этих и других, используемых на практике, метрик полноты тестового покрытия можно найти в [9].
3. Генетический алгоритм генерации тестов Рассмотрим следующую задачу генерации тестов:
60
Задача 1. Для заданной тестовой системы S построить тестовый набор , удовлетворяющий критерию (4). Для построения генетического алгоритма решения этой задачи необходимо определить: множество кандидатов; структуру представления кандидатов; оценочную функцию; оператор кроссовера; оператор мутации; условие останова.
3.1. Простейший алгоритм Рассмотрим простейший генетический алгоритм для решения задачи 1. В качестве множества кандидатов возьмём множество ; в качестве оценочной
функции возьмём метрику тестового покрытия M ( S ) для заданной F
тестируемой системы S . Условием останова будет наличие в текущем поколении решения , удовлетворяющего критерию (4). Структуру представления кандидатов, а также операторы кроссовера и мутации мы пока уточнять не будем. Заметим, что такой алгоритм допускает ситуацию, в которой критерий (4) не выполняется ни для одного тестового набора из текущего поколения, но, тем не менее, выполняется для некоторого объединения тестовых наборов из текущего и предшествующих поколений. Иными словами, все тесты, необходимые для построения решения, уже найдены, но само решение ещё не построено. В этой ситуации алгоритм не способен эффективно построить искомое решение, целенаправленно объединив подходящие тесты из разных тестовых наборов. Причина проблемы в том, что при построении алгоритма не использовалась имеющаяся информация о структуре критерия (4). Заметим также, что каждое последующее поколение тестов формируется путём применения операторов кроссовера и мутации к тестам из предыдущего поколения. Если в предыдущем поколении не было ни одного теста, покрывающего некоторый элемент тестового покрытия q , то в последующем поколении такой тест может появиться только как результат кроссовера или мутации тестов, не покрывающих q . Как бы мы не определяли операторы кроссовера и мутации, нет никаких оснований полагать, что получить таким способом тест, покрывающий q , проще, чем при полностью случайной генерации. Из этих замечаний следует, что эффективность данного генетического алгоритма, вообще говоря, не выше, чем у полностью случайного алгоритма генерации тестов. 61
3.2. Целенаправленный поиск Учитывая структуру критерия (4), из задачи 1 можно выделить следующую подзадачу: Задача 2. Для заданной тестовой системы S и заданного элемента тестового покрытия q , построить тест t T , удовлетворяющий условию f ( q t ) • . Для решения исходной задачи 1, достаточно решить задачу 2 для n QS F
попарно различных элементов тестового покрытия q1 q2 … qn QS , то есть F
построить тесты t1 t2 … tn такие, что
f (q1 t1 ) • f (q2 t2 ) • … f (qn tn ) • Решением задачи 1 будет множество {t1 t2 … tn } . Рассмотрим генетический алгоритм решения задачи 2. В качестве множества кандидатов возьмём множество тестов T . Условие останова: в текущей популяции присутствует тест q такой, что f ( q t ) • . Оценочная функция
mq T R каждому тесту t ставит в соответствие числовую меру mq (t ) того, насколько тест t близок к тому, чтобы покрыть элемент тестового покрытия q . При этом оценочная функция f q достигает своего максимального значения на тех и только на тех тестах, которые удовлетворяют условию f (q t ) • . Иными словами:
f (q t ) • mq (t ) max mq (t ) tT
(5)
В частности, в качестве оценочной функции можно использовать следующую функцию, удовлетворяющую условию (5):
0 при f (q t ) mq (t ) 1 при f (q t ) • В такой оценочной функции считается, что все тесты, не покрывающие элемент тестового покрытия q , одинаково далеки от того, чтобы покрыть элемент q . При использовании этой оценочной функции эффективность генетического алгоритма будет не выше, чем при случайном поиске. Примеры более эффективных оценочных функций для некоторых метрик полноты тестового покрытия можно найти в [8,5,2]. 62
4. Оценочные функции В этом разделе подробно рассматриваются три известных критерия полноты тестового покрытия, и для каждого из них предлагается оценочная функция.
4.1. Покрытие операторов исходного кода Тестовый набор удовлетворяет критерию покрытия операторов исходного кода, если при выполнении этого тестового набора каждый оператор исходного текста программы выполняется хотя бы один раз. Элементами тестового покрытия в данном случае являются операторы исходного текста. Для заданного оператора q значение оценочной функции mq (t ) тем больше, чем ближе тест t к тесту, покрывающему оператор q . Для построения оценочной функции рассмотрим граф потока управления тестируемой системы S . Вершинами графа являются операторы исходного кода, то есть множество
QSF . В графе существует ребро, идущее из вершины q1 в вершину q2 тогда и только тогда, когда оператор q2 может быть выполнен непосредственно после оператора
q1 . Пусть (q t ) — это множество всех элементов q из QSF , для
которых выполняются следующие условия: существует путь в графе потока управления ведущий из
q в q или
q q ; f (q t ) • . dist (q q) длину кратчайшего пути в графе потока управления, ведущего из q в q ( dist ( q q ) 0 ). Тогда оценочную функцию Обозначим через
можно определить следующим образом:
mq (t ) min dist (q q )
(6)
q ( q t )
Выражение, стоящее справа, определяет, за какое минимальное количество переходов можно добраться до элемента покрытия q от уже покрытых F S
Q . Функция mq (t ) принимает значение 0 на тех и только тех тестах, которые покрывают элемент q . Заметим, что элементов из множества
mq (t ) 0 mq (t ) 0
f (q t ) • f (q t )
4.2. Покрытие ветвей потока управления Тестовый набор удовлетворяет критерию покрытия ветвей потока управления, если при выполнении этого тестового набора управление хотя бы один раз проходит по каждому ребру графа потока управления. Заметим, что любой 63
тестовый набор, удовлетворяющий этому критерию, удовлетворяет также и критерию покрытия операторов исходного кода. Обратное утверждение, однако, неверно [9]. Элементами тестового покрытия являются переходы в графе потока управления. С каждым переходом в графе потока управления можно связать условие, при котором этот переход может быть выполнен. Переход от оператора q к оператору r , с которым связано условие p ,
r . Для выполнения перехода q r необходимо и обозначим как q достаточно, чтобы был выполнен оператор q , и чтобы после этого условие p обратилось в истину. Соответственно, для тестов, не покрывающих оператор q , в качестве оценочной подходит функция mq , определённая уравнением (6), p
p
p для оценки таких тестов роли не играет. Для тестов, покрывающих оператор q , функция mq (t ) обращается в 0 . Для таких так как истинность условия
тестов оценочная функция должна определять, насколько близок тест к тесту, для которого после выполнения оператора q будет истинным условие p . Таким образом, в общем виде оценочную функцию для критерия покрытия ветвей потока управления можно определить следующим образом:
mq (t ) при f (q t ) (7) mq ( t ) p r q p (t ) при f (q t ) • Значение функции q p T R тем больше, чем ближе заданный тест к тесту, в котором условие p выполняется после выполнения оператора q . При этом функция q p (t ) достигает своего максимума на тех и только тех тестах, в которых после выполнения оператора q выполняется условие p , то есть тех, которые покрывают переход Функцию
q p (t )
p q r .
можно определять по-разному в зависимости от характера
условия p . Если условие имеет форму простого (не)равенства xy , где « » обозначает одно из отношений « », « », « », « » или « », то для определения функции q p (t ) можно использовать значение x y , например, следующим образом:
q p (t )
e
2 при xy при ( xy )
x y
Если условие представляет собой конъюнкцию качестве значения функции 64
q p (t )
p p1 p2 … pn , то в
можно взять количество членов этой
конъюнкции, принимающих значение «истина». В общем случае эффективно определить функцию q p (t ) затруднительно.
4.3. Покрытие путей потока управления Тестовый набор удовлетворяет критерию покрытия путей потока управления, если его выполнение хотя бы один раз проходит по каждому возможному пути в графе потока управления ведущему от точки входа до точки завершения работы. Этот критерий сильнее критерия покрытия ветвей потока управления. Каждый путь представляет собой последовательность переходов
R 1 2 … n , где i
pi qi qi 1 , при 1 i n . Упорядоченным подмножеством пути 1 2 … n назовём последовательность i1 i2 … im такую, что 1 i1 i2 … im n .
имеет
вид
Заметим, что в упорядоченном подмножестве пути конечный оператор перехода может не совпадать с начальным оператором следующего за ним перехода. Пусть есть два пути R 1 2… l и R 1 2… k , и пусть
i1 j1 i2 j2 … im jm
1 i1 i2 … im l и 1 j1 j2 … jm k . Тогда пути R и R имеют общее упорядоченное подмножество размера m . Обозначим через length( R) длину пути R , а через common( R R) – максимальный размер общего упорядоченного подмножества путей R и R . причём
Определим оценочную функцию для критерия покрытия путей потока управления следующим образом:
mR (t ) length( R) length( path(t )) 2 common( R path(t )) Здесь path(t ) — это путь, по которому приходит управление при выполнении теста t . Значение в правой части равно количеству переходов в путях R и path(t ) , не входящих в максимальное общее упорядоченное подмножество этих путей. Оно равно 0 тогда и только тогда, когда пути path(t ) совпадают.
R и
65
5. Заключение Применение генетических алгоритмов для генерации тестов предъявляет дополнительные требования к используемым критериям полноты тестового покрытия. Это вызвано тем, что критерий полноты используется не только для оценки качества сгенерированных тестов, но и непосредственно в процессе генерации для оценки близости полученных тестов к нужным результатам. Таким образом, нужно иметь оценочную функцию, позволяющую измерить эту близость, определить, насколько перспективными являются уже построенные тесты с точки зрения их использования в качестве основы для построения новых тестов. Кроме того, нужно иметь в виду, что тривиальные решения — функции вида «покрыто – 0, не покрыто – 1» — работают очень плохо. Для критериев, связанных с покрытием тех или иных путей в коде программы, удается построить достаточно удобные оценочные функции, основанные на количестве непокрытых дуг в пути, который нужно покрыть. В статье построены такие функции для некоторых широко распространённых критериев полноты тестового покрытия. Литература 1. Г. Мейерс. Искусство тестирования. М.: Финансы и статистика, 1982. 2. André Baresel, Harmen Sthamer, and Michael Schmidt. Fitness function design to improve evolutionary structural testing. In GECCO, pages 1329–1336, 2002. 3. Chandrasekhar Boyapati, Sarfraz Khurshid, and Darko Marinov. Korat: Automated testing based on Java predicates. In Proceedings of the International Symposium on Software Testing and Analysis (ISSTA 2002), Rome, Italy, 22–24 July 2002. IEEE. 4. Ugo A. Buy, Alessandro Orso, and Mauro Pezzè. Automated testing of classes. In ISSTA, pages 39–48, 2000. 5. Yoonsik Cheon and Myoung Kim. A fitness function for modular evolutionary testing of object-oriented programs. Technical Report 05-35, Department of Computer Science, University of Texas El Paso, Nov 2005. 6. Yoonsik Cheon, Myoung Kim, and Ashaveena Perumendla. A complete automation of unit testing for Java programs. In Hamid R. Arabnia and Hassan Reza, editors, Proceedings of the 2005 International Conference on Software Engineering Research and Practice (SERP ’05), Volume I, Las Vegas, Nevada, June 27-29, 2005, pages 290– 295. CSREA Press, 2005. 7. John H. Holland. Adaptation in natural and artificial systems. MIT Press, Cambridge, MA, USA, 1992. 8. Paolo Tonella. Evolutionary testing of classes. In ISSTA ’04: Proceedings of the 2004 ACM SIGSOFT international symposium on Software testing and analysis, pages 119– 128, New York, NY, USA, 2004. ACM Press. 9. Hong Zhu, Patrick A. V. Hall, and John H. R. May. Software unit test coverage and adequacy. ACM Comput. Surv., 29(4):366–427, 1997.
66
Применение технологии UniTesK для тестирования систем с различной конфигурацией активных потоков управления С.Г. Грошев,
[email protected]
1. Введение В связи с возрастанием размера и сложности программного обеспечения (ПО), которое требуется для удовлетворения потребностей пользователей и поддержки стабильного развития современного общества, задача автоматизации тестирования становится одной из ключевых в разработке качественного ПО. Одним из перспективных подходов к решению этой задачи является UniTesK – технология автоматизированного функционального тестирования на основе формальных методов, разработанная в Институте системного программирования РАН [1, 2, 4, 5]. Данная технология позволяет автоматизировать разработку и выполнение тестов, которые с высокой степенью надежности проверяют корректность поведения тестируемой системы. В дальнейшем в статье вместо термина «тестируемая система» используются термины «целевая система» или просто «система» – во избежание путаницы с похожим термином «тестовая система», который также будет использоваться. Большинство реальных систем обладает некоторым внутренним состоянием, которое может влиять на выдаваемые системой реакции и изменяться как при получении воздействий извне, так и в процессе собственной работы системы. Поскольку внутреннее состояние системы в общем случае неизвестно, и, кроме того, оно может содержать много несущественных с точки зрения поставленной задачи тестирования деталей, в технологии UniTesK строится модель целевой системы времени тестирования (в дальнейшем – просто «модель»), отражающая ее проверяемые свойства в удобном для тестирования компактном виде и скрывающая несущественные реализационно-зависимые детали. Повышение уровня абстракции позволяет использовать один и тот же комплект тестов для тестирования различных реализаций одного набора функций. Компонент, называемый медиатором, отображает реализацию и модель друг в друга: реализационное состояние целевой системы – в модельное состояние, модельные тестовые стимулы – в реализационнозависимые воздействия на целевую систему, а реализационно-зависимые 67
реакции целевой системы – в модельные реакции. В дальнейшем везде, где не оговорено специально, речь будет идти именно о модельных состояниях, стимулах и реакциях. Тестовый сценарий UniTesK перебирает состояния целевой системы и в каждом из них перебирает подаваемые ей на вход тестовые стимулы вплоть до достижения заданной степени тестового покрытия. Специальный компонент, получаемый на основе спецификаций и называемый оракулом, автоматически анализирует выходные реакции и выдает вердикт об их корректности. Корректность поведения целевой системы определяется его соответствием спецификации. Также автоматически оценивается степень достигнутого тестового покрытия [2, 5]. В технологии UniTesK последовательность тестовых воздействий строится интерактивно, по мере выполнения теста. Для этого используются специальные компоненты, называемые обходчиками. Задача обходчика – обход графа состояний системы, причем этот граф не задается заранее, а строится прямо в процессе взаимодействия тестовой и целевой систем. Очередное тестовое воздействие определяется на основе предыдущей истории взаимодействия. Такой подход обеспечивает простоту написания тестовых сценариев и гибкость тестирования. Интерактивная природа тестирования порождает ряд проблем, связанных с взаимной активностью тестовой и целевой систем: целевая система может иметь несколько взаимодействующих между собой потоков управления, взаимодействовать непредсказуемым образом с внешними системами, неподконтрольными тестовой среде, выдавать реакции на определенные воздействия по прошествии значительного времени и выполнять какую-то собственную деятельность. Все это может влиять на состояние целевой системы и, в результате, на наблюдаемые тестовой системой реакции. Кроме того, даже при полной пассивности целевой системы может возникнуть необходимость тестирования ее работы в окружении множества параллельно работающих активных потоков, создающих запросы к ней. Во всех случаях тест должен определять корректность поведения целевой системы и добиваться заданной степени тестового покрытия. Опыт использования технологии UniTesK для тестирования реальных систем показывает, что она подходит для тестирования практически любой конфигурации активных потоков теста, целевой системы и внешних систем, однако различные конфигурации требуют построения различных моделей целевой системы и использования различных инженерных решений в построении тестовой системы. Данная работа посвящена особенностям тестирования различных конфигураций активных потоков с помощью технологии тестирования UniTesK. Целями работы являются: исследование возможности использования технологии тестирования UniTesK для построения тестов с различной конфигурацией потоков управления, принадлежащих тестовой системе и существующих независимо от нее; 68
систематизация опыта тестирования различных конфигураций активных потоков; разработка подходов для случаев, на которые технология UniTesK в существующем на данный момент виде не рассчитана.
2. Классификация тестируемых систем в соответствии с конфигурацией потоков управления В технологии UniTesK целевая система рассматривается как «черный ящик». На входы ей подаются стимулы, а на выходах наблюдаются реакции. Для целей тестирования «черного ящика» важны только возможные соотношения между подаваемыми стимулами и получаемыми реакциями, и неважно, что именно происходит внутри целевой системы. Именно на основе этого принципа опре-деляются границы целевой системы, и строится предлагаемая классификация. Например, пусть имеются три различных системы. Система А обладает одним собственным потоком управления и может в ответ на полученный стимул начать некую собственную активность ограниченной продолжительности; в ходе этой активности она может как принимать новые стимулы, так и выдавать реакции на старые. Система Б аналогична по внешнему поведению, но внутри нее имеется несколько собственных потоков управления. Система В никакой собственной активности не ведет, но также может сразу выдать в ответ на стимул несколько реакций, причем между системой В и тестовой системой находится сеть, которая вносит недетерминированные задержки в передачу как стимулов, так и реакций (случай тестирования через сложную среду). С точки зрения тестирования все эти три системы эквивалентны: про них известно только то, что после подачи стимула в течение некоторого времени возможен как приём реакций от них, так и подача новых стимулов. Поэтому с точки зрения тестирования все они относятся к классу систем с отложенными реакциями [6]. В случае систем А и Б это определяется наличием их собственных активных потоков, а в случае системы В – функционированием сетевого оборудования и системного ПО. В последнем случае у теста может отсутствовать возможность определить, по чьей вине реакция была искажена, потеряна или задержана – системы В или промежуточной сети. Поэтому разумно включить в рамки целевой системы также и сеть и тестировать именно совместное поведение системы В и сети – вместе они составляют некую систему с отложенными реакциями В′, правильность работы которой и проверяется. Если же среда допускает возможность сделать такую проверку (например, взаимодействие тестовой и целевой систем происходит по протоколу TCP, который маскирует все эти искажения), то система В может быть отнесена к более простому классу, позволяющему применять более простые модели и инструменты тестирования. В ходе исследований выделены следующие различающиеся с точки зрения тестирования «черного ящика» конфигурации потоков управления. 69
1. Целевая система может быть представлена в виде API, предполагающего однопоточное выполнение и не обладающего собственным потоком управления. Существует только один активный поток, принадлежащий тестовой системе. Целевая система не выполняет никаких действий и не меняет свое состояние вне вызовов из тестовой системы. 2. Аналогично п.1, но целевой API предполагает работу в многопоточной среде. Собственным потоком управления целевая система не обладает. Необходимо протестировать корректность работы при параллельном вызове методов целевого интерфейса. Существует несколько активных потоков, все они принадлежат тестовой системе. Целевая система не выполняет никаких действий и не меняет свое состояние вне вызовов из тестовой системы. 3. Существует единственный поток управления, не контролируемый тестом. Активный поток иногда передает управление тесту, но тот обязан быстро вернуть управление, и при этом он не может выполнить свою работу за доступное ему за один раз время. Реакции на все или некоторые стимулы поступают только при следующих вызовах теста. Никакие внешние сообщения в целевую систему не поступают. 4. Аналогично п. 3, но возможны внешние воздействия на целевую систему или ее собственная длительная активность. 5. Существуют два потока управления, один из которых принадлежит целевой системе, другой – тесту; при этом в любой момент времени активен ровно один из них. 6. Тест – активный поток, целевая система – один или несколько полуактивных потоков: при получении стимула она может выдавать на него реакции в течение ограниченного времени, после чего стабилизируется и уже не может менять состояние или выдавать реакции до получения следующего стимула. Во время активности целевая система продолжает принимать новые стимулы, причем реакции на них могут выдаваться в произвольном порядке, и получение новых стимулов может влиять на реакции, выдаваемые системой в ответ на старый стимул. 7. Кроме теста, существуют и другие активные потоки. Целевая система может менять состояние и выдавать сколь угодно много реакций в течение неограниченного времени в ответ на полученный стимул и даже при отсутствии стимулов. Других конфигураций активных потоков, существенно отличающихся с точки зрения тестирования, обнаружить не удалось. Прежде чем приступить к подробному рассмотрению перечисленных видов конфигураций активных потоков, введем несколько определений.
70
Определение 1. Реакция на некоторый стимул называется немедленной, если с точки зрения тестовой системы она поступает непосредственно после подачи стимула. Это означает, что между моментом подачи стимула и моментом получения реакции на него на временнóй линии модели целевой системы не могут происходить никакие события, существенные с точки зрения модели. Если несколько реакций на один стимул приходят непосредственно после подачи этого стимула, и с точки зрения модели несущественно, какой из них пришел раньше, то они объединяются в одну сложную немедленную реакцию. Например, возврат из метода нескольких выходных параметров можно рассматривать как одну немедленную реакцию. Простой возврат управления также считается немедленной реакцией. Вообще, к данному классу можно отнести гораздо более широкий спектр реакций, чем может показаться на первый взгляд. Например, вызываемый метод может реально выполняться где угодно (даже на группе удаленных машин) и работать сколь угодно долго, но если вызывающий его поток блокируется на все это время, то возврат управления и выходных значений в вызывающем потоке считается реакцией, происходящей непосредственно после подачи стимула вызова метода. Кроме того, реакция на стимул может приходить не в тот поток управления, из которого был подан этот стимул, но если в используемой модели реакция и стимул проецируются на единую временнýю линию, и на этой линии между ними не может произойти никаких других событий, то такая реакция также считается немедленной. Иногда реакцию можно считать немедленной и в тех случаях, когда целевая система блокируется на время обработки стимула (даже если при этом не блокируется вызывающий ее поток) и задерживает новые стимулы до полной обработки текущего стимула и выдачи всех реакций на него. В этом случае события в модели можно переупорядочить таким образом, чтобы стимулы следовали в порядке поступления их в целевую систему, а все реакции на каждый стимул следовали непосредственно после него. Определение 2. Реакция на некоторый стимул называется отложенной, если на временнóй линии используемой модели целевой системы событие применения данного стимула и событие получения данной реакции на него происходят в существенно различные моменты времени. Моменты времени считаются существенно различными, если в промежутке между ними возможны другие события, существенные с точки зрения используемой модели (в том числе, применение других стимулов и получение других реакций). Как уже было показано выше, любой блокирующий вызов (локальный или удаленный) без пост-эффектов не является стимулом с отложенной реакцией. Определение 3. Целевая система называется системой с немедленными реакциями, если с точки зрения используемой модели она производит только немедленные реакции на все стимулы.
71
Определение 4. Целевая система называется системой с отложенными реакциями, если на некоторые стимулы она производит отложенные реакции, причем в любой момент можно определить промежуток времени, за которое при отсутствии новых тестовых стимулов система гарантированно придет в стационарное состояние. Состояние целевой системы называется стационарным, если при дальнейшем отсутствии стимулов она гарантированно сколь угодно долго не будет менять свое состояние и выдавать новых реакций. Поскольку внутреннее состояние целевой системы нам в общем случае неизвестно, имеется в виду стационарность модельного состояния системы. Следствия из определений: в ответ на получение стимула целевая система может выдать не более одной немедленной реакции, а количество отложенных реакций может при этом быть любым; реакция является отложенной или немедленной не сама по себе, а с точки зрения модели, используемой для тестирования. Для одной и той же системы могут быть построены разные модели, и если в одной модели допускаются какие-либо события между подачей стимула и получением на него реакции, а в другой – нет, то в первой модели реакция будет отложенной, а во второй – немедленной; соответственно, целевая система в целом может являться или не являться системой с отложенными реакциями в зависимости от используемой модели. Разработчики спецификаций и тестов могут выбирать ту или иную модель в зависимости от свойств целевой системы и поставленной задачи тестирования. Модели с отложенными реакциями включают в себя всю выразительную мощь моделей без отложенных реакций, но более сложны. Рекомендуется всегда выбирать для тестирования из множества моделей, отражающих все проверяемые свойства системы, модель наименьшей возможной сложности.
3. Особенности построения тестовой системы и модели целевой системы в зависимости от конфигурации активных потоков 3.1. Пассивные последовательные системы с немедленными реакциями. (Единственный поток управления принадлежит тестовой системе.) Примеры: большинство подсистем стандартной библиотеки libC. Целевая система считается относящейся к данному классу, если она удовлетворяет следующим требованиям: в ответ на каждый подаваемый стимул она выдает пустую или немедленную реакцию; 72
вне вызовов из тестовой системы целевая система не выполняет никаких действий и не меняет свое состояние; поставленная задача тестирования не включает проверку работы целевой системы в многопоточном окружении, или такая работа невозможна. Исходно технология UniTesK разрабатывалась для тестирования именно данного класса систем. Рассмотрим вкратце способ тестирования, применяемый в данном случае. При создании тестового сценария используется абстракция обобщенных состояний, где каждое обобщенное состояние соответствует некоторому классу эквивалентности реализационных состояний целевой системы. Обычно в модели обобщенных состояний используется значительно более высокий уровень абстракции, чем в модели, используемой для проверки корректности работы целевой системы. В любой момент времени целевая система находится в некотором однозначно определенном обобщенном состоянии и может изменить его только в результате применения тестового стимула. После применения некоторого стимула в некотором обобщенном состоянии целевой системы анализируется полученная реакция и вычисляется новое обобщенное состояние. Задача тестового сценария состоит в обходе ребер ориентированного графа, вершины которого соответствуют обобщенным состояниям целевой системы, а ребра – применяемым к ней обобщенным стимулам, где каждый обобщенный стимул соответствует некоторому классу эквивалентности возможных стимулов, применяемых к целевой системе. Граф строится интерактивно, по мере выполнения теста, на основе правил вычисления текущего обобщенного состояния целевой системы и правил перебора стимулов, применяемых в зависимости от обобщенного состояния. Модель обобщенного состояния и обобщенных стимулов строится так, чтобы при повторном применении обобщенного стимула в некотором обобщенном состоянии целевая система всегда переводилась в то же самое обобщенное состояние, в которое она ранее переходила при применениях этого стимула в том же состоянии. Также накладывается дополнительное требование сильной связности графа обобщенных состояний. Практика тестирования реальных систем показывает, что для систем, удовлетворяющих минимальным требованиям к детерминизму поведения, построение модели, удовлетворяющей изложенным требованиям, возможно. Способы тестирования, применяемые в технологии UniTesK для данной конфигурации активных потоков, более подробно описаны в [2, 4]. Случай недетерминированного поведения целевой системы частично рассматривается ниже, но в целом выходит за рамки данной работы [7]
73
3.2. Пассивные параллельные системы с немедленными реакциями. (Множественные потоки управления, принадлежащие тестовой системе.) Примеры: реализация любой подсистемы стандартной библиотеки libC, заявленная как устойчивая к многопоточности («thread-safe»). Библиотека управления многопоточностью libThreads. В технологии UniTesK задача тестирования систем данного класса решается с помощью специальных сценариев, определяющих наборы параллельных воздействий [4]. Кроме проверок поведения целевой системы, выполняемых после применения каждого тестового стимула, проверяется следующий постулат сериализации: «Результат любого параллельного применения стимулов к целевой системе аналогичен некоторому последовательному их применению». Именно необходимость проверки данного постулата не позволяет отнести такие системы к классу 1. Для проверки постулата модель целевой системы включает в себя сериализацию событий (примененных стимулов и полученных реакций), которая строится следующим образом. Все события (применение стимулов и получение связанных с ними реакций), произошедшие в ходе выполнения тестового сценария, протоколируются. На множестве событий строится частичный порядок: линейный порядок определен для всех событий, произошедших в пределах одного потока управления; на основании дополнительных знаний о целевой и тестовой системах может быть определен частичный порядок событий, произошедших в разных потоках. порядки событий, Строятся все возможные линейные удовлетворяющие построенному частичному порядку. Работа целевой системы признается корректной, если найден хотя бы один такой линейный порядок событий, который удовлетворяет спецификации целевой системы. Замечание: В процессе выполнения теста существует несколько потоков управления, принадлежащих тестовой системе, и, соответственно, в используемой в это время модели целевой системы может иметься несколько ´ линий. Однако, после построения сериализации все события временных выстраиваются на единой временнóй линии, так что к моделируемой системе становится полностью применимым данное выше определение системы с непосредственными реакциями. Именно на этом основании системы данного класса отнесены к классу систем с немедленными реакциями.
74
2.3
Если тест находится в состоянии ожидания некоторого состояния целевой системы: 2.3.1 Если не все ожидаемые реакции получены и время ожидания еще не вышло, вернуть управление; 2.3.2 Если нужное состояние достигнуто, перейти к шагу 2.4; 2.3.3 иначе сообщить об ошибке; 2.4 Вычислить очередной стимул, который следует применить к целевой системе в текущем состоянии; 2.5 Если такой стимул найден, применить его, проанализировать немедленные реакции, при необходимости перейти в состояние ожидания реакций и вернуть управление; 2.6 Если существует состояние целевой системы N, которое уже существовало в процессе выполнения теста, но в котором были применены не все возможные стимулы, то попытаться перейти в него; найти в уже обойденной части графа состояний путь из текущего состояния в искомое, вычислить и подать нужные стимулы, перейти в режим ожидания состояния N и возвратить управление; 2.7 Если такое состояние не найдено, значит во всех обнаруженных состояниях применены все возможные стимулы; тест завершает работу.
3.3. Тестирование с отложенными реакциями без собственного активного потока. (Тестовая система не обладает собственным активным потоком. Целевая система изолирована. Отложенные реакции.) Пример: Object Request Broker (ORB) RACE, разработанный для однопроцессорных встроенных систем. И тест, и целевая система – объекты, существующие под управлением этого ORB. Стимулы и реакции – сообщения ORB, причем сообщения передаются между объектами только в промежутке между получением ими управления. В данном случае для задачи тестирования неважно, сколько реально существует активных потоков (см. ниже обсуждение случая 6). В любом случае тест не обладает собственным потоком управления и может в течение некоторого времени после применения тестового стимула получать реакции на него от целевой системы и выдавать новые стимулы. Именно эти критерии заставляют относить целевую систему к данному классу систем. Если активный поток не является единственным, то требуется построение модели, аналогичной той, которая используется в случае 6 (см. ниже). Если такой поток – единственный, то данный случай проще случая 6, поскольку не требуется специальное построение сериализации событий. Однако случай 3 не может быть сведен к случаям 1 или 2, потому что возможны отложенные реакции целевой системы. Задача построения модели для систем данного класса решается аналогично случаю 6. Однако для системы с конфигурацией такого рода потребовалась особая реализация обходчика: обычный тест UniTesK выполняется в собственном потоке управления с собственным стеком, в котором хранятся вызовы методов и их локальные переменные; в данном же случае тест не обладает собственным стеком вызовов и не имеет возможности выполнять какие-либо длительные действия. Упрощенный алгоритм работы тестовой системы в случае 3 выглядит следующим образом: 1. Обеспечить регулярное получение управления в активном потоке (например, подписаться на получение сигналов от таймера); 2. При очередном получении управления: 2.1 Проверить реакции от целевой системы, полученные за время ее самостоятельной работы, и состояние, в котором она находится; при необходимости сообщить об ошибках; 2.2 Если тест находится в состоянии ожидания реакций от целевой системы: 2.2.1 Если получены не все ожидаемые реакции и время ожидания еще не вышло, вернуть управление; 2.2.2 Если все ожидаемые реакции получены, перейти к шагу 2.4; 2.2.3 Иначе сообщить об ошибке; 75
3.4. Полуконтролируемое тестирование с отложенными реакциями без собственного активного потока. (Поток управления не принадлежит тестовой системе. Существуют внешние воздействия на целевую систему. Отложенные реакции.) Пример: аналогично случаю 3, но целевая система регулярно выполняет по расписанию какую-то работу, влияющую на ее состояние. В качестве такого дополнительного источника воздействий на целевую систему при этом выступает таймер, неподконтрольный тестовой среде. Если в данном примере тестовая среда имеет возможность изолировать таймер и создавать стимулы от него целевой системе тогда и только тогда, когда это необходимо для целей тестирования, то такую целевую систему следует отнести к классу 3. Из-за наличия неконтролируемых источников воздействий на целевую систему данный случай не может быть сведен к случаям 3, а тем более случаям 1 и 2, поскольку в рассмотренных выше случаях предполагается полная подконтрольность всех стимулов, получаемых целевой системой. При этом для целей тестирования неважно, сколько активных потоков реально существует и откуда поступают воздействия на целевую систему: от внешних активных потоков или от других объектов, существующих на аналогичных условиях под управлением единого потока управления. В любом случае, когда возможна неконтролируемая тестом активность целевой системы, сложность 76
модели целевой системы времени тестирования будет аналогична сложности модели, используемой в случае 7, который обсуждается ниже. С точки зрения особенностей реализации тестовой системы, она аналогична случаю 3.
3.5. Два потока управления, работающих строго поочередно. Пример: тестовая конфигурация такого рода была построена для тестирования Verilog-моделей [3]. В данном случае, как и в случае 3, целевая система обладает собственным активным потоком управления, на который можно влиять извне; отличается только реализация теста. При этом и целевая, и тестовая система ведут себя каждая как единственный активный поток, что позволяет запускать целевую систему в обычном для нее режиме, пользуясь при этом классической для UniTesK архитектурой тестового сценария, рассчитанной на единственный поток с немедленными реакциями. Выполнение в каждый момент времени в точности одного из этих потоков достигается средствами межпоточной синхронизации. Такая реализация удобна тем, что оба потока управления (и целевой системы, и теста) обладают собственными стеками. Модель целевой системы в данном случае аналогична используемой в случае 1, различие только в реализации тестовой среды. Схема взаимодействия компонентов при этом выглядит следующим образом: 1. 2. 3. 4. 5.
6. 7. 8.
Целевая система инициализируется и вызывает встроенный в нее медиаторный код, который запускает в отдельном потоке тестовый сценарий UniTesK; Тестовый сценарий инициализируется и переходит в режим ожидания сигнала; Целевая система вызывает медиаторный код, который посылает потоку тестового сценария сигнал, описывающий текущее состояние целевой системы, и переходит в режим ожидания сигнала; Тестовый сценарий получает сигнал от медиатора и анализирует его корректность в соответствии со спецификацией; Тестовый сценарий выбирает очередное тестовое воздействие по правилам, описанным для случая 1; если такое воздействие не найдено, то он завершает работу: иначе сценарий передает медиатору сигнал, описывающий воздействие, и переходит в режим ожидания; Получив сигнал, медиатор выполняет описанное в нем тестовое воздействие и получает ответную реакцию целевой системы; Медиатор посылает тестовому сценарию сигнал, описывающий реакцию целевой системы и ее состояние, и переходит в режим ожидания; Переходим к шагу 4. 77
Более подробно об архитектуре тестовой системы, рассматриваемой конфигурации, можно прочитать в [3].
применяемой
в
3.6. Система с отложенными реакциями. (Тест – активный поток, целевая система – один или несколько полуактивных потоков.) Пример: FTP-сервер при одновременной работе нескольких клиентов. После получения запроса на сканирование каталога в течение какого-то времени выполняется сканирование, и клиент получает сетевые пакеты с результатами запроса. При этом параллельно другие клиенты могут модифицировать содержимое того же каталога. Поскольку стандарт не гарантирует однозначность результата сканирования при одновременных модификациях сканируемого каталога, результаты запроса могут различаться в зависимости от действий других клиентов, выполняемых в процессе обработки запроса. В данном случае для задачи тестирования неважно, сколько реально существует активных потоков. В любом случае, в течение некоторого времени после применения тестового стимула возможны как получение от целевой системы реакций на этот стимул, так и подача новых стимулов. Именно эти критерии заставляют относить целевую систему к данному классу систем. Поскольку в случаях 1, 2 и 5 предполагаются только немедленные реакции, к ним не могут быть сведены системы данного класса. Для систем с отложенными реакциями в технологии UniTesK разработана специальная архитектура теста [6]. В тестовом сценарии имеется один основной активный поток. Этот поток регулярно опрашивает входные каналы для анализа реакций, полученных от целевой системы. При необходимости создаются вспомогательные потоки управления, в которых выполняются так называемые «кэтчеры» (catcher). Задача кэтчера – регистрация реакций, полученных от целевой системы, и передача их во входные каналы основного потока управления тестового сценария. Кэтчеры могут также встраиваться в существующие потоки управления целевой системы. Модель целевой системы времени тестирования включает в себя информацию о тех ранее примененных стимулах, в ответ на которые еще возможно получение реакций (стационарным считается такое модельное состояние целевой системы, в котором это множество пусто). При получении реакции проверяется, допускает ли спецификация получение реакции такого рода в данный момент. Получение неожиданной реакции (в том числе получение любой реакции в стационарном модельном состоянии системы), а также неполучение ожидаемой реакции в течение допустимого времени рассматриваются как ошибки. Тестовый сценарий перебирает серии тестовых стимулов и применяет их к целевой системе. После подачи такой серии сценарий ждет перехода целевой 78
системы в стационарное состояние, анализируя при этом все полученные от нее реакции. Задача тестирования определяется как обход ребер ориентированного графа, вершины которого соответствуют обобщенным состояниям целевой системы, а ребра – применяемым к ней обобщенным стимулам, где каждый обобщенный стимул соответствует некоторому классу эквивалентности серий применяемых к целевой системе стимулов. Граф состояний строится интерактивно, по мере работы теста. Все события протоколируются, а после завершения теста строится их сериализация. Правила построения частичного порядка событий описываются спецификацией целевой системы и, в общем случае, могут быть более сложными, чем в случае 2. Работа целевой системы признается корректной, если найдена хотя бы одна сериализация событий, удовлетворяющая спецификации.
3.7. Полуконтролируемое тестирование. (Тестирование в окружении неконтролируемых тестом активных потоков, влияющих на наблюдаемое поведение целевой системы, которая может обладать отложенными реакциями.) Пример: таймер. Стимул может быть единственным (подписка на получение сигналов от таймера) или вообще отсутствовать, реакции наблюдаются сколь угодно долго. К данному классу относятся системы, работающие в активном окружении, если окружение способно влиять на наблюдаемое поведение целевой системы. К нему также относятся системы, обладающие собственными активными потоками управления, если они могут самопроизвольно менять свое наблюдаемое поведение вне зависимости от тестовых стимулов. С точки зрения тестирования, для таких систем несущественно количество активных потоков, реально существующих вне теста. Все источники внешних воздействий на целевую систему, а также ее собственная активность, вызывающая неконтролируемые изменения состояния, могут быть промоделированы неким единым неконтролируемым источником внешних воздействий. Все системы такого рода могут быть представлены, например, в виде недетерминированного конечного автомата, допускающего переходы по пустому входному символу, возможно, с выдачей непустого выходного символа. Поэтому все такие системы отнесены к единому классу. Для целей тестирования приходится так или иначе моделировать все, что может повлиять на наблюдаемые тестовой системой реакции, и тестировать получившуюся составную систему. В принципе, практически любая система обладает той или иной степенью недетерминизма, но именно способность целевой системы данного типа менять свое состояние и поведение в широких пределах при отсутствии 79
тестовых стимулов существенно усложняет тестирование и не позволяет отнести ее к рассмотренным выше классам. В отделе операционных систем ИСП РАН ведутся исследования по вопросам тестирования недетерминированных систем, но пока что не найдена единая методика тестирования, пригодная для любых систем такого рода. Разработаны методы тестирования систем, допускающих в определенных пределах недетерминизм выдаваемых реакций и изменений внутреннего состояния [7], однако, общем случае, невозможно достоверно оценивать корректность поведения системы, допускающей сколь угодно много реакций и изменений наблюдаемого снаружи состояния при отсутствии тестовых воздействий. Понятно, что проверять корректность поведения целевой системы можно только при наличии некоторых ограничений на возможные последствия неконтролируемых воздействий. Именно из доступных ограничений такого рода и нужно исходить при моделировании. Мы можем предложить только некоторые общие рекомендации по уменьшению степени недетерминизма модели, позволяющие свести систему такого рода к одному из более простых случаев. По возможности изолировать целевую систему, помещая ее в «песочницу» («sandbox»), в которой все связи с внешним миром заменены связями с соответствующими заглушками – компонентами тестовой системы. Это классическая техника, однако в ряде случаев она не может быть применена, например, если целевая система – большой монолитный компонент, обладающий собственными активными потоками управления, и отсутствует доступ к исходному коду. Моделировать только ту часть целевой системы, поведение которой не зависит от неконтролируемых активных потоков. Корректность реакций целевой системы на воздействия, неподконтрольные тесту (и, возможно, непосредственно им не наблюдаемые), при этом не проверяется. Конструировать модель целевой системы так, чтобы она отражала только те свойства целевой системы, которыми она должна обладать при любых сценариях ее работы, независимо от возможных последствий взаимодействия с неконтролируемыми потоками управления. При этом моделируемые аспекты являются инвариантами модели относительно неподконтрольных воздействий. Технология UniTesK изначально приспособлена для такого способа решения проблемы, потому что основана на вычислении предикатов над поведением целевой системы: спецификация описывает не точное поведение системы, а только условия, которым это поведение должно удовлетворять. Такой подход позволяет описывать и тестировать системы с достаточно высоким уровнем недетерминизма [2, 5, 7]. 80
С помощью приведенных методов в большинстве случаев задача тестирования может быть сведена к задаче тестирования системы более простого класса: 1, 2 или 6. Однако общей методики решения этой задачи не существует, как не существует и общего для любых систем способа автоматического построения их моделей для целей тестирования. Задача построения моделей целевой системы и преобразования моделей в более «удобный» для тестирования вид требует творческого подхода, и не существует единого метода, решающего эти задачи для любых целевых систем
4. Заключение. В данной статье проведена классификация тестируемых систем в соответствии с видом конфигурации активных потоков и рассмотрены особенности тестирования систем из каждого класса с помощью технологии UniTesK. Рассмотрены способы применения технологии UniTesK для тестирования без собственных активных потоков теста, которые ранее не рассматривались. Приведены некоторые способы уменьшения при моделировании недетерминизма поведения, которые позволяют привести тестируемую систему к виду, допускающему тестирование с помощью UniTesK. К сожалению, не существует единого способа моделирования, пригодного для тестирования систем со сколь угодно высокой степенью недетерминизма поведения, однако постоянно ведется работа по расширению класса систем, допускающих тестирование с помощью технологии UniTesK. Литература 1. http://www.unitesk.com/ru/ — сайт, посвященный технологии тестирования UniTesK и поддерживающим ее инструментам. 2. В. В. Кулямин, А. К. Петренко, А. С. Косачев, И. Б. Бурдонов. Подход UniTesK к разработке тестов. Программирование, 29(6), стр. 25-43, 2003. 3. В. П. Иванников, А. С. Камкин, В. В. Кулямин, А. К. Петренко. Применение технологии UniTesK для функционального тестирования моделей аппаратного обеспечения. Препринт 8. Институт системного программирования РАН. Москва, 2005. 4. I. Bourdonov, A. Kossatchev, A. Petrenko, and D. Galter. KVEST: Automated Generation of Test Suites from Formal Specifications. FM'99: Formal Methods. LNCS 1708, pp. 608-621, Springer-Verlag, 1999. 5. V. Kuliamin, A. Petrenko, I. Bourdonov, and A. Kossatchev. UniTesK Test Suite Architecture. Proc. of FME 2002, LNCS 2391, pp. 77-88, Springer-Verlag, 2002. 6. V. Kuliamin, A. Petrenko, N. Pakoulin, I. Bourdonov, and A. Kossatchev. Integration of Functional and Timed Testing of Real-time and Concurrent Systems. Proc. of PSI 2003, LNCS 2890, pp. 450-461, Springer-Verlag, 2003. 7. И. Б. Бурдонов, А. С. Косачев, В. В. Кулямин. Неизбыточные алгоритмы обхода графов: недетерминированный случай. Программирование, 30(1), стр. 2-17, 2004.
81
Предложенный в ИСП РАН подход к работе с данными сложной структуры является частью разработанной в ИСП РАН технологии UniTesK тестирования программного обеспечения на основе формальных спецификаций и моделей [17–19]. Этот подход основан на использовании формального описания данных сложной структуры.
Генерация тестовых данных сложной структуры с учетом контекстных ограничений
2. Представление тестовых данных
А.В. Демаков, С.В. Зеленов, С.А. Зеленова
1. Введение Тестирование программных систем является важным компонентом всех проектов, связанных с разработкой программного обеспечения. По мере роста масштабов и сложности программных систем растет трудоемкость тестирования. Решить задачу повышения качества и сокращения расходов на тестирование можно за счет привлечения эффективных средств автоматизации разработки тестов. Одной из наиболее распространенных задач, возникающей при тестировании сложных программных систем, является генерация тестовых данных сложной структуры. Такие задачи характерны для тестирования систем, использующих интернет-протоколы, XML-технологии, SQL-интерфейсы баз данных, интерпретаторов и трансляторов языков программирования, спецификаций, командных языков, а также многих других видов программных систем. Существенным недостатком имеющиеся в настоящее время инструментов для генерации тестовых данных сложной структуры для тестирования, например, приложений над базами данных [1–3], XML-приложений [4–6], компиляторов и других обработчиков формальных языков [7–10] является то, что в них очень узок предоставляемый спектр возможностей по управлению процессом генерации тестовых данных. Поэтому для достижения приемлемого качества тестирования приходится генерировать очень большие множества тестовых данных, что сопряжено с большими накладными расходами по использованию ресурсов как на этапе генерации тестовых данных, так и на этапе анализа результатов прогонов тестов. В настоящей статье представлена технология автоматической генерации тестовых данных сложной структуры, которая предполагает возможность тонкой настройки процесса генерации тестовых данных и оптимизации этого процесса под особенности функциональности конкретного тестируемого приложения. В основу предлагаемой технологии положен опыт работ ИСП РАН по разработке тестовых данных и созданию автоматических генераторов тестовых данных на основе языковых моделей [11–16]. 83
Многие языки формального описания данных сложной структуры, по сути, описывают некоторое атрибутированное дерево. Среди таких языков – BNF грамматики формальных языков, XMLSchema для описания структуры XML-документов, ASN.1 для описания формата данных телекоммуникационных протоколов и многие другие. Действительно, сама структура записи информации в компьютере предполагает такую форму: всякий объект записывается в памяти как некая последовательность бит, в которой можно выделить подпоследовательности, в них – другие подпоследовательности и т.д. При этом выделенные подпоследовательности могут быть связаны между собой, например, равны друг другу.
Рис.1. Представление данных сложной структуры в виде последовательности бит и в виде атрибутированного дерева. Основываясь на этом наблюдении, можно свести генерацию данных сложной структуры к генерации атрибутированных деревьев. При этом необходимо учитывать и горизонтальные связи, возникающие в сложных структурах. 84
В этой статье мы опишем своеобразный framework для создания генераторов атрибутированных деревьев с учетом горизонтальных связей, или, иными словами, с учетом контекста элементов дерева.
3. Предварительные сведения и понятия В начале введём понятие атрибутированного дерева. Как известно, дерево – это граф, в котором нет циклов. Мы будем рассматривать ориентированные деревья, то есть деревья, у которых имеется выделенная вершина – корень, и все ребра ориентированы от более близких к корню вершин к более дальним.
содержаться указание на то, что детей (или атрибутов) данного типа может быть несколько (то есть они образуют список), а также на то, что какой-то ребёнок или атрибут является необязательным, то есть может отсутствовать у вершины данного типа. Пример. Рассмотрим следующее определение типа вершины: вершины типа A должны содержать одного ребёнка типа B с именем child, необязательный атрибут name типа “строка”, список list детей типа С. Примеры вершин типа A приведены на рис.4.
Рис.4. Примеры вершин типа А Рис.2.Ориентированное дерево. Пусть A – некоторая вершина ориентированного дерева. Если она не является корнем, то существует единственная вершина B, из которой в A идет ребро. Вершину B мы будем называть родителем вершины A, а вершину A, соответственно, ребёнком вершины B. В отличие от родителя, детей у вершины может быть много. Деревья, которые мы рассматриваем, являются атрибутированными, то есть с каждой вершиной могут быть связаны её атрибуты – объекты произвольного типа.
Рис.3. Атрибутированное дерево. Мы будем рассматривать гетерогенные деревья, то есть деревья, у которых каждая вершина имеет определённый тип. Тип регламентирует, какие дети и атрибуты и какого типа могут быть у вершины данного типа. При этом все дети и атрибуты именуются. Кроме того, в определении типа может 85
Заметим, что на рис. 4 Б) нет атрибута name – это возможно, так как в определении типа этот атрибут заявлен необязательным, в то же время список list присутствует всегда, хотя может и не содержать элементов. Итак, в определении типа вершины должны быть описаны: атрибуты вершины: их имена и типы, а также то, являются ли они обязательными; списки атрибутов: их имена и типы элементов этих списков; дети вершины: их имена и типы, а также то, являются ли они обязательными; списки детей: их имена и типы элементов этих списков. Детей, атрибуты и списки вершины мы будем называть полями этой вершины. Пусть имеется конечное множество T типов вершин, замкнутое относительно использования типов (то есть все типы вершин, на которые есть ссылки в определениях типов из этого множества, определены). Далее мы будем рассматривать множество D деревьев, типы вершин которых принадлежат множеству T.
4. Идея метода Множество вершин типа t изоморфно декартову произведению множеств объектов, которые могут быть её полями. Поясним это на примере. Пусть вершина типа t содержит: список list детей типа p; 86
ребёнка child типа s; необязательного ребёнка opt_child типа r; атрибут attr типа W; Пусть Ml – множество списков вершин типа p, Ms – множество вершин типа s, Mr – множество вершин типа r, Mw – множество объектов типа W. Тогда множество вершин типа t изоморфно декартову произведению множеств Ml, Ms, Mw, Mr U ε (здесь ε – символ отсутствия ребёнка). Действительно, любой кортеж из этого декартого произведения – набор возможных значений полей вершины типа t.
значение и устанавливаем его в качестве соответствующего поля вершины. Затем строим множества значений для полей, зависящих от уже установленного поля, выбираем из них значения, устанавливаем выбранные значения в качестве соответствующих полей вершины и т.д. Таким образом, множества значений полей строятся в соответствии с уже построенным контекстом, поэтому результирующая вершина будет удовлетворять всем наложенным ограничениям. Для построения детей вершины можно использовать такой же метод генерации вершин соответствующего типа (см. Рис. 6; стрелками указано направление движения данных от одного генератора к другому).
Это наблюдение обеспечивает общий метод генерации вершин типа t. Итак, для построения (конечного) множества вершин типа t необходимо построить конечные множества значений полей такой вершины и выбрать подмножество из декартова произведения этих множеств. До сих пор мы говорили о ситуации, когда нет никаких связей между атрибутами, а также нет никаких ограничений на значения полей вершины. Если такие связи или ограничения есть, то множества значений полей зависят друг от друга. То есть для того, чтобы определить множество допустимых значений для одного поля, нужно знать значения каких-то других полей. Если в строящемся дереве два атрибута разных вершин зависят друг от друга, то эта зависимость может быть описана через зависимость полей некоторого узла (см. Рис. 5).
Рис.5. Зависимость атрибутов как зависимость полей одной вершины. Процесс генерации теперь будет выглядеть несколько по-другому. Сначала нужно определить зависимости между значениями полей. Граф зависимостей должен быть ациклическим, иначе будет невозможно установить порядок построения полей вершины. Когда порядок построения полей определен, строим множество значений для независимого поля, выбираем из него какое-то 87
Рис.6. Система генераторов вершин разных типов. Откуда могут возникать ограничения и связи между элементами дерева? Есть четыре основных источника. первый источник – это семантические требования на данные, такие как требование существования определения используемой переменной в языках программирования или требование уникальности значений какого-то атрибута в XML-документе; второй источник – это удовлетворение требований определённого критерия покрытия. В большинстве практических случаев множество всевозможных деревьев с вершинами определённых типов – это бесконечное множество, и мы, конечно же, не можем построить его целиком. Но это и не нужно, так как обычно такое множество можно разбить на конечное число классов таким образом, что в одном классе будут находиться элементы, “эквивалентные” с точки зрения целевой задачи. Такое разбиение называется критерием покрытия множества деревьев. Для одного и того же множества деревьев можно формулировать разные критерии покрытия; они будут зависеть от целевой задачи; 88
третий источник – это ограниченность ресурсов. Часто даже в пределах одной задачи сформулировать для неё точный критерий покрытия невозможно или же слишком трудоёмко. Поэтому приходится брать более сильный критерий покрытия, разбивающий множество деревьев на более мелкие классы. При этом число классов может весьма сильно возрасти. О том, как можно бороться с этой проблемой, будет сказано немного позже; наконец, четвертый источник – это наличие рекурсии во множестве типов вершин T. Наличие рекурсии влечет необходимость её ограничения, то есть запрещения перебора деревьев с глубиной рекурсии, большей некоторого числа. При отсутствии ограничений на рекурсию множество допустимых деревьев бесконечно, а это значит, что генератор может войти в бесконечный цикл. Итак, в самом общем виде идея состоит в том, чтобы создать систему генераторов вершин. Генераторы образуют иерархическую структуру, соответствующую структуре типов вершин. При этом работа генератора поля вершины может зависеть от результата работы генераторов других полей этой же вершины в соответствии с требованиями, накладываемыми на строящееся дерево, то есть работа генераторов зависит от контекста строящейся вершины.
5. Способ описания ограничений Чтобы как-то учитывать требования при построении дерева, необходимо организовать движение информации по дереву. Иными словами, при построении очередной вершины мы должны знать, в каком контексте находится эта вершина и какие ограничения должны быть наложены на поля вершины в соответствии с этим контекстом. В каждый момент построения дерева мы имеем какую-то конфигурацию вершин и атрибутов. Для конкретной вершины в этом недостроенном дереве мы можем определить её состояние – это то, что окружает вершину в данный момент. Фактически, это вся построенная часть дерева, но с точки зрения данной вершины. Рассмотрим понятие состояния на примере. На рис. 7 показано дерево с тремя вершинами типа Def (определение переменной). Чем отличается состояние, например, второй вершины от состояния первой? Состояния этих вершин различны, так как положения их в дереве различны. Из состояния (положения) первого определения видно, что в нём не могут быть использованы ссылки на переменные, так как до этой инструкции нет определённых переменных, а во втором определении можно использовать ссылку на переменную, поскольку к этому моменту уже есть определённая переменная v1.
89
Рис.7. Разные состояния вершин одного типа. При построении дерева обычно не требуется полностью знать состояние вершины. Нужны некоторые аспекты построенной части дерева, которые могут влиять на построение полей данной вершины. Эти аспекты будем называть аспектами состояния вершины. В приведённом выше примере для построения новой инструкциии определения переменной нам нужен только один аспект состояния – список уже определённых переменных. Заметим, что при построении очередного элемента дерева все аспекты состояний вершин, для которых существенен этот элемент, должны измениться. Итак, множество значений поля вершины может зависеть от значений других полей этой вершины; некоторых аспектов состояния вершины. Введём понятие элементарного ограничения. Это ограничение накладывается на поле вершины. Чтобы его определить, нужно задать: тип вершины t; поле f вершины типа t, на которое накладывается ограничение; поля вершины f1, f2, …, fk, от которых зависит множество допустимых значений поля f; аспекты состояния вершины S1, S2, … , Sn, от которых зависит множество допустимых значений поля f; способ вычисления множества допустимых значений поля f; функцию проверки значения поля f на соответствие определяемому ограничению; функцию ограничения количества используемых значений поля f. Итак, в каждой вершине строящегося дерева определены: аспекты состояния этой вершины, существенные для её построения; ограничения на поля вершины. 90
Систему аспектов состояния вершины и ограничений на её поля будем называть контекстом данной вершины. Контексты детей вершины зависят от контекста вершины. Контекст ребёнка, который зависит от какого-то поля, должен вычисляться с учётом построенного значения этого поля.
6. Итераторы В определении элементарного ограничения указано, что для него нужно задать способ вычисления множества допустимых значений. Мы будем использовать для этого технику итераторов. В самом общем виде итератор – это объект, имеющий несколько последовательных состояний. Итератор может перевести себя в начальное состояние; находясь в каком-то состоянии, он может перевести себя в следующее состояние, а также ответить на вопрос, имеется ли у него следующее состояние. Итератором значений называется итератор, связанный с некоторым упорядоченным множеством. Итератор значений можно рассматривать как “указатель” на элемент множества. Для него имеются три операции: встать в начало множества, передвинуться на следующий элемент множества и выдать текущий элемент множества (вместо “выдачи” текущего элемента итератор значений может проделать с ним любую другую операцию). Помимо итераторов значений, существуют комбинаторы. Комбинатор – это итератор, который управляет другими итераторами, то есть указывает им, в каком порядке передвигаться по состояниям. Состоянием комбинатора обычно является кортеж состояний подотчётных ему итераторов. Пример. Пусть имеются итераторы значений i1, i2, связанные с множествами {a,b,c},{d,e} соответственно. И пусть мы хотим построить итератор значений декартова произведения этих множеств.
состояний итераторов i1, i2. Таким образом, чтобы перейти к начальному состоянию, наш комбинатор переводит итераторы i1, i2 в начальное состояние. Чтобы перейти к следующему состоянию, комбинатор смотрит, в каком положении находится итератор i2. Если у этого итератора есть следующее состояние, то итератор в него переводится – получается новая пара состояний итераторов i1, i2, то есть новое состояние для комбинатора. Если у итератора i2 нет следующего состояния, то комбинатор смотрит, есть ли следующее состояние у итератора i1; если есть, то итератор i1 переводится в следующее состояние, а итератор i2 – в начальное, снова получается новая пара состояний итераторов i1, i2, а значит и новое состояние комбинатора. Если же следующего состояния нет ни у итератора i1, ни у итератора i2, то это означает, что и комбинатор не имеет следующего состояния. Другой важный пример – это комбинатор системы зависимых итераторов. Пусть нам нужно проитерировать пары букв и цифр, при этом для каждой буквы есть своё множество цифр, с которыми она может быть в паре. Пусть для буквы a – это множество {0, 1}, для буквы b – множество {2, 3, 5}, для буквы c – множество {4}. Заведём итераторы для множеств цифр и букв: итератор i1 для множества {a, b, c}, итераторы i2, i3, i4 – для множеств {0, 1}, {2, 3, 5}, {4} соответственно. Состоянием комбинатора теперь будет пара состояний двух других итераторов: итератора i1 и итератора, соответствующего состоянию итератора i1. Работа комбинатора проиллюстрирована рис. 9. При переходе итератора букв в следующее состояние в зависимости от этого состояния выбирается итератор цифр. При переходе к другой букве итератор цифр будет другим.
Рис.9. Комбинатор зависимых итераторов.
Рис.8. Пример комбинатора итераторов. Используем для этого такой комбинатор (Рис. 8): его состояния – это тройка состояний итераторов i1, i2. Начальное состояние – тройка начальных 91
Можно определить комбинатор системы зависимых итераторов для итерации кортежей длины n. Точно так же, как было в примере с парами буква-цифра, итератор первого компонента кортежа последовательно проходит все свои состояния. В зависимости от его состояния выбирается итератор второго компонента кортежа, который также проходит все свои состояния (в это время состояние итератора первой компоненты кортежа менять нельзя). В зависимости от состояний итераторов первого и второго компонентов 92
выбирается итератор третьего компонента кортежа и т.д. Итераторы, от которых зависит выбор других итераторов, будем называть осями зависимости. Так итератор первого компонента кортежа – это первая ось зависимости, итератор второго компонента – это вторая ось зависимости и т.д. В приведенном выше примере одна ось зависимости – итератор букв.
7. Схема генерации В данном разделе описан один из возможных подходов, использующих определённые ранее понятия. Тот генератор, который мы опишем, является итератором, то есть он строит деревья последовательно, по одному. Как отмечалось раньше, для каждого типа вершин имеется свой генератор. Все эти генераторы появляются по требованию и зависят от уже построенной части дерева, то есть контекста. Итак, опишем генератор вершин типа t. Контекст для строящейся вершины типа t – это внешний параметр генератора. 1. Генератор создаёт вершину-заготовку, в которой поля не установлены. 2. По контексту вершины генератор вычисляет граф зависимостей между полями вершины. Для этого он использует информацию из элементарных ограничений. 3. Из графа зависимостей вычисляется порядок построения полей: f1 → f2 → … → fn. 4. Далее строится комбинатор зависимых генераторов значений полей, у которого первая ось зависимости – генератор независимого поля f1, вторая ось зависимости – генератор поля f2, зависимого от поля f1, и т.д. При этом генераторы полей при переходе в новое состояние проставляют значение соответствующего поля вершины. 5. Генератор значений конкретного поля fi создаётся так: если поле – ребёнок c типа s, то генератор строит для него контекст, основываясь на контексте вершины-родителя и значений полей, от которых зависит поле fi. Для полученного контекста строится генератор вершин типа s, который используется в качестве генератора значений поля; если поле – атрибут, то генератор значений для него строится так: o для всех элементарных ограничений, наложенных на данный атрибут, вычисляются множества допустимых значений M1, M2, …, Mk. Как было сказано ранее, они задаются итераторами; o строится итератор, перебирающий значения из объединения множеств M1, M2, …, Mk, которые удовлетворяют всем элементарным ограничениям, наложенным на данный атрибут. Этот итератор и будет генератором значений данного атрибута; если поле – список, то для него строится комбинатор зависимых итераторов, у которого первая ось зависимости – итератор длины 93
списка, вторая – итератор первого элемента списка, третья – итератор второго элемента списка, и т.д. Этот комбинатор используется в качестве генератора значений списка. Генераторы значений элементов списка строятся так же, как и генераторы значений полей. Заметим, что при генерации должны своевременно обновляться все аспекты состояний вершин, которые могут использоваться в элементарных ограничениях для вычисления множества допустимых значений.
8. Использование абстрактных моделей В описании схемы генерации почти ничего не говорилось об использовании аспектов состояний вершин. Между тем, помимо снижения вычислительных затрат, этот механизм даёт весьма важные преимущества при решении задач снижения количества генерируемых данных, достижения критериев покрытия и целенаправленной генерации. Для начала введём понятие абстрактной модели. Абстрактная модель объекта – это фактор-объект, получаемый с помощью абстрагирования от некоторых деталей. В качестве примера можно привести граф наследования классов в языках Java или C++. Граф наследования не содержит никакой информации о методах или полях классов, он является абстрактной моделью системы классов, для которой эта информация несущественна. Абстрактная модель может иметь разные представления, так, например, граф наследования можно представить в виде списка рёбер, или в виде последовательности чисел, или, графически, в виде дерева. То есть модель эквивалентна некоторой части объекта, но может не совпадать с ней (Рис. 10).
Рис.10. Абстрагирование от деталей. По абстрактной модели можно построить много различных объектов, реализующих эту модель. Для графа наследования классов это можно сделать, меняя состав методов и полей. Опишем теперь, как можно использовать абстрактные модели для генерации атрибутированных деревьев. 94
Пусть мы хотим построить группу деревьев, отвечающих некоторой абстрактной модели. Для этого мы немного расширим понятие состояния вершины. Добавим к нему новый аспект, который будет содержать описание абстрактной модели этой вершины. Автоматически в корневой вершине возникает элементарное ограничение на соответствие дерева имеющейся абстрактной модели. При переходе к генерации полей мы должны будем добавлять к их контексту абстрактные модели, моделирующие эти поля (они могут быть получены из абстрактной модели корневой вершины), а также ограничение соответствия значения поля его абстрактной модели (Рис. 11). Таким же образом можно моделировать не только дерево целиком, но и отдельные его части.
Рис.11. Использование абстрактной модели при генерации. С помощью техники абстрактных моделей можно эффективно уменьшать количество деревьев, увеличивать целенаправленность генерации. Действительно, при использовании “переборного” способа генерации для построения дерева, отвечающего абстрактной модели, требуется перебор многочисленных деревьев, не соответствующих этой модели, а при применении описанного способа требуемое дерево получается с самого начала. Кроме того, использование абстрактных моделей – это удобный способ ограничения рекурсии.
9. Заключение В статье представлена технология автоматической генерации тестовых данных сложной структуры, которая предполагает возможность тонкой настройки процесса генерации тестовых данных и оптимизации этого процесса под особенности функциональности конкретного тестируемого приложения. Эта возможность позволяет организовывать целенаправленную генерацию тестовых данных и, как следствие, генерировать множества тестовых данных относительно небольшого размера и с достаточно высоким качеством. Предложенная технология автоматической генерации тестовых данных сложной структуры будет востребована, в первую очередь, в таких областях 95
разработки ПО, как телекоммуникационные приложения, в частности, интернет-приложения; приложения, работающие над базами данных; компонентное ПО, использующее XML-интерфейсы; языковые процессоры. Литература 1. Quest Software Datafactory. http://www.quest.com/datafactory/ 2. Canam Software Turbodata. http://www.turbodata.ca/ 3. IBM DB2 Database Test Generator. http://www-306.ibm.com/software/data/db2imstools/db2tools/db2tdbg/ 4. XML-XIG. http://sourceforge.net/projects/xml-xig 5. Sun XML Instance Generator. http://www.sun.com/software/xml/developers/instancegenerator/ 6. XML Generator. http://www.stylusstudio.com/xml_generator.html 7. Paul Purdom. A sentence generator for testing parsers. // Behavior and Information Technology. 12(3):366–375, 1972. 8. B.A. Wichmann, B.Jones. Testing ALGOL 60 compilers. // Software Practice and experience. 6 (1976) 261–270. 9. A. Celentano, S. Crespi Reghezzi, P. Della Vigna, C. Ghezzi, G. Granata, and F. Savoretti. Compiler Testing using a Sentence Generator. // Software – Practice and Experience. 10:897–918, 1980. 10. A.G. Duncan, J.S. Hutchison. Using Attributed Grammars to Test Designs and Implementation. // In Proceedings of the 5th international conference on Software engineering. 170–178, 1981. 11. А.К. Петренко и др. Тестирование компиляторов на основе формальной модели языка // Препринт института прикладной математики им. М.В Келдыша, № 45, 1992. 12. A. Kalinov, A. Kossatchev, A. Petrenko, M. Posypkin, V. Shishkov. Using ASM Specifications for Compiler Testing // Proceedings of Abstract State Machines Advances in Theory and Applications 10th International Workshop, ASM 2003. 13. С.В. Зеленов, С.А. Зеленова, А.С. Косачев, А.К. Петренко. Генерация тестов для компиляторов и других текстовых процессоров // Программирование, Москва. – 2003. – 29. – № 2. – с. 59-69. 14. С.В. Зеленов, С.А. Зеленова. Генерация позитивных и негативных тестов парсеров // Программирование, том. 31, №6, 2005, 25–40. 15. С.В. Зеленов, С.А. Зеленова, А.С. Косачев, А.К. Петренко. Применение модельного подхода для автоматического тестирования оптимизирующих компиляторов // CIT Forum, 2003. http://www.citforum.ru/SE/testing/compilers/ 16. М.В. Архипова. Генерация тестов для модулей проверки статической семантики в компиляторах. // Труды ИСП РАН, т. 8, 2004, с. 59-76. 17. A.K. Petrenko. Specification Based Testing: Towards Practice // LNCS. – 2001. – 2244. – p. 287-300. 18. В.В. Кулямин, А.К. Петренко, А.С. Косачев, И. Б. Бурдонов. Подход UniTesK к разработке тестов. // Программирование, 29(6):25–43, 2003. 19. V. Kuliamin, A. Petrenko, A. Kossatchev, I. Bourdonov. UniTesK: Model Based Testing in Industrial Practice. // Proceedings of the 1-st European Conference on Model-Driven Software Engineering, Nurnberg, December 2003.
96
Паттерны проектирования тестовых сценариев1 В.С. Мутилин,
[email protected] Аннотация. Рассматриваются вопросы использования типовых решений (паттернов проектирования) для построения тестовых программ, основанных на обобщенных моделях тестируемых систем в форме неявно заданных конечных автоматов. В качестве базовой технологии применяется технология UniTesK [1].
1. Введение В тестировании на основе моделей модели используются для нескольких целей: для проверки соответствия тестируемой системы требованиям, представленным в модели; для задания покрытия; для генерации тестовых данных. Нас, в первую очередь, будет интересовать последний случай – применение моделей для генерации тестовых данных. При тестировании на основе моделей широко распространенным подходом к генерации тестовых данных является представление системы в виде конечного автомата и генерация тестовой последовательности на основе его обхода [2, 3, 4]. Авторы, использующие конечные автоматы для тестирования, отмечают, что это позволяет повысить качество тестирования, улучшить сопровождаемость тестов [5, 6]. В работе [7] отмечается, что использование конечных автоматов дает возможность получать последовательности, которые было бы сложно получить при применении других подходов. В отличие от ручного тестирования и тестирования с автоматизированным прогоном тестов, при использовании автоматов тестовая последовательность строится автоматически. В случайных тестовых последовательностях сложно управлять тестами, направлять их на проверку определенных требований. В результате часть важных требований может быть не проверена вовсе. В данной работе в качестве базовой технологии рассматривается технология UniTesK [1]. В UniTesK для генерации тестовых данных используются модели конечных автоматов, задаваемые в тестовых сценариях. Тестовая последовательность получается автоматически в результате обхода такого автомата. Для проверки требований и задания покрытия используются формальные спецификации. Технология UniTesK позволяет использовать 1
Работа поддержана грантом РФФИ (05-01-999). 97
только тестовые сценарии без спецификаций; при этом проверка требований и оценка покрытия может производиться в тестовом сценарии. Паттерны, описываемые в статье, могут применяться для разработки тестовых сценариев и без использования спецификаций. Однако обычно применение технологии предполагает наличие спецификации системы, и поэтому мы будем считать, что для задания требований используется спецификация, определяющая модельное состояние. Заметим лишь, что в случае отсутствия спецификации в паттернах вместо модельного состояния следует оперировать состоянием реализации. Практически для всех методов, основанных на конечных автоматах, увеличение количества состояний приводит к увеличению длины тестовой последовательности и времени работы тестов. Поэтому на практике для методов характерны ограничения на приемлемое количество состояний, при котором тесты работают не слишком долго. Так, для метода, описанного в [2], приемлемым оказывается несколько десятков состояний. Для технологии UniTesK приемлемое количество состояний гораздо больше – несколько сотен. Опыт показывает, что размер спецификации составляет четвертую-пятую часть размера реализации, а в некоторых случаях может достигать размера реализации. Поэтому число модельных состояний, определяемых спецификацией, часто оказывается слишком большим для того, чтобы использовать их в качестве состояний конечного автомата. Кроме того, это может оказаться и вовсе невозможным, если число состояний модели бесконечно. Для борьбы с разрастанием числа состояний в технологиях, в которых для построения тестовых последовательностей используются конечные автоматы, часто используется обобщение состояний, представляющее собой разбиение состояний модели на классы эквивалентности. В работе [6] такие классы называются гиперсостояниями. В технологии UniTesK обобщение состояний задается в тестовом сценарии с помощью функции, возвращающей обобщенное состояние на основе состояния модели. Для использования обобщенного состояния для построения тестовой последовательности необходимо выполнение требований накладываемых обходчиком, который осуществляет обход конечного автомата. Одно из основных требований обходчиков – это присутствие детерминированности в той или иной степени: просто детерминированности, наличия детерминированного сильно связного покрывающего подавтомата или сильной дельта-связности. Таким образом, выбор обобщенного состояния в тестовом сценарии – это поиск компромисса между количеством состояний, возможностями обходчика, использующего это состояние для построения тестовой последовательности, и разнообразием состояний, т.е. возможностью покрыть в процессе обхода требуемые тестовые ситуации. В следующем разделе будет показано, что разработка тестового сценария по технологии UniTesK – это сложная задача, и автоматизировать ее не удается. Процесс разработки затрудняется тем обстоятельством, что количество 98
состояний модели велико, а порой и бесконечно. Построение автомата приемлемых размеров не гарантируется; кроме того, не для всех моделей можно построить автомат, удовлетворяющий требованиям обходчиков. Поскольку автомат задается неявно, т.е. его окончательный вид определяется только в процессе обхода, проверка требований обходчика затруднительна до запуска тестов, что еще больше усложняет задачу. В данной статье предложены паттерны проектирования, использование которых позволяется упростить разработку тестовых сценариев. Паттерны, которые рассматриваются в статье, во многом схожи с паттернами, предложенными Кристофером Александером [8] для проектирования зданий. По его словам, «любой паттерн описывает задачу, которая снова и снова возникает в нашей работе, а также принцип ее решения, причем таким образом, что это решение можно потом использовать миллион раз, ничего не изобретая заново». Еще больше описываемые паттерны схожи с паттернами проектирования объектно-ориентированных программ [9]. Так же, как и для паттернов объектно-ориентированного проектирования, при описании паттернов проектирования тестовых сценариев упор делается на решение, а не на проблему. Паттерны описываются без использования формального представления. На данном этапе важно исследовать пространство паттернов, а не формализовать его. Так же, как и для паттернов Александера, возможно последовательное применение описываемых паттернов, и в большинстве случаев это приводит к хорошему решению. Однако, в отличие от паттернов Александера, описываемые паттерны не составляют «полный» набор, из которого можно вывести пошаговые инструкции по созданию тестового сценария. Паттерны получены на основе изучения существующих тестовых сценариев, написанных с использованием технологий KVEST и UniTesK. Технология UniTesK появилась, как развитие технологии KVEST, разработанной для тестирования ядра операционной системы Nortel Networks [10]. UniTesK в течение многих лет успешно применяется для тестирования различного программного обеспечения. Для нахождения паттернов было проанализировано около трехсот тестовых сценариев. Статистика показывает, что найденные паттерны используются в 80% тестовых сценариев, и лишь в 20% случаев требуются дополнительные соображения. Паттерны позволяют передать опыт, накопленный разработчиками тестовых сценариев. Опытный разработчик, вместо решения каждой задачи с нуля, старается повторно пользоваться теми решениями, которые оказались удачными в прошлом. Отыскав хорошее решение, он будет прибегать к нему снова и снова. Разработчик, знакомый с паттернами, может сразу применять их к решению новой задачи, не пытаясь каждый раз «изобретать велосипед».
2. Процесс разработки тестового сценария В технологии UniTesK задание конечного автомата, на основе которого строится тестовая последовательность, вынесено в отдельный компонент – 99
тестовый сценарий [11]. В тестовом сценарии задаются обобщенные состояния и итерации параметров методов. Обобщенные состояния определяют состояния конечного автомата и задаются с помощью функции, возвращающей обобщенное состояние на основе состояния модели. Таким образом, состояния модели разбиваются на классы с одинаковым обобщенным состоянием. Обобщенные состояния позволяют уменьшить количество состояний автомата. Итерации параметров методов задают перебор параметров, с которыми следует вызвать методы, определяя тем самым возможные переходы автомата. Однако при таком задании переходов состояние после выполнения метода неизвестно до вызова метода. Поэтому окончательный вид автомата определяется только в процессе обхода автомата. На основе описания обобщенного состояния и итераций параметров методов специальный компонент тестовой системы, называемый обходчиком, строит обход переходов автомата, получая тестовую последовательность. В процессе обхода, выполняя вызовы методов, обходчик определяет окончания переходов автомата. Определение тестовых ситуаций
Выбор обобщенного состояния и итераций
Запуск тестов
Детерминизация
Аналитическая проверка требований
Анализ результатов Рис. 1. Процесс разработки тестового сценария Процесс разработки тестового сценария начинается с определения тестовых ситуаций (рис. 1). Тестовая ситуация определяет множество состояний, метод, и наборы параметров при которых она может быть покрыта. Тестовая ситуация является достижимой в данном состоянии, если в этом состоянии существует такой набор параметров, что метод, вызванный с этим набором параметров в данном состоянии, покрывает эту ситуацию. Тестовые ситуации позволяют определить, какие состояния являются «интересными» для тестирования, а 100
также судить о разнообразности обобщенных состояний. Обобщенные состояния являются разнообразными, если они позволяют покрыть выбранные тестовые ситуации. Основным источником тестовых ситуаций является покрытие, определенное в спецификациях, так как именно оно является основным критерием завершенности тестирования в технологии UniTesK. При определении тестовых ситуаций могут учитываться также дополнительные соображения, не нашедшие отражения при определении покрытия в спецификации. Например, если в качестве модельного состояния используются деревья, то дополнительным соображением может быть наличие разнообразных деревьев: высоких, широких, сбалансированных и т.д. Подобные свойства сложно отображать в спецификации. Другой пример дополнительного соображения – перебор значений свойства, не определяющего внешнее поведение реализации и, соответственно, не отраженнного в спецификации. Однако внутреннее поведение реализации может зависеть от этого свойства, используемого для оптимизации. На следующем шаге процесса разработки тестового сценария происходит выбор обобщенного состояния и итераций параметров методов. Выбор обобщенного состояния является непростой задачей Это поиск компромисса между количеством состояний, их разнообразием и возможностями обходчика, использующего данное состояние для построения тестовой последовательности. Количество состояний непосредственным образом отражается на длине тестовой последовательности и времени работы тестов. Приемлемое количество состояний зависит от скорости работы системы. Опыт показывает, что для быстрых систем приемлемое количество состояний – несколько сотен, для медленных – несколько десятков. Обобщенные состояния должны быть достаточно разнообразными, чтобы обход всех переходов автомата покрывал выбранные тестовые ситуации. Для этого достаточно в качестве обобщения взять разбиение состояний на группы, в которых достижимы одинаковые наборы тестовых ситуаций. Такое разбиение обычно не приводит к большому количеству состояний, так как количество тестовых ситуаций обычно невелико. Рассмотрим пример обобщения. На рис. 2 в левой части показаны состояния, у каждого из которых нарисованы возможные переходы. Переходы, соответствующие разным тестовым ситуациям, показаны стрелками с разной штриховкой. Обобщением является объединение состояний с одинаковым набором штриховок выходящих стрелок. Очевидно, что обход, покрывающий все переходы автомата, покроет все возможные тестовые ситуации.
Рис. 2. Пример начального обобщения Если количество обобщенных состояний слишком велико, то приходится жертвовать разнообразием, укрупняя обобщенные состояния. Укрупнение обобщенных состояний может приводить к тому, что в двух модельных состояниях одного обобщенного состояния, достижимыми являются разные множества тестовых ситуаций. Может получиться так, что в одном модельном состоянии тестовая ситуация достижима, а в другом – нет. Однако можно надеяться, что в процессе обхода будут получены оба состояния, и все тестовые ситуации будут все равно покрыты. В ситуации, показанной на рис. 3, обобщение состояний приводит к тому, что не всякий обход автомата покроет переходы, заштрихованные вертикальными линиями, сеточкой и точками. На рисунке эти стрелочки обозначены пунктирной линией.
Рис.3. Укрупнение состояний 101
102
В ряде случаев достигнуть покрытия, не меняя обобщенного состояния, удается с помощью введения дополнительных сценарных методов или путем разработки дополнительного тестового сценария, возможно, с другим обобщенным состоянием. На этом же шаге процесса разработки тестового сценария выбираются итерации параметров методов. Итерации задаются в сценарных методах тестового сценария. В общем случае одному тестируемому методу может соответствовать несколько сценарных методов, и один сценарный метод может задавать итерации сразу для нескольких методов, а также последовательность вызовов. В наиболее частом случае каждому методу соответствует один сценарный метод. Итерация параметров может быть выполнена с фильтрацией по критерию покрытия. В этом случае итерируются идентификаторы элементов покрытия (тестовых ситуаций), и для каждого элемента подбирается набор параметров, покрывающий выбранный элемент. Фильтрацию также называют обобщением переходов, так как она разбивает параметры и состояния на группы, соответствующие разным элементам тестового покрытия. Фильтрация может использоваться как для сокращения количества переходов, так и для детерминизации автомата. После начального выбора обобщенного состояния и итераций следует проверить выполнение требований обходчика. На данный момент в инструментах UniTesK есть пять видов обходчиков: 1. базовый обходчик; 2. обходчик детерминированных автоматов; 3. обходчик детерминированных автоматов с функцией сброса; 4. обходчик автоматов, имеющих детерминированный, сильно связный покрывающий подавтомат; 5. обходчик сильно дельта-связных автоматов. Во всех этих обходчиках требуется, чтобы число состояний и переходов было конечно. В базовом обходчике не используется обобщенное состояние; считается, что у автомата имеется одно единственное состояние, и, таким образом, не накладываются какие-либо дополнительные ограничения. В обходчике детерминированных автоматов требуется, чтобы автомат, описываемый сценарием, был детерминированным и сильно связным. В обходчике детерминированных автоматов с функцией сброса требуется детерминированность автомата, а сильная связность обеспечивается с помощью функции сброса, задаваемой разработчиком сценария. Четвертый обходчик может работать с недетерминированными автоматами, однако в нем требуется, чтобы существовал детерминированный сильно связный подавтомат, который содержит все состояния исходного автомата. В последнем обходчике требуется, чтобы для любых двух состояний автомата существовала адаптивная тестовая последовательность, ведущая из одного состояния в другое. Заметим, что этому требованию заведомо удовлетворяют автоматы, содержащие детерминированный сильно связный покрывающий подавтомат. 103
Для удовлетворения требований обходчиков можно использовать следующие методы: 1. дробление состояний; 2. введение связующих переходов; 3. обобщение переходов. Метод дробления состояний описан в [12] (в статье ему соответствуют алгоритмы 1 и 2 построения дельта детерминированного и вполне определенного фактор-графа). Здесь он называется методом дробления, так как его применение для заданного начального обобщения состояний и переходов приводит к разбиению состояний, принадлежащих одному обобщенному состоянию, на несколько непересекающихся групп, которые образуют новые обобщенные состояния (рис. 4). Использование метода дробления позволяет во многих случаях достичь детерминированности автомата. Многие предлагаемые в данной статье паттерны могут быть получены применением метода дробления. Однако для применения метода требуется представление модели в виде конечного автомата. Преставление модели в виде конечного автомата зачастую бывает слишком сложным из-за слишком большого числа получающихся состояний и переходов. Кроме того метод применим не для всех автоматов. a
a
a
a
Рис. 4. Метод дробления Метод дробления может построить автомат, удовлетворяющий требованиям обходчика, однако число его состояний может быть слишком велико. Тогда для уменьшения количества состояний можно жертвовать разнообразностью состояний. Метод связующих переходов используется совместно с обходчиком автоматов, имеющих детерминированный, сильно связный покрывающий подавтомат. К уже имеющемуся недетерминированному переходу добавляется дополнительный детерминированный переход, гарантированно переводящий систему в заданное состояние. Например, если метод в зависимости от некоторых свойств переводит систему в два разных состояния, можно ввести дополнительный переход (сценарный метод), при выполнении которого перед вызовом метода свойства устанавливаются таким образом, чтобы перейти в требуемое состояние. На рис. 5 показан метод add, который в зависимости от 104
некоторых свойств либо изменяет, либо не изменяет состояние. Для обеспечения существования детерминированного подграфа вводится сценарный метод add2, гарантированно переводящий систему в новое состояние. add2 add add
Рис. 5. Метод введения связующих переходов Метод обобщения переходов состоит в использовании фильтрации при итерации параметров методов. Например, если при тестировании метода добавления элемента во множество целых чисел в качестве обобщенного состояния выбирать размер множества, то простая итерация параметров приводит к недетерминированному автомату. Обобщение переходов по ветвям функциональности «добавляемый элемент есть во множестве», «добавляемого элемента нет в множестве» позволяет получить детерминированный автомат (см. рис. 6). add(2)
{1}, {2}
add(2)
add(существующий)
{1, 2}
add(новый)
{1}, {2}
{1, 2}
Рис. 6. Метод обобщения переходов До запуска тестов желательно проверить выполнение требований обходчика. Однако данная проверка затруднительна, поскольку до запуска тестов сложно представить точный вид автомата. В сценарии задаются лишь состояние автомата и итерации переходов, а результирующие состояния переходов определяются только во время работы теста. С одной стороны, неявное задание автомата позволяет упростить задание автомата в тестовом сценарии, но, с другой стороны, затрудняет проверку требований обходчика без запуска тестов. Кроме того, при неоднозначности в спецификации возможных пост-состояний тестирование разных реализаций может приводить к построению разных автоматов, отличающихся результирующими состояниями переходов. 105
В процессе запуска тестов определяется точный вид автомата. Результатами запуска тестового сценария является: 1. достигнутое покрытие спецификаций (тестовых ситуаций); 2. нарушение или выполнение требований обходчика; 3. количество состояний, переходов и время работы. Таким образом, процесс построения тестового сценария оказывается сложным. Процесс затрудняется тем обстоятельством, что количество состояний модели велико, а порой и бесконечно. Построение автомата приемлемых размеров не гарантируется; кроме того, не для всех моделей можно построить автомат, удовлетворяющий требованиям обходчиков. Поскольку окончательный вид автомата определяется в процессе обхода, проверка требований обходчика до запуска тестов затруднительна, что еще больше усложняет задачу.
3. Понятие паттерна Паттерны представляют собой удачные решения часто встречающихся задач. Там, где использование процесса разработки тестового сценария приводит к хорошему результату, паттерны позволяют повторно использовать полученные результаты. Там, где использование процесса затруднительно, знание паттернов позволяет выделить части, к которым они применимы. Паттерны позволяют использовать инструментальную поддержку. Паттерны, описываемые в данной статье, получены на основе анализа более чем десятилетнего опыта разработки тестов ИСП РАН [13] в различных проектах: Nortel Networks (ядро ОС); Luxoft (банковское приложение); Intel (стандартная библиотека Java); Microsoft Research (протокол IPv6); Вымпелком (детализация по счетам); НИИ системных исследований РАН (ОС 2000); Persistent (Service Data Objects, реализация BEA). Было проанализировано около трехсот тестовых сценариев. В результате анализа проектов выделено десять наиболее распространенных паттернов. Названия выделенных паттернов, их краткая характеристика и статистика использования показаны в таблице 1. Наиболее широкое применение имеет группа паттернов с размером структуры данных в качестве обобщенного состояния: длина списка, размер множества, размер отображения, число вершин дерева. Эти паттерны используются в более чем половине случаев применения паттернов.
106
Название
Краткая характеристика
Длина списка
В качестве обобщенного состояния выбирается длина списка В качестве обобщенного состояния выбирается размер множества В качестве обобщенного состояния выбирается размер отображения В качестве обобщенного состояния выбирается число элементов дерева В качестве обобщенного состояния выбирается декартово произведение других обобщенных состояний Паттерн основан на выделении элементов обладающих некоторыми свойствами В качестве обобщенного состояния выбирается одно единственное состояние В качестве обобщенного состояния выбирается мультимножество, элементами которого являются числа – количество непосредственных детей для каждой вершины дерева В качестве обобщенного состояния выбирается код дерева, однозначно определяющий его структуру Все промежуточные состояния объединяются в одно обобщенное состояние
Размер множества Размер отображения Число вершин дерева Декартово произведение Выделение элементов Единственное состояние Мультимножество чисел детей Код дерева Среднее состояние ? (без паттерна)
Статистика использования 13% 17% 41% 8% 3% 18% 5% 10% 39%
обобщенных состояний, соответствующих разным структурам данных, используется паттерн декартово произведение. В паттерне выделение элементов учитываются свойства элементов, так как в остальных паттернах при выборе обобщенного состояния предполагается, что тестовые ситуации от свойств элементов не зависят. Описание каждого паттерна состоит из следующих частей: 1. название; 2. краткое описание; 3. область применения; 4. обобщенное состояние; 5. итерация параметров методов; 6. примеры; 7. совместное использование; 8. использование в проектах. Название служит для краткого именования паттерна. Область применения описывает ситуации, в которых применим описываемый паттерн, а также известные расширения паттерна. В части примеров приводятся простые и наглядные примеры применения паттернов; в данной статье примеры приводятся на расширении языка Java [14]. В части совместного использования приводятся паттерны, с которыми можно удачно использовать описываемый паттерн. Примеры использования паттерна в проанализированных проектах описываются в части использования в проектах.
3%
4. Описание паттернов 2% 1% 20%
Таблица 1. Паттерны проектирования В большой части паттернов явным образом определяется обобщенное состояние; это такие паттерны, как длина списка, размер множества, размер отображения, число вершин дерева, единственное состояние, мультимножество чисел детей, код дерева, среднее состояние. Оставшиеся два паттерна декартово произведение и выделение элементов явным образом состояние не определяют, а используются совместно с другими паттернами. Паттерны покрывают большинство распространенных структур данных: списки, множества, отображения, деревья. Для объединения нескольких 107
В этом разделе приводятся подробные описания четырех паттернов: длина списка, размер множества, декартово произведение и мультимножество чисел детей. Паттерн размер отображения очень похож на паттерн размер множества применительно к множеству ключей отображения, отличие состоит только в дополнительной итерации значений отображения при добавлении элементов. Паттерны число вершин дерева и код дерева – это упрощенные версии паттерна мультимножество чисел детей. Первый паттерн не покрывает деревья разнообразной структуры, однако позволяет тестировать деревья с большим количеством вершин. Второй паттерн позволяет протестировать деревья всевозможных структур, однако применим лишь для небольшого числа вершин. Паттерн среднее состояние позволяет тестировать достижение максимального количества элементов в различных структурах данных (списках, множествах, отображениях, деревьях) за счет объединения всех промежуточных состояний между максимальным и минимальным состояниями в одно обобщенное состояние. 108
Паттерн единственное состояние применяется для тестирования методов, не зависящих от состояния, и для построения простого тестового сценария, в котором разнообразие покрываемых тестовых ситуаций полностью зависит от выбора итераций параметров методов. Паттерн выделение элементов основан на выделении элементов, обладающих некоторыми свойствами. Он применяется совместно с другими паттернами: длина списка, размер множества, размер отображения, число вершин дерева и другими. Цель применения – покрытие тестовых ситуаций, зависящих от свойств элементов. Данный паттерн может применяться совместно с паттерном декартово произведение с целью достижения детерминизма.
4.1.5. Примеры
4.1. Длина списка
Тестовые ситуации для метода add: 1. список пуст; 2. список не пуст. Тестовые ситуации для метода remove: 1. индекс index отсутствует в списке; 2. индекс index есть в списке: a. список пуст; b. список содержит единственный элемент; c. список содержит больше одного элемента. Обобщенное состояние – IntGenState, параметр конструктора – длина списка modelList: modelList.size(). Для ограничения количества состояний в сценарий добавляется переменная int maxSize. Для метода add с использованием конструкции iterate итерируются элементы списка – целые числа; итерация происходит, только если длина списка не превышает maxSize. Для метода remove итерируются индексы списка:
4.1.1. Краткое описание В качестве обобщенного состояния выбирается длина списка, присутствующего в модельном состоянии или сконструированного на его основе.
4.1.2. Область применения Применяется для присутствующих в модельном состоянии списка, очереди, стека и любых других перечислений. Использование паттерна предполагает, что тестовые ситуации не зависят от элементов списка. Для покрытия разнообразных элементов списка предполагается использование дополнительных технических приемов: дополнительных сценарных методов, паттерна выделение элементов.
4.1.3. Обобщенное состояние Используется целочисленное или натуральное состояние 0, 1, 2, … . Функция вычисления обобщенного состояния возвращает количество элементов списка. Для обеспечения конечности обобщенных состояний вводится ограничение на количество состояний, задаваемое как параметр сценария.
4.1.4. Итерация параметров методов Может использоваться простая итерация элементов списка, их конструирование. Обязательно присутствие методов, добавляющих элементы в список, увеличивающих длину списка, а также методов, удаляющих один или несколько элементов списка. Допустимо использовать обобщение переходов. Обобщение в простом случае существенно сокращает количество переходов. В более сложных случаях обобщение позволяет побороть недетерминизм, возникающий при зависимости методов добавления и удаления от свойств элементов списка.
List modelList; // Метод добавляет элемент e в список. void add(Integer e); // Метод удаляет элемент по индексу index из списка. // Если индекс выходит за границы списка, // вырабатывается исключение IndexOutOfBoundsException. void remove(int index) throws IndexOutOfBoundsException;
scenario boolean add() { //objectUnderTest – модель, содержащая спецификационные методы //add и remove if(objectUnderTest.modelList.size()<maxSize) { iterate(int i=0; i<10; i++; ) { //вызов спецификационного метода add objectUnderTest.add(new Integer(i)); } } return true; } scenario boolean remove() { iterate(int i=-1; i<=objectUnderTest.modelList.size(); i++;) { //вызов спецификационного метода remove objectUnderTest.remove(i); } return true;
} 109
110
Количество файлов в директории; количество выделенных идентификаторов; количество выделенных семафоров; количество элементов меню; количество слушателей сообщений интерфейса (action listeners); количество элементов списка (List); размер списка ожидающих обработки операций send, receive; количество сообщений в очереди; количество синхронизированных потоков (joined threads); количество потоков, которые могут быть синхронизированы (joinable); количество отмененных (canceled), заблокированных (blocked), отсоединенных (detached) потоков.
Для обеспечения более детального покрытия, нежели покрытие ветвей, может использоваться сочетание обобщений по ветвям и по более детальному покрытию. В случае недетерминированности обобщенных переходов по более детальному покрытию обобщение по ветвям обеспечивает существование детерминированного подавтомата и позволяет использовать обходчик детерминированных подавтоматов. В сочетании с обобщением параметров, обеспечивающих существование детерминированного подавтомата, может использоваться и простая итерация параметров. Такая итерация, как правило, обеспечивает большее число различных переходов и позволяет покрыть более разнообразные тестовые ситуации, например, тестовые ситуации, зависящие от элементов множества. В итерациях обязательно присутствие методов, добавляющих элементы во множество, увеличивающих размер множества, а также методов, удаляющих один или несколько элементов множества.
4.2. Размер множества
4.2.5. Примеры
4.1.6. Совместное использование Используется совместно с паттерном выделение элементов. Для тестирования списков при максимальном заполнении рекомендуется использовать паттерн среднее состояние.
4.1.7. Использование в проектах
Set modelSet;
4.2.1. Краткое описание В качестве обобщенного состояния выбирается размер множества, присутствующего в модельном состоянии или сконструированного на его основе.
4.2.2. Область применения Применяется для множества, присутствующего в модельном состоянии, или сконструированного на его основе. Использование паттерна предполагает, что тестовые ситуации не зависят от элементов множества. Для покрытия ситуаций, зависящих от элементов множества, предполагается использование дополнительных технических приемов: дополнительных сценарных методов, паттерна выделение элементов.
4.2.3. Обобщенное состояние Используется целочисленное или натуральное состояние 0, 1, 2, … . Функция вычисления обобщенного состояния возвращает количество элементов множества. Для обеспечения конечности обобщенных состояний вводится параметр сценария, который может задавать ограничение как на количество состояний, так и на количество разнообразных элементов, итерируемых в сценарных методах.
4.2.4. Итерация параметров методов Для итерации параметров предпочтительнее использовать обобщение параметров по ветвям функциональности, соответствующим наличию или отсутствию во множестве добавляемого или удаляемого элемента.
111
// Метод добавляет элемент e в множество. void add(Integer e); // Метод удаляет элемент e из множества. // Если элемент присутствовал во множестве, возвращает true. // Иначе false. boolean remove(Integer e);
Тестовые ситуации для метода add: 1. множество пусто; 2. множество не пусто: a. добавляемый элемент присутствует в множестве; b. добавляемый элемент отсутствует в множестве. Тестовые ситуации для метода remove: 1. множество пусто; 2. множество содержит единственный элемент: a. удаляемый элемент присутствует во множестве; b. удаляемый элемент отсутствует во множестве; 3. множество содержит более одного элемента: a. удаляемый элемент присутствует во множестве; b. удаляемый элемент отсутствует во множестве. Обобщенное состояние – IntGenState, параметр конструктора – размер множества modelSet: modelSet.size(). Для ограничения количества состояний в сценарий добавляется переменная int maxSize. Для методов add и remove итерируются ветви функциональности, соответствующие отсутствию или присутствию элемента во множестве. Для каждой ветви перебираются элементы множества до тех пор, пока не будет 112
найден элемент, попадающий в выбранную ветвь функциональности. Для ограничения количества обобщенных состояний итерация для метода add происходит, только если размер множества не превышает maxSize. scenario boolean add() { //objectUnderTest–модель, содержащая спецификационные методы //add и remove if(objectUnderTest.modelSet.size()<maxSize) { // 0 – элемент отсутствует во множестве // 1 – элемент присутствует во множестве iterate(int b=0; b<2; b++; ) { //поиск элемента, удовлетворяющего заданной ветви for(int i=0; i<10; i++) { Integer e = new Integer(i); if(b==0 && !objectUnderTest.modelSet.contains(e) || b==1 && objectUnderTest.modelSet.contains(e)) { //вызов спецификационного метода add objectUnderTest.add(e); break; } } } } return true; } scenario boolean remove() { iterate(int b=0; b<2; b++; ) { //поиск элемента, удовлетворяющего заданной ветви for(int i=0; i<10; i++) { Integer e = new Integer(i); if(b==0 && !objectUnderTest.modelSet.contains(e)) { || b==1 && objectUnderTest.modelSet.contains(e)) { //вызов спецификационного метода remove objectUnderTest.remove(e); break; } } } return true; }
4.2.7. Использование в проектах Размер множества свободных идентификаторов; размер множества ресурсов разделяемой памяти; размер множества выделенных буферов (allocated buffers); размер пула выделенных ресурсов; размер пула выделенных процессов; размер пула выделенных семафоров; размер множества активных RMI-объектов; размер множества идентификаторов активных RMI-объектов; размер множества дескрипторов очереди сообщений.
4.3. Декартово произведение
4.3.1. Краткое описание В качестве обобщенного обобщенных состояний.
состояния
выбирается
произведение
других
4.3.2. Область применения Применяется совместно с другими паттернами. Позволяет объединять тестовые ситуации, задаваемые элементами декартова произведения. При увеличении количества элементов декартова произведения количество состояний резко возрастает. Если между состояниями нет зависимостей, то количество состояний произведения есть произведение количеств состояний, задаваемых каждым элементом. Опыт показывает, что допустимо произведение лишь небольшого числа элементов, в пределах десяти.
4.3.3. Обобщенное состояние Тип состояния зависит от типов элементов произведения. В общем случае можно пользоваться PairComplexGenState и ListComplexGenState, конструируемыми из пары и списка обобщенных состояний соответственно. Для произведения целочисленных состояний можно пользоваться классами обобщенных состояний IntPairGenState, IntTripleGenState, IntListGenState.
4.3.4. Итерация параметров методов
4.2.6. Совместное использование
Вообще говоря, итерация параметров зависит от элементов произведения и в каждом случае выбирается по-разному. Однако существует два достаточно распространенных случая. Первый случай – произведение одинаковых обобщенных состояний, являющихся обобщением одинаковых структур. В этом случае к итерации параметров для каждого метода добавляется итерация по структурам, составляющим элементы произведения. Второй случай – произведение разных обобщений одной и той же структуры. В этом случае итерации в ряде случаев можно оставить неизменными.
Используется совместно с паттерном выделение элементов. Для тестирования максимального заполнения множества рекомендуется использовать паттерн среднее состояние. 113
114
iterate(int j=-1;j<=objectUnderTest.modelList.size();j++;) { //вызов спецификационного метода remove objectUnderTest.remove(j); }
4.3.5. Примеры Пример 1. Произведение длин списков. Спецификация описывает список, такой же, как в примере для паттерна Длина списка.
} return true;
List modelList;
}
// Метод добавляет элемент e в список. void add(Integer e);
Пример 2. Активные идентификаторы. // Отображение из идентификаторов объектов в статус объекта. // true – объект активный, false – объект неактивный Map modelMap;
// Метод удаляет элемент по индексу index из списка. // Если индекс выходит за границы списка, // вырабатывается исключение IndexOutOfBoundsException. void remove(int index) throws IndexOutOfBoundsException;
В сценарии заводится массив ListMediator testLists[], в котором хранятся списки, сконструированные для тестирования. Т.е. в этом массиве хранятся те же объекты, что используются для тестирования одного списка (objectUnderTest), – медиаторы списков с присоединенными оракулами. Обобщенное состояние – IntListGenState. При конструировании обобщенного состояния производится итерация по элементам массива testList и добавляется длина каждого списка modelList: modelList.size(). Так же, как и для тестирования одного списка, вводится ограничение на максимальную длину всех списков int maxSize. Для тестирования методов добавления и удаления в сценарных методах итерируются тестируемые списки, а затем параметры методов, так же, как для одного списка. scenario boolean add() { iterate(int i=0; i<=objectUnderTest.testLists.length; i++; ) { objectUnderTest = testLists[i]; //objectUnderTest–модель,содержащая спецификационные методы //add и remove if(objectUnderTest.modelList.size()<maxSize) { iterate(int j=0; j<10; i++; ) { //вызов спецификационного метода add objectUnderTest.add(new Integer(j)); } } } return true; } scenario boolean remove() { iterate(int i=0; i<=objectUnderTest.testLists.length; i++; ) { objectUnderTest = testLists[i];
115
// Метод связывает ключ key со значением value. // Если ключ присутствовал в отображении, // возвращает предыдущее значение, связанное ключом, // иначе возвращает null. Object put(Integer key, Boolean value); // Метод удаляет ключ key из отображения. // Возможно удаление только неактивного идентификатора. // Возвращает true, если ключ успешно удален // или не присутствовал в отображении; иначе возвращает false. boolean remove(Integer key);
Тестовые ситуации для метода put: 1. отображение пусто; 2. отображение не пусто: a. добавляемый идентификатор присутствует в отображении: i. присутствующий идентификатор активен: 1. добавляемый идентификатор активен; 2. добавляемый идентификатор не активен; ii. присутствующий идентификатор неактивен: 1. добавляемый идентификатор активен; 2. добавляемый идентификатор не активен; b. добавляемый идентификатор отсутствует в отображении: i. добавляемый идентификатор активен; ii. добавляемый идентификатор не активен. Тестовые ситуации для метода remove: 1. отображение пусто; 2. отображение содержит единственный идентификатор: a. удаляемый идентификатор присутствует в отображении: i. удаляемый идентификатор активен; ii. удаляемый идентификатор не активен; b. удаляемый идентификатор отсутствует в отображении: 116
i. удаляемый идентификатор активен; ii. удаляемый идентификатор не активен; 3. отображение содержит более одного идентификатора: a. удаляемый идентификатор присутствует в отображении: i. удаляемый идентификатор активен; ii. удаляемый идентификатор не активен; b. удаляемый идентификатор отсутствует в отображении: i. удаляемый идентификатор активен; ii. удаляемый идентификатор не активен. Обобщенное состояние – IntPairGenState, параметры конструктора – размер отображения и количество активных идентификаторов. Количество активных идентификаторов – это обобщенное состояние, которое получено выделением из отображения элементов, обладающих свойством активности. Таким образом, в этом примере применяются три паттерна: размер отображения, выделение элементов и декартово произведение. Для ограничения количества состояний в сценарий добавляется переменная int maxSize. Для методов put и remove итерируются ветви функциональности. Для метода put выделим шесть ветвей функциональности: 1. отсутствует, добавляем активный; 2. отсутствует, добавляем неактивный; 3. присутствует активный, добавляем активный; 4. присутствует активный, добавляем неактивный; 5. присутствует неактивный, добавляем активный; 6. присутствует неактивный, добавляем неактивный. Для метода remove: 1. отсутствует; 2. присутствует активный; 3. присутствует неактивный. Для каждой ветви перебираются идентификаторы до тех пор, пока не будет найден идентификатор, попадающий в выбранную ветвь функциональности. Для ограничения количества обобщенных состояний итерация для метода put происходит, только если размер отображения не превышает maxSize. scenario boolean put() { //objectUnderTest – модель, содержащая спецификационные методы //put и remove if(objectUnderTest.modelMap.size()<maxSize) { //b = 0 – 5 описанные выше iterate(int b=0; b<6; b++; ) { //поиск параметров, удовлетворяющих заданной ветви for(int i=0; i<10; i++) { Integer k = new Integer(i); for(int j=0; j<2; j++) { boolean v = (j==0)?false:true;
117
if(!objectUnderTest.modelMap.containsKey(k)){ if(b==0 && v || b==1 && !v) { objectUnderTest.put(k, new Boolean(v)); break; } } else { boolean existing = ((Boolean) objectUnderTest.modelMap.get(k)).booleanValue(); if(b==2 && existing && v || b==3 && existing && !v || b==4 && !existing && v || b==5 && !existing && !v) { //вызов спецификационного метода put objectUnderTest.put(k, new Boolean(v)); break; } } } } } } return true; } scenario boolean remove() { iterate(int b=0; b<3; b++; ) { //поиск идентификатора, удовлетворяющего заданной ветви for(int i=0; i<10; i++) { Integer k = new Integer(i); if(b==0 && !objectUnderTest.modelMap.containsKey(k)) { objectUnderTest.remove(k); break; } if(objectUnderTest.modelMap.containsKey(k)) { boolean existing = ((Boolean) objectUnderTest.modelMap.get(k)).booleanValue(); if(b==1 && existing || b==2 && ! existing) { objectUnderTest.remove(k); break; } } } } return true; }
118
4.3.6. Совместное использование Используется совместно с паттерном выделение элементов. Для элементов декартова произведения используются другие паттерны.
На рис. 7 показаны два дерева, в которых одна вершина имеет две дочерних, одна имеет одну дочернюю, и две вершины в каждом дереве не имеют дочерних вершин. Таким образом, обоим этим деревьям соответствует одно и тоже обобщенное состояние, мультимножество {0,0,1,2}.
4.3.7. Использование в проектах Произведение количества свободных ресурсов и количества выделенных ресурсов; количество свободных элементов в каждом пуле из списка; произведение количества выделенных элементов и множества идентификаторов; произведение количества слушателей и количества уникальных слушателей; количество элементов в списке для каждого списка из набора; произведение типа упорядочивания в отображении и размера отображения; произведение зарегистрированных RMI объектов и количества активных объектов RMI; произведение количества зарегистрированных и количества пересоздаваемых при перезапуске RMI-объектов; произведение количества зарегистрированных и количества экспортированных RMIобъектов; произведение глубины дерева, количества компаний и количества активных компаний; произведение типа доступа к очереди сообщений и ее размера; произведение максимального размера очереди, максимального размера сообщения и размера очереди; произведение количества ждущих вызовов send, receive, максимального размера очереди, размера очереди и типа блокировки; произведение количества заявок, количества заявок ожидающих обработку и количества заявок находящихся в обработке.
4.4. Мультимножество чисел детей
4.4.1. Краткое описание В качестве обобщенного состояния выбирается мультимножество, элементами которого являются числа – количество непосредственных детей для каждой вершины дерева.
4.4.2. Область применения Применяется при тестировании программ, для описания поведения которых в качестве состояния спецификации используется дерево. Считается, что в спецификации описаны методы, позволяющие изменять структуру дерева и свойства узлов.
4.4.3. Обобщенное состояние В качестве обобщенного состояния выбирается мультимножество, элементы которого – количество непосредственных дочерних вершин. Рассмотрим пример.
119
A
A
B
D
B
C
C
D
Рис. 7. Примеры деревьев Результаты подсчета количества обобщенных состояний в зависимости от числа вершин приведены в таблице 2. Как видно, количество обобщенных состояний значительно меньше числа корневых деревьев для того же числа вершин. Это делает данное обобщенное состояние практически пригодным для использования при тестировании. Число вершин
1
5
10
15
20
25
Число корневых деревьев
1
9
719
87811
12826228
2067174645
Число обобщенных состояний
1
5
30
135
490
1575
Таблица 2. Количество обобщенных состояний Вместе с тем, данное обобщенное состояние определяет разнообразные виды деревьев. Мультимножества вида {0, …, 0, N}, где N – количество вершин определяют широкие деревья, а мультимножества вида {0, 1, …, 1} определяют высокие деревья.
4.4.4. Итерация параметров методов Будем считать, что интерфейс содержит методы add, delete и createRoot. У метода add имеются два параметра: вершина, к которой нужно добавить ребенка, и добавляемая вершина. Метод требует, чтобы вершина, к которой 120
добавляется ребенок, существовала. У метода delete имеется один параметр – удаляемая вершина. Можно выделить две разновидности метода: метод удаляет только листовые вершины, метод удаляет все поддерево, корнем которого является заданная вершина. Метод createRoot создает корневую вершину; вершину можно создать, если дерево пусто. Описанные таким образом методы оказываются детерминированными, так как по заданному дереву и набору параметров любого метода однозначно определяется вид результирующего дерева. Легко видеть, что итерация вершин дерева в качестве параметров методов описывает недетерминированный автомат. На рис. 8 показано обобщенное состояние {0, 1, 1} и два соответствующих ему дерева, которые различаются порядком вершин. В этом обобщенном состоянии переход, соответствующий вызову метода add(C, D), может переводить автомат как в обобщенное состояние {0, 1, 1, 1} так и в обобщенное состояние {0, 0, 1, 2} в зависимости от дерева, соответствующего исходному обобщенному состоянию.
add(i, A); delete(i); createRoot(A), где i – элемент мультимножества обобщенного состояния, А – добавляемая вершина. Переходы по методу add становятся детерминированными, так как мультимножество после перехода определяется однозначно: выбранный элемент мультимножества заменяется большим на единицу, и дополнительно к мультимножеству добавляется нуль (см. рис. 9). add(1, D)
add(С, D)
A
B
C
C
B
B
A B
A
A
A C
D
B
C
A
D delete(0)
delete(0)
B
D
C {0, 0, 1, 2} {0, 1, 1}
add(С, D)
{0, 0, 2} Рис. 9. Итерация элементов мультимножества
A
A
{0, 1, 1}
B
C
C
B
D
D {0, 1, 1, 1}
{0, 0, 1, 2}
Рис. 8. Простая итерация параметров Этот недетерминизм легко преодолеть, заменив итерацию вершин итерацией различных элементов мультимножества обобщенного состояния. Элементу мультимножества при выполнении перехода ставится в соответствие произвольная вершина дерева с заданным числом непосредственных детей. Таким образом, имеем следующие переходы: 121
методу delete по-прежнему оказываются Однако переходы по недетерминированными. В примере, показанном на рис. 9, в состоянии {0, 0, 1, 2} результирующее состояние при удалении вершины без детей зависит от того, сколько было детей у родителя. Если у родителя было два ребенка, то результирующим состоянием является {0, 1, 1}, а если один, то {0, 0, 2}. Таким образом, для данного обобщенного состояния и выбора итераций оказываются неприменимыми обходчики, требующие детерминизма автомата. Для обходчика сильно дельта-связных автоматов требуется существование адаптивных тестовых последовательностей, связывающих любые два состояния автомата. При нашем выборе состояния и итераций из любого состояния существует тестовая последовательность, ведущая в состояние, которое соответствует пустому дереву. На каждом шаге данной последовательности удаляется вершина без детей, и за число шагов, равное размеру мультимножества, данная последовательность приводит в состояние {}. Далее, используя переходы по методу add, можно построить тестовую последовательность, ведущую из начального состояния в любое другое. 122
Таким образом, к данному выбору итераций применим обходчик сильно дельта-связных автоматов. Обходчик автоматов, имеющих сильно связный детерминированный покрывающий подавтомат, также оказывается применимым при добавлении в сценарий дополнительного перехода, переводящего автомат в состояние {}. В случае, когда метод delete удаляет поддеревья, в дополнительном переходе следует удалить корень дерева. Если же метод удаляет только листовые вершины, то следует написать последовательность методов delete, в которой на каждом шаге удаляется листовая вершина до тех пор, пока дерево не станет пустым. На практике часто оказывается, что методы add и delete зависят не только от структуры дерева. В таком случае для выполнения требований обходчика требуется введение дополнительных переходов, при которых вершина гарантированно добавляется или удаляется. Например, методы могут зависеть от свойств вершины; тогда перед добавлением или удалением свойства вершины следует поменять так, чтобы вершина гарантированно добавилась или удалилась.
4.4.5. Примеры Tree modelTree; // Метод добавляет вершину node к вершине parent, // если parent есть в дереве. // Если вершины parent нет в дереве, вершина не добавляется. add(Node parent, Node node);
1. дерево пусто; 2. дерево не пусто. Обобщенное состояние – мультимножество целых чисел IntMultisetGenState. Считаем, что имеется метод, возвращающий вершину дерева по номеру от нуля до числа вершин в дереве modelTree.getNodeByIndex(int index). IntMultisetGenState genstate = new IntMultisetGenState(); for(int i=0; i
Для ограничения количества состояний в сценарий добавляется переменная int maxSize. Для метода add итерируем элементы мультимножества, и для каждого элемента подбираем вершину с соответствующим количеством детей. Считаем, что имеется функция, которая возвращает вершину с указанным количеством детей getNodeWithChildrenSize(int size); если таких вершин несколько, возвращается произвольная. Итерация происходит, если количество вершин не превышает maxSize. scenario boolean add() { //objectUnderTest–модель, содержащая спецификационные методы //add и delete if(objectUnderTest.modelTree.size()<maxSize) { IntMultisetGenState ms = getGenState(); //итерация элементов мультимножества iterate(IntegerIteratorInterface iter=ms.getIterator(); !iter.stopIteration(); iter.next(); ) { //подбор соответствующей вершины Node parent = objectUnderTest.modelTree.getNodeWithChildrenSize(iter.value()); //итерация добавляемых вершин iterate(int j=0; j<10; j++) { Node node = new Node(j); //вызов спецификационного метода add objectUnderTest.add(parent, node); } } } return true; }
// Метод удаляет вершину node, если таковая есть в дереве // и является листовой. // Иначе вершина не удаляется. delete(Node node); // Метод создает корневую вершину; вершину можно создать, // если дерево пусто. Node createRoot();
Тестовые ситуации для метода add: 1. родитель не существует; 2. родитель существует: a. родитель не имеет дочерних вершин; b. родитель имеет дочерние вершины. Тестовые ситуации для метода delete: 1. вершина не существует; 2. вершина существует: a. вершина не имеет дочерних вершин; b. вершина имеет дочерние вершины. Тестовые ситуации для метода createRoot:
Для метода delete итерируются номера вершин дерева, а также вводится дополнительный сценарный метод, гарантированно удаляющий вершину. scenario boolean delete() { //итерация вершин дерева
123
124
iterate(int i=0; i
Для покрытия тестовых ситуаций, при которых не существуют родитель в методе add и удаляемая вершина в методе delete, вводятся дополнительные сценарные методы. scenario boolean add_neg() { if(objectUnderTest.modelTree.size()<maxSize) { //итерация родителей – произвольных вершин iterate(int i=0; i<10; i++; ) { //итерация добавляемых вершин iterate(int j=0; j<10; j++) { Node parent = new Node(i); Node r node = new Node(j); //вызов спецификационного метода add objectUnderTest.add(parent, node); } } } return true; } scenario boolean delete_neg() { //итерация произвольных вершин iterate(int i=0; i<10; i++; ) { Node node = new Node (i); //вызов спецификационного метода delete objectUnderTest.delete(node); } return true; }
125
4.4.6. Совместное использование Используется совместно с паттерном выделение элементов. Для тестирования максимального количества вершин в дереве рекомендуется использовать паттерн среднее состояние.
4.4.7. Использование в проектах Паттерн использовался для деревьев, представляющих иерархию компаний, в которой дочерние компании присутствовали как в основной, так и в альтернативной иерархии. В качестве обобщенного состояния выбиралось мультимножество пар: количество дочерних компаний в основной иерархии и количество дочерних компаний в альтернативной иерархии. Паттерн использовался для тестирования модели данных Service Data Objects, представляющей собой дерево со ссылками, в котором можно хранить XMLданные, реляционные данные, EJB. В качестве обобщенного состояния выбиралось мультимножество пар: количество дочерних вершин, количество ссылок на другие вершины.
5. Заключение Методы, использующие конечные автоматы для тестирования программ, накладывают ограничения на приемлемые размеры автоматов, при которых они применимы на практике. В этом смысле не является исключением и технология UniTesK, в которой приемлемое количество состояний составляет несколько сотен. Для борьбы с разрастанием состояний в UniTesK используется обобщение состояний, задаваемое в тестовом сценарии. Выбор обобщенного состояния в тестовом сценарии – это поиск компромисса между количеством состояний, их разнообразием и возможностями обходчика, использующего это состояние для построения тестовой последовательности. Процесс разработки тестовых сценариев по технологии UniTesK является сложной задачей. Процесс затрудняется тем обстоятельством, что количество состояний модели велико, а порой и бесконечно. Построение автомата приемлемых размеров не гарантируется; кроме того, не для всех моделей можно построить автомат, удовлетворяющий требованиям обходчиков. Поскольку окончательный вид автомата определяется в процессе обхода, проверка требований обходчика до запуска тестов затруднительна, что еще больше усложняет задачу. В данной статье предложены паттерны проектирования, позволяющие упростить разработку тестовых сценариев. Паттерны получены в результате анализа более чем десятилетнего опыта разработки тестов ИСП РАН в семи различных проектах. Было проанализировано около трехсот тестовых сценариев. Статистика показывает, что выделенные паттерны используются в 80% тестовых сценариев и лишь в 20% требуются дополнительные соображения. 126
Паттерны представляют собой удачные решения часто встречающихся задач. Паттерны позволяют повторно использовать полученные результаты, передать опыт разработчиков тестовых сценариев. Знание паттернов дает возможность начинающему разработчику сценариев работать так, как работает эксперт; помогает выделить в модели части, к которым применимы паттерны. Использование паттернов позволяет опираться на библиотечные обобщенные генерацию итераций параметров, состояния и автоматическую поддерживаемые в инструментах тестирования. При использовании паттернов тестировщик может больше сосредоточиться на написании спецификаций к системе, нежели на выборе обобщенного состояния и итераций, которые должны удовлетворять требованиям обходчиков, сложно проверяемым без экспериментов. Паттерны покрывают большинство распространенных структур данных: списки, множества, отображения, деревья. Такие структуры наиболее часто встречаются при моделировании систем. Это дает уверенность в том, что и в дальнейшем в большинстве случаев можно будет использовать выделенные паттерны.
13. http://unitesk.ispras.ru – сайт, посвященный технологии тестирования UniTesK и реализующим ее инструментам. 14. I.B. Bourdonov, A.V. Demakov, A.A. Jarov, A.S. Kossatchev, V.V. Kuliamin, A.K. Petrenko, S.V. Zelenov. Java Specification Extension for Automated Test Development. Proceedings of PSI'01. LNCS 2244, pp. 301-307. Springer-Verlag, 2001.
Литература 1. В.В. Кулямин, А.К. Петренко, А.С. Косачев, И.Б. Бурдонов. Подход UniTesK к разработке тестов. Программирование, 29(6): 25-43, 2003. 2. Б. Бейзер. Тестирование черного ящика. Технологии функционального тестирования программного обеспечения и системы. Питер, 2004. 3. D. Lee and M. Yannakakis. Principles and methods of testing finite state machines – a survey. Proceedings of the IEEE, volume 84, pp. 1090-1123, Berlin, Aug 1996. 4. H. Robinson. Graph Theory Techniques in Model-Based Testing. Proceedings of the International Conference on Testing Computer Software, 1999. 5. S. Rosaria, H. Robinson. Applying Models in your Testing Process. Information and Software Technology. Volume 42, Issue 12, Sept. 2000. 6. W. Grieskamp, Y. Gurevich, W. Schulte, and M. Veanes. Generating Finite State Machines from Abstract State Machines. ISSTA 2002, International Symposium on Software Testing and Analysis, July 2002. 7. H. Robinson. Intelligent Test Automation. Software Testing and Quality Engineering, September/October 2000, pp. 24-32. 8. C. Alexander, S. Ishikawa, M. Silverstein, M. Jacobson, I. Fiksdahl-King, S. Angel. A Pattern Language. Oxford University Press, New York, 1977. 9. Э. Гамма, Р. Хелм, Р. Джонсон, Дж. Влиссидес. Приемы объектноориентированного проектирования. Паттерны проектирования. Спб: Питер, 2001. 10. I. Bourdonov, A. Kossatchev, A. Petrenko, and D. Galter. KVEST: Automated Generation of Test Suites from Formal Specifications. FM’99: Formal Methods. LNCS 1708, Springer-Verlag, 1999, pp. 608-621. 11. I. Bourdonov, A. Kossatchev, V. Kuliamin, and A. Petrenko. UniTesK Test Suite Architecture. Proc. of FME 2002. LNCS 2391, pp. 77-88, Springer-Verlag, 2002. 12. И.Б. Бурдонов, А.С. Косачев, В.В. Кулямин. Применение конечных автоматов для тестирования программ. Программирование, 26(2):61-73, 2000.
127
128
Автоматическая генерация тестовых данных для оптимизаторов графических моделей С.В. Зеленов, Д.В. Силаков
1. Введение В настоящее время графическое моделирование активно применяется в различных отраслях промышленности: автомобилестроении (Ford, General Motors [6], Daimler Chrysler [7]), авиастроении (Boeing [8]), аэрокосмической промышленности (American Institute of Aeronautics and Astronautics [5]) и других. Графическое моделирование позволяет создавать модели разрабатываемых систем из различных элементарных деталей на экране монитора, как в конструкторе. При этом от разработчиков не требуется изучения каких-либо формальных языков описания моделей. Одной из наиболее распространенных сфер применения графического моделирования в настоящее время является создание исполняемого кода для микропроцессоров встроенных систем. Вместо написания кода вручную инженеры создают модель, описывающую работу устройства, а на основе этой модели специальная программа – генератор кода – создает код на языке программирования. Среди существующих инструментов для создания моделей можно выделить инструменты Simulink и Stateflow, входящие в пакет Matlab от MathWorks [1], ASCET от ETAS [2], который может быть интегрирован в Matlab, а также Statemate от I-Logix [3], позволяющий создавать модели с помощью UMLдиаграмм. Для генерации кода на основе моделей Matlab используются Real-Time Workshop, входящий в Matlab, а также инструмент TargetLink от компании dSpace [4]. Существует инструмент для генерации кода на основе моделей ASCET. В автомобилестроении в настоящее время стандартом де-факто являются пакеты Matlab и TargetLink, используемые компаниями Daimler Chrysler, General Motors, Ford, Toyota [6] и др. 129
Можно выделить следующие основные достоинства графического моделирования: – простота моделирования для разработчиков (не требуется изучения формальных языков для описания моделей); – автоматическая генерация спецификации и документации (фактически, сама модель является наглядной и в то же время формальной спецификацией системы); – автоматическая генерация исполняемого кода, моделирующего работу системы, а также кода для исполнения в микропроцессорах встроенных систем; – кроме того, современные средства моделирования обеспечивают простоту модульного проектирования. Генераторы кода широко используются в таких областях, где к используемым программам предъявляются очень высокие требования (встроенные системы автомобилей, самолетов и т.п.). Соответственно, высокие требования предъявляются и к генераторам кода. Можно выделить два основных требования: – получаемый код должен быть сравним с кодом, написанным вручную, как по требованиям к ресурсам, так и по времени выполнения; – структура кода должна соответствовать модели. Инструменты, удовлетворяющие первому требованию, появились на рынке относительно недавно, в конце девяностых годов. Для достижения приемлемого качества кода выполняются различные оптимизирующие преобразования моделей. При этом остро встает вопрос о соответствии кода, полученного на основе оптимизированной модели, исходной конструкции. Проверка выполнения второго требования после проведения оптимизирующих преобразований является одной из основных задач тестирования генераторов кода. Число всевозможных преобразований, как правило, велико, и для проверки корректности генератора требуется большое количество тестов, поэтому встает вопрос об автоматизации разработки тестовых данных. Для формального описания преобразований моделей, осуществляемых трансляторами, используются так называемые правила преобразования графов [7]. Каждое правило состоит из двух частей – левой и правой. Левая часть описывает шаблонную конструкцию, которая будет подвергнута трансформации, и ограничения на параметры этой конструкции, при выполнении которых трансформация будет произведена. Правая часть описывает результирующую конструкцию, которая будет получена после преобразования. Генераторы кода начали применяться в промышленности недавно, и работ, посвященных их тестированию, немного. Из существующих подходов можно выделить формальное доказательство корректности работы генератора 130
(например, [9]) и тестирование на основе правил преобразований графов – Classification Tree Method (CTM) [10, 11]. При использовании формального доказательства (например, [9]) возникают традиционные для этого метода проблемы – длительные сроки и сложность проверки, приводящие к высокой стоимости тестирования. Кроме того, в настоящее время для основных существующих генераторов кода отсутствуют общедоступные формальные описания. В промышленных масштабах формальные доказательства на данный момент не используется. Метод CTM [10, 11] состоит из двух этапов: – разбиение шаблона для тестирования на независимые области; – разбиение полученных областей на классы эквивалентности. На рис. 1 проиллюстрирован пример применения метода к оптимизации, выполняющей вычисление константных аргументов блока Sum. В качестве областей здесь выделены блоки Sum и Const, классы эквивалентности строятся согласно количеству блоков Const в модели.
далее на классы эквивалентности осуществляется вручную; при этом для реальных систем число получаемых классов эквивалентности, как правило, очень велико. Для полноты тестирования для каждого из этих классов необходимо получить, по крайней мере, один тест, а инструментов для автоматической генерации тестов в настоящее время не существует. В настоящей статье предложен метод GraphOTK автоматической генерации тестовых данных для тестирования оптимизирующих трансляторов графических моделей. Предложенный метод позволяет решить проблему автоматической генерации тестовых данных, а также за счет параметризации генератора позволяет варьировать количественные и качественные характеристики получаемых тестовых данных.
2. Метод автоматизации тестирования оптимизаторов графических моделей Метод GraphOTK автоматической генерации тестовых данных для тестирования оптимизирующих трансляторов графических моделей является развитием метода генерации тестовых данных для оптимизаторов в компиляторах языков программирования [12]. Метод заключается в том, чтобы построить для тестируемого оптимизатора представительное множество входных графических моделей следующим образом: - построить абстрактную тестовую модель входных данных оптимизатора; - в терминах абстрактной тестовой модели сформулировать критерий покрытия этих входных данных; - перебрать соответствующие тестовые данные.
2.1. Построение абстрактной тестовой модели
Рис. 1. Пример применения метода CTM Преимуществом метода CTM является простота определения покрытия. Также имеется большой опыт использования этого метода в смежной области – для полуавтоматического создания тестов для готовых систем на основе их моделей; последним фактом обусловлен большой интерес промышленности к методу CTM [11]. Основным недостатком использования CTM для тестирования генераторов кода является отсутствие автоматизации. Разбиение шаблона на области и 131
Тестовая модель строится на основе абстрактного описания правил оптимизирующих трансформаций. Алгоритм оптимизации формулируется с использованием формализма правил преобразования графов. Оптимизатор для осуществления своих трансформаций ищет шаблонную конструкцию – такое сочетание элементов графической модели, которое присутствует в левой части какого-нибудь правила преобразования графов. Поэтому для построения тестовой модели будем рассматривать только те элементы графических моделей, которые задействованы хотя бы в одном шаблоне. Итак, на основании информации о шаблонных конструкциях из всех правил преобразования графов для данной оптимизации составляется список элементов графической модели, задействованных в этих шаблонах. После этого описывается множество тестовых модельных строительных блоков со следующими свойствами: 132
- каждому элементу графической модели из полученного списка соответствует свой вид тестового модельного строительного блока; - строительные блоки могут связываться между собой, чтобы иметь возможность образовывать структуры, соответствующие шаблонам. Будем называть тестовой модельной структурой граф, вершины которого – строительные блоки, а ребра – связи между строительными блоками.
2.2. Формулировка критерия покрытия Проекция графических моделей в тестовые модельные структуры для данной абстрактной тестовой модели индуцирует разбиение множества графических моделей на классы эквивалентности. Один класс эквивалентности состоит из графических моделей, которые имеют одинаковое тестовое модельное представление, т.е. которые неразличимы для алгоритма оптимизации. Это свойство позволяет нам выдвинуть гипотезу, согласно которой на эквивалентных графических моделях оптимизатор работает одинаково. Следовательно, в желаемом тестовом наборе достаточно иметь не более одного представителя из каждого класса эквивалентности. Поскольку множество тестовых модельных структур, т.е. множество классов эквивалентности, в общем случае бесконечно, то для создания тестового набора мы должны выбрать некоторое его конечное подмножество. Основанием для этого выбора должны служить те шаблоны, которые были выделены при анализе алгоритма оптимизации. Таким образом, критерий тестового покрытия формулируется в терминах абстрактной тестовой модели.
2.3. Создание генератора тестов Для получения множества тестовых данных для целевого оптимизатора графических моделей в соответствии с методом GraphOTK необходимо разработать соответствующий генератор. Этот генератор должен создавать представительное (т.е. удовлетворяющее сформулированному критерию покрытия) множество тестовых данных (т.е. графических моделей). Создание генератора представительного множества тестовых воздействий начинается с анализа алгоритма тестируемого оптимизатора, построения абстрактной модели и формулировки критерия тестового покрытия, как это было описано выше. После этого происходит разработка собственно генератора. Генератор тестов состоит из двух компонентов. Первый компонент, называемый итератором, отвечает за последовательную генерацию тестовых модельных структур. Второй компонент, называемый меппером, отвечает за отображение каждой тестовой модельной структуры в графическую модель. Итератор должен создавать множество модельных структур в соответствии с выбранным критерием тестового покрытия.
133
Для данной модельной структуры S меппер должен строить соответствующие тестовые данные, обладающие следующим свойством: графическая модель, построенная по тестовой модельной структуре S, имеет модельное представление, совпадающее с S. По окончании разработки итератора и меппера они собираются в генератор. После этого можно проводить генерацию множества тестовых данных.
3. Генераторы тестовых данных для оптимизаторов графических моделей 3.1. Генератор тестовых данных для оптимизатора Switch-блока Набор тестов предназначен для генератора кода, осуществляющего трансляцию и оптимизацию блока Switch. У блока Switch (Рис. 2) имеются три входных сигнала (In1, control и In2). Каждый из входов может быть константой (блоком, подающим на выход всегда одно и то же число), внешним источником сигнала (блок InPort) или шаблонной контрукцией (например, арифметическим выражением). Switchблок описывает структуру if-then-else – в зависимости от величины сигнала на входе control он передает на выход либо сигнал со входа In1, либо сигнал со входа In2. Если сигнал на входе control больше либо равен пороговому значению threshold, являющемуся параметром блока, на выход подается сигнал со входа In1, в противном случае на выход подается сигнал со входа In2.
Рис. 2. Блок Switch При генерации кода оптимизатор анализирует возможные значения величины сигнала на входе control и определяет, будет ли значение булевского выражения «control >= threshold» постоянным в процессе выполнения. Если это так, то одна из веток конструкции if-then-else, описываемой Switch-блоком, никогда не будет выполняться (т.е. на выход блока всегда будет подаваться сигнал с одного и того же входа) и может быть удалена из модели. 134
В соответствии с методом GraphOTK, на основе описания алгоритма оптимизации строится абстрактная модель тестов. В случае оптимизации Switch-блока в описании алгоритма используется следующие термины: блок Switch и различные блоки библиотеки Simulink, из которых могут быть составлены модельные конструкции для входов блока Switch. Для данного алгоритма оптимизации являются важными значения параметров блоков модели, влияющие на величину выходного сигнала блока (например, амплитуда сигнала у блока Sine Wave). Таким параметрам в абстрактной модели соответствуют свойства терминов. Шаблоном для оптимизатора является блок Switch c различными комбинациями значений порогового параметра и области возможных значений входа control. В процессе генерации строятся различные модели Simulink, содержащие блок Switch. В качестве входов блока Switch строятся различные модельные конструкции с различными параметрами. Целью генерации является получение набора тестов, удовлетворяющего следующим условиям: - набор должен содержать модели с блоком Switch; в качестве каждого входа блока Switch должны быть перебраны все наследники узла InputSystemBlock абстрактной модели тестов; - для блоков, имеющих более одного входа, в качестве каждого входа должны быть перебраны все наследники узла InputSystemBlock; если на вход должен подаваться непрерывный сигнал, то в качестве такого входа должны быть перебраны все наследники блока Signal; - для блоков с переменным количеством входов с минимально допустимым числом входов N тесты должны содержать модели, где каждый такой блок будет иметь N входов, и модели, где он будет иметь N+1 вход; - тесты должны содержать модели, где величина сигнала на входе control у блока Switch всегда не меньше порогового значения, тесты, где эта величина всегда меньше порогового значения, а также тесты, где значение булевского выражения «control >= threshold» изменяется в процессе выполнения модели; - для блоков с несколькими входами, над каждым из которых может быть произведена одна из операций некоторого фиксированного набора (например, у блока Sum для каждого входа можно указать знак – ‘+’ или ‘–‘), набор тестов должен содержать блоки со всеми возможными операциями (например, для упомянутого выше блока Sum тесты должны содержать блоки, где по крайне мере один аргумент имеет знак ‘+’, и блоки, где по крайне мере один аргумент имеет знак ‘–’).
135
Путем настройки параметров генератора можно получать тесты, не содержащие блоков-генераторов сигналов и блоков-преобразователей сигналов. По умолчанию в генерируемых тестах присутствуют как корректные, так и некорректные модели, выполнение которых в среде Matlab приводит к возникновению исключительных ситуаций. Возможна настройка генератора для генерации только корректных моделей. В этом случае, в частности, гарантируется соблюдение динамической семантики при выполнении математических функций, для чего в процессе генерации осуществляются следующие действия: - для математических функций, область определения которых является отрезком, интервалом или полуинтервалом (asin, acos, acosh, atanh, log, log10, sqrt) на пути входного сигнала помещается блок Saturation, ограничивающий величину сигнала; - для фунции reciprocal (1/x) и для блока Product в случае деления входной сигнал сравнивается с нулем (с помощью блока Relational Operator) и результат сравнения (1 – если сигнал равен нулю, 0 в противном случае) прибавляется к величине сигнала; - для блоков, осуществляющих побитовые операции (BitwiseLogicalOperator) берется абсолютное значение входного сигнала (с помощью блока Abs) и входной сигнал приводится к типу uint32 (с помощью блока DataTypeConversion); - для функции возведения в степень (блок Math Function, функция pow) в случае, если первый аргумент (основание степени) равен нулю, а второй (показатель степени) отрицателен, вместо нуля блоку передается значение ‘1’. (Сравнение значений осуществляется при помощи блоков Relational и Logic, результат сравнения прибавляется к первому аргументу блока, осуществляющего возведение в степень). В случае, когда требуется получать как корректные, так и некорректные модели, перечисленные выше действия не осуществляются. Таблица 1 содержит объем и время генерации сгенерированного множества тестовых данных тестов. В приложении A приведен аннотированный пример сгенерированных тестовых данных для оптимизации SwitchBlock. Количество тестов
Размер тестов, MB
Время генерации
3 112
64
5 m. 07 s.
Таблица 1. Характеристики сгенерированных тестов для оптимизации SwitchBlock.
136
3.2. Генератор тестовых данных для оптимизатора Flowchart Набор тестов предназначен для генератора кода, осуществляющего трансляцию и оптимизацию графа Flowchart блока Stateflow Chart, описывающего конструкцию if-then-else. Оптимизатор ищет в графе вершины, соединенные двумя и более дугами. Каждой дуге ставится в соответствие условие, в случае выполнения которого осуществляется переход по этой дуге, а также может быть определено действие, осуществляемое при переходе. Для каждой вершины графа одна из исходящих дуг не должна иметь условия – она соответствует ветви “else” конструкции “if-then-else”. Все дуги, соединяющие одни и те же вершины графа, заменяются оптимизатором на одну дугу. Все проверки условий и выполнение соответствующих действий описываются как действие новй дуги. Пример преобразования графа, осуществляемого оптимизатором, показан на Рис.3. LHS
RHS
J1
T3
T2
A3
J1
C2 A2
J2
T1
C1
T1
A1
J2
A’1 String Code= If (C1) { A1; } else { if (C2) { A2; } else { A3 ; } }
Рис.3. Пример преобразования, осуществляемого оптимизатором Flowchart В соответствии с методом GraphOTK, на основе описания алгоритма оптимизации строится абстрактная модель тестов. В случае оптимизации Flowchart в описании алгоритма используются следующие термины: StateflowMachine (часть блока Simulink Stateflow Chart, которая содержит описание flowchart-графа) и вершины графа. Шаблоном для оптимизатора является блок Stateflow Chart, который содержит не менее двух вершин, соединенных дугами. В процессе генерации для блока Stateflow Chart строятся различные циклические и ациклические графы, описывающие структуры if-then-else различной разветвленности и глубины вложенности. Гарантируется, что из 137
начальной вершины графа можно достичь любой другой его вершины. Число вершин в графах изменяется от 3 до 10. Число дуг, соединяющих произвольные две вершины, изменяется от 0 до 3. Каждой (кроме одной) дуге, исходящей из каждой вершины, поставлено в соответствие условие, заключающееся в проверке принадлежности входного сигнала блока заданному интервалу. Гарантируется, что условия conditionузлов дуг, исходящих из одной вершины, являются взаимоисключающими. Каждой дуге поставлено в соответствие действие; в результате выполнения действий формируется величина выходного сигнала блока в зависимости от величины входного сигнала. Для дуг, исходящих из начальной вершины графа, действие заключается в присваивании выходному сигналу величины входного сигнала, умноженной на некоторый коэффициент (различный для различных дуг). Для остальных дуг действие заключается в прибавлении к величине выходного сигнала величины входного сигнала, умноженной на некоторый коэффициент (различный для различных дуг). Каждый тест представляет собой модель, содержащую блок Statflow Chart. Входной сигнал подается извне (с помощью блока InPort), выходной сигнал подается на выход модели (блок OutPort). Целью генерации является получение набора тестов, содержащих блок Stateflow Chart и удовлетворяющих следующим требованиям: - тесты должны содержать графы с количеством конечных вершин (т.е. вершин, у которых нет исходящих дуг) от 1 до 5 (значение 5 выбрано в целях получения приемлемого количества тестов); - между начальной вершиной графа и каждой из конечных вершин должны быть пути (по дугам через другие вершины графа, без учета циклов) длины от 1 до 5 (длина равна числу дуг на пути); должны быть перебраны все возможные сочетания длин пути (т.е. должны быть графы, где все пути имеют длину 1, графы с путями длины 1 и 2 и т.д.); - для каждой упомянутой выше комбинации длин пути набор тестов должен содержать как ациклические графы, так и графы с циклами; - число дуг между вершинами графа на каждом из путей должно варьироваться от 1 до 3 (что соответствует безусловному переходу, конструкции “if-else” и конструкции “if-elseif-else”). Объем и время генерации сгенерированного множества тестовых данных тестов приведены в Таблица 2. В приложении A приведен аннотированный пример сгенерированных тестовых данных для оптимизации Flowchart. Количество тестов 1 335
Размер тестов, MB 60
Время генерации 3 m. 45 s.
Таблица 2. Характеристики сгенерированных тестов для оптимизации Flowchart. 138
4. Заключение В статье предложен метод автоматической генерации тестовых данных для тестирования оптимизирующих трансляторов графических моделей. Предложенный метод позволяет решить проблему автоматической генерации тестовых данных, а также за счет параметризации генератора позволяет варьировать количественные и качественные характеристики получаемых тестовых данных. В соответствии с предложенным методом были разработаны генераторы тестовых данных для нескольких оптимизаторов графических моделей, которые используются в коммерческих проектах в автомобильной промышленности.
Приложение A. Примеры сгенерированных тестовых данных Пример 1. Тестовые данные для оптимизации SwitchBlock. Графическая модель тестовых данных для примера 1 изображена на Рис. 4.
Литература [1]. [2]. [3]. [4]. [5].
[6]. [7]. [8]. [9]. [10]. [11]. [12].
The MathWorks, www.mathworks.com ETAS ASCET. http://en.etasgroup.com/products/ascet/ I-Logix. http://www.ilogix.com dSPACE, www.dspace.com Paul A. Barnard. Software Development Principles Applied to Graphical Model Development. // AIAA Modeling and Simulation Technologies Conference and Exhibit, San Francisco, California, Aug. 15-18, 2005. (https://tagteamdbserver.mathworks.com/ttserverroot/Download/28446_Barnard%20AIA A-2005-5888.pdf) Ranville S., Black P. Automated Testing Requirements – Automotive Perspective. // The Second International Workshop on Automated Program Analysis, Testing and Verification. 2001. (http://hissa.nist.gov/~black/Papers/autoTestReqsWAPATV.rtf) Conrad, M., Dörr, H., Schürr, A., Stürmer, I. Graph-Transformations for Model-based Testing. // GI-Lecture Notes in Informatics. 2002. N 12. P. 39-50. MathWorks Tools Help Land Unpiloted Boeing Spacecraft. MathWorks User Stories. (https://tagteamdbserver.mathworks.com/ttserverroot/Download/452_9797v00_Boeing_S MV_ROI.pdf) Glesner S., Geiss R., Boesler B. Verified Code Generation for Embedded Systems. // Electronic Notes in Theoretical Computer Science. 2002. 65. N 2. Grochtmann M., Grimm K. Classification-Trees For Partition Testing. // Software Testing, Verification and Reliability. 1993. N 3 (2). P. 63-82. Sturmer I. Integration of the Code Generation Approach in the Model-Based Development Process By Means Of Tool Certification. // Journal of Integrated Design and Process Science. 2004. Vol. 8 (2). P.1-11 С.В. Зеленов, С.А. Зеленова, А.С. Косачев, А.К. Петренко. Применение модельного подхода для автоматического тестирования оптимизирующих компиляторов // CIT Forum, 2003. http://www.citforum.ru/SE/testing/compilers/
139
Рис. 4. Графическая модель сгенерированных тестовых данных для оптимизации SwitchBlock (пример 1). В этой модели Swith2.threshold = Switch3.threshold = -1. Оба блока всегда будут передавать на выход сигнал с верхнего входа; в результате оптимизации блок Switch2 может быть удален из модели. Величина сигнала на входе control блока Switch1 не может быть оценена на основе анализа данной системы, поскольку этот сигнал подается извне. Пример 2. Тестовые данные для оптимизации Flowchart. Графическая модель тестовых данных для примера 2 изображена на Рис. 5. 140
Рис. 5. Графическая модель сгенерированных тестовых данных для оптимизации Flowchart (пример 2). Этот граф содержит два простых цикла. Условия переходов таковы, что в процессе исполнения ни при каких значениях входного сигнала на верхнем цикле зацикливания не произойдет, а на нижнем зацикливание возникнет при любых входных сигналах.
141
Интегрированная среда описания системы команд встраиваемых процессоров В.В. Рубанов, А.С. Михеев Аннотация. В статье рассматривается интегрированная среда MetaDSP для описания системы команд встраиваемых процессоров. Такое описание включает спецификацию синтаксиса и поведения команд процессора и позволяет автоматически настроить набор кросс-инструментария разработки (ассемблер, дисассемблер, симулятор, отладчик), а также сгенерировать документацию прикладного программиста для целевого процессора. Эти возможности позволяют использовать MetaDSP и соответствующий настраиваемый кросс-инструментарий на этапе дизайна аппаратуры для прототипирования встраиваемых процессоров.
1. Введение В последнее время за счет развития технологий и удешевления производства, появляется огромное количество так называемых встраиваемых процессоров и их модификаций. К таким процессорам, в частности, относят цифровые процессоры обработки сигнала (DSP) и микроконтроллеры. Встраиваемые процессоры характеризуются ориентированностью на оптимальное (с точки зрения стоимости, размера кристалла, производительности и энергопотребления) решение узкого класса конкретных задач. В процессе создания таких решений важным этапом является прототипирование возможных альтернатив дизайна (Design Space Exploration – DSE). Для этого важно уметь получать оценки эффективности реализации конкретных алгоритмов и программ для возможных модификаций предполагаемого процессора. Одним из методов решения этой задачи является использование настраиваемых кросс-средств разработки, которые позволяют получать искомые оценки с помощью симуляции и профилирования тестовых программ для целевого прототипа с использованием инструментальной машины [11]-[12]. Для настройки инструментальных средств (прежде всего, ассемблера и симулятора/профилировщика) на конкретный целевой процессор необходимо уметь эффективно и формально задавать спецификацию этого процессора. Заметим, что для построения ассемблера и симулятора нет необходимости задавать детали реализации процессора на низком уровне, как это делается в синтезируемых описаниях на Verilog или VHDL. Для целей проверки 143
функциональной корректности тестовой программы и ее временных характеристик достаточно описать систему команд процессора (синтаксис, поведение и свойства команд, а также регистры и подсистему памяти). В данной работе рассматривается интегрированная среда MetaDSP, которая позволяет визуально описывать систему команд встраиваемых процессоров. Необходимость разработки интегрированной среды с графическим интерфейсом обусловлена требованиями возможности быстрого внесения согласованных изменений и автоматической верификации описаний «на лету». Ввиду наличия во встраиваемых процессорах, как правило, нерегулярной системы команд с множеством различных форматов, внесение изменений напрямую в текст спецификации чревато большим количеством ошибок. Среда MetaDSP позволяет упростить внесение изменений, обеспечивая при этом средства контроля над согласованностью изменений в ассемблере, дисассемблере, симуляторе и документации пользователя. Статья состоит из введения и пяти разделов. В первом разделе представляется общая архитектура системы MetaDSP. Второй и третий разделы характеризуют возможности системы MetaDSP по описанию синтаксиса/бинарного кодирования и поведения команд соответственно. В четвертом разделе дается описание концепции иерархического описания системы команд и наследования кода операции, операндов и поведения. Пятый раздел содержит описание основных элементов интерфейса пользователя системы MetaDSP.
2. Архитектура системы MetaDSP Система MetaDSP предназначена для автоматизации описания системы команд встраиваемых процессоров с целью обеспечения последующей автоматической настройки ассемблера, дисассемблера, симулятора, отладчика и генерации документации прикладного программиста. При таком подходе вся необходимая информация содержится в одном месте, а именно в файле описания MetaDSP, что позволяет поддерживать целостность и согласованность этого описания для использования в процессе настройки соответствующих кросс-инструментов. При этом уменьшается количество ошибок, ускоряется процесс внесения изменений и повышается наглядность описания. Эти характеристики очень важны для использования рассматриваемой системы в процессе прототипирования встраиваемых процессоров. Общая схема форматов описания MetaDSP и их использование для настройки соответствующих кросс-инструментов показаны на Рис. 1. Из файла описания MetaDSP генерируется файл спецификации синтаксиса команд и бинарного кодирования на языке ISE [14], который используется для настройки ассемблера, дисассемблера и отладчика, а также декодера симулятора. Основная часть симулятора процессора генерируется из файлов на языке ISE-Exec, в которых определяется поведение каждой инструкции. 144
Документация прикладного программиста для целевой системы команд генерируется из описания MetaDSP в виде документа MS Word. Описание MetaDSP
Дефисы в этой строке не являются значимыми и используются для косметического разграничения групп битов. Символы 0 и 1 ставятся на местах, которые вместе образуют так называемый код операции (КОП). Символ X означает, что данная битовая позиция не используется (может быть как 0, так и 1). Другие символы обозначают, что на данных местах располагаются коды операндов (в данном примере заданы операнды YY, SS и AA).
3.2. Ассемблерный синтаксис инструкции
Синтаксис и кодирование команд на языке ISE
Поведение команд на языке ISE-Exec
Файл MS Word
Ассемблер и дисассемблер
Симулятор, профилировщик и отладчик
Документация прикладного программиста
Синтаксис команды задается строковым шаблоном, в котором можно использовать комбинацию статических символов и ссылок на операнды, заключенных в фигурные скобки. Ссылки на операнды должны быть разделены статическим текстом, пробелы не являются значимыми: IF {cond} XOR {GRs}, {GRt} Здесь IF, XOR и запятые задают статическую часть синтаксиса команды, а cond, GRs и GRt являются ссылками на операнды. Синтаксис операндов определяется их типом, который задается отдельно для каждого операнда.
Рис. 1. Форматы описаний в системе MetaDSP и их использование
3.3. Операнды инструкции Таким образом, для различных целей (настройки различных инструментов и генерации документации) требуется свое подмножество общего описания системы команд; при этом эти подмножества имеют различные пересечения. Например, информация о бинарном кодировании команд используется для настройки всех инструментов и для генерации документации. Информация о поведении команд используется только для настройки симулятора и профилировщика, а информация об ассемблерном синтаксисе инструкций – для настройки ассемблера, дисассемблера, отладчика и генерации документации. Использование среды MetaDSP позволяет согласованно вносить изменения в различные части описания системы команд и корректно учитывать эти изменения при настройке всех инструментов.
3. Описание синтаксиса и бинарного кодирования команд В этом разделе описываются возможности системы MetaDSP для спецификации бинарного кодирования инструкции, ее ассемблерного синтаксиса, статических свойств и ограничений, а также правил определения межкомандных конфликтов.
3.1. Бинарное кодирование инструкции Бинарное кодирование команды (формат) задается в виде строки вида:
Для каждой команды описывается ноль или более операндов. При этом для каждого операнда задаются следующие параметры: имя операнда, определяющее ссылку на операнд в строке синтаксиса команды (например, GRs); тип операнда (например, регистр общего назначения, адресный регистр, 8-битная константа и так далее). Типы операндов задаются отдельно и определяют семантику в виде ссылки на объект в состоянии симулятора, синтаксис, разрядность и кодирование в виде перечисления пар значений – {синтаксис=код}. Например, {{GR0=00}, {GR1=01}}. Заметим, что тип операнда не определяет место кодирования операнда в машинном слове; место кодирования операнда в бинарном коде инструкции в виде строки, в которой буквы, отличные от X, определяют положение кода операнда (наиболее значимый бит слева). Например, ХХХХ-XXXXXXXX-XXAA определяет положение двухбитового операнда AA в двух младших битах машинного кода команды. Коды различных операндов в принципе могут перекрываться, если это не вызывает конфликта, кроме того, код операнда может иметь разрывы, например, четырехбитный операнд AAAA может кодироваться, как ХХХХXXXX-XAXA-XXAA – такие возможности очень важны для описания нерегулярных систем команд.
00YY-0000-SSAA-XXXX 145
146
3.4. Ограничения на значения и связи операндов В ряде случаев для данной инструкции допустимыми являются не все возможные значения операнда данного типа. Например, если в данной инструкции в качестве операнда GRt могут использоваться только регистры общего назначения GR0, GR1, GR2, GR3, а не все 16 регистров, имеющиеся в процессоре, то необходимо отдельно описать ограничения на такой операнд. Ограничения можно задать в виде предикатов. Если предикат ложный, то ограничение не выполнено, и ассемблер выдаст сообщение об ошибке. Например, для операнда GRt ограничение можно задать так: Predicate : GRt <= 4 Message : Only GR0 - GR3 can be used for this operand В предикатах можно использовать логические и арифметические операции над одним или несколькими операндами. Например, (GRt/4+1)*2-GRt >=GRt*2 представляет собой пример допустимого предиката. Использование предикатов над несколькими операндами позволяет задать ограничения на связи операндов, например, GRs=GRt обозначает тождественность операндов в синтаксисе.
3.5. Межкомандные конфликты Каждой команде можно назначить некоторый набор свойств и задать для них значения и области активации. В качестве значения свойства может выступать либо константа, либо значение одного из операндов команды. Область активации задает диапазон соседних команд, на котором данное свойство активно. По умолчанию [1;1] область активации затрагивает только текущую команду. Пример:
указывается набор пар свойств (пары разделяются запятыми, свойства в паре – знаком «=»). Предикат истинен для пары команд, когда выполняются следующие условия: первая команда обладает всеми свойствами из левых частей пар, вторая обладает всеми свойствами из правых частей пар; при этом для каждой пары значения свойств совпадают на пересечении их областей активации. Предикаты совместимости оцениваются ассемблером для всех пар команд при ассемблировании программы - так обнаруживаются конфликтующие команды. Заметим, что оценки работают гарантированно корректно только на линейных участках. Пример: [write_acr=read_acr] % warning: “WAR conflict for ACRs” Данный предикат будет верен, если у пары команд значение свойства write_acr первой команды совпадет со значением свойства read_acr второй команды на пересечении областей активации этих свойств. В данном примере это отражает конфликт по данным (по аккумуляторным регистрам) типа WRITE AFTER READ. Зарезервировано специальное свойство «any», которым по умолчанию обладает любая инструкция. [any=X] дает истинный предикат, если вторая инструкция обладает свойством X (независимо от его значения).
3.6. Дополнительная информация об инструкции Дополнительно для каждой команды можно указать:
MAC {acr},{grs},{grt} [read_grn:grs, read_grn:grt, write_acr:acr:2;2] Данная команда обладает следующими свойствами: read_grn – двойное свойство со значениями, равными значениям операндов grs и grt. Область активации по умолчанию затрагивает только текущую команду (здесь это означает, что значения регистров, заданных операндами grs и grt, читаются на первом такте); write_acr – значение свойства равно значению операнда acr. Область активации [2;2] затрагивает следующую команду (здесь это означает, что значение acr будет записано на втором такте). На механизме описания свойств базируется способ задания ограничений на использование ресурсов. Задается список предикатов совместимости свойств. В предикате совместимости свойств в квадратных скобках 147
идентификатор команды;
текстовое описание на естественном языке;
указание на то, что инструкция состоит из нескольких параллельно выполняющихся частей;
различные метрики инструкции, такие как количество тактов, которое занимает выполнение данной инструкции, энергия, потребляемая процессором при выполнении данной инструкции, и т.п. Эти данные используются профилировщиком.
4. Описание поведения команд В этом разделе рассматриваются средства системы MetaDSP для описания поведения инструкций. Для этой цели используется язык ISE-Exec, являющийся расширением C++. Описание на этом языке проходит через генератор, который преобразует его в чистый С++. Цель разработки этого языка состоит в том, чтобы повысить уровень абстракции для учета специфических потребностей и более удобного описания поведения команд процессора. 148
4.1. Обращение к операндам и битовым полям При описании поведения инструкции необходимо знать конкретные значения операндов инструкции. В рассматриваемом языке эти значения можно получить двумя путями.
Путем использования просто имени операнда, как обычной переменной. Такая ссылка автоматически преобразуется в необходимый код, который извлечет нужное значение из машинного кода команды в процессе симуляции.
Путем непосредственного обращения к именованным битовым полям машинного слова. Это осуществляется с помощью использования синтаксиса: #[+]LLLL Здесь символ + является не обязательным; если он есть, то закодированное число нужно трактовать как знаковое. На месте LLLL может быть любое количество подряд идущих букв в бинарном коде инструкции. При задании этих букв нужно следить, чтобы их положение в бинарном шаблоне команды определялось однозначно (см. 3.1). Вот простой пример обращения к битовым полям инструкции: MOVE {XM0}({YAv} + {offset}), {GRs} с кодом: 00DX-0100-01AA-RRRR-MMMM-MMMM Данная инструкция осуществляет пересылку в память с именем XM0 (это может быть память данных DM0 или TM0) регистра общего назначения GRs (в качестве GRs может выступать один из регистров общего назначения GR0,GR1,…, GRF). Адрес является суммой явно заданной константы offset и значения адресного регистра YAv (это может быть один из адресных регистров DA0 или TA0). Здесь кодирование соответствует операндам следующим образом: D : Destination memory field (XM0) AA : Destination address pointer field (YAv): RRRR : General register field (GRs): MMMMMMMM : offset field constant [-128;127] При описании поведения возможны следующие выражения: GRs, #RRRR, YAv + #+MMMMMMMM
4.2. Обращение к состоянию процессора и системы Состояние процессора и системы задается значением регистров и всех памятей системы. Обращение к этим ресурсам происходит следующим образом.
Обращения к памяти осуществляются с помощью макроса: MEM(memory_name, address) 149
Здесь memory_name обозначает индекс памяти, а address – адрес, по которому идет обращение. Обращение к регистрам производится путем использования макросов вида:
(register_name) Здесь register_type – тип регистра, а register_name – имя или код регистра этого типа. Например, для описанной выше инструкции MOVE XM0(YAv + offset), GRs поведение можно задать так: MEM(XM0, ARN(YAv)+offset) := GRN(GRs); Здесь YAv – адресный регистр типа ARN, а GRs – регистр общего назначения типа GRN. Стоит обратить внимание на операцию «:=», использованную в данном примере. Эта операция необходима, если в левой или правой части присваивания используется ресурс системы (регистр или память). При этом обращение транслируется в соответствующие функции доступа (set/get), которые кроме собственно чтения/записи значения обеспечивают сбор необходимых статистик для профилировки. Допустимые типы памятей и регистров, их разрядность и размер задаются разработчиком общей части симулятора.
4.3. Микрооперации в описании поведения При описании поведения инструкции, помимо стандартных операций С++, можно использовать описываемые дополнительно микрооперации (например REVERSEBITS(REG)). Стандартные микрооперации входят в библиотеку поддержки; разработчик общей части симулятора может определить свои микрооперации.
4.4. Временные переменные в описании поведения При описании поведения инструкции можно использовать любое число временных переменных. При этом для удобства эти переменные можно не объявлять, а можно просто написать имя переменной в левой части операции ‘=’. После обработки генератором все определения переменных будут сгенерированы автоматически.
5. Иерархичность описания команд в системе MetaDSP В среде MetaDSP система команд описывается иерархично в виде дерева. Листьями дерева являются команды, промежуточные узлы соответствуют группам команд (группы могут быть вложенными). Например, инструкции пересылки можно разместить в одной группе дерева, арифметические в другой, инструкции управления в третьей. При этом инструкции пересылки можно 150
далее дробить на более мелкие группы: пересылки память-память, регистрпамять и т. д. Иерархичность описания системы команд позволяет использовать наследование определенных свойств от родительских групп. Наследование возможно для следующих элементов: код операции (КОП), отдельные операнды и поведение. Наследование этих свойств может выполняться независимо и позволяет автоматизировать изменение групп инструкций, так как изменения у родителя вызывают аналогичные изменения у всех его потомков.
5.1. Наследование кода операции Под наследованием кода операции (КОП) подразумевается наследование битовых полей, установленных в бинарном коде инструкции в фиксированные значения – ‘0’ или ‘1’. Например, пусть есть две инструкции: MOVE GRs, GRt с кодом 0111-0011-GGGG-RRRR и MOVE ARs, GRt с кодом 0011-0011-AAAA-RRRR. Видно, что в этих двух инструкциях общим КОПом является: 0X11-0011-XXXX-XXXX В таких случаях в системе MetaDSP обычной практикой является вынесение описания общего КОП в родительский узел. При этом для каждого потомка этого узла можно указать, наследует ли он КОП родителя или нет (по умолчанию – да). Для узлов, наследующих КОП родителя, изменение значения зафиксированных в родителе битовых позиций запрещено, то есть в потомке можно определять только те позиции шаблона, в которых в родителе стоит X. Наследование КОП может быть многоуровневым в соответствии с организацией дерева системы команд. При добавлении новых потомков к родителю унаследованные биты КОП автоматически проставляются в потомке. Это ускоряет добавление новых инструкций.
5.2. Наследование операндов Под наследованием операндов подразумевается использование в инструкции родительских операндов. При этом изменение таких операндов возможно только на уровне родителя. В качестве примера возьмем те же инструкции, рассмотренные при описании наследования кода операции: MOVE GRs, GRt с кодом 0111-0011-GGGG-RRRR, где операнды GRs, GRt кодируются битами, помеченными в бинарном коде инструкции буквами GGGG и RRRR соответственно, и MOVE ARs, GRt с кодом 0011-0011-AAAA-RRRR, где операнды ARs, GRt кодируются битами, помеченными в бинарном коде инструкции соответственно буквами AAAA и RRRR. 151
Видно, что вторые операнды этих двух инструкций GRt имеют одинаковый тип и одинаковое положение в бинарном коде. Таким образом, если в дереве инструкций добавить родительский узел с определенным в нем операндом: Имя: GRt Тип: General Purpose Register Кодирование: XXXX-XXXX-XXXX-RRRR, а затем добавить к этому родительскому узлу двух потомков: MOVE GRs, GRt с операндами: Имя: GRs Унаследован: нет Тип: General Purpose Register Кодирование: XXXX-XXXX-GGGG-XXXX, Имя:
GRt Унаследован: да
и MOVE ARs, GRt с операндами: Имя: ARs Унаследован: нет Тип: Address Register Кодирование: XXXX-XXXX-AAAA-XXXX, Имя: GRt Унаследован: да то эти потомки будут содержать унаследованный операнд GRt. При изменении каких либо свойств этого операнда в родительском узле соответствующие изменения автоматически применятся к потомкам. Опыт авторов показывает, что в условиях должной организации дерева команд при добавлении новой инструкции большая часть операндов наследуется от родительского узла. Наследовать можно любое количество операндов, по-разному расположенных в бинарном коде родителя, в том числе и с перекрытием. При удалении операнда у родителя соответствующий унаследованный операнд удаляется у всех потомков.
5.3. Наследование поведения инструкций Поведение инструкции задается в виде кода на языке ISE-Exec, описанном в разделе 4. Под наследованием поведения подразумевается использование в описании поведения команды определенных частей родительского описания. Важность этого механизма обусловлена тем, что при описании поведения часто возникают ситуации, когда у нескольких дочерних инструкций описания очень похожи между собой. Чаще всего похожи начала или концы этих описаний (например, загрузка и выгрузка данных), а в середине они различны (обработка данных, специфичная для каждой команды). Поэтому в системе 152
MetaDSP существует возможность определить общие части поведения в родительском узле и наследовать их в потомках. Описание поведения у каждого узла дерева системы команд разделяется на две части: начало и конец. При этом в описании, принадлежащем данному узлу, можно использовать операнды, видимые на уровне этого узла. Дочерние узлы могут наследовать начало и конец описания родителя, то есть начало описания дочернего узла является конкатенацией начала описания родителя и своего начала, а конец описания дочернего узла является конкатенацией своего конца и конца описания родителя (матрешка). При таком подходе общий код в потомках большей частью может быть вынесен на уровень родителя, что обеспечивает более удобное и быстрое внесение изменений, а также уменьшает количество ошибок.
Основные окна программы: Instruction Set – это окно (Рис. 3) отображает дерево инструкций и является основным элементом навигации. При выделении определенного узла дерева информация об этом узле автоматически отображается в других окнах. Узлы дерева имеют контекстное меню, позволяющее удалять, копировать и добавлять новые узлы:
Рис. 3. Окно Instruction Set. Instruction Properties – это окно (Рис. 4, 5) отображает и позволяет редактировать свойства узлов, в частности бинарное кодирование, синтаксис, операнды и ограничения:
Рис. 2. Графический интерфейс системы MetaDSP.
6. Интерфейс системы MetaDSP Визуальная часть среды MetaDSP реализована на С++ с использованием графического интерфейса Windows и элементов управления библиотеки Codejock Xtreme Toolkit. Программа использует многооконный интерфейс с плавающими окнами (Рис. 2). 153
Рис. 4. Задание шаблона бинарного кодирования в окне Instruction Properties. 154
зону и зону, унаследованную от родителя (темный фон). Текст в унаследованных зонах заблокирован для редактирования (он задается в родителе - см. 5.3). Operand Types – в этом окне (Рис. 7) описываются глобальные типы операндов, которые потом используются при задании операндов в командах: Добавление, удаление и изменение типов осуществляется с помощью контекстного меню (Рис. 8).
Рис. 5. Задание ограничений в окне Instruction Properties.
Рис. 6. Окно поведения команды. Рис. 8. Диалог редактирования типов операндов. Additional Information – в этом окне задается дополнительная информация для системы команд в целом: описание межкомандных конфликтов, список свойств команд, список сообщений об ошибках. Свойства команд и сообщения об ошибках используются при задании ограничений (как внутрикомандных, так и межкомандных). Output – в это окно выводятся сообщения об ошибках или предупреждениях при автоматической верификации описаний, а также выводятся результаты аналитических запросов (в частности, статистика об использовании бинарного пространства и свободных кодах). Рис. 7. Окно типов операндов. Simulator Description – в этом окне (Рис. 6) описывается поведение команды. Окно разделено на две части, каждая из которых разделяется на собственную 155
156
7. Заключение В данной работе представлены возможности интегрированной среды MetaDSP по описанию системы команд для встраиваемых процессоров. Используя MetaDSP, можно определить синтаксис, бинарное кодирование и поведение команд в степени, достаточной для автоматической настройки основных кроссинструментов разработки: ассемблера, дисассемблера, симулятора с профилировщиком и отладчика. Кроме того, использование автоматической верификации описаний, графического интерфейса с встроенными контекстными редакторами и иерархичность описания команд позволяют значительно повысить эффективность редактирования процессорных описаний и внесения согласованных изменений, тем самым сокращая цикл внесения типовых изменений и их отладки для десятка команд до минут в сравнении с часами и даже днями без использования интегрированной среды. Это позволяет использовать эту систему для проведения этапа проектирования (DSE) в процессе разработки встраиваемых решений. При этом получаемые кросс-инструменты обладают качеством, достаточным для производственного применения при разработке прикладных программ для целевой системы. Система MetaDSP была успешно применена в коммерческих проектах с компаниями Freehand и VIA Technologies, в которых были получены наборы инструментальных средств кросс-разработки для пяти различных встраиваемых процессоров (включая процессоры цифровой обработки сигналов и RISC-контроллер) и десятков их модификаций.
12. Ashok Halambi, Peter Grun, Vijay Ganesh, Asheesh Khare, Nikil Dutt and Alex Nicolau. EXPRESSION: A Language for Architecture Exploration through Compiler/Simulator Retargetability, DATE 99. 13. Prabhat Mishra, Frederic Rousseau, Nikil Dutt, Alex Nicolau. Architecture Description Language Driven Design Space Exploration in the Presence of Coprocessors. SASIMI 2001. 14. В.В. Рубанов, Д.А. Марковцев, А.И. Гриневич. Динамическая поддержка расширений процессора в кросс системе. Труды ИСП РАН, том 5, 2004.
Литература 1. Hiroyuki Tomiyama, Ashok Halambi, Peter Grun. Architecture Description Languages for Systems-on-Chip Design. Center for Embedded Computer Systems, Univertsity of California. 2000. 2. Wei Qin, Sharad Malik. Architecture Description Languages for Retargetable Compilation. The Compiler Design Handbook, CRC Press, 2003. 3. Clifford Liem, Pierre G. Paulin, Ahmed A.Jerraya. Retargetable Compilers for Embedded Core Processors. Kluwer Academic Publishers, 1997. 4. Rainer Leupers. Retargetable Code Generation for Digital Signal Processors. Kluwer Academic Publishers, 1997. 5. Lin Yung-Chia. Hardware/Software Co-design with Architecture Description Language. Programming Language Lab. NTHU. 2003. 6. A. Fauth, J. Van Praet, M. Freericks. Describing Instruction Set Processors Using nML. Proc European Design and Test Conf., Paris, March 1995. 7. Mark R. Hartoog, James A. Rowson, Prakash D. Reddy. Generation of Software Tools from Processor Descriptions for Hardware/Software Codesign. Alta Group of Cadence Design Systems, Inc. DAC 1997. 8. Sim-nML Homepage. http://www.cse.iitk.ac.in/sim-nml/ 9. ISDL Project Homepage. http://caa.lcs.mit.edu/caa/home.html 10. George Hadjiyannis, Silvina Hanono. ISDL: An Instruction Set Description Language for Retargetability. Srinivas Devadas. Department of EECS, MIT. DAC 1997. 11. EXPRESSION Homepage. http://www.cecs.uci.edu/~aces/index.html
157
158
Использование языков описания процессоров высокого уровня для генерации платформо-зависимых частей операционной системы Ю. Фонин Аннотация. В статье проанализированы языки описания архитектуры процессора. Рассмотрены основные элементы микропроцессора. Выделены базовые элементы архитектуры процессора, необходимые для генерации ядра операционной системы, их параметры и свойства.
1. Введение В настоящее время аппаратное обеспечение все чаще и чаще разрабатывается под конкретное системное программное обеспечение. Такой подход позволяет добиться оптимизации конечной программно-аппаратной системы по многим критериям: производительность, стоимость, потребляемая мощность. Основным достоинством данного подхода является возможность адаптировать аппаратные ресурсы под требования конкретного программного обеспечения. Процесс разработки носит интерактивный характер. При каждом изменение в аппаратном обеспечении требуется модификация системного программного обеспечения (компилятор, компоновщик, операционная система). Внесение таких изменений вручную может занимать до нескольких человеко-месяцев и тем самым значительно замедлять процесс разработки системы. Представленное в статье формальное описание архитектуры позволяет разрабатывать системы для автоматического анализа процессора и генерации системного программного обеспечения. Данное описание строится по аналогии с промежуточным представлением, используемым компиляторами языков высокого уровня с учетом специфики архитектуры процессора. В качестве генератора такого описания может выступать компилятор языка описания архитектуры (ЯОА). Подобные языки используются для разработки процессоров, причем как для создания непосредственно чипа или его программного симулятора, так и для автоматической генерации соответствующего программного обеспечения (компилятора, ассемблера, отладчика). Статья состоит из введения, трех разделов и заключения. Во втором разделе статьи представлен краткий обзор существующих языков описания 159
архитектур, приводятся основные конструкции таких языков. В разделе 3 перечисляются базовые элементы процессора, а также свойства и характеристики элементов, необходимые для генерации операционной системы. Последний раздел содержит описание базовых элементов операционной среды, полученных в процессе структурно-функционального проектирования архитектуры процес-сора. В заключении приводится план будущих направлений развития темы.
2. Языки описания архитектуры процессора В настоящее время существуют три различных стиля для описания архитектуры процессора: структурный, поведенческий и смешанный [1]. Зачастую при описании конкретной архитектуры используются все три типа стилей. Структурное описание используется в основном для проектирования цифровых схем, поведенческое, как правило, – для моделирования, так как содержит конструкции, которые невозможно реализовать в виде схемы.
2.1. Структурное описание (structural description) Структурное описание соответствует стилю описания, используемому в языках регистровых передач, когда архитектура представляется в виде иерархии связанных компонентов. Для спецификации аппаратуры на уровне регистровых передач (register transfer level — RTL) используются языки категории HDL (Hardware Description Languages), наиболее известными из которых являются Verilog [2] и VHDL [3]. В качестве примера языка структурного описания приведем язык MIMOLA [4]. Описание процессора на языке MIMOLA представляется в виде набора блоков, таких как арифметико-логическое устройство, регистры и шины для доступа к памяти, а также набора соединений между блоками. Анализировать такое описание для генерации компилятора или операционной системы сложно. Поэтому в языке MIMOLA предусмотрена возможность ввода дополнительной спецификации компонентов, а именно, предназначение регистров или расположение памяти программ: LOCATION_FOR_PROGRAMCOUNTER PCReg; LOCATION_FOR_INSTRUCTIONS IM[0..1023]; Такие спецификации позволяют получить дополнительную информацию об элементах процессора, необходимую для автоматической генерации компиляторов, операционных систем и создания отладочного программного обеспечения. В частности, в приведенном выше примере указано, что регистр PCReg является регистром счетчика команд, а область памяти IM[0..1023] предназначена для хранения инструкций. Однако отсутствие представления инструкций процессора в явном виде делает процесс автоматической генерации модулей операционной системы крайне сложным. 160
2.2. Поведенческое описание (behavioral description) Для решения задачи автоматической генерации компонентов операционной системы предназначены языки поведенческого класса ADL (Architecture Description Languages), в которых функциональность каждой инструкции описывается в явном виде. Рассмотрим более подробно некоторые из таких языков. К языкам класса ADL относится язык nML [5]. В nML система команд процессора описывается с помощью атрибутных грамматик. Атрибуты включают в себя поведение (action), ассемблерный синтаксис (syntax) и отображение в машинные коды (image). В исходном варианте nML отсутствовали механизмы описания многотактовых команд. В последних версиях nML поддерживается синтез VHDL-описания. В nML инструкция или группа инструкций описывается в виде отдельного блока, например: op num_instruction(a:num_action, src:SRC, dst:DST) action { temp_src = src; temp_dst = dst; a.action; dst = temp_dst; } op num_action = add | sub | mul | div op add() action = { temp_dst = temp_dst + temp_src } ... Данный пример содержит функциональное описание блока инструкций. Описывается группа инструкций num_instruction, выполняющих базовые арифметические операции: сложение, вычитание, умножение и деление. При вызове данной инструкции выполняются следующие операции: 1. Значение аргумента src заноситься в переменную temp_src; 2. Значение аргумента dst заноситься в переменную temp_dst; 3. В зависимости от значения аргумента a выполняется одна из четырех возможных операций (a.action); 4. Результат операций сохраняется в dst. Множество возможных операций определяется типом аргумента a (num_action). В приведенном примере это множество состоит из четырех операций: add | sub | mul | div. Для каждой операции разработчик должен описать ее функциональность в блоке action (см. пример описания операции add). Как видно из примера, функциональность инструкции или группы инструкций в nML может быть описана в виде иерархии функциональных блоков или групп 161
функциональных блоков. В приведенном примере такой группой является группа из блоков add, sub, mul и div. Подобное описание позволяет получить в явном виде дерево каждой инструкции, описывающее ее функциональность, которое может быть использовано для генерации компилятора или операционной системы. Последователем nML стал язык Sim-nML [6]. Основное отличие состоит в присутствии в грамматике описания команд дополнительного атрибута использования ресурсов (uses), что позволяет специфицировать использование ресурсов и, тем самым, обнаруживать конфликты между командами. В рамках проекта Sim-nML были разработаны генераторы, позволяющие автоматически генерировать программные коды симулятора, ассемблера и дисассемблера на основе спецификации процессора на языке Sim-nML.
2.3. Смешанное описание Языки смешанного типа похожи на поведенческие языки, однако они позволяют при описании инструкций специфицировать использование конкретных аппаратных блоков, например, арифметико-логического устройства или специальных умножителей, а также описывать конвейер инструкций. В смешанных языках, как и в поведенческих, инструкции описываются в явном виде. Но при создании дерева инструкций могут возникать трудности, связанные с извлечением информации о функциональности внешних блоков. Язык EXPRESSION [7] позволяет создавать интегрированное описание структуры и поведения подсистемы процессор-память. Спецификация на EXPRESSION состоит из шести секций (первые три отвечают за поведение, последние три – за структуру): спецификация операций: набор атомарных команд с кодами, описанием параметров и семантики (поведения); описание формата команды: команда состоит из ячеек, ответственных за определенный функциональный модуль, которые могут заполняться атомарными операциями для параллельного выполнения; отображение общих операций компилятора на машинные операции, описанные в первой секции; данное описание используется в кодогенераторе компилятора; описание компонентов: функциональные устройства, шины, порты и т.п.; описание конвейера и связей компонентов; описание иерархии памяти: регистровая память, кэш, SRAM, DRAM. Язык LISA [8] разрабатывался в качестве средства описания аппаратуры для генерации симуляторов. К ключевым характеристикам LISA можно отнести подробное описание конвейера на уровне операций с возможностью задания зависимостей и блокировок. Конвейерные конфликты задаются явно. Каждая команда специфицируется в виде набора операций, которые определяются как регистровые пересылки за время одного такта синхронизации. Описание 162
аппаратуры на языке LISA состоит из двух основных частей: спецификации ресурсов и описания операций. Описание операций в свою очередь содержит следующие секции: DECLARE: определение объектов и групп через другие объекты и операции – фактически, правила грамматики; CODING: описание бинарного кодирования операции; SYNTAX: описание ассемблерного синтаксиса и параметров; BEHAVIOR и EXPRESSION: описание поведения операции в виде кода на языке C/C++; ACTIVATION: описание задержек (timings) и поведения конвейера. Языки LISA и EXPRESSION позволяют детально описать архитектуру процессора, что важно для синтеза микросхемы. С другой стороны, поскольку инструкции процессора описаны в явном виде, создание систем автоматической генерации операционных систем на основе EXPRESSION или LISA-описаний процессора существенно проще, чем создание таких систем на основе структурных языков. К недостаткам решения на основе этих языков по сравнению с поведенческими языками следует отнести относительную трудоемкость описания архитектуры, связанную с необходимостью детально описывать структурную составляющую. В этом смысле языки EXPRESSION и LISA стоят между чистыми поведенческими ADL-решениями (типа nML) и структурными описаниями уровня HDL.
одной стороны, можно получить из ADL-описания процессора, а с другой стороны, являются необходимыми для генерации ОС.
3.1. Структурное и поведенческое описание процессора Структурные элементы описываются набором свойств, соответствующих типу элемента. Функциональные элементы описываются деревом функциональности. Узлами дерева являются микрооперации, указатели на структурные элементы или множества элементов. Функциональный базис определяет множество возможных элементарных операций, которые могут быть применены при описании инструкций. ASG
ADD REG
1
2.4. Выводы Анализ современных ADL высокого уровня показывает, что из ADL-описания процессоров можно получить сведения о структуре и функциональности процессора. Фактически, вопрос состоит только в степени сложности использования того или иного языка категории ADL для автоматической генерации операционной системы. Отметим также, что для генерации операционной системы требуется лишь некоторое подмножество информации об архитектуре, которая может быть получена из описания процессора. Поэтому целесообразно разработать некий формат промежуточного представления (ПП) процессора, аналогичный представлению, применяемому в компиляторах языков программирования высокого уровня. Такое представления, в первую очередь, должно быть удобным и полным для генерации ОС, а с другой стороны, должна существовать возможность создания ПП на основе конструкций языков ADL.
3. Компоненты промежуточного представления архитектуры, необходимые для автоматической генерации ОС
ARG
2
1
Рис. 1. Пример функционального дерева для инструкции ADD Ri, #IMM На Рис 1 приведен пример представления инструкции сложения регистра с константой. При описании инструкции использованы следующие микрооперации: ADD (op1,op2) – сложение двух операндов; ASG (dst,src) – присвоение элементу dst значения выражения src; REG (lst,idx) – регистр с индексом idx из списка регистров с номером lst; ARG(NUM) – значение аргумента инструкции с номером NUM.
3.2. Базовые элементы архитектуры микропроцессора Архитектура процессора описывается на основе набора базовых элементов. Рассмотрим более подробно базовые элементы, приведенные на Рис.2.
Для автоматической генерации ОС на основе ADL-описания процессора необходимо, выделить список базовых элементов процессора, которые, с 163
ARG
164
номер бита в регистре; ассемблерное имя.
Базовые элементы
Структурные
Функциональные
Регистры/массивы регистров Флаги Шины памяти Сигналы прерывания
Инструкции Аппаратные обработчики прерываний
Регистр флагов также является частью контекста. Однако возможна ситуация, при которой одна часть флагов одного регистра принадлежит контексту, а другая часть определяет состояние процессора в целом и не принадлежат контексту. В таком случае при переключении контекста операционной системой должна быть восстановлена только та часть регистра флага, которая определяет состояние исполняемой программы.
3.2.3. Память процессора
Рис. 2. Базовые элементы процессора.
3.2.1. Регистры Регистры процессора предназначены для хранения промежуточных результатов вычислений (регистры данных), адресов (адресные регистры) и информации о режиме работы процессора (системные регистры). Каждый регистр описывается следующими параметрами: размер регистра в битах; ассемблерное имя (если есть); список подрегистров (если есть). Множество регистров, используемых для хранения промежуточных данных пользовательской программы, называется контекстом. Если операционная система поддерживает многопоточность (multi-threading), то ее частью является функция переключения контекста. При переключении контекста операционная система должна сохранить регистры прерываемой задачи и восстановить регистры загружаемой задачи.
3.2.2. Флаги Флаги – это элементарные однобитовые ячейки памяти. Флаги могут быть использованы для хранения результатов арифметических операций, например, бита переполнения или знака результата. Кроме того, специальные флаги могут использоваться для управления системой прерываний. Часто флаги являются битами регистров, и изменение состояния флага возможно только через изменение значения регистра. Флаги характеризуются следующими свойствами: имя регистра, которому принадлежит флаг; 165
Информацию о доступной процессору памяти можно описать в виде набора из трех компонентов: шины, блоки памяти и соединения. Рассмотрим составляющие каждого из компонентов. Шины Шина предоставляет интерфейс для работы процессора с памятью и описывается следующим набором характеристик: размер шины адреса (в битах); размер шины данных (в битах). Блоки памяти Блок памяти определяет имеющуюся физическую память и описывается следующим набором характеристик: размер блока в байтах; размер одной адресуемой ячейки памяти (в битах); тип памяти: только для чтения или для чтения и записи. Соединения Соединение описывает отображение блоков физической памяти на адресное пространство и определяется следующим набором характеристик: блок памяти; шина памяти, через которую осуществляется обращение к блоку; стартовый адрес, с которого начинается блок. Информация о блоках памяти необходима для конфигурации менеджера памяти операционной системы. Как правило, операционная система сама отвечает за выделение памяти под стек. Кроме того, во многих современных ОС (включая ОС для встроенных систем) поддерживается динамическое выделение памяти. Таким образом, при портировании ОС на новую архитектуру необходимо иметь информацию о размере, адресах и типах блоков физической памяти, доступной процессору.
3.2.4. Прерывания Прерывания описываются следующим набором характеристик: номер сигнала – запроса на прерывание; 166
приоритет прерывания или регистр, значение которого определяет
приоритет прерывания; процедура обработки запроса на прерывания. Одной из задач операционной системы является корректная обработка прерываний процессора. Во многих современных ОС для встроенных систем имеются стандартные функции пролога и эпилога. Функция пролога сохраняет регистры и флаги процессора, которые могут быть модифицированы при запуске стандартных функций. Функция эпилога восстанавливает регистры процессора, сохраненные функцией пролога, а также осуществляет корректный возврат из прерывания. Наличие в ОС функций пролога и эпилога позволяет реализовывать драйверы устройств, а также стандартные обработчики прерываний (исключения и программные прерывания) на языке высокого уровня, не задумываясь об особенностях архитектуры конкретного процессора. Еще одной функцией ОС, связанной с обработкой прерываний, является динамическая инициализация обработчиков прерываний, а также динамическое управление приоритетами прерываний. Операционная система предоставляет набор функций для подключения обработчика прерывания и установки приоритета того или иного прерывания. Очевидно, что для реализации перечисленных функций необходима информация о прерываниях, аппаратных обработчиках прерываниях и приоритетах прерываний или способах установки приоритетов.
3.2.5. Инструкции Одним из основных компонентов архитектуры является набор инструкций. Каждая инструкция имеет следующий набор свойств:
длина кода инструкции – сколько байт в памяти занимает инструкция; шаблон бинарного кода инструкции; список аргументов; ассемблерный синтаксис – шаблон инструкции для языка ассемблера; дерево, описывающее функциональность инструкции; список режимов, в которых данная инструкция может быть выполнена; количество тактов, необходимых для выполнения инструкции. Аргумент инструкции:
стартовый бит в коде инструкции; длина аргумента в битах; шаблон аргумента для ассемблера. Информация о наборе инструкций процессора необходима для реализации функций операционной системы. 167
4. Выбор базовых элементов для построения ядра ОС Проведенный в разд. 2 анализ языков описания процессоров показывает, что в процессе структурно-функционального проектирования архитектуры процессора появляется также возможность выявления базовых элементов, необходимых для автоматической генерации ОС. Из модели описания архитектуры процессора явным образом можно выделить следующую информацию:
список регистров процессора; список флагов процессора; список сигналов «запрос на прерывание»; операции, выполняемые процессором при возникновении прерывания; адресные пространства памяти; список инструкций для генерации кода. Данный набор элементов архитектуры процессора является необходимым, но не достаточным для построения ядра ОС. Помимо перечисленных выше элементов, для построения операционной системы необходимо дополнительно описать следующие элементы:
список регистров контекста – множество регистров, которые должны быть сохранены при сохранении состояния задачи; указатель контекста – регистр, который содержит адрес в памяти для сохранения контекста; регистры состояния системы прерываний; регистры управления системой прерываний; счетчик команд. Для ряда процессоров перечисленная информация может быть получена из анализа архитектуры, однако это возможно не всегда. Например, в процессоре ARM7 любой из 16 регистров общего назначения может выполнять функции указателя контекста. В таких случаях необходима дополнительная спецификация элементов. Также существуют параметры, которые невозможно получить явным или неявным путем из описания архитектуры процессора. Такие параметры должны быть определены разработчиком при проектировании системы: список volatile-регистров, значения которых не восстанавливаются после вызова функций; распределение приоритетов прерываний, которое может быть определено только пользователем в соответствии с потребностями всей системы.
168
5. Заключение В статье рассмотрены особенности выделения и описания базовых элементов архитектуры процессора, необходимых для генерации ядра ОС, приведен обзор языков описания архитектуры процессора, а также описаны ключевые моменты, позволяющие достичь эффективного решения поставленной задачи. Дальнейшее развитие технологии автоматической генерации ОС на основе описания архитектуры процессора заключается в разработке методологии анализа структуры и функциональности процессора и создании алгоритмов генерации функций ОС на основе данного анализа. Литература 1. Architecture Description Languages for Retargetable Compilation. Wei Qin, Sharad Malik Department of Electrical Engineering Princeton University. 2. IEEE Standard Hardware Description Language Based on the Verilog® Hardware Description Language, IEEE Std 1364-1995. 3. IEEE Standard VHDL Language Reference Manual, IEEE Std 1076-1987. 4. The integrated design of computer systems with MIMOLA. Peter Marwedel University of Kiel.
169
Анализ и трансформации исполняемых UML моделей Е.Д. Волкова, А.Д. Страбыкин Аннотация. В статье1 рассмотрены конечные автоматы языка UML, представлен подход к анализу исполняемых моделей UML. На основании выборки моделей, использованных в промышленных проектах, исследованы их количественные свойства и продемонстрирована актуальность трансформации моделей. Выделены образцы, часто используемые при построении автоматов. Предложены новые трансформации, улучшающие структуру модели, описан процесс их применения к реальной системе.
1. Введение При создании сложных инженерных систем принято использовать приемы моделирования. Сложность большинства создаваемых сегодня программных систем не уступает сложности многих инженерных сооружений, поэтому моделирование программных систем является весьма актуальной задачей. Более того, в таких концепциях, как MDA (Model Driven Architecture – архитектура на основе моделей) и MDD (Model Driven Development – разработка на базе моделей), моделям отводится центральная роль в процессе создания программного продукта. Основной идеей этих концепций является представление процесса создания программного продукта в виде цепочки трансформаций его исходной модели в готовую программную систему. Почти во всех инструментальных средствах, воплощающих идеи MDD, в качестве языка моделирования используется язык UML (Unified Modeling Language – унифицированный язык моделирования), целиком или какие-либо его части. UML – это язык, предназначенный для визуализации, специфицирования, конструирования и документирования программных систем. Слово «унифицированный» в названии языка означает, что UML может использоваться для моделирования широкого круга приложений от встроенных систем и систем реального времени до распределенных webприложений. Выразительные средства языка позволяют описать систему со всех точек зрения, имеющих отношение к разработке и развертыванию.
1
Работа выполнена при поддержке РФФИ, проект 05-01-00998-а. 171
В свете инициатив MDA и MDD роль моделей в жизненном цикле программного обеспечения (ПО) претерпевает значительные изменения. Если ранее моделирование рассматривалось как одно из удобных средств документирования, и, соответственно, жизненный цикл моделей был близок к жизненному циклу артефактов документации, то в последнее время работа с моделями становится все более похожа на работу с исходными кодами. Подобный подход ставит перед исследователями новые задачи исследования применимости к моделям методик и приемов работы, используемых для работы с исходными кодами. Одной из таких методик является рефакторинг. Рефакторинг – это изменение внутренней структуры ПО, имеющее целью облегчить понимание и упростить модификацию, но не затрагивающее при этом наблюдаемого поведения. Рефакторинг, как набор методик преобразования программ, помогает решать две глобальные задачи: облегчение процесса повторного использования каких-либо компонентов программной системы и снижение расходов на поддержку и сопровождение системы. Первые рефакторинги появились в результате обобщения опыта нескольких экспертов в области объектно-ориентированного проектирования. В этом отношении рефакторинги достаточно близки к широко известным на сегодняшний день паттернам проектирования. Существует много исследовательских работ и публикаций, посвященных методам и алгоритмам применения рефакторинга. Полноценная поддержка рефакторинга ставит перед производителями следующий ряд задач: 1. Поиск плохо спроектированных участков кода (модели), для которых требуется проведение рефакторинга; 2. Определение рефакторинга (синтез из поддерживаемых базовых рефакторингов), который следует применить; 3. Проверка или доказательство неизменности поведения системы после выполнения преобразований; 4. Реализация применения рефакторинга и, в частности, разработка пользовательского интерфейса и диалогов, поддерживающих процесс применения рефакторинга; 5. Сохранение целостности модели, то есть распространение произведенных изменений на другие части модели (диаграммы, тесты); 6. Оценка эффекта, полученного в результате применения рефакторинга. По каждому из указанных пунктов ведутся научные разработки, но лишь в немногих из них учитывается специфика UML. Анализ существующих UML моделей, приводимый в данной статье, показывает, что их структура сложна для понимания и содержит недостатки, которые можно было бы устранить путём проведения эквивалентных трансформаций. Особое внимание уделяется анализу и поиску методов рефакторинга для конечных автоматов языка UML, которые являются основой для полностью автоматической генерации исполняемого кода по UML172
моделям. На базе проведённого анализа и выявленных описывается новая трансформация, специфичная для UML.
недостатков Название модели
2. Анализ исполняемых UML-моделей
Aircraft Simulator
С целью выявления особенностей использования конечных автоматов UML в реальных промышленных проектах было проведено статистическое исследование набора моделей. Все рассмотренные модели описывают поведение системы с использованием конечных автоматов, по которым можно сгенерировать исполняемый код. Конечные автоматы UML могут описывать поведение следующих элементов исполняемых моделей: активный класс (active class); операция (operation); составное состояние (composite state). В зависимости от своего происхождения, все исследованные модели UML можно разделить на два класса: 1. Модели, изначально спроектированные на языке UML (например, в таких программных системах, как Rational Rose, Telelogic Tau G2, ILogix Rhapsody, Borland Together); 2. Модели, изначально спроектированные на языке SDL (например, в таких программных системах, как Telelogic SDL Suite, Verilog ObjectGeode) и трансформированные в UML вручную или при помощи специальных утилит (например, Telelogic Tau G2 — Import SDL). Исполняемые UML-модели второго класса в основном описывают различного рода коммуникационные системы (то есть такие классы систем, для моделирования которых предназначен язык SDL). Исполняемые модели первого класса в связи с универсальностью языка UML описывают гораздо более широкий спектр систем.
Central Interface IOS Algorithms Llama Simulator MMI MV-IOS6 3gN ATM and Banklib Local Exchange Access Control
Mobile Pager cc_layer common Executor 1xevdo ATC_ENV CpCallm S SS_RCS Tarif_c7 DC2000_5 23 модели
173
Симулятор самолета Система контроля доступа Система ввода/ вывода
Банкомат
Система контроля доступа
DEL_REL Inres
2.1. Характеристика конечных автоматов Общая статистика по исследованным моделям представлена в таблице 1. Перечисленные модели были заимствованы из реальных проектов коммерческих компаний. Для сбора и анализа необходимой информации был разработан дополнительный модуль к промышленной среде UMLмоделирования Telelogic Tau G2. Ожидалось, что модели, используемые в реальных проектах, будут иметь достаточно высокий уровень сложности. Тем не менее, более 90% от всех описанных автоматов содержат не более трех состояний, а доля автоматов без состояний (включающих только один начальный переход) близка к 75% (Рис. 1). Причем доля таких автоматов растет вместе с размером модели.
Предмет моделирования
Мобильный телефон Пейджер
Происхо- Объем ждение (Кб)
Кол-во автоматов, реализующих Общее количество активпассивДиагсоставные ные опера-ции ные типы раммы состояния классы данных2
UML
371
1
2
0
0
34
UML
253
4
0
1
8
24
UML
1 611
11
41
2
70
203
UML
894
6
2
0
31
126
UML UML UML
3 267 675 8 660
17 5 12
20 0 175
0 0 0
45 14 2708
112 118 нет инф
SDL
144
4
1
0
4
12
SDL
178
3
5
0
2
13
SDL
281
8
1
0
10
34
SDL SDL
190 121
3 4
2 0
0 0
21 4
22 15
SDL
772
14
0
0
27
156
SDL SDL
161 1 066
3 3
4 19
0 0
4 45
14 39
SDL
1 396
5
39
0
12
89
SDL SDL SDL SDL SDL SDL
21 817 5 710 67 295 380 20 572 881
21 17 9 1 3 2
482 62 760 9 94 25
0 0 0 0 0 0
350 867 2262 0 175 4
1457 226 1009 34 547 104
SDL
19 420
33
226
0
447
1483
189
1969
3
7110
5871
8
86
0
309
267
7 - UML 152 М 16 - SDL В среднем: 6.6 М
Таблица 1. Следует отметить, что автомат без состояний практически не обладает семантикой автомата и может использоваться только в качестве одной из форм записи некоторой последовательности действий, выполняемой в процессе во время начального перехода. Более того, текстовый синтаксис кажется намного более удобным средством для подобных спецификаций. Таким образом, оказывается, что в промышленных проектах примерно в половине случаев 2
174
В колонке «пассивные типы данных» учитывались следующие типы: пассивный класс, тип данных (datatype), перечислимый тип (enum), синоним типа (syntype), объединение (choice)
конечные автоматы используются не по своему прямому назначению. Причиной этому может служить недостаточный уровень владения инструментом у разработчиков модели или же, например, требование унифицировать все описания поведенческих аспектов системы с использованием для этого конечных автоматов.
>6 состояний 9%
0 состояний 1%
4-6 состояний 18% 1 состояние 45%
2-3 состояния 8%
4-6 состояний >6 состояний 2% 4%
1 состояние 11%
2-3 состояния 27%
0 состояний 75%
Рис. 2. Количество состояний в конечных автоматах, реализующих классы
Рис. 1. Количество состояний в конечных автоматах На основе полученных данных использование конечных автоматов без состояний может быть объяснено следующим образом. В рассмотренных моделях операции практически не обладали семантикой состояний, поэтому 99% операций описывались автоматами без состояний, вырождаясь в императивную последовательность действий. Таким образом, использование автоматов для спецификации операций, как правило, не оправдано, и, тем не менее, широко применяется на практике. Если рассмотреть автоматы, реализующие классы, то распределение количества состояний значительно изменяется (Рис. 2). Для спецификации классов практически не используются автоматы без состояний, в то время как преобладают автоматы, имеющие одно состояние. Такая структура характерна для классов, не обладающих сложной внутренней логикой, а реализующих некоторый сервис для других компонентов системы. В единственном имеющемся состоянии, которое очень часто носит имя “Idle” или “Wait”, класс ожидает запроса на выполнение какой-либо операции. Получение запроса инициирует срабатывание перехода, в процессе которого выполняются необходимые действия. По завершении обработки класс вновь возвращается в исходное состояние. 175
Автоматы, специфицирующие иерархические состояния, составили чуть менее 2% от всех обнаруженных автоматов и были найдены всего лишь в нескольких из рассмотренных моделей, что позволяет сделать вывод об их достаточно редком использовании, несмотря на их выразительную мощность. Причиной тому может служить тот факт, что составные состояния не являлись частью языка SDL до его версии SDL-2000. Большинство крупных промышленных моделей SDL, впоследствии трансформированных в UML, было разработано до того, как появился новый стандарт SDL-2000. На Рис. 3 приведена статистика количества переходов, которые могут сработать в каждом из состояний автомата. И здесь снова 84% процента состояний достаточно просты в понимании, так как имеют не более 6 переходов. Однако состояния с большим числом переходом могут заметно затруднить понимание автомата, а их доля приближается к 15%; более того, как правило, эти состояния являются ключевыми в понимании алгоритмов, заложенных в конкретный автомат. Таким образом, в среднем, автомат, реализующий класс, содержит 3 состояния и около 12 переходов и 4 диаграмм, при этом около 90% автоматов содержат не более 6 состояний, и, следовательно, их понимание не должно вызывать серьезных затруднений у разработчиков. Однако внутренняя логика работы системы, как правило, реализуется оставшимися 10%, среди которых встречаются автоматы, насчитывающие до 30 состояний. Вполне очевидно, что умственные затраты на понимание такого автомата достаточно велики; соответственно, значительно затрудняется процесс его модификации, поиска 176
ошибок и проч. Поэтому средства, уменьшающие сложность автоматов, сохраняя их внешние свойства, действительно востребованы на практике.
>6 переходов 15%
препятствовать пониманию. В то же время для 10% автоматов, описывающих внутреннюю логику работы системы и содержащих более 6 состояний и переходов, количество диаграмм, на которых описан автомат, возрастает до пятидесяти, что очень сильно затрудняет понимание целостной картины работы системы.
2.2. Используемые конструкции
0 переходов 1%
Для повышения уровня выразительности и упрощения описания сложных систем в состав средств описания конечных автоматов UML был включен ряд специальных конструкций. Их использование позволяет во многом упростить и сократить описание сложных автоматов, и поэтому одной из целей проведенного исследования было выявление характера использования подобных конструкций. Далее приведен обзор полученных результатов. За счет использования операторов ветвления в действиях, выполняемых при срабатывании перехода в автомате, один и тот же переход может в различных условиях перевести автомат в различные состояния. Максимально возможное использование ветвления означало бы наличие в каждом состоянии не более чем одного перехода для любого сигнала. В этом случае выбор состояния, в которое перейдет автомат, происходил бы в процессе интерпретации действий, приписанных переходу. Результаты статистического исследования приведены на Рис. 5.
1 переход 23%
4-6 переходов 18%
2-3 перехода 43%
Рис. 3. Количество переходов из состояния
Ветвистость переходов
Распределение количества символов по диаграммам
31-35 символов 4%
>35 cимволов 10%
4 ветви 6% 3 ветви 10%
0-5 символов 8%
26-30 символов 5%
5 ветвей >5 ветвей 3% 3%
6-10 символов 28%
21-25 символов 10%
2 ветви 17% 16-20 символов 20%
1 ветвь 61%
11-15 символов 15%
Рис. 5. Ветвистость переходов Рис. 4. Распределение количества символов на диаграммах Анализ диаграмм состояний показал (см. Рис. 4), что, в среднем, автомат, реализующий класс, включает в себя 3-4 диаграммы, каждая из которых содержит около 9 символов и 9 линий, что не должно в значительной степени 177
Как и следовало ожидать, большинство переходов не разветвляются, а около 90% из них имеет не более трех ветвей. Однако 3% переходов, имеющие более 5 ветвей, могут заметно усложнить понимание логики работы системы. Абсолютный максимум составил 21 ветвь в одном переходе. 178
Использование графического синтаксиса позволяет проводить графическую декомпозицию диаграмм состояний – распределять сложные автоматы по нескольким графическим сущностям, не упрощая при этом структуру автомата. Этот подход позволяет облегчить процесс понимания деталей работы сложного автомата, однако затрудняет восприятие автомата как единого целого, что немаловажно для понимания логики работы сложной системы. Одним из средств графической декомпозиции UML являются метки. Они позволяют графически отделить участки диаграммы состояний, чтобы, например, перенести их на другую диаграмму или расположить отдельно на исходной диаграмме. Кроме того, введение меток способствует повторному использованию фрагментов диаграмм, так как переход на единожды описанную метку может быть выполнен многократно из различных частей автомата. Статистика использования меток приведена на Рис. 6. Распределение переходов на метки
>4 переходов 3% 4 перехода 3% 2 перехода 8%
2.3. Типичные способы построения конечных автоматов
3 перехода 4%
1 переход 20%
перечислить несколько имен состояний, и тогда все переходы, выходящие из этого символа, будут относиться ко всем перечисленным состояниям. Кроме того, если в качестве имени состояния указать символ «*», то переходы, выходящие из этого символа, будут относиться ко всем состояниям автомата. Также имеется возможность исключить определенные состояния из множества состояний, описываемого символом «*». Умелое использование этих возможностей позволяет значительно упростить описание переходов, применимых более чем к одному состоянию. Результаты статистического исследования показали, что символ * присутствует в 12% символов состояния, что свидетельствует о достаточно активном использовании этой подстановки и необходимости более детального изучения вариантов ее использования и возможных трансформаций с выделением или заменой символа «*». Кроме того, при описании состояния, в которое должен быть совершен переход, UML позволяет использовать символ «-», означающий состояние, в котором был инициирован исходный переход. Согласно статистике более трети символов состояния содержит символ «-». Это снова свидетельствует об удобстве и востребованности этой конструкции, а также о необходимости исследовать затрагивающие ее трансформации.
0 перходов 62%
Анализ полученной выборки не выявил каких-либо стандартов или «правил хорошего тона» при разработке конечных автоматов. Единственным «паттерном» можно считать применяемую одной из компаний методику, когда при описании автомата для каждого перехода из заданного состояния используется отдельная диаграмма, и еще одна диаграмма используется для всех общих описаний. Естественным недостатком такого подхода является сложность получения целостного представления о моделируемом автомате по причине разрозненности отдельных диаграмм, описывающих состояния.
3. Улучшение структуры конечных автоматов UML Рис. 6. Распределение переходов на метки
3.1. Трансформация «выделение метода» для конечных автоматов UML
Распределение количества команд перехода на метки очень похоже на распределение количества ветвей. В обоих случаях наиболее простые варианты (одна ветвь и отсутствие переходов на метки) обеспечивают около 60% случаев, а следующие по сложности варианты (две ветви и одна команда перехода на метку) – около 20%, в то время как остальные варианты имеют по 3-4%. Однако в автоматах встречались и переходы, перегруженные командами перехода на метки. Для некоторых переходов в автомате максимальное количество команд перехода на метку превысило 20. Чтобы избежать дублирования переходов для различных состояний, можно использовать несколько приемов. В UML в символе состояния можно 179
Идея трансформации “Extract method” состоит в создании нового метода и переносе части исходного автомата в добавленный метод. Данная трансформация во многом аналогична известному рефакторингу «Extract Method» для объектно-ориентированных языков программирования, описанному в каталоге Фаулера [1]. Суть традиционной трансформации состоит в выделении участка кода и перемещении его в другой метод. Это позволяет сделать код исходного метода более понятным и повышает вероятность повторного использования выделенного метода. Для корректного выполнения традиционного рефакторинга “Extract method” требуется тщательный анализ потока данных в выделяемом участке кода, так 180
как все используемые переменные должны быть переданы в метод в качестве параметров, а все изменения переменных должны быть тем или иным образом возвращены исходному методу, если измененные переменные используются в нем далее. Для первичного рассмотрения проблемы выделения метода в автомате эту проблему можно обойти следующим образом. Если используемая переменная является атрибутом автомата или сущности, содержащей автомат, то она будет видна и в выделенном методе и, следовательно, ее не нужно передавать в качестве параметра. Если же используемая переменная является локальной для действий, выполняемых в переходе, то при перенесении всех действий перехода в выделяемый метод определение локальной переменной и все ее использования будут также перенесены. Для выделения метода, в который помещаются не все действия, выполняемые в переходе, требуется дополнительный анализ потока данных. Следует подчеркнуть исключительную важность автоматизированной поддержки рефакторинга при проведении подобных преобразований, ибо сложность проводимого анализа будет способствовать ошибкам. Идея, лежащая в основе традиционного рефакторинга “Extract method”, может быть применена к конечным автоматам несколькими способами.
Для конечных автоматов UML можно применить традиционную трансформацию «выделение метода», которая состоит из выделения подпоследовательности действий одного из переходов конечного автомата в метод. В рамках описываемого исследования был разработан новый вариант трансформации «выделение метода», специфичный только для конечных автоматов UML, – «выделение в метод части конечного автомата», который подразумевает перенос в выделяемый метод не только действий, связанных с переходом, но и самих переходов и состояний.
3.2. Выделение в метод части конечного автомата Рассмотрим определение части конечного автомата, представленное на Рис. 7. Выбрав часть перехода вместе со следующим состоянием, можно выделить метод, в который войдет часть состояний конечного автомата, начиная с состояния Y. Будем называть такую трансформацию Extract Sub State Machine. Применимость данной трансформации связана со следующим свойством. Состояния, переносимые в выделяемый метод, перестают принадлежать исходному автомату и, следовательно, команды перехода, приводящие из состояний исходного автомата в состояния, перенесенные в выделенный автомат, некорректны. Такие команды перехода (смены состояния) должны быть заменены командами вызова выделяемого метода. Однако у автомата, реализующего метод, может быть только одна входная точка, поэтому либо все 181
такие команды должны осуществлять переход в одно и то же состояние, либо можно использовать целочисленный параметр для передачи номера того состояния, с которого должно начаться выполнение метода. Но введение такого параметра и добавление его обработки в начальном переходе усложняет выделяемый автомат и затрудняет его понимание. X
Y
Sig1()
Sig2()
Sig3()
Sig1()
/* 1 */
/* 2 */
/* 3 */
/* 4 */
Z
Z
Y
X
Риc. 7. Часть автомата, допускающая выделение метода Для обработки обратных переходов из состояний выделенного метода в состояния исходного автомата может быть применен следующий прием. Все состояния исходного метода, в которые можно попасть из выделяемого метода, нумеруются последовательными натуральными числами. Все команды перехода, ведущие из состояний выделяемого метода в состояния исходного, заменяются командами возврата из метода, использующими в качестве возвращаемого значения номер того состояния, в которое должен был бы осуществиться переход. После замены тела выделенного метода его вызовом возвращаемое им значение присваивается новой локальной переменной, и после возврата из метода оно анализируется для определения состояния, в которое должен был осуществиться переход. Описанный прием, хотя и позволяет при выделении метода не накладывать ограничений на количество обратных переходов, на практике зачастую только затрудняет понимание автомата, что противоречит целям проведения рефакторинга. Таким образом, несмотря на то, что количество вариантов применения предлагаемой трансформации достаточно велико, далеко не все из них служат цели упрощения понимания автомата. Тем не менее, можно выделить несколько специальных случаев, когда все прямые и все обратные переходы ведут в одно состояние. Введём несколько обозначений. Обозначим через RS(x, y) множество, содержащее все состояния автомата, в которые можно попасть из состояния y, 182
не проходя при этом через состояние x, включая y и исключая x. Специальный символ stop добавляется в множество RS(x, y), если из состояния y за некоторое количество переходов можно дойти до действия, завершающего работу автомата (stop). Обозначим множество всех переходов некоторого конечного автомата A через All_T(A), а переход из состояния а в состояние b по сигналу z – через t(a, z->b).
упрощает его понимание Результат преобразования представлен на Рис. 8. Выделенный метод показан на Рис. 9. Выделенный метод можно использовать повторно для уменьшения дублирования кода. X
Определение 1. Множество состояний S замкнуто на множестве переходов T, если не существует перехода t(x’,e->s)T : sS, x’S Трансформация Extract Sub State Machine для перехода t(x,e -> y) конечного автомата A, может быть применена при выполнении следующих трех условий:
Sig1()
Sig2()
/* 1 */
/* 2 */
Sig3()
P();
1) x != y, иначе RS пусто и это будет случай выделения автомата без состояний; 2) множество RS(x,y) замкнуто All_T(A)\t(x,e->y);
на
множестве
переходов Z
Z
-
3) stop RS(x, y). Трансформация Extract Sub State Machine для перехода t(x,e заключается в следующем.
->
y)
Рис. 8. Часть автомата после проведения преобразования Extract Method
1) Создаётся и добавляется в активный класс метод P с реализацией в виде конечного автомата.
Y
RS(X,Y) = { Y }
2) В этот метод перемещаются все состояния из множества RS(x, y) . 3) Действия, приписанные переходу t(x,e -> y), становятся действиями, приписанными начальному переходу конечного автомата метода P(). Вместо них в исходный конечный автомат вставляется вызов метода P() и команда перехода в исходное состояние x.
/* 3 */
4) Все команды перехода в состояние x в созданном конечном автомате заменяются на команды возврата из метода (return). Часть автомата, выделенная в метод, обладает следующей семантикой: получив сигнал Sig3(), автомат выполняет некоторые действия, начиная с состояния y, по завершении которых возвращается в состояние x. Подобная логика близка по смыслу к вызову метода: выполнение задачи с последующим возвратом в исходное состояние. Именно это и служит основанием для выделения метода. В результате преобразования выделяется структурная единица автомата – метод, а диаграмма, описывающая конечный автомат, уменьшается, что 183
Y
Sig1()
/* 4 */
Рис. 9. Описание выделенного метода Существует несколько частных случаев трансформации Extract Sub State Machine. 184
1) Ни для одного состояния из RS(x, y) нет перехода в x. Это значит, что возврат из созданного метода невозможен, в конечном автомате найден бесконечный цикл; возможно, это «серверная составляющая» исходного автомата. 2) Множество RS(x,y) содержит символ stop, и ни для одного состояния из RS(x, y) нет перехода в x. Это означает, что выделенная в метод часть автомата рано или могла завершить его работу: либо выделенные действия реализуют необходимую подготовку к завершению работы автомата (аналог деструктора в объектно-ориентированном программировании), либо найдена «серверная составляющая» исходного автомата (только если есть цикл). Во втором случае выделение метода корректно при выполнении следующих условий: a) в выделенном методе все команды завершения работы автомата (stop) должны быть заменены командами возврата из метода (return); b) вместо действий, приписанных исходному переходу, должен быть добавлен вызов метода P() и команда завершения работы автомата (stop). В рассматриваемом случае преобразованный автомат будет выглядеть так, как показано на Рис. 10.
3.3. Пример «Мобильный телефон» Продемонстрируем применение трансформации «Выделение части конечного автомата в метод» на одном из конечных автоматов системы Mobile, моделирующей работу мобильного телефона. В исходной системе конечный автомат представлен на 28 диаграммах, каждая из которых описывает ровно один переход (Рис. 11).
X
Sig1()
/* 1 */
Z
Sig2()
/* 2 */
Sig3()
Рис. 11. Исходный вид конечного автомата Такое представление не позволяет понять цельную структуру конечного автомата. Для упрощения понимания была создана дополнительная диаграмма, схематично описывающая весь конечный автомат, иллюстрирующая все состояния и переходы со всеми ветвлениями (Рис. 12). Приведённый алгоритм позволяет найти и выделить из данного конечного автомата три метода. На первом шаге в метод Initialize() выделяются четыре последовательных состояния (Рис. 13).
P();
Z
Рис. 10. Результаты применения второго варианта трансформации
185
186
На втором шаге выделяется метод TalkingThePhone() (Рис. 14), после чего становится возможным выделить ещё один метод, который назовём Working().
Рис. 12. Краткое описание всего конечного автомата Рис. 14. Выделение метода TalkingThePhone Обратим внимание на то, что выделение метода Working возможно только после выделения метода TalkingThePhone. Процесс применения трансформации итеративный. Поиск частей конечного автомата, которые можно вынести в отдельный метод, можно автоматизировать. В результате применения трансформаций исходный конечный автомат сильно упростился и свободно помещается на одной диаграмме (Рис. 15). Теперь он содержит только одно состояние (вместо четырнадцати состояний в исходном автомате) и вызов двух методов. Выделены три метода Initialize(), TalkingThePhone() (Рис. 16) и Working() (Рис. 17), содержащие 4, 5 и 4 состояния соответственно.
4. Заключение. Таким образом, задача трансформации моделей UML является достаточно актуальной. Проведенные исследования подтвердили гипотезу о возможности улучшения структурных качеств и упрощения понимания моделей, применяемых в реальных промышленных проектах. Это ставит перед исследователями задачи поиска новых трансформаций, в которых учитывается
Рис. 13. Выделение метода Initialize 187
188
специфику моделей UML. Для оценки применимости и полезности трансформаций необходимо продолжение работы по формализации подмножества конечных автоматов UML, описанию семантики их выполнения, а также создание инструментальных средств, автоматизирующих сбор необходимой информации и процесс трансформации моделей.
Рис. 15. Результат трансформации
Рис. 16. Метод Working 189
190
Литература 1. Фаулер М., Бек К., Брант Д., Робертс Д., Апдайк У. Рефакторинг: улучшение существующего кода. - СПб.: Символ-Плюс, 2002. - 432 с. 2. William F. Opdyke, "Refactoring Object-Oriented Frameworks". PhD Thesis, University of Illinois at Urbana-Champaign. Also available as Technical Report UIUCDCS-R-921759, Department of Computer Science, University of Illinois at Urbana-Champaign. 3. Tom Mens. A Survey of Software Refactoring, IEEE Transactions on Software Engineering, Vol. 30, No. 2, February 2004. 4. Van Gorp, P.; Stenten, H.; Mens, T. and Demeyer, S. Towards Automating Source Consistent UML Refactorings, in Proc. Unified Modeling Language Conf. 2003, 2003. 5. Astels. D., 'Refactoring with UML', in Marchesi, M and Succi, G (eds). XP 2002 Proceedings of the 3rd International Conference on eXtreme Programming and Flexible Proceses in Software Engineering, 2002. 6. Tom Mens, Niels Van Eetvelde, Dirk Janssens, and Serge Demeyer. Formalising refactorings with graph transformations. Fundamenta Informaticae, 2003. 7. Robert France, Dae-Kyoo Kim, Sudipto Ghosh, and Eunjee Song, “A UML-Based Pattern Specification echnique,” IEEE Transactions on Software Engineering, Vol.30, No.3, pp. 193-206, March 2004. 8. Marciniak J. J. The Encyclopedia of Software Engineering // Wiley Publishers. 2002. – 2076p.: il. 9. Буч Г., Рамбо Д., Джекобсон А. UML Руководство пользователя // М.: ДМК Пресс. 2001. – 432 с.: ил. 10. Меллор С., Кларк Э., Футагами Т. Разработка на базе моделей // Сайт журнала «Открытые Системы»: URL: http://www.osp.ru/os/2003/12/030.htm (2005. 25
июня). 11. Селич Б., Практические аспекты разработки на базе моделей // Сайт журнала «Открытые системы»: URL: http://www.osp.ru/os/2003/12/033.htm (2005. 25 июня). 12. Zs. Pap, I. Majzij, A. Pataricza, A. Szegi. Completeness and Consistency Analysis of UML Statechart Specifications // I.Maizik homepage: URL: http://home.mit.bme.hu/~majzik/publicat/ddecs2001.pdf (2005. 25 июня). 13. Unified Modeling Language: Superstructure // OMG official website: URL: http://www.omg.org/docs/formal/05-07-04.pdf (2005. 25 июня). 14. UML 2.0 OCL Specification // OMG official website: URL: http://www.omg.org/cgi-bin/apps/doc?ptc/03-10-14.pdf (2005. 25 июня). 15. H. Eriksson, M. Penker, B. Lyons, D. Fado. UML2 Toolkit // Indiapolis: Wiley Publishing Inc. 2004. – 511 p. il.
Рис. 17. Метод TalkingThePhone 191
192
Использование ролей в сценариях взаимодействия А. Волков ([email protected]) Аннотация. Сценарий взаимодействия описывает поведение системы через описания взаимодействий некоторых объектов. Такими объектами выступают компоненты системы и внешние объекты, называемые акторами (actors). Обычно в сценарии взаимодействия подразумевается уникальность каждого объекта системы, т.е. предполагается, что описываются физически различные экземпляры объектов. Однако в ряде случаев это приводит к дублированию при описании сценариев. В работе предлагается расширить аппарат сценариев взаимодействия, разрешив использование ролей объектов. Роль соответствует некоторому срезу поведения определенного физического объекта. При таком подходе к описанию поведения возникает задача композиции поведения объектов в различных ролях. В данной статье рассматривается возможность систематического использования понятия роли в сценариях и исследуются средства, позволяющие строить общее поведение для объектов моделируемой системы по их поведению в различных ролях. Для представления сценариев используется модель взаимодействий UML (UML Interactions). В качестве абстрактных моделей для описания общего поведения объектов рассматриваются автоматные модели и модели, основанные на сетях Петри, в нотации UML (машины состояний и активности соответственно).
1. Введение Модельно-ориентированный подход (Model Driven Architecture, MDA), предложенный консорциумом OMG, направлен на достижение интероперабельности разрабатываемых систем. В нем используется идея разделения бизнес-логики проектируемой системы и конкретной технологии ее реализации. Для описания бизнес-логики используются платформонезависимые модели проектируемой системы. В рамках этого подхода основополагающую роль играет построение моделей системы, проверка их корректности и отображение в модели других уровней. Для унификации моделирования различных аспектов системы в рамках подхода MDA предлагается использовать универсальную нотацию – язык UML [1]. На ранних фазах проектирования программных системы, особенно на фазе анализа требований, с успехом используется сценарный подход, заключающийся в определении вариантов использования (use cases) системы и описания сценариев ее поведения в каждом таком варианте. Каждый сценарий представляет собой описание последовательности взаимодействий, 193
направленной на достижение некоторой цели. Сценарии могут быть заданы с помощью какой-либо нотации, позволяющей описывать поведение, однако, как правило, для описания сценариев используются нотации, обладающие высокой степенью наглядности. Построение формализованной сценарной модели позволяет производить как статический анализ требований, так и генерировать исполняемый прототип системы для динамического исследования системы; тем самым достигается возможность проверки требований. Для построения поведенческих моделей в языке UML 2.0 существуют следующие средства: взаимодействия (interactions), семантика которых задает отношение частичной упорядоченности событий в различных компонентах системы и акторов, активности (activities), семантически эквивалентные иерархическим раскрашенным сетям Петри, машины состояний (state machines), использующие семантику расширенных конечных автоматов в алфавите событий системы. По сути, взаимодействия UML обеспечивают абстракцию трасс системы, активности – абстракцию потоков (управления и данных) в системе, а машины состояний – абстракцию последовательности состояний для каждого компонента системы. Как правило, для описания сценариев на ранних фазах наиболее адекватно соответствует семантика взаимодействий UML, так как в сценариях, по существу, описываются трассы взаимодействий с будущей системой. Далее, говоря о сценариях, мы будем подразумевать, что они описываются в рамках модели взаимодействий UML. Семантика моделей взаимодействий носит декларативный характер. При определенных ограничениях возможно статическое исследование таких моделей (например, см. в [2] использование линеаризаций последовательностей событий и анализ формальных языков, получающихся в результате их рассмотрения). Для определения исполняемой семантики модели взаимодействий возможен подход, заключающийся в построении специальной абстрактной машины (вариант такого построения можно найти в [3]). Однако при составлении требований основной акцент делается, как правило, на описание ожидаемых ситуаций в системе. Кроме того, поведение описывается для некоторой совокупности объектов системы совместно, в то время как во многих случаях подразумевается задание протокола взаимодействия существенно независимых объектов. Абстрактная реализация модели поведения будет полностью следовать описанным сценариям, однако для указанных систем в ходе разработки сценариев может остаться неучтенной это существенная независимость объектов системы. В отличие от моделирования требований, для описания прототипа системы строятся, как правило, модели, описывающие поведение каждого объекта системы в отдельности; такие модели могут быть с легкостью отображены в 194
целевой код прототипа. Для описания этих моделей хорошо подходят событийные автоматы, которым соответствуют машины состояний UML. Различие в поведении такого прототипа системы и поведении, задаваемом моделью требований в виде взаимодействий, в некоторых случаях свидетельствует о недопонимании требований к системе. Таким образом, для анализа требований системы в форме сценариев взаимодействия оказывается полезным исследовать поведение системы, которое описывается моделями, отличными от модели последовательностей. Для этого важна возможность преобразования одних модели поведения в другие. При этом встает вопрос о соотношениях между различными моделями поведения системы, т.е. вопрос об эквивалентности поведений, задаваемых разными моделями или, по крайней мере, о степени точности аппроксимации поведения одной модели поведением другой. Исследование неточностей, появляющихся в результате такой аппроксимации может помочь выявить те особенности подразумеваемого поведения системы, которые не были учтены в исходных сценариях. Привлекательной является возможность автоматического преобразования исходной сценарной спецификации, моделируемой последовательностями UML, в другие модели, аппроксимирующие исходную. На текущий момент существует алгоритм построения по сценариям, записанным в нотации диаграмм последовательностей, автоматной модели для каждого объекта, входящего в систему; совокупность этих автоматов аппроксимирует исходную модель. Этот алгоритм реализован в семействе инструментов Bridge, разрабатываемых в Институте Системного программирования РАН совместно с компанией Klocwork Inc. Данная работа ведется в рамках этого проекта. Для описания UML-взаимодействий в данной работе используются диаграммы последовательностей (sequence diagrams) UML, а для описания композиции поведения – обзорные диаграммы взаимодействий (interaction overview diagrams). Заметим, что сходную нотацию предоставляет стандарт MSC [4], который включает базовые (basic) и высокоуровневые (high-level) MSCдиаграммы. Отметим, что обзорные диаграммы взаимодействий имеют в некоторых случаях недостаточно определенную семантику для адекватного объединения поведения компонентов системы в различных сценариях, что затрудняет и статический анализ требований. Каждое описание взаимодействий производится для определенного контекста, который определяется набором экземпляров объектов, принадлежащих системе, и внешними акторами. Возможность абстракции такого контекста от остальной системы может быть ценной при необходимости повторного описания таких взаимодействий для другого аналогичного контекста, так как тогда расширяется возможность повторного использования (reusability) описанных взаимодействий. Если в качестве абстракции экземпляров объектов традиционно рассматриваются классы объектов, то в качестве абстракции 195
объектов в рамках определенного контекста могут быть рассмотрены роли. В спецификации UML явно говорится о том, что проявления типов объектов в некотором контексте имеет смысл рассматривать как роли этих объектов. Абстракция роли достаточно исследована со стороны ее структурной реализации, но не с точки зрения объединения поведения в различных сценариях. Данная статья посвящена построению аппарата, позволяющего систематически использовать понятие роли при написании сценариев, определению средств нотации диаграмм последовательностей UML, которые могут быть использованы для этого, и исследованию того, каким образом полученная сценарная модель может быть отображена в другие модели. Далее работа построена следующим образом: во втором разделе приводятся примеры, иллюстрирующие использование понятия роли в моделях взаимодействия; в третьем разделе производится некоторая формализация понятия роли; в четвертом разделе рассматриваются средства нотации диаграмм последовательностей и обзорных диаграмм взаимодействия UML для описания композиции поведения объектов в различных ролях; пятый раздел посвящен исследованию возможности отображения модели взаимодействий в другие поведенческие модели, рассматривается расширение алгоритма синтеза событийных автоматов при использовании средств композиции поведения в различных ролях, рассмотренных перед этим.
2. Роли в моделях взаимодействия Описание взаимодействий в виде диаграммы последовательностей представляет собой набор осей (lifelines), некоторым образом сопоставляемых объектам, каждая из которых задает порядок наступления определенных событий для соответствующего объекта и выполнения им некоторых действий. Мы будем придерживаться следующего соглашения об использовании синтаксиса именования оси: <идентификатор_оси> ::= [<имя_элемента>] [: <имя_класса>] (причем <идентификатор оси> не может быть пустым). Трактоваться <идентификатор_оси> будет следующим образом: <имя_элемента> обозначает имя роли объекта, которому данная ось ставится в соответствие, а <имя_класса> – тип объекта. В случае, когда опущено имя роли, предполагается, что объект играет некую, так называемую, анонимную роль. Если опущено имя класса, предполагается, что тип объекта может быть произвольным. 196
В качестве близкого к реальным системам примера рассмотрим модель сети мобильной связи, в которой есть телефоны (Telephone), передатчики (Transmitter) и центральный коммутатор (Switch). Для такой системы можно выделить следующие протоколы: установление соединения; передача данных (разговор); разрыв соединения; регистрация в сети; смена станции в сети. Мы не будем описывать полную сценарную спецификацию для этой системы, однако приведем ее некоторые возможные сценарии (Рис. 1): запрос на установление соединения (Connection), передача данных (Transmitting), смена станции (Relocation).
Рис. 1. Сценарии системы мобильной связи Далее, во избежание громоздкости, в качестве модельного примера будет рассматриваться следующая спецификация достаточно простой системы, ее можно считать сильным упрощением описанной системы мобильной связи. Итак, рассмотрим систему обмена данными (Рис. 2), состоящую из двух узлов A и B, которые могут по очереди обмениваться сообщениями с данными (data) до тех пор, пока один из них не пошлет сообщение о завершении серии обменов (stop). По существу, в рассматриваемой спецификации имеются только два различных взаимодействия (Рис. 3; здесь опущены конкретные имена типов объектов) – это протокол передачи данных и протокол завершения обмена данными; для данного модельного примера эти протоколы тривиальны. Протокол передачи данных описывает поведение двух сторон – объекта, играющего роль отправителя данных (DataSender), и объекта, играющего роль получателя данных (DataReceiver). В контексте завершения сеанса передачи можно говорить о роли объекта, инициирующего завершение (TerminationInitiator), и роли объекта, извещаемого о завершении (TerminationAcceptor). В этом примере следует обратить внимание, на то, что, прибегая к абстрагированию, можно говорить не об описании поведения объектов, а об описании поведения ролей, которые объекты могут выполнять.
sd Transmiting Sender : Telephone
Inbound : Transmitter
Outbound : Transmitter
Receiver : Telephone
VoiceData (Data) VoiceData (Data) VoiceData (Data)
197
198
В данной спецификации эти протоколы пришлось продублировать для вариантов разных сочетаний объектов A и B и их возможных ролей. Теперь рассмотрим следующие два вопроса: если объекты A и B – различных типов, то как избежать дублирования описания общего для них поведения? если объекты A и B одинакового типа, то как описать композицию поведения объектов этого типа? С использованием понятия ролей эти вопросы могут быть переформулированы следующим образом: как для объектов различных типов, которые могут выполнять в некотором контексте одну и ту же роль, описывать включение поведения в этой роли в их суммарное поведение и строить такой результат? как для объектов одного типа, но выполняющих разные роли, описывать композицию поведений в них и строить результат этой композиции? При успешном решении этих задач выделение понятия роли в сценариях позволит избежать дублирований при их описании. Случаи осмысленного выделения ролей, соответствуют ситуациям, когда в описаниях сценариев имеются: объекты одного типа, участвующие в одном и том же взаимодействии различным образом (т.е. в различных ролях); объекты разных типов, которые в некоторых взаимодействиях могут вести себя одинаково (т.е. выполнять одну и ту же роль).
3. Формализация понятия роли Этот раздел посвящен уточнению понятия роли для последующего использования этого понятия в моделях. Сначала мы рассмотрим свойства ролей, рассматривающиеся в литературе, а затем обозначим контекст использования нами этого понятия в сценариях взаимодействий.
Рис. 2. Спецификация модельной системы обмена данными
3.1. Свойства ролей
Рис. 3 Взаимодействия в системе обмена данными. 199
В литературе понятие роли используется преимущественно в контексте моделей данных и в архитектурных моделях. В таких работах исследуются средства структурной композиции свойств объекта из свойств его ролей; нас же будет интересовать, прежде всего, объединение функциональности, соответствующей поведению в различных ролях. Тем не менее, сами свойства ролей, как в структурном, так и в поведенческом аспектах сходны. Основываясь на обзоре, произведенном в [5], отметим следующие свойства ролей: констектуальность: роли имеют смысл только в контексте некоторого взаимодействия; 200
объекто-зависимость: экземпляры роли не существуют без объектов; множественность: объект может играть несколько ролей одновременно (в частности, несколько экземпляров одной и той же роли); иерархичность: роли могут образовывать иерархии ролей. Эти свойства ролей прослеживаются и в сценариях. Подчеркнем различие между классами и ролями: классы задают свойства отдельных объектов; роли задают позицию и ответственность (responsibility) объекта в рамках некоторой системы или подсистемы. Удобно различать понятие роли и экземпляра роли – по аналогии с различением понятий класса и экземпляра класса. Под существованием во время выполнения (run-time) экземпляра роли мы будем понимать факт выполнения экземпляром агента или компонента системы данной роли в процессе некоторого взаимодействия. В процессе функционирования системы каждый из ее объектов выполняет (или играет) некоторую роль (возможно, несколько ролей одновременно); таким образом, можно говорить о существовании экземпляров ролей (одного или нескольких, если объект выполняет сразу несколько ролей одновременно), соответствующих объекту в каждый момент времени. В случае отсутствия явного выделения роли объекта можно говорить о выполнении им некоторой анонимной роли. Метамодель, иллюстрирующая эти соотношения, может быть описана диаграммой, представленной на Рис. 4.
Абстракция роли достаточно исследована с точки зрения ее структурной реализации, например, модели данных, использующие роли [6], связь понятия роли и интерфейса [7], шаблоны реализации ролей [8]; однако с точки зрения объединения поведения в различных сценариях роли практически не исследуются. Произведем краткий обзор шаблонов для представления ролей, рассматриваемых в работе [8]: объединение в единый тип для ролей (single role type) – все особенности каждой из ролей объединяются в один общий тип; использование отдельного типа для каждой роли (separate role type) – каждая роль трактуется как отдельный тип; использование ролевого объекта (role object) – особенности, присущие роли, объединяются в специальный объект; основной объект является хостом (host object), объединяющим несколько таких ролевых объектов; при обращениях внешних объектов он использует нужный из ролевых объектов в зависимости от контекста; использование ролевого отношения (role relationship) – специальный объект, объединяющий особенности данной роли, моделирует связь основного объекта в данной роли с некоторым внешним объектом. Структурировать описание поведения можно различным образом; при рассмотрении поведений, соответствующих различным ролям в рамках некоторой архитектурной ролевой модели, можно достичь соответствия между ролями в их структурном и поведенческом понимании.
Рис. 5. Фрагмент упрощенной модели взаимодействий UML
3.2. Роли в сценариях взаимодействия Говоря об описании взаимодействий в системе, можно предложить следующее определение роли: роль – это абстракция агента или компонента системы в рамках его ответственностей в некотором взаимодействии. По сути, в результате такого абстрагирования, мы получаем некоторый срез поведения
Рис. 4. Метамодель, описывающая соотношения между ролями, классами и их экземплярами 201
202
объекта. Ответственности участников взаимодействия определяются логикой описываемого сценария. В модели взаимодействий UML при описании некоторого сценария каждой роли естественно сопоставлять оси с одинаковым именем на различных диаграммах. В контексте некоторого упрощения метамодели взаимодействий UML (Рис. 5) взаимосвязь ролей с моделью взаимодействий можно проиллюстрировать диаграммой, представленной на Рис. 6.
Рис. 6. Роли в метамодели взаимодействий
3.3. Взаимосвязь с аспектами Использование ролей близко соотносится с аспектно-ориентированным программированием. Под аспектом понимается некоторый срез системы, который объединяет функциональность ее различных частей, соответствующую некоторому классу задач. Таким образом, отчетливо видна связь между описанием сценариев системы и ее аспектами поведения, так как по существу каждый сценарий является описанием какого-либо аспекта системы; при этом каждому аспекту соответствует некоторый набор ролей – эта связь отражена на диаграмме, представленной на Рис. 7.
Рис. 7. Взаимосвязь ролей и аспектов с взаимодействиями UML Исследования взаимосвязей в использовании ролей и аспектов можно найти, например, в [9] и [10].
4. Композиция поведения в разных ролях В данном разделе будут рассматриваться подходы, позволяющие решать задачу описания построения композиции поведения в различных ролях. Для этого будут рассмотрены некоторые расширения семантики диаграмм 203
взаимодействия. Прежде чем перейти к рассмотрению расширений, уточним семантику обзорных диаграмм взаимодействия.
4.1. Уточнение семантики композиции Каждая диаграмма последовательностей естественным образом предполагает наличие постоянного контекста описываемого взаимодействия, т.е. в ней описывается своего рода транзакция взаимодействия объектов. Набор осей задает классы объектов и их возможные роли, которые они могут выполнять. В дальнейшем под обзорными диаграммами взаимодействия, мы понимаем соответствующие диаграммы UML (UML Interactions Overview Diagrams), им родственна нотация высокоуровневых диаграмм MSC (High-level MSC, HMSC). Обзорные диаграммы взаимодействия декларируются в спецификации UML как вариант диаграмм активностей, однако же это отражено лишь в нотации. Спецификация UML определяет семантику этих диаграмм декларативно; для целей сравнения поведений имеет смысл определить их исполняемую семантику – как вариант семантики диаграмм активности. Семантика же диаграмм активностей определяется в UML в стиле семантики сетей Петри, т.е. через поток маркеров (tokens) по графу, состоящему из мест, переходов и дуг, их соединяющих. В рассматриваемом случае этим местам соответствуют активности, каждая из которых описывается диаграммой взаимодействия, а переходам соответствуют условные и параллельные ветвления потока маркеров. Для определения поведения сопоставим каждому объекту набор маркеров. Каждому маркеру ставится в соответствие экземпляр некоторой роли, выполняемой объектом; понятия маркера и экземпляра роли, таким образом, можно в определенном смысле отождествлять. Нахождение маркера для экземпляра роли в некоторой активности, означает, что объект, которому соответствует этот экземпляр роли, участвует во взаимодействии, описываемом сопоставленной этой активности диаграммой взаимодействия. Состояние объекта определяется набором ролей, которые он исполняет, т.е. соответ-ствующим множеством экземпляров ролей и их состояниями. Состояние всей системы определяется текущей разметкой диаграмм маркерами. Возможность активации перехода для маркера означает, что для объекта, соответствующего этому маркеру одновременно: в активности, в которой он находится, выполнены все взаимодействия; в диаграмме, в которую ведет переход, выполнены предусловия, задаваемые первым событием, находящимся на оси, которая соответствуюет данному объекту. Предусловие зависит от вида такого события следующим образом: для проверки условного выражения предусловие перехода соответствует этому выражению; 204
для приема сообщения предусловие заключается в наличии такого сообщения во входной очереди сообщений объекта; для других видов событий (так называемых, активных событий) предусловие соответствует тождественно истинному выражению, и переход может быть произведен (активирован) в любом случае. При активации перехода происходит перемещение маркера: маркер, соответствующий данному объекту, убирается из активности, в которой он находился, а в активность, в которую ведет сработавший переход, помещается маркер, соответствующий той роли, которую объект будет в ней выполнять. В случае перехода с распараллеливанием маркеры помещаются во все активности, в которые ведут ветви перехода. Для перехода, объединяющего параллельные ветви, условиями его срабатывания являются наличия маркеров, соответствующих одному и тому же объекту. Отметим, что такое описание композиции поведения объектов сходно с использованием сетей Петри для описания композиции ролей, предложенном в [11], где рассматривалась программная среда для разработки приложений, основанных на агентной архитектуре. Рассмотрим теперь следующие методы описания композиции поведения в различных ролях: использование специальных меток для «склейки» поведения; использование параметризуемых по осям диаграмм; задание потоков объектов на обзорных диаграммах взаимодействий.
4.2. Композиция через конструкции «продолжения» Спецификация UML предлагает дополнительный способ композиции поведения через конструкцию продолжения (continuation) осей. Семантика таких конструкций носит характер «склейки» поведения по меткам: после фрагмента взаимодействий, заканчивающегося конструкцией продолжения с именем “X”, допустимо продолжение по тем ветвям следующего фрагмента взаимодействий, которые начинаются с конструкции продолжения с тем же именем “X”. Это может быть проиллюстрировано примером, взятым из спецификации UML: диаграмма Continue со ссылкой на диаграмму Question (Рис. 8) эквивалентна диаграмме, представленной на Рис. 9.
sd Question :A
:B ask
Рис. 9. Пример из спецификации UML: результат композиции с помощью конструкции «продолжения»
alt DoSth
Для целей композиции поведения объектов в различных ролях разрешим использование конструкции продолжения не только в варианте, когда она покрывает все оси объектов, участвующих во фрагменте взаимодействия. Кроме того, дополнительно будем использовать следующее правило композиции: на диаграмме, следующей за данной диаграммой, в качестве возможных осей, которые соответствуют продолжению описания поведения, задаваемого осью на данной диаграмме взаимодействий (следование задается переходом между диаграммами на обзорной диаграмме), рассматриваются оси с тем же типом, роль же может быть другой. Естественно, что в качестве продолжения при исполнении выбирается лишь одна ось, в соответствии с выполнением предусловий для данного объекта.
OK nonono notOK
Рис. 8. Пример из спецификации UML: композиция с помощью конструкции «продолжения» 205
206
Тогда можно рассмотреть спецификацию системы обмена данными, представленную на Рис. 10, которая будет эквивалентна исходной (Рис. 2), в предположении, что объекты A и B в исходном случае – это узлы некоторого одного типа Node.
взаимодействия, вследствие необходимости отслеживать имена меток конструкций продолжения. Следует отметить, что в этой спецификации есть некоторая неопределенность, связанная с тем, что в самом начале взаимодействия не задан способ выбора роли (Sender или Receiver) объектом типа Node. Эта неопределенность может приводить в данном случае к тупиковым ситуациям в функционировании системы, когда объекты совместно начинают выполнять роль Receiver, в результате чего бесконечно ожидают приема сигнала.
sd Calc_f1_f2
ref
Calc_f1
ref
Calc_f2
Рис. 10. Спецификация модельной системы обмена данными с помощью конструкций «продолжения» Данная спецификация задает поведение объектов типа Node. Каждый вариант взаимодействия в этой спецификации описан один раз для своего набора ролей, однако несколько утеряна наглядность, присущая диаграммам 207
Рис. 11. Спецификация системы вычислителя с несколькими терминалами 208
Такое расширение композиции через конструкции продолжения дополнительно обеспечивает следующую заслуживающую внимания возможность. Рассмотрим систему, состоящую из вычислителя (Calculator), который может вычислять функции f1 и f2 и терминалов (Terminal), обращающихся к нему за вычислением этих функций. Пусть терминал должен запрашивать вычисление функций, чередуя f1 и f2, а вычислитель должен обладать возможностью производить вычисления функций в любом порядке. Поведение этой системы может быть описано с помощью диаграмм, представленных на Рис. 11. Тогда в соответствии с правилом композиции описанное здесь поведение для терминала может быть проиллюстрировано обзорной диаграммой взаимодействий, изображенной на Рис. 12.
Будем считать параметризацию осей возможной. Тогда, используя ее, рассматриваемую модель обмена данными можно описать с помощью диаграмм, представленных на Рис. 13. sd Transfer (Sender, Receiver) Sender
Receiver data
sd Termination (Initiator, Partner) Initiator
Partner stop
sd Calc_f1_f2_for_Terminal
ref
ref
Calc_f1
Calc_f2
Рис. 12. Поведение вычислителя В рассмотренном варианте задания композиции элементы, собственно специфицирующие эту композицию, оказываются инкапсулированными внутрь описания отдельного взаимодействия. Это приводит к снижению наглядности описания системы. Помимо этого, использование такой нотации привносит некоторую автоматоподобную семантику описания, причем в рамках каждого отдельного объекта системы, что не слишком соответствует логике нотации моделей взаимодействий и увеличивает вероятность создания внутренне противоречивых моделей.
4.3. Композиция через параметризацию диаграмм по осям Рассмотрим альтернативный вариант композиции, в котором используется параметризация диаграмм. Спецификация UML не говорит явно о возможности параметризации имен осей, однако заметим, что стандарт MSC позволяет задавать имена осей в качестве параметров MSC-диаграммы. 209
Рис. 13. Спецификация модельной системы обмена данными с помощью параметризации диаграмм по осям Можно рассматривать статическую и динамическую семантику параметризации. Для статического случая результат соответствует макроподстановке диаграммы вместо ссылки на нее; динамическому случаю соответствует вызов поведения некоторой подсистемы. Связывание фактического параметра, соответствующего некоторому объекту определенного типа, и формального параметра, соответствующего роли в данном взаимодействии, означает вовлечение (acquirement) этого объекта в данную роль. В зависимости от семантики параметризации можно говорить либо о статическом «обладании» объектом своими ролями, либо о 210
динамическом «принятии» в роли; это согласуется с различиями архитектурного моделирования ролей. Заметим, что в данном случае пришлось продублировать описания ссылок на описания протоколов; вместо этого можно попытаться явно описывать задающие значения параметров потоков объектов между диаграммами.
4.4. Композиция через задание потоков объектов Снимем некоторые ограничения обзорных диаграмм взаимодействия, приблизив их к диаграммам активности. Рассмотрим возможность ограничения на потоки объектов между диаграммами путем задания их типа. Для этого мы будем использовать символы объектов на дугах диаграмм. Мы будем считать, что такой переход может сработать для маркера, если он поставлен в соответствие объекту, имеющему тип, который соответствует типу, указанному на этом переходе (в качестве вариантов можно рассматривать как строгое соответствие, так и соответствие какому-либо более общему типу). Для переходов с отсутствием такой спецификации предполагается, что они могут срабатывать для маркеров любого типа. По смыслу, такое описание (рис. 14) сходно с параметризацией, но с явным указанием входных и выходных потоков фактических параметров. Применение таких потоков позволило избежать дублирования ссылок, но при этом сделало более сложным само описание композиции.
5. Отображение в другие модели
Рис. 14. Спецификация модельной системы обмена данными с помощью явного задания потоков объектов 211
Необходимость композиции поведения объектов в различных ролях приводит к еще одному источнику нежелательного поведения системы, не отраженного в сценарной модели; мы столкнулись с одним из таких примеров в предыдущем разделе. Как говорилось во введении, аппроксимация поведения модели взаимодействия другими моделями может помочь выявить пробелы в понимании требований к системе. Ниже мы рассмотрим возможность отображения модели последовательностей в две другие модели UML, позволяющие описывать поведение системы – это автоматные модели (машины состояний) и активности. Модель машины состояний хорошо подходит для описания поведения отдельного объекта. Для описания систем, состоящих из некоторого набора взаимодействующих объектов, используются модели, представляющих собой набор таких машин состояний, каждая их которых дает описание поведение отдельного объекта системы. При этом предполагается, что существует некоторое окружение (среда поддержки), которое отвечает за передачу сообщений между экземплярами таких машин. Это окружение относится уже к платформо-зависимому уровню системы, сами же машины достаточно просто отображаются в код, поэтому такие модели широко используются для прототипирования. С другой стороны, в таких моделях утеряны явные связи между отдельными объектами системы, модели же активностей, моделирующие как потоки управления, так и потоки данных в системе, позволяют исследовать поведение системы не покомпонентно, а в целом. Поэтому интерес представляет отображение в обе эти модели.
212
5.1. Отображение в модель машин состояний Для спецификации поведения объектов системы применяются подходы, основанные на событийных автоматах; в таком случае поведение каждого компонента системы задается расширенным конечным автоматом. Событийные автоматы довольно легко отображаются в код на целевых языках; кроме того, существуют такие распространенные нотации как SDL [12] и машины состояний UML, в которых используется семантика автоматов. Под машинами состояний (State Machines) мы понимаем расширенные конечные автоматы в алфавите событий, возможных в системе. Для этих автоматов должны выполняться некоторые дополнительные требования, которые заключаются в выделении некоторых типов состояний автомата, различающихся видами событий, по которым возможны переходы из этих состояний. Эти виды следующие: активные состояния, из которых возможен только один переход, происходящий по активному событию (выполнение действия (Action), посылка сигнала и т.д.); состояния проверки условия, из которых возможны переходы по проверке условий; состояния ожидания получения сообщения; из них возможны переходы по событиям приема сообщений. Состояния ожидания автоматов соответствуют собственно состояниям машин, остальные состояния автоматов соответствуют так называемым псевдосостояниям.
5.1.1. Алгоритм построения машин состояний по диаграммам последовательностей Сделаем обзор алгоритма построения машины состояния, работающего в случае анонимных ролей: синтаксический разбор (parsing); построение графов, соответствующих диаграммам (ordering/linking); объединение поведений, заданных диаграмма (placement/integration); построение срезов (slicing); построение автоматов, их преобразование в детерминированные автоматы, трансформация в машины состояний (automata generation & minimization); кодогенерация (codegeneration). Кратко опишем эти фазы. Фазы синтаксического разбора и построения графов диаграмм зависят от синтаксиса их записи. В результате для каждой диаграммы обзора взаимодействий строится внутреннее представление графа, который состоит из диаграмм и связей между ними, задающих их последовательность. Для диаграмм последовательностей строятся события (и так называемые 213
псевдособытия) и связи, задающие их последовательность на данной оси. Для inline-конструкций и ссылок на другие диаграммы строятся псевдособытия. Также строятся связи между множествами событий (и псевдособытий) на разных осях, если они взаимосвязаны – т.е. для пар событий посылки-приема сообщения, псевдособытий, соответствующих началам и концам ссылок на другие диаграммы, началам и окончаниям inline-конструкций. Для объединения поведений в соответствии с графом использования (через ссылки) диаграммами друг друга выбирается диаграмма самого верхнего уровня (считается недопустимым случай рекурсивных ссылок диаграмм друг на друга), и, начиная с этой диаграммы, рекурсивно применяется подстановка поведения используемых в ней диаграмм. После этого производится объединение поведений, заданных различными диаграммами: для каждой пары следования фрагментов взаимодействия происходит построение связей между псевдособытием «конца» каждой оси из предшествующего фрагмента с псевдособытием «начала» оси последующего фрагмента. По срезам модели, построенным для каждого объекта системы взаимодействий, строятся автоматы: каждое событие среза отображается в переход автомата по этому событию, а каждая связь между событиями на одной оси – в состояние автомата. Для псевдособытий строятся пустые переходы. После этого производится преобразование автоматов в эквивалентные детерминированные автоматы, после чего полученные автоматы структуризируются в соответствии с требованиями машин состояний. Таким образом, в полученной машине состояний событиям соответствуют те элементы диаграмм взаимодействия, которые носят конструктивную семантику – т.е. посылка и прием сообщения, выполнение действия, порождение объекта. Элементам, семантика которых носит характер ограничений, как, например, конструкции временных ограничений, инварианты, соответствуют некоторые атрибуты событий, задающие условия, невыполнение которых означает динамическую ошибку. Описанный алгоритм реализован для MSC-диаграмм в синтезаторе Bridge, разработанном в Институте системного программирования РАН совместно с компанией Klocwork. По построенным автоматам может генерироваться модель системы на языке SDL (очень близком к протокольным машинам состояний UML), прототип системы на языках Си/C++ или Java, тесты для системы на языках TTCN и PPL. Для рассмотренного в начале примера спецификации системы обмена данными будут сгенерированы (с точностью до имен состояний) автоматы, изображенные в форме машин состояний на Рис. 15. Степень аппроксимации исходных сценариев автоматными моделями, построенными таким образом, обсуждается в работе [13]. 214
Рассматриваемые расширения алгоритма синтеза касаются фаз подстановки и построения срезов.
5.1.3. Структуризация поведения по ролям Построение структурированной модели машин состояний делает их более масштабируемыми, что расширяет возможности дальнейшего применения получаемых моделей. Наличие взаимосвязи между структурой этой модели и структурой исходной модели взаимодействий дает возможность более тесно сочетать использование обеих этих моделей при описании поведения системы, что позволяет рассматривать поведение системы с разных точек зрения. Далее мы рассматриваем вариант расширения алгоритма синтеза, заключающийся в структуризации поведения по ролям, т.е. генерации иерархической структуры, состоящей из машин состояний, в которых происходит вызов других машин состояний (т.е. подмашин) в соответствии с использованием ролей. Для этого вместо подстановки поведения, соответствующего роли, для каждой роли генерируется конструкция вызова подмашины, которая состоит из: переходов, соответствующих событиям, которые происходят в данной роли; состояний, все переходы в которые происходят по событиям, происходящим в данной роли. Для рассматриваемого примера обмена данными такие подмашины весьма тривиальны (Рис. 16). ReceiveProtocol
SendProtocol
Рис. 15. Машины состояний для объектов модельной системы обмена данными.
data
data
5.1.2. Расширение алгоритма синтеза при использовании средств для композиции ролей В настоящее время для приведенного алгоритма в синтезаторе Bridge реализовано расширение, заключающееся в возможности использования параметризованных ссылок; в том числе, параметризованы могут быть и оси диаграммы. Для этого на фазе интеграции поведений в момент подстановки копии диаграммы, на которую идет ссылка, производится замена формальных параметров на фактические; по существу, это соответствует выполнению параметризованной макроподстановки. Для возможности построения композиции с помощью конструкций продолжения требуется некоторое расширение алгоритма, позволяющее производить такую «склейку» осей. 215
Рис. 16. Подмашины состояний, соответствующие поведению объектов в различных ролях 216
Другими словами, каждая подмашина состоит просто из отрезка перехода между состояниями по событию принятия или посылки соответствующего сигнала. В данном случае производить такую инкапсуляцию не слишком целесообразно. Тем не менее, если протокол пересылки носит более сложную форму, то такая группировка может быть гораздо более значимой. Конкретное представление такой иерархии можно рассмотреть в контексте задачи генерации прототипа архитектурной модели системы. Существуют различные способы структурной реализации ролей. Тривиальный способ заключается в простой комбинации всех поведений; простая подстановка именно этому способу и соответствует. В качестве менее тривиального способа структурной реализации ролей можно рассмотреть шаблон, соответствующий использованию ролевого объекта в работе [5] (Рис. 17).
Рис. 17. Шаблон «ролевой объект» для представления ролей в структуре системы
Рис. 18. Машины состояний для объектов модельной системы обмена данными, структурированные по ролям
Если в системе существует поведение, связанное с некоторой ролью, то для такой роли заводится интерфейс Role и реализация этого поведения – Role_Impl. Если объекты типа Class могут выполнять эту роль, то такой класс должен быть унаследован от интерфейса Role, и он должен агрегировать объект типа Role_Impl, который по своей сути соответствует экземпляру роли. Поведение же, соответствующее данной роли, должно делегироваться в Class из Role_Impl. Для примера с передачей данных, если считать типы объектов A и B разными, структура системы будет описываться диаграммой на Рис. 18. Этот пример показывает, что по описанию взаимодействий может быть автоматически построен и прототип архитектурной модели системы, структура которого соответствует логике исходных сценариев.
217
5.2. Отображение в модель активностей Диаграммы активностей, используя семантику, сходную с семантикой сетей Петри, хорошо описывают работу системы в целом, так как явным образом моделируют и передачу сообщений. Построение моделей активностей UML по сценариям, описанным с помощью моделей взаимодействий, могло бы позволить производить статическую проверку требований к системе. Соображения о возможности построения отображения из моделей взаимодействий UML в модели активностей основываются на возможности использования для обзорных диаграмм взаимодействия семантики в стиле сетей Петри, а также возможности отображения отдельных взаимодействий в активности, что продемонстрировано на Рис. 19.
218
Отметим, что существуют работы, в которых рассматриваемся формализация исполняемой семантики взаимодействий через сети Петри – см. [14]. Результат фазы интеграции в алгоритме построения машин состояний, описанном в предыдущем разделе, практически соответствует диаграмме активности и может быть отображен в нее. Можно рассматривать вопрос и о структурировании диаграмм активностей в соответствии с имеющимися сценариями и общими в них ролями. Также возможно отображение машин состояний в диаграммы активностей [15]. Можно сделать предположение, что существо различия между моделью активностей, построенной по UML-взаимодействиям, и моделью активностей, построенной по машинам состояний, которые сгенерированы их тех же UMLвзаимодействий, заключается в том, что моделирование передачи сообщений в первом случае происходит отдельными потоками сообщений между активностями посылки и приема, а во втором представляет собой некоторый общий поток, соответствующий каналу передачи. В этом случае модель активностей потенциально может являться некоторым базисом для сравнения поведений.
Выполнение действия:
Пересылка сообщения:
6. Заключение В статье было продемонстрировано, что, в отличие от традиционной интерпретации, каждое описание взаимодействий может трактоваться как ориентированное не на собственно объекты, а на роли, выполняемые ими в данном взаимодействии. Для более широкой применимости моделей взаимодействий и более формального их использования можно использовать рассмотренный аппарат для оперирования этими ролями. В данной работе показано, как можно адаптировать имеющуюся нотацию UML для описания ролей, прежде всего, в плане задания их композиции. Для этого было рассмотрено использование конструкций продолжения и расширение возможностей параметризации UML-взаимодействий, однако у каждого из этих способов имеются свои недостатки. Следует отметить, что композиция через конструкции продолжения ближе к машинам состояний, так как семантика этих конструкций близка семантике к переходам на метку; композиция же через параметризацию – ближе по смыслу к семантике UMLактивностей, так как она тем или иным образом задает потоки объектов. В качестве модели для абстрактного выполнения были рассмотрены машины состояний UML, которые удобны для целей симуляции и генерации прототипа компонентов системы. Было рассмотрено расширение алгоритма синтеза машин состояний по UML-взаимодействиям на случай использования композиции различных ролей, и указаны те из этих расширений, которые поддерживаются разрабатываемым инструментом Bridge, позволяющим автоматически производить подобный синтез. Кроме этого, была обозначена возможность отображения модели UML-взаимодействий в модель
Создание объекта:
Рис. 19. Отображение взаимодействий в активности 219
220
активностей, которая удобна для анализа поведения системы в целом. Анализ моделей, поведение которых дает аппроксимацию поведения, задаваемого описаниями требований к системе в виде моделей взаимодействий, обеспечивает возможность выявлять пробелы в понимании требований к системе. Другим результатом экспериментов с новой нотацией является вывод о том, что при композиции различных ролей, как и вообще при композиции сценариев, могут возникать неопределенности. В статье был приведен типичный пример возникновения неопределенности с выбором роли, в результате которого у системы может существовать поведение, приводящее к тупиковой ситуации. Еще одним следствием использования ролей при использовании для описания сценариев модели взаимодействий является достижение взаимосвязи с аспектно-ориентированным программированием. В качестве развития данной тематики имеет смысл рассмотреть следующие задачи: описание преобразований UML-взаимодействий в UML-машины состояний в терминах метамодели языка UML или некоторого ее упрощения; строгая формализация исполняемой семантики композиции UMLвзаимодействий через диаграммы обзора взаимодействий; исследование ситуаций возникновения неопределенностей, связанных с композицией ролей, на основе расширении алгоритма синтеза машин состояний по взаимодействиям UML; исследование возможности генерации модели активностей UML по моделям последовательностей и их взаимосвязь с исходными моделями и с моделями машин состояний, полученными по тем же моделям взаимодействий.
7. F. Steimann. Role = Interface: a merger of concepts. Journal of Object-Oriented Programming 14:4, 2001 8. M. Fowler. Dealing with Roles. Working Draft (http://martinfowler.com/apsupp/roles.pdf), July 1997. 9. E. A. Kendall. Aspect-Oriented Programming for Role Models. Lecture Notes Computer Science, Vol. 1743, 1999. 10. G. Georg, R. B. France. UML Aspect Specification Using Role Models. OOIS 2002: Object-Oriented Information Systems, 8th International Conference, Montpellier, France, 2002. 11. M. Becht, T. Gurzki, J. Klarmann, M. Muscholl. ROPE: Role Oriented Programming Environment for Multiagent Systems. Proceedings of the 4th IFCIS Conference on Cooperative Information Systems (CoopIS'99), Edinburgh, Scotland, September 1999. 12. ITU-T Recommendation Z.100 System Description and Definition Language (SDL2000). International Telecommunication Union (ITU), Geneva, 1999. 13. N. Mansurov, D. Vasura. Approximation of (H)MSC semantics by Event Automata. SAM'2000 workshop, Grenoble, France, 2000. 14. St. Heymer. A Semantics for MSC Based on Petri-Net Components. SAM 2000, 2nd Workshop on SDL and MSC, Col de Porte, Grenoble, France, 2000. 15. Z. Hu, S. M. Shatz. Mapping UML Diagrams to a Petri Net Notation for System Simulation. Proceedings of the 16th International Conference on Software Engineering & Knowledge Engineering (SEKE'2004), Banff, Alberta, Canada, 2004.
Литературы 1. UML 2.0 Superstructure Specification FTF. October 8, 2004, OMG Document: ptc/0410-02 (convenience document). 2. B. Bollig, M. Leucker, T. Noll. Generalised Regular MSC Languages. Proceedings of the 5th International Conference on Foundations of Software Science and Computation Structures (FOSSACS '02), Grenoble, France, 2002. 3. B. Jonsson, G. Padilla. An Execution Semantics for MSC-2000. SDL 2001: Meeting UML, 10th International SDL Forum Copenhagen, Denmark, 2001. 4. ITU Recommendation Z.120 Message Sequence Charts (MSC-2000). International Telecommunication Union (ITU), Geneva, 1999. 5. F. Steimann. On the Representation of Roles in Object-Oriented and Conceptual Modelling. Data & Knowledge, Volume 35, Issue 1, October 2000. 6. R. K. Wong, H. L. Chau, F. H. Lochovsky. A Data Model and Semantics of Objects with Dynamic Roles. Proceedings of the Thirteenth International Conference on Data Engineering table of contents, 1997
221
222
Применение методов выявления закономерностей для классификации химических соединений Г.Т. Маракаева Аннотация. Целью статьи является постановка задачи классификации неизвестных химических соединений. С помощью классификации выявляются признаки, характеризующие группу, к которой принадлежит тот или иной объект. Это делается посредством анализа уже классифицированных объектов и формулирования некоторого набора правил. Целью различных алгоритмов классификации является построение классификационной модели, называемой классификатором, которая будет предсказывать класс для заданного примера на основании имеющихся значений атрибутов. Как правило, классификация рассматривается, как задача Data Mining, что по-русски язык означает “обнаружение знаний в базах данных”, “выявление закономерностей”. В рамках исследования задачи совместно с экспертами предметной области были проанализированы возможности формализации экспертных знаний в системе. Кроме того, были изучены формат данных и значения атрибутов химических соединений в нескольких лабораториях. Результатами этих работ стали выводы о возможности применения различных алгоритмов классификации для определения классов химических соединений. Эти выводы являются предметом следующей статьи.
1. Введение Целью статьи является постановка задачи классификации химических соединений. Работа лаборатории заключается в проведении экспериментов над различными пробами по определению их свойств (например, содержание серы, вязкость и т.д.), а также по определению класса пробы (например, питьевая вода, природная вода и т.д.). Каждый полученный результат фиксируется в лабораторном журнале. В данном случае ключом записи является идентификационный номер пробы, значения ее параметров и класс пробы. После некоторого времени в лаборатории накапливается достаточно большое число записей о пробах, содержащих значения параметров и класс. Каждая проба может исследоваться, во-первых, на значение атрибутов, а во-вторых, на принадлежность к одному из классов химических проб. Задача классификации состоит в определении класса новой пробы по неполному набору значений атрибутов. В лабораторию поступает новая проба 223
неизвестного класса. Задача сотрудника – определить класс пробы. В общем случае сотрудник должен по внешним факторам экспертно определить возможный класс и выполнить полный набор экспериментов, призванных подтвердить или опровергнуть его гипотезу. Если гипотеза не подтверждается, то проводится следующая серия экспериментов для исследования другой гипотезы. Среди экспертов химической области наблюдается больший интерес к использованию информационных систем в своей работе. С привлечением все большего числа специалистов будут накапливаться базы информации об исследуемых соединениях, пробах и т.д. и, следовательно, возникнет необходимость анализа этих данных. Так как описание проб с помощью признаков является унифицированным для всех экспертов, то накапливаемая база может использоваться повсеместно. Классификация очень часто рассматривается, как один из способов выявления закономерностей в большом объеме данных (Data Mining) [1, c. 19]. Под понятием «выявления закономерностей» понимается итеративный автоматический или ручной процесс изучения данных. [2, c.2]. Выявление закономерностей делятся на следующие категории [2, c.2;1, с.19]. Классификация (classification) – c помощью классификации выявляются признаки, характеризующие группу, к которой принадлежит тот или иной объект. Это делается посредством анализа уже классифицированных объектов и формулирования некоторого набора правил [1, c.20]. Целью различных алгоритмов классификации является построение классификационной модели [4, c.29] называемой классификатором, которая будет предсказывать класс для заданного образца на основании имеющихся значений атрибутов [2, с.139]. Другими словами, классификация – это процесс определения ярлыка с дискретным значением (класс) для непомеченной записи, а классификатор – это модель (результат классификации), которая предсказывает один атрибут – класс образца – если заданы некоторые другие атрибуты этого образца. [2, с. 139]. Кластеризация (clustering) – очень близка к определению классификации, за исключением того, что изначально классы предметной области не заданы Ассоциация (dependency model) – выявление правил взаимосвязей между атрибутами или между значениями атрибутов во всем множестве данных или в его подмножестве. Последовательность – выявление цепочек событий, связанных во времени. Прогнозирование (predictability) – изучение данных с целью прогнозирования реальных значений. 224
Определение изменений и отклонений (change and deviation detection) – исследование наиболее значимых отклонений во множестве данных. Основные термины, участвующие в постановке задачи приводятся в разделе 2. В этом разделе приводятся как формальные термины для задачи классификации, так и специфические понятия для заданной предметной области, а также их соответствие между собой. В разделе 3 приводится общая постановка задачи классификации, использующей вышеописанные термины. Раздел 4 содержит описание девяти алгоритмов классификации, наиболее часто встречающихся в литературе. В разделе 5 приведен подход к классификации пакетов и описаны наиболее распространенные системы. Раздел 6 содержит краткое описание выбранного алгоритма для классификации химических проб. В “Заключении” подведен итог работы и кратко описаны следующие статьи по данной тематике.
2. Терминология Введем некоторые термины: Признаком
назовем
пару
x имя, множество _ значений .
Множество
значений признака обозначим через Т. Множество значений может быть задано типом признака: целое или вещественное, перечисление или диапазон. Словарь признаков представляет собой множество всех признаков xi ni , Ti , i 1,..., N , где ni – имя, Тi – множество значений для признака xi. Пространство признаков представляет собой декартово произведение множеств значений признаков Ti , i=1,..,N: T1 T2 ... TN Объект
– представляет собой пару имя , ( x1 ,..., x N ) , где x1 ,..., x N –
точка в признаковом пространстве. Множество всех объектов обозначим через W. Множество W является конечным или счетным, и все объекты могут быть занумерованы. Пусть на множестве объектов W определено m классов 1 ,..., m . Для каждого класса существует характеристическая функция: 1, (1) 1,
Таким образом, класс представляет собой пару имя , , где χ – характеристическая функция, определенная формулой (1). Если для всех j=1,..,m заданы характеристические функции χj, то для каждого объекта W можно определить его класс j . Таким образом, множество χj определяет классы объектов. 225
Обучающая выборка – это множество объектов V 1 ,..., l1 , l1 1 ,..., l2 ,..., lm 1 ,..., lm , для которых известны включающие
их классы: 1 ,..., l1 1 , l1 1 ,..., l2 2 ,..., lm 1 ,..., lm m Помимо характеристических функций χj будем рассматривать функции fj(x,V), про которые известно, что fj(x,V)=-1, если x j , fj(x,V)=1, если x j . Такие функции будем называть разделяющими функциями. Если задана обучающая выборка, то по ней можно построить разделяющие функции. Классификация – это сопоставление каждому объекту определенного класса, то есть определение множества пар объект, класс Тестовый объект – объект с некоторым заданным набором значений признаков и неизвестным классом. Задача классификации. Заданы множество имен классов и обучающая выборка. Требуется построить разделяющие функции для этих классов. Далее следуют пояснения к терминам рассматриваемой предметной области и их соответствие вышеописанным терминам: Проба – некоторое количество (вообще говоря, неизвестного) химического соединения, достаточное для проведения экспериментов по выявлению количественных и качественных характеристик соединения, которые требуются для идентификации соответствующего химического соединения. В алгоритмах классификации проба – это объект предметной области. Идентификация химического соединения состоит в определении класса, которому принадлежит проба. Пробе соответствует объект. Атрибут пробы – какое-либо химическое или физическое свойство соединения. У каждого атрибута пробы имеется тип, описывающий его возможные значения. Атрибут пробы является признаком объекта. Обучающая выборка в терминах предметной области – это набор проб, для которых известны классы (вообще говоря, с некоторой степенью вероятности).
3. Постановка задачи классификации Для тестовой пробы, или, другими словами, тестового объекта класс неизвестен. Поэтому для программиста задача ставится как нахождение класса для тестового объекта по некоторым заданным значениям атрибутов этого объекта. Для нахождения класса строится система, другими словами, модель, или классификатор. Обучение системы происходит с помощью обучающей выборки, то есть множества объектов, для которых значения их классов достоверно известны. Кроме того, в систему должны вводиться экспертные знания о классификации проб с помощью шаблонов, то есть система должна аккумулировать множество экспертных шаблонов. Будет считаться, что все данные, используемые на стадии обучении системы, являются истинными. 226
У задачи классификации для химической предметной области имеется ряд важных особенностей, которые существенно влияют на выбор решения. Вопервых, данные для построения и обучения системы носят не только экспериментальный, но и экспертный характер. В системе необходимо учитывать экспертные шаблоны. Во-вторых, типы многих атрибутов являются числовыми, поэтому многие шаблоны задаются с диапазонами значений атрибутов, а не перечислением. Шаблоны же, полученные на стадии обучения, в предикатных условиях которых содержится знак равенства, должны какимлибо образом быть преобразованы в шаблоны, задающие диапазонные предикаты. В-третьих, множество классов является бесконечным. Поэтому система никогда не будет окончательно сформировавшейся, она всегда будет обучаться новыми пробами.
4. Обзор алгоритмов решения задач классификации Существует достаточно много различных алгоритмов классификации. Каждый из них может давать хорошие результаты для одного класса задач и плохие результаты для другого класса задач. Например, работа классификатора на основе нейронной сети будет давать хороший результат при достаточно большом объеме данных, на которых проводится обучение, и при наличии примерно одинакового числа тестовых объектов в каждом классе. Если же во входных данных находится очень много объектов одного класса, то результат работы сети будет часто склоняться именно к этому классу, независимо от значимости остальных объектов. Для оценки методов классификации можно использовать следующие критерии: точность и правильность прогнозирования – способность корректно определять класс для новых данных; скорость работы – параметр вычислительных затрат на использование модели; робастность – способность делать корректный прогноз при “зашумленных” входных данных или данных с неполным набором атрибутов; масштабируемость – способность поддерживать эффективность метода при увеличении объема подаваемой на вход информации; интерпретируемость – характеристика метода, отвечающая за уровень понимания и способность проникновения в суть. Ниже перечислены основные алгоритмы классификации, упоминаемые в литературе и применяемые в промышленных системах. Каждый из этих алгоритмов обладает определенными свойствами, позволяющими делать предположения об их применимости на конкретном классе задач. Кроме того, у каждого из алгоритмов может иметься множество разновидностей, как, например, в п. 4). Как правило, в литературе приводятся общие принципы к построению классификатора, тогда как для реализации 227
требуется подробный анализ предметной области и правильный подбор детализированных алгоритмов.
4.1. Статистический метод Статистические заключения – это наилучшая форма анализа данных, имеющих причинные связи. Теория статистических заключений состоит из методов, таких что на основе одного из них можно сделать заключение или обобщение о всем пространстве объектов. Такие методы могут быть разбиты на две основные группы: вычисления и проверка гипотез.
4.2. Дерево решений Дерево решений – это определенный вид дерева, в котором каждый внутренний узел представляет собой контрольный критерий для атрибута, каждая ветвь представляет собой результат теста, а лист – это класс или подкласс. Самый верхний узел дерева – корень. Для классификации нового образца используется тестирование значений атрибутов этого образца в узлах дерева. Дерево решений может быть легко трансформировано в классификационные правила. На рис. 1 представлен пример графического представления дерева решений: o
o Правило 3
Правило 2
x>3.75
o
нет X
Правило 1
o Правило 4
X
o 3
нет
да
x<6.5
XX
X X
o
y>
да
7
X
Правило 6 Правило 5
Ответ “о”
Ответ “о”
…
10
Рис. 1. Графические представления дерева решений
4.3. Алгоритмы обратного распространения (нейронные сети) Алгоритм обратного распространения является одним из наиболее популярных. Для него требуется задание набора параметров (например, топологии сети), определение которых лучше всего производить эмпирически. Нейронные сети критикуются за низкую способность к интерпретации. [3, с.27] Плюсами является высокая толерантность к зашумленным данным, а 228
также способность классифицировать данные, которые ранее не участвовали в обучающей выборке. Кроме того, уже разработано несколько алгоритмов, способных извлекать из нейронных сетей закономерности или правила.
4.4. Моделирование подмножества данных Примеры вариантов моделирования подмножества данных представлены на Рис. 2. o o oxx x xx o xx oo
o o oxx x xx o xx oo
o o oxx x xx o xx oo
o o oxx x xx o xx oo
пар “атрибут-значение”, а y – это класс. Правила, требующие минимальных предопределенных условий, называются повторяющимися. Правило, требующее минимальной специфики, называется точным. Метод ассоциативной классификации состоит из двух шагов. На первом шаге ищется набор всех вероятных правил для точных и повторяющихся правил. Используется итеративный метод, где знания от предыдущих шагов используются для упрощения поиска правила. На втором шаге для конструирования классификатора используется эвристический метод, в котором полученные правила располагаются в порядке значимости, основанной на их значениях точности и повторяемости. Алгоритм может требовать нескольких прогонок над множеством данных, в зависимости от длины самого длинного найденного правила. Когда классифицируется новый образец, для классификации используется первое найденное правило. Классификатор также содержит правило по умолчанию, имеющее наименьшую значимость и определяющее "типичный" класс для образца, класс которого не был рассмотрен в обучающем наборе данных. В третьем методе, называемом CAEP (Classification by Aggregating and Emerging Patterns), используются знания об объектах данных для получения “выявленных шаблонов”, на основе которых в дальнейшем конструируется классификатор. Алгоритм CAEP является достаточно хорошим (по параметру точности) по сравнению с другими алгоритмами. Он также показывает хороший результат, когда в наборе данных основной класс содержит мало тестовых наборов по сравнению с другими классами.
4.6. Классификация по k-ближайшему соседу
Рис. 2. Моделирование подмножества данных
4.5. Ассоциативные правила Существует несколько разновидностей классификаторов, основанных на ассоциативных правилах. Первый метод извлекает ассоциативные правила, основанные на кластеризации, и затем использует эти правила для классификации. В ARCS (Association Rule Clustering System) используется метод ассоциативных правил по формуле A(quan1) Λ A(quan2) => A(cat), где A(quan1) и A(quan2) – это тесты над множеством значений атрибутов (где значения определяются динамически), и A(cat) – символизирует класс для атрибутов, представленных в тестовых данных. Ассоциативные правила наносятся в двухмерную картинку. Алгоритм проходит по этой картинке в поисках правил, составляющих прямоугольные кластеры. Примыкающие наборы появляющихся числовых атрибутов могут быть скомбинированы в кластерные правила. Кластеризованные ассоциативные правила, полученные в результате работы алгоритма ARCS, могут применяться для классификации. Второй метод – ассоциативная классификация. Метод характеризуется правилами в форме condset => y, где condset – это множество наборов 229
Классификатор по ближайшему соседу основан на изучениях аналогий. Обучающая выборка описывается в n-мерном пространстве атрибутов. Каждый образец представляется в виде точки этого пространства. Если классификатору на вход дается неизвестный объект, то он ищет k ближайших к нему объектов из обучаемого множества. Степень близости определяется понятиями евклидового пространства. Для объектов X=(x1, x2,…,xn) и Y=(y1,y1,…,yn)d(X,Y) = √∑ (xi-yi)². Неизвестному объекту назначается класс, наиболее часто встречающийся среди k ближайших соседей. Такой алгоритм является “ленивым”, так как начинает строить классификатор, только когда дается неизвестный пример. В то же время, например, алгоритм с построением дерева, строит структуру независимо от того, надо классифицировать что-то или нет. Поэтому метод k-ближайших соседей может быть очень долгим для большого числа данных и большого заданного k. Для ускорения процесса можно вводить индексы.
4.7. Классификация, основанная на прецедентах В отличие от предыдущего алгоритма, где данные представлялись в виде точек Евклидова пространства, этот алгоритм преобразует данные или “случаи” как 230
сложные символические описания. Алгоритм используется для решения проблем, относящихся к обслуживанию клиентов, когда, например, случаи описывают диагностическую проблему, связанную с продуктом. Также он применяется в таких областях, как инженерия и право, где случаями являются технический проект или законы соответственно. Когда для классификации подается новый случай, сначала проверяется, не существует ли уже точно такая же ситуация (прецедент). Если подобная ситуация найдена, то в качестве решения выдается решение прецедента. Если не находится ни одного прецедента, то алгоритм пытается найти ситуации, в которых повторяются компоненты. Концептуально, эти обучающие ситуации могут рассматриваться как ближайшие соседи для новой ситуации. Если ситуации представляются в виде графа, то поиск ведется по подграфам, похожим на подграфы неизвестной ситуации. Затем алгоритм пытается сопоставить все решения “соседей”, чтобы выдать решение по новой ситуации. Если по какому-либо решению появляется несовместимость, то делается откат в поисках других решений. В этом алгоритме могут использоваться какие-либо дополнительные знания и выдавались правдоподобные стратегия решения задачи, чтобы комбинированные решения. Сложными задачами в данном алгоритме является поиск хорошей метрики для сравнения подграфов, разработка эффективной техники индексирования обучающих ситуаций и методы комбинирования решений.
4.8. Генетические алгоритмы. Обучение нейронной сети Подход генетических алгоритмов [3, с.27] является попыткой объединить идеи природной эволюции. Начальная популяция создается по случайным правилам. Каждое правило может быть представлено набором или строкой битов. В качестве простейшего примера можно рассмотреть тестовую выборку с двумя классами С1 и С2, представленную двумя атрибутами A1 и A2. Тогда правило “если А1 и не А2, то С2” может быть закодировано строкой “100”, где два левых бита представляют атрибуты А1 и А2, а правый – класс. Аналогично, правило “если не А1 и не А2, то С1” представляется в виде “001”. Вообще, если у атрибута имеется 2k различных значений, то для кодирования значения этого атрибута могут быть использованы k бит. Основываясь на правиле сохранения подходящих данных, новая популяция формируется из правил, подходящих для текущей популяции, а также “отпрысков” (объектов, порожденных этими правилами) этих правил. Обычно подходящие правила проверяются на пригодность на основе использования набора тестовых данных. “Отпрыски” создаются путем применения генетических операторов перехода и мутации. В переходе меняются местами подстроки из пары правил, образуя 231
новую пару правил. В мутации инвертируются (меняются местами) произвольно выбранные биты строки. Процесс формирования новой популяции, основанный на правилах предшествующих популяций, продолжается до тех пор, пока популяция P “развивается” и каждое правило из Р удовлетворяет предопределенным подходящим условиям. Генетический алгоритм легко распараллеливается и используется как для классификации, так и для решения задач оптимизации. При поиске закономерностей он может использоваться для оценки пригодности других алгоритмов.
4.9. Классификация для неточных множеств Теория неточных множеств может использоваться для классификации и выявления структурных связей в нестрогих или зашумленных данных. Этот подход применяется для атрибутов, имеющих дискретные значения. Если атрибуты принимают не дискретные значения, то они должны быть дискретизированы. Теория неточных множеств основана на установлении эквивалентных классов для заданной обучающей выборки. Все образцы множества, относящееся к эквивалентному классу, являются неразличимыми, т.е. эти образцы считаются идентичными, несмотря на наличие различий в значениях атрибутов. Для множества данных из реальной жизни часто оказывается, что классы не могут быть различаться в терминах возможных значений атрибутов. Неточные множества могут использоваться для неточных или “грубых” классов.
Класс С Верхняя аппроксимация С Нижняя аппроксимация С
Рис. 3. Графическое представление верхней и нижней аппроксимаций
232
Неточные множества могут использоваться для будущего восстановления (когда могут быть выявлены и удалены атрибуты, не способствующие классификации) и анализа (когда значения каждого атрибута определяются с учетом поставленной задачи). Задача нахождения минимального подмножества (предела) атрибутов, которые могут описать все концепции имеющегося множества, является NP-сложной. Пример. У классификаторов, основанных на правилах, имеется тот недостаток, что они строго разделяют непрерывные атрибуты. Рассмотрим, например, приложение для подтверждения потребительского кредита. Изначально правило говорит, что приложение подтверждает кредитоспособность клиентов, работающих более двух лет и обладающих достаточно высоким доходом (например, 50000 в год): Если {стаж_работы 2} & {доход 50000}, то кредит = “подтвержден”. В соответствии с этим правилом клиент получит кредит, если он работает более двух лет и получает, например, 50000, но никак не 49000. Такой строгий порог может выглядеть несправедливым. Нечеткая логика допускает более “мягкие” правила или границы. Вместо того, чтобы строго нарезать множества по категориям, нечеткая логика использует истинностные значения от 0.0 до 1.0 для представления степени принадлежности определенного значения данной категории. Таким образом, при использовании нечеткой логики мы можем пропустить и доход в 49000, но не с такой высокой степенью достоверности, с какой будет одобрен доход в 50000. Нечеткая логика применима для систем поиска закономерностей, выполняющих классификацию. Это позволяет использовать преимущества работы с высоким уровнем абстракции. В общем случае, использование нечеткой логики в системах, основанных на правилах, предполагает следующее. Значения атрибутов переводятся в нестрогую форму. Они разбиваются на дискретные категории: высокую, среднюю и низкую. Как правила, системы, основанные на нечеткой логике, сопровождаются графическими инструментами для пользователей, чтобы они могли задавать эти категории. Для нового объекта могут применяться несколько нестрогих правил. Каждое подходящее правило способствует нахождению этого объекта в нужной категории. Обычно истинностные значения для каждой предполагаемой категории суммируются. Суммы, полученные на предыдущем шаге, комбинируются в выходное значение. Этот процесс может проводиться путем взвешивания истинностных сумм для каждой категории путем умножения на среднюю величину правдивости категории.
233
Нестрогая логика используется в ряде систем классификации, например для здоровья, и финансовой сфере.
5. Существующие пакеты для классификации В химической инженерии развитые модели используются для описания реакций и взаимодействий различных химических процессов. Современные средства разрабатываются также для визуализации этих структур и процессов [2, c.336]. Одной из разработок, поддерживающей подобные функции, является Paviolion Technologies Process Insights, в которой комбинируются нейронные сети, нечеткая логика и статистические методы. Это приложение успешно используется Eastman Kodak и другими компаниями для разработок в химическом производстве и контроле приложений для уменьшения потерь, улучшения качества продукции и повышения производительности. Это показывает большой интерес экспертов химической области к использованию в своей работе информационных систем. С привлечением все большего числа специалистов будут накапливаться базы информации об исследуемых соединениях, пробах и т.д. и, следовательно, возникнет необходимость анализа этих данных. Поскольку описание проб с помощью признаков является унифицированным для всех экспертов, накапливаемая база может использоваться повсеместно. Для химической области можно провести некую аналогию с фармацевтикой. На сегодняшний день в медицинской и фармацевтической областях используется классификация. В фармацевтике накоплен огромный объем знаний, разработаны системы для предоставления доступа пользователей к этой информации и организована подписка на обновление данных. Таким образом, пользователи во всем мире могут использовать экспертные знания. Если говорить о пакетных решениях в области выявления закономерностей, то, как правило, они делятся на три категории: профессиональные, универсальные и специализированные. Профессиональные пакеты рассчитаны на пользователя-специалиста в области статистики. Все универсальные пакеты имеют много пересечений по составу статистических процедур. Специализированные пакеты ориентированы на одну предметную область или несколько смежных областей, их алгоритмы заточены под определенные данные и наиболее актуальные задачи предметной области. В некоторых пакетах реализованы алгоритмы классификации [1, c. 35]. Описания некоторых пакетов приведены ниже: Наиболее старым продуктом на рынке считается система SAS. Этот пакет включает более 20 различных программных продуктов. Традиционно сложилось, что основными пользователями системы в России являются крупные предприятия, ВПК, государственные структуры. Для классификации в SAS представлены следующие модули: 234
BASE SAS – ядро системы со встроенным языком программирования 4GL и поддержкой языка работы с базами данных SQL, средств управления данными, индексов баз данных, возможностей доступа к широкому набору форматов данных, процедур описательной статистики и генерация отчетов; FSP обеспечивает полноэкранный доступ к данным, ввод, редактирование, преобразование данных, генерацию отчетов; GRAPH поддерживает деловую, научную, рекламную графику, различные шрифты и карты; STAT включает в себя многофункциональный набор статистических процедур анализа данных Основным достоинством SAS считают мощное интеллектуальное ядро, поддержку архитекторы клиент-сервер, возможность доступа и интеграции данных из любых источников. Главный недостаток системы – ее громоздкость, трудности в освоении, высокие требования к квалификации пользователя. Система считается универсальной. Пакет SPSS является представителем универсальной категории, хотя в первую очередь предназначен для работы статистиков-профессионалов [1, c.38]. Пакет обладает весьма полным набором статистических (более 60) и графических процедур, а также процедур создания отчетов. Кроме того, пакет отличается простым и удобным интерфейсом, и отличается высокой точностью вычислений. Пакет STATISTICA также ориентирован на пользователей-профессионалов. В нем имеется широкий спектр функциональных алгоритмов. По своей структуре STATISTICA состоит из нескольких связанных между собой “мини-пакетов”. Эти пакеты взаимодействуют между собой, поддерживая один и тот же формат системных файлов.
класса выдают ответ о принадлежности объекта с такими атрибутами к классу соответствующей разделяющей функции. Построение функций для каждого класса производится независимо друг от друга. Объекты обучающей выборки разбиваются на группы, принадлежащие одному классу и содержащие «подряд идущие значения атрибутов». Если рассмотреть отрезки, в которых содержатся объекты группы, то в них разделяющая функция определяется порядковым номером класса этого объекта. Граничные точки будут определяться по некоторой формуле от тестового объекта и обучающей выборки. Определение класса неизвестного объекта (пробы) производится с помощью разделяющих функций. Объект принадлежит тому классу, который выдает истинное значение на значениях атрибутах тестового объекта. Если разделяющие функции для всех классов не дают положительного результата, то считается, что тестовый объект принадлежит неизвестному классу.
7. Заключение В данной статье поставлена задача классификации, сделан обзор алгоритмов и решений по этой тематике, а также описан алгоритм классификации химических проб. В последующих работах будет представлено обоснование алгоритма, описанного в разделе 5, а также будет описана реализация этого алгоритма и результаты тестирования на реальных данных предметной области. Литература 1. Дюк В., Самойленко А. «Data Mining: учебный курс». СПб: Питер, 2001. 2. Mehmed Kantardzic, “Data Mining. Concepts, Models, Methods and Algorithms”. Wiley-Interscience, 2003 3. Роберт Калан, “Основные концепции нейронных сетей”. Вильямс, 2003 4. Ю.А. Шрейдер, А.А. Шаров “Системы и модели”, Москва «Радио и связь», 1982.
6. Как будет решаться поставленная задача В качестве основы для реализации классификации химических проб был выбран алгоритм классификации на основе разделяющей функции. В алгоритме используются знания объектов данных (обучающей выборки) для построения разделяющих функции, которые в дальнейшем применяются для конструирования классификатора. Описание алгоритма в литературе дает лишь общие принципы работы, а как его реализации могут существенно различаться. Ниже представлено описание реализации алгоритма для классификации. Работа классификатора делится на два больших этапа: обучение системы и классификация тестового объекта. Рассмотрим первый этап, на котором строится классификационная модель. Входными данными для этого этапа служит обучающая выборка. Проводится построение разделяющих функций, которые по набору значений атрибутов 235
236
Формирование профессиональных компетенций современного разработчика ПО В. В. Кулямин, В. А. Омельченко, О. Л. Петренко {kuliamin, vitaliy, olga}@ispras.ru Аннотация. Разработка программного обеспечения (ПО) состоит не только из инженерно-технических видов деятельности. Она включает также когнитивные и социальные аспекты, адекватное внимание к которым не менее важно для успеха, а в сложных проектах играет решающую роль. Важность этих аспектов также повышается в тех проектах, где используются новые подходы к разработке ПО, включая формальные методы. Поэтому для обеспечения успешного применения подходов такого рода, участники таких проектов должны обучаться не только используемым в них техникам, но и навыкам социального характера, позволяющим прилагать их к практическим задачам в рамках определенного контекста. Серьезную помощь в этом могут оказать курсы и тренинги на основе активных методов обучения, поскольку они гармонично сочетают в себе обучение обоим видам необходимых умений — содержательно такой курс учит формальным методам, а форма процесса обучения способствует развитию необходимых когнитивных и социальных навыков.
1. Введение Развитие современной науки и техники связано с использованием информационных технологий в различных областях. Эффективность информационных технологий и качество их результатов во многом определяются характеристиками используемого программного обеспечения (ПО). В настоящее время большинство требований к этим характеристикам формируются не самими программистами, а другими группами пользователей информационных технологий, которые чаще всего не знакомы с особенностями и спецификой работы программистов. Поэтому требования к ПО разнообразны и часто противоречивы. Они описывают такие его характеристики, как надежность, понятность интерфейса, удобство обучения работе с ПО, его пригодность для использования в заданной предметной области, производительность, набор платформ, на которых оно должно работать, и т.д. Для удовлетворения всех этих требований необходим адекватный анализ потребностей пользователей и ограничений предметной области в рамках каждого конкретного проекта по разработке ПО, и, конечно же, нужны 237
специалисты, умеющие проводить такой анализ. Чтобы обеспечить стабильное развитие отрасли производства ПО на дальнюю перспективу и соответствие ее возможностей нуждам современного общества необходимо готовить специалистов, владеющих необходимыми знаниями и навыками для решения задач такого рода. Обычно разработка программного обеспечения описывается как деятельность, связанная с решением технических задач и увязкой этих решений с необходимыми экономическими показателями производимых продуктов и услуг. Практика показывает, что есть неучитываемые таким подходом, но играющие ключевую роль в успешности проектов виды человеческой деятельности, включенные в разработку ПО. Решения, принимаемые всеми разработчиками на различных этапах, и способность членов проектной команды эффективно обмениваться информацией как внутри команды, так и с другими заинтересованными лицами, играют в успешности проекта большую роль, чем используемые в нем техники разработки. Навыки принятия решений в условиях нехватки информации и умение эффективно общаться важны не только для людей, играющих ключевые роли в проекте, таких как его руководитель, бизнес-аналитик или архитектор, но и для обычных разработчиков. Много раз в ходе проекта им приходится принимать минирешения, о том, реализовать ли некую функциональность в виде одного компонента или двух, использовать ли тот или иной алгоритм, и, конечно же, им очень часто приходится общаться между собой, с архитектором, тестировщиками и руководителем проекта, а иногда и с пользователями. Сферы деятельности, в которых должен разбираться разработчик ПО для создания программ высокого качества, представлены на Рис. 1. Навыки, необходимые любому разработчику, могут быть (не слишком строго) разделены на три вида. Технические навыки, которые чаще всего рассматриваются как единственные необходимые. Они характеризуют знание разработчиком основных методов и техник разработки программ и умение использовать их на практике в контексте решаемых на практике задач. Когнитивные навыки, связанные с пониманием требований и ограничений в рамках новой предметной области, умением отделять существенное от несущественного для данной задачи, упорядочиванием задач по их приоритетности, построением решений на основе неполной информации о требованиях и выбором из них наиболее подходящего. Социальные навыки, заключающиеся в умении поддерживать эффективное общение с другими людьми, принимать цели проекта, умении работать в команде и координировать свою работу с нуждами ее членов. Социальные навыки также определяют стиль общения человека — то, как он/она слушает других людей, реагирует на услышанное, участвует в обсуждениях и выдвигает аргументы в пользу собственной точки зрения. 238
Внимание к социальным навыкам было, наверное, впервые привлечено осознанием в 50-е-60-е годы в США того факта, что большинство людей живут и работают в группах, но чаще всего не отдают себе отчета в том, как они в них участвуют, какими их видят другие люди, какие реакции вызывает у окружающих их поведение. Одной из первых работ, в которых эта идея развивалась в приложении к разработке ПО, является книга Ф. Брукса «Мифический человеко-месяц» [1]. Методы моделирования Цели проекта Изобретение решений
Командная работа Разработчик
Предметная область
Техническое общение Оценка рисков
Используемые технологии Рис. 1. Области, в которых должен разбираться разработчик современного ПО. Проблемы овладения такими навыками достаточно актуальны в более широком контексте. В частности, меморандум Европейской Комиссии о постоянном обучении [2] подчеркивает важность для эффективного обучения таких социальных умений, как умение действовать уверенно, умение направлять собственные действия на достижение нужных результатов и умение принимать рискованные решения, а также таких когнитивных навыков, как способность учиться, приспосабливаться к изменяющемуся окружению, быстро воспринимать необходимые новые навыки и находить нужную информацию в широком информационном потоке, обрушивающемся на каждого включенного в общественную жизнь человека в наше время. Обычно эти навыки считают необходимыми только для тех студентов, чья будущая профессиональная деятельность связана с общением с людьми, т.е. для обучающихся на менеджеров, учителей, социальных работников и пр. Но в тех случаях, когда в проекте используются новые подходы к разработке ПО, например, формальные методы, такие навыки становятся критическими как для успеха проекта самого по себе, так и для успешного приложения новых техник. Это вызвано значительно более сложными познавательными проблемами в рамках таких проектов — нужно не только вникнуть в предметную область и понять задачи, которые предстоит решить, но и изложить полученные знания в 239
новой, необычной форме, необходимой для эффективного использования формальных подходов. Кроме того, само понимание задач должно быть гораздо более глубоким и детальным, иначе формальные методы становятся гораздо тяжелее традиционных в использовании. А число микро-решений, которые необходимо принять, становится значительно больше. Серьезные социальные навыки необходимы, чтобы сделать эту работу понятной и полезной для различных заинтересованных лиц и других разработчиков, не использующих формальные методы напрямую. Применяя новые техники, нужно помнить, что люди обычно не любят иметь дело с новыми вещами, что нужно уметь разговаривать с живыми людьми, для которых формальное доказательство — не решающий аргумент, а лишь средство давления, используемое против них, — а персональные привязанности, эмоции, амбиции и привычки имеют большое значение. Нужно уметь работать в разных организациях, учитывая доминирующий в них вид организационной культуры при получении необходимой информации и представлении результатов проекта. Таким образом, успешность создания ПО, связана не только с техническими знаниями участников проектной команды, но и с их умением адаптировать свои личные качества и навыки к контексту проекта и сочетать их с интересами коллектива. Иначе «невежество, амбициозность, эгоизм, леность, слабость и разного рода страсти приводят к тому, что общие интересы ослабевают, уступая место интересам личным, и это обстоятельство порождает вечную борьбу» [3], а не успех. Итак, для обеспечения успеха при применении формальных методов на практике, нужно обучать персонал не только их методологическим основам, но и прививать ему необходимые когнитивные и социальные навыки. Эти навыки должны стать для такого инженера или студента не менее привычными, чем умение доказывать теоремы. Мы утверждаем, что активные методы обучения [4,5] являются гораздо более эффективным инструментом обучения передовым методам разработки ПО, и формальным методам в частности, чем традиционные подходы к обучению. Активные методы эффективно вырабатывают у студентов необходимые социальные навыки за счет акцента на автономную работу, самостоятельный выбор направления действий, самостоятельное принятие решений, необходимость общения с другими учащимися, необходимость подстраиваться под изменения в рамках образовательного процесса. В то же время классические подходы к обучению подталкивают обучаемых к пассивному восприятию информации и превращают их в хорошо информированных людей, неспособных без внешнего руководства использовать свои огромные знания на практике в постоянно меняющемся контексте. Активное обучение построено на том, что преподаватель более не является просто источником информации, а вовлекает студентов в процесс обучения, координирует их самостоятельную деятельность, вынуждает их 240
самостоятельно размышлять над материалом курса и применять изучаемые методы на практике. Классический подход к обучению, основанный на предоставлении информации, напротив, нацеливает учащихся на запоминание и воспроизведение в дальнейшем некоторого объема знаний. Такой подход делает акцент на внимании и запоминании данных, в то время, как для эффективного овладения социальными и когнитивными навыками должны быть задействованы все аспекты восприятия и эмоциональной сферы обучаемого. Активные методы обучения — это методы, не направленные на изложение готовых знаний и запоминание их студентами, а побуждающие учащихся к активной мыслительной и практической деятельности в процессе овладения учебным материалом. Активная деятельность в обоих этих направлениях должна стать основой самостоятельного овладения ими знаниями и умениями. Многие авторы ([6]) выделяют следующие признаки активных методов обучения: принудительная активизация мышления, вынуждающая обучаемого быть активным независимо от его желания; достаточно длительное время вовлечения обучаемых в учебный процесс, поскольку их активность должна быть не кратковременной и эпизодической, а в значительной степени устойчивой и длительной; самостоятельная творческая выработка решений и повышенная степень мотивации. В рамках одной статьи невозможно описать все техники, которые могут помочь при развитии необходимых разработчикам ПО когнитивных и социальных навыков. Далее мы попытались предоставить несколько примеров таких техник, которые подталкивают студентов к активному мышлению и использованию получаемых при обучении знаний в практической деятельности.
2. Активное обучение формальным методам Для каждого вида профессиональной деятельности важны те или иные технические, когнитивные и социальные навыки. Для области формальных методов мы выделили следующие группы социальных и когнитивных навыков, имеющих большое значение: умение выслушивать оппонентов; умение эффективно общаться с другими людьми в рамках профессиональной деятельности, где под эффективностью понимается способность получения необходимой информации в кратчайшие сроки и с наименьшими затратами со стороны коллег; умение адаптировать свои знания к изменяющимся реальным условиям. 241
Далее рассматриваются несколько приемов, которые позволяют развить у студентов наряду с техническими навыками перечисленные выше когнитивные и социальные. Эти техники были использованы на практике и продемонстрировали хорошие результаты. Это ролевая игра, кластер, мозговой штурм и дебаты.
2.1. Ролевая игра Ролевая игра является одной из техник, нацеленных на выработку навыков самостоятельного решения проблем, хотя в обучении техническим и инженерным предметам он используется не очень часто [7,8]. Ролевая игра — это искусственно сконструированная последовательность ситуаций, имитирующая те или иные важные стороны реальной деятельности. Во время игры ее участники играют определенные роли и ведут себя, исходя из соответствующих точек зрения на определенную ситуацию. Цель такой игры заключается в том, чтобы найти возможные решения проблем, которые возникают в рассматриваемых ситуациях. Например, следующая игра может быть организована для студентов старших курсов с целью оценки ценности предложенной научной идеи. Участники разыгрывают различные роли, чтобы получить оценку идеи с различных точек зрения. ‘Пропагандист’ объясняет идею, ее перспективы и возможные положительные последствия ее разработки и применения; ‘Теоретик’ рассматривает связи идеи с основами предметной области и другими теоретическими вопросами; ‘Технический эксперт’ оценивает полезность и важность идеи с точки зрения эффективного применения ее на практике, а также удобство ее использования; ‘Менеджер’ выясняет влияние и последствия использования идеи на практике на промышленные процессы, распределение работ, командную работу; ‘Инвестор’ оценивает потенциальные выгоды от возможных применений идеи, возможность и экономическую оправданность разработки коммерческих продуктов на ее основе, или интеграции практических применений идеи в различные существующие решения. В Таб. 2 представлен пример результата ролевой игры — оценки идеи с различных точек зрения. Ролевые игры позволяют повысить интерес к изучаемому материалу, выявить пробелы в знаниях и развить социальные навыки. Однако, ролевая игра ведется по жестким правилам и не может отразить многообразие ситуаций и динамизм, присущие реальной жизни. Очень трудно составить набор ролевых игр, которые позволят отработать большинство важных реальных ситуаций в данной области. 242
Идея: Генерация тестовой последовательности в результате обхода графовой модели тестируемой системы как неизвестной местности Пропагандист
Технический эксперт Явные графовые Идея актуальна Графовые мокак достаточно дели сложных модели дают надежный метод возможность систем могут лучше контро- контроля быть предкачества. ставлены очень лировать ход компактно. тестирования.
Достаточно сложные ситуации могут быть протестированы автоматически. Качества такого тестирования очень высокое.
Теоретик
Менеджер
Инвестор
Идея обещает некоторое повышение качества ПО.
Идея может быть реализована в виде продукта, только если продукт будет нацелен на использование при разработке ПО с повышенными требованиями к надежности. Нет ясной связи Нужно предМожет исполь- Неявные гразоваться как фовые модели между тестиложить менее способ посттяжелее описать, рованием и трудоемкий роения адапособенно тем, требованиями — способ описания зачем нам тести- графов. тивных тестовых кто прежде с ровать все эти последователь- ними не сталкивался. ситуации? ностей. Этот подход Для того, чтобы Идея может нужно сделать научиться использоваться более удобным использовать для создания для испольэтот метод, модулей зования. тестировщикам тестирования, необходим подключаемых к специальный средам тренинг. разработки. Необходим инструмент с графическим пользовательски м интерфейсом, который будет помогать строить модели такого рода.
активности, и человек приобретает умение, с одной стороны, успешно выходить из критической ситуации, а с другой, позитивно отражать эти ситуации в своем социальном опыте. Иначе говоря, в соответствии с этим методом надо не только не бояться сложных, негативных ситуаций, но использовать их для поступательного развития у студентов навыков принятия решений и уверенности в собственных силах.
2.2. Кластер Методы активного обучения предполагают не только создание новой методики обучения и изменение критериев оценки подготовки студентов, но, прежде всего, осознание преподавателем своей новой роли, в которой главной является помощь обучаемым в самостоятельном овладении предметом, а не его преподавание. При активном обучении приоритетными являются не усвоение и воспроизведение готовых знаний, а самостоятельное приобретение и особенно применение полученных знаний. Активные методы обучения вынуждают студентов вести активную работу со знанием, задумываясь над ним, анализируя его и сравнивая с уже имеющимися у них знаниями. При активном обучении не должен поддерживаться подход к обучению некоторых студентов, когда они говорят: “А вы скажите как нужно, мы так и сделаем”. Некоторые способы изменения отношения к учебному материалу основаны на личностном принятии знаний, в результате которого полученное знание становится не пересказом какого-либо учебника, а собственным знанием студента. Отмечено, что студенты, обучающиеся по такой системе, хорошо объясняют полученные знания, так как они ими приняты. Для привязки знаний к личному опыту можно использовать кластер [9]. Кластер создает условия для построения ассоциативных связей между новым материалом и имеющимся опытом, причем эти связи основаны не только на подобии, но и на эмоциональном восприятии, интуиции, а также на основе предположений, возможно ошибочных. Перед началом лекции по новой теме каждый студент рисует кластер на эту тему. Последовательность действий при рисовании кластера следующая: 1. Посередине чистого листа написать ключевое слово или фразу, ключевую для данной темы. 2.
Вокруг него без какого-либо плана или упорядоченности записываются слова или фразы, выражающие идеи, факты, образы, подходящие для данной темы или кажущиеся как-то связанными с ней.
3.
По мере записи, появившиеся слова соединяются связями с ключевым понятием. Такое соединение может быть иерархическим. При этом должны соблюдаться следующие правила:
Таб. 2. Пример результатов ролевой игры. Эффективность ролевых игр можно повысить за счет использования метода "развивающего дискомфорта". Действенность этого метода основана на психологической перестройке личности под воздействием стихийно возникающих или специально организованных эмоционально негативных ситуаций. В результате такой перестройки меняется характер проявляемой 243
не нужно бояться записывать все, что приходит на ум. Дать волю воображению и интуиции; 244
продолжать работу, пока не кончится время или не иссякнут идеи; стараться построить как можно больше связей и не следовать по заранее определенному плану. Бесполезны на практике С трудом интегрируются в современные процессы разработки Интересный подход
Позволяют добиться высокой надежности ПО
Формальные спецификации Очень сложны
Мотивируют людей к приобретению новых умений
Предназначены для построения правильного ПО
Многие люди не могут их понять
Вызов для способностей разработчика
Рис. 3. Пример кластера понятия ‘Формальные спецификации’. На Рис. 3. представлен пример кластера понятия ‘Формальные спецификации’. Перед началом рисования кластера преподаватель должен четко сказать, сколько времени отводится на эту работу. Когда кластер у каждого нарисован, нужно попросить одного студента (только по желанию!) нарисовать свой кластер на доске. Если желающих рисовать нет, то преподаватель рисует свой собственный кластер. Обсуждать ничего не нужно. Каждый самостоятельно сравнивает свой кластер с тем, который нарисован на доске и при желании может внести изменения в свой кластер. После окончания лекции каждый студент может дорисовать кластер: добавить новое знание, или что-нибудь убрать. Очень полезно при возможности организовать обсуждение кластера в малых группах или в парах. Результатом такого обсуждения должны стать невыясненные вопросы, ответы на которые должны быть получены на последующих занятиях. Кроме того, такое обсуждение позволяет повысить коммуникабельность студентов.
245
2.3. Мозговой штурм Для студентов старших курсов, начинающих исследовательскую деятельность, очень важно иметь некоторый набор идей в качестве отправной точки исследований. Для получения стартового набора идей может использоваться мозговой штурм [10]. Мозговой штурм организуется в несколько этапов. 1. Участники штурма должны быть ознакомлены с темой исследований студента. Эта часть может быть организована следующим образом. (a) Студент и его руководитель подготавливают конспект по теме, проблемам исследуемой области и используемым в ней методам. (b) Каждый участник штурма читает подготовленный конспект. (c) Участники формулируют вопросы, касающиеся проблем в рассматриваемой области. Эти вопросы записываются на доске. (d) Студент делает доклад на заданную тему, в котором должны быть сформулированы его интересы в исследуемой области и даны ответы на вопросы участников штурма. (e) После доклада вопросы, на которые были получены ответы (то, что ответ на вопрос получен, должен подтвердить участник, задавший этот вопрос), отмечаются знаком “+”. Никаких новых вопросов задавать нельзя. 2. Участники предлагают идеи возможных направлений дальнейшего исследования. При этом должны соблюдаться следующие правила. (a) Каждый участник имеет право на выступление в течение 30 секунд. Во время такого выступления должно быть сформулировано не больше одной идеи. (b) Количество выступлений одного участника неограниченно. (c) Выступление нельзя прерывать. (d) Каждый участник имеет право ничего не говорить. (e) Идея выступления должна быть сформулирована по возможности четко, желательно в одном предложении. (f) Во время выступления нельзя обсуждать, критиковать и оценивать предыдущие выступления. (g) Запрещено использование в выступлениях выражений “очевидно, что...”, “вы не понимаете...”, “не была дана научная концепция...”, “я хочу уточнить...”, “я хочу объяснить...” и т.п. (h) Порядок и корректность выступлений обеспечивает ведущий штурма. (i) Все предложенные идеи записываются ведущим. 3. Участники оценивают все предложенные идеи на субъективной основе, но в то же время некоторым систематическим образом, например, на основе таблице ПМИ (Плюс-Минус-Интересно). Этот процесс может быть организован следующим образом. 246
(a) Для каждой предложенной идеи и каждого участника подготавливаются листы бумаги, содержащие название идеи и три колонки для записей. В первую колонку вписываются аргументы в поддержку идеи, во вторую — против нее, в третье колонке записываются интересные аспекты, касающиеся данной идеи. В другой схеме наряду с плюсами и минусами формулируются возможные последствия применения идеи на практике. (b) Каждый участник получает листы со всеми идеями и заполняет их. Необязательно оценивать все идеи. Пример заполненного листа показан в Таблице 4. (c) Все листы собираются для составления интегральных оценок. 4. Студент и его научный руководитель подводят итоги рассмотрения всех идей и определяют наиболее перспективные идеи. Идея: Использование методов морфологического анализа для нахождения дефектов в ПО Плюсы
Минусы
Интересные моменты
Совсем новая
Необходим исходный код ПО
Может быть использована в комбинации с обычными методами статического анализа
Может быть Нужно прояснить понятие дефекта использована для нахождения очень тонких дефектов Если в качестве дефекта будет рассматриваться корректный код, нужно предпринимать сложные действия по исправлению такой ситуации
Стандарт POSIX предоставляет строгое описание функциональности операционной системы За Против Стандарт по определению является Формальные правила оформления строгим описанием. Он создается на документа не гарантируют строгости основе формальных правил, представленного в нем регламентирующих описания содержательного текста. отдельных функций. Поддержка: Если почитать POSIX, легко заметить, что его требования оформлены в соответствии с определенным набором правил. POSIX представляет выработанный POSIX является компромиссом между большой группой экспертов консенсус многими крупными поставщиками по поводу функциональности операционных систем (ОС), взгляды операционных систем. Он существует которых на их разработку могут значительно отличаться. более 20 лет, за это время все существенные дефекты были найдены и устранены. Поддержка: Двусмысленности неизбежны в стандарте, объединяющем несколько разных подходов к проектированию ОС. Множество операционных систем успешно используют POSIX как рекомендации по предоставляемым функциям. Возьмем, например, функцию создания потока (thread). POSIX говорит, что эта функция может взаимодействовать с подсистемой управления динамической памятью, но ничего не говорит о том, в чем именно может заключаться это взаимодействие. Таб. 5. Пример результата дебатов.
Таб. 4. Пример субъективной оценки идеи, полученной в результате мозгового штурма. Организация мозгового штурма должна помогать участникам генерировать и высказывать интересные идеи, какими бы фантастическими или безумными они не казались на первый взгляд. Также участники должны быть освобождены от давления авторитетных мнений, они не должны быть вынуждены защищать свои идеи — любые идеи должны приниматься к рассмотрению. 247
2.4. Дебаты Одним из важных навыков для разработки ПО является умение выслушать мнение, отличное от собственного, понять, на чем оно основывается, и принять это во внимание в дальнейшей работе. Дебаты [11] позволяют отработать данный навык. 248
В начале на доске записывается спорное утверждение, которое будет обсуждаться. Каждый участник по отдельности или каждая группа участников выдвигает аргументы, поддерживающие или опровергающие рассматриваемое утверждение. Каждый аргумент может быть в свою очередь поддержан другими утверждениями. Все аргументы и поддерживающие их утверждения записываются на доске. Целью дебатов является предоставление каждому участнику материала для формирования собственной точки зрения на обсуждаемую тему. Другой целью является тренировка навыков в отстаивании своей точки зрения и понимании других, отличных от нее. Студенты знают, что им могут возразить, они психологически готовы к этому, они тренируются концентрироваться на существенных деталях, анализировать и оценивать различные точки зрения на предмет обсуждения и относиться с уважением к противоположным и, может быть, непопулярным точкам зрения. Примеры записей на доске после дискуссии по поводу некоторого утверждения представлены в Таблице 5.
3. Заключение В статье предложена идея интегрированного обучения техническим, когнитивным и социальным навыкам, необходимым при использовании передовых методов разработки ПО, в частности формальных методов, на практике. Такое интегрированное обучение может быть организовано на основе методов активного обучения. В статье представлены несколько таких методов, помогающих вырабатывать необходимые навыки социального плана — умение слушать и принимать во внимание противоположные точки зрения, умение взаимодействовать эффективно при обсуждении профессиональных проблем, умение применять знания в реальных нестандартных ситуациях. В статье описаны четыре техники обучения — ролевая игра, кластер, мозговой штурм и дебаты. Авторы успешно применяли эти техники в обучении современным методам тестирования ПО, основанного на моделях [12,13], и уверены, что они могут использоваться для обучения любым формальным методам и особенно полезны при обучении их использованию на практике. Было проведено несколько тренингов с использованием описанных техник и некоторых других. Тренинги продемонстрировали, что при таком обучении студенты лучше усваивают знания и становятся более активным и самостоятельными в практической работе.
[3]. А. Файоль, Г. Эмерсон, Ф. Тейлор, Г. Форд. Управление — это наука и искусство. Республика, М. 1992. [4]. S. C. Brock. Practitioners’ views on teaching the large introductory college course. 12 pp. Kansas State University Center for Faculty Evaluation and Development. ERIC Document Reproduction Service No. ED 171 208, 1976. [5]. Labinowicz, Ed. The Piaget Primer: Thinking, Learning, Teaching. Menlo Park, CA: Addison-Wesley Publishing Co., 1980. [6]. Педагогические технологии. Под ред. В. С. Кукушкина. М., 2004. [7]. Dennis M. Adams. Simulation Games: An Approach to Learning. Worthington, Ohio, Charles A. Jones Publishing Company, 1973. [8]. John P. Hertel, and Barbara J. Mills. Using Simulations to Promote Learning in Higher Education: An Introduction. Stylus Publishing, Herndon, VA, 2002. [9]. Tony Buzan. Use Both Sides of Your Brain. New York: Dutton, 1974. [10]. James L. Marra. Advertising Creativity: Techniques For Generating Ideas. Englewood Cliffs, NJ: Prentice Hall, 1990. [11]. Ronald T. Hyman. Improving Discussion Leadership. New York: Columbia Univ., Teachers College Press, 1980. [12]. O. L. Petrenko, V. A. Omelchenko. Rapid Trainings on Specification Based Testing Tools. In Proc. of 1-st South-East European Workshop on Formal Methods, Thessaloniki, Greece, 2003. В. А. Омельченко, О. Л. Петренко. Обучение передовым [13]. В. В. Кулямин, технологиям разработки ПО: проблемы и методы их решения. Труды ИСП РАН, т. 5, 2004, стр. 101–120.
Литература [1]. Ф. Брукс. Мифический человеко-месяц или как создаются программные системы. Спб., Символ-Плюс, 2001. [2]. A Memorandum on Lifelong Learning: Commission StaffWorking Paper. European Commission. Directorate General for Education and Culture. European Commission, 2000. SEC (2000) 1832.
249
250