Разработка программного модуля для компьютерной игры

Основные стадии разработки, принципы тестирования и отладка программного модуля "VFS". Особенности проектирования на языке UML. Методы "грубой силы" и их применение при отладке программы. Вредные факторы, присутствующие на рабочем месте программиста.

Рубрика Программирование, компьютеры и кибернетика
Вид дипломная работа
Язык русский
Дата добавления 07.03.2012
Размер файла 827,0 K

Отправить свою хорошую работу в базу знаний просто. Используйте форму, расположенную ниже

Студенты, аспиранты, молодые ученые, использующие базу знаний в своей учебе и работе, будут вам очень благодарны.

Размещено на http://www.allbest.ru/

Содержание

Введение

Раздел 1. Специальный раздел

1.1. Исследовательская часть

1.1.1. Постановка задачи

1.1.2. Предварительные НИР

1.1.3. Информационные потребности пользователей

1.1.4. Требования, предъявляемые к системе

1.2. Конструкторская часть

1.2.1. Структура входных и выходных данных

1.2.2. Общая схема работы модуля

1.2.3. Выбор платформы проектирования и его обоснование

1.2.4. Проектирование архитектуры модуля

1.2.5. Конфигурация технических средств

1.2.6. Алгоритмы работы модуля

1.2.7. Методика тестирования

1.2.8. Результаты экспериментальной проверки

Раздел 2. Технологический раздел

2.1. Проектирование на языке UML

2.1.1. Концепция Unified Modeling Language

2.1.2. Виды диаграмм UML

2.1.3. Связь с объектно-ориентированными языками

2.2. Идеология STL в применении к архитектуре модуля8

2.2.1. Шаблоны в C++

2.2.2. Контейнеры

2.2.3. Алгоритмы

2.2.4. Потоки

2.2.5. Умные указатели

2.3. Специализированный инструментарий

2.3.1. Средства работы с zip-архивами

2.3.2. Шифрация по алгоритму CRC32

2.4. Тестирование

2.4.1. Модульное тестирование

2.4.2. Типы тестов

2.4.3. Планирование модульных тестов

2.4.4. Примеры тестирования

2.4.5. Методы “грубой силы” и их применение при отладке программы

Раздел 3. Организационно-экономический раздел

3.1. Цели определения себестоимости и цены модуля

3.2. Методы определения себестоимости

3.2.1. Метод калькуляции

3.2.2. Расчет на основе нормо-часа

3.2.3. Метод удельных показателей

3.2.4. Метод коэффициентов

3.3. Расчет себестоимости VFS

3.4. Методы расчета цены

3.4.1. По стоимости изготовления

3.4.2. На основе отчислений с продаж («роялти»)

3.4.3. На тиражируемый продукт

3.5. Расчет цены VFS

3.6. Выводы

Раздел 4. Раздел производственно-экологической безопасности

4.1. Производственная безопасность

4.2. Анализ работы за компьютером с точки рения производственной безопасности

4.2.1. Психофизиологические факторы

4.2.2. Электромагнитные излучения

4.2.3. Освещение рабочего места

4.2.4. Электробезопасность

4.2.5. Микроклимат

4.2.6. Зашумленность

4.3. Инженерный расчет освещенности машинного зала

4.4. Экологическая безопасность

4.5. Пожарная безопасность

4.6. Выводы

Заключение

Список литературы

Приложения

Введение

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

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

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

1) Для представления данных в табличном или объектном виде чаще всего нужна их обработка. Сама по себе СУБД - отдельное приложение, с которым нужно устанавливать связь, разделять ресурсы и т.д. Редактирование данных на этапе разработки вызовет сложности. Также тяжело решить некоторые специфичные проблемы, речь о которых пойдет ниже.

2) Современные быстродействующие СУБД стоят довольно больших денег, и в случае с распространением игры как товара, как правило, требуется отдельное лицензирование.

Чаще всего разработчики реализуют иерархические структуры на диске. В силу внешних причин отдельные части ресурсов могут быть заархивированы и/или зашифрованы. Часть ресурсов может быть оставлена на ключевом носителе (сейчас в этой роли часто выступает оптический диск), который тоже может быть защищен по какой-либо технологии (оптический диск - StarForce, LaserLock и другие). Также ресурсы могут быть размещены на сетевом сервере, что накладывает свои требования к доступу, верификации и иногда к защите. Ситуация в индустрии диктует ещё одно требование: необходима возможность работы с несколькими параллельными версиями ресурсов - это нужно для корректной установки патчей (пакетов исправлений и дополнений) и пользовательских модификаций. В результате разработчики часто оказываются перед распределенным комплексом весьма сложных по структуре хранилищ. Кроме того, необходимость знать о типах хранилищ и особенностях работы с ними, вызывает резкое усложнение кода и затрудняет его повторное использование.

В данной работе рассмотрен известный подход к организации подобных хранилищ, названный «виртуальная файловая система». Основные положения этого подхода:

1) Любые обращения к файловой системе идут по виртуальному «дереву» каталогов, начиная с корня (root).

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

3) Под «файлом» подразумевается любой потоковый источник информации, это могут быть дисковый файл, устройство или программно сформированные данные в памяти.

Этот подход был заложен в операционных системах UNIX. Он отличается высокой гибкостью и прозрачностью в использовании. Его реализация позволяет полностью инкапсулировать код, отвечающий за непосредственную работу с разнородными хранилищами, и предоставить единый интерфейс на все файловые операции.

Используя этот подход, был разработан модуль, задачей которого является прозрачная работа с файловыми ресурсами вне зависимости от хранилища, из которого они получены - дисковый каталог, сеть, архив, зашифрованное хранилище, exe-файл или dll-файл. Архитектура модуля предусматривает максимально простое подключение к существующим проектам, а так же простое расширение функционала:

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

2) Наращивание механизмов эвристики по выбору вариантов файлов в случае использования параллельных веток. Предусмотрен механизм передачи внешнего функтора для сравнения файлов.

Модуль реализует работу с алгоритмами архивации zip, шифрованием по алгоритму CRC32, с сетью и с ресурсами исполняемых файлов Win32.

Выбор средств разработки определялся удобством интеграции в существующие проекты, а также быстродействием результирующего модуля. В силу того, что программирование в MiSTland ведется на языке С++, модуль также был реализован на C++ и с использованием идеологии STL. Из инструментальных средств, выбор пал на Microsoft Visual Studio 7.1, STLport, BOOST и zlib. Проектирование велось в Rational Rose, для не-UML схем использовался Microsoft Visio.

Предлагаемый модуль является dll-библиотекой, подключаемой к любому проекту через интерфейс на языке С++, и обеспечивающий требуемый функционал, используя стандартные потоки языка C++.

В технологической части и программной документации освещены этапы решения задачи, принципы тестирования и отладка. Отдельная глава посвящена вопросу надежности.

В организационно-экономическом разделе рассчитаны себестоимость разработки модуля и его цена.

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

Раздел 1. Специальный раздел

1.1 Исследовательская часть

В специальном разделе пояснительной записки к дипломному проекту я опишу основные стадии разработки программного модуля «VFS»: постановка задачи, предварительные НИР и техническое задание. На этапе эскизного проектирования будут определены требования к входным и выходным данным, составлен общий алгоритм работы модуля. При техническом проектировании будет определена архитектура модуля, детализированы функциональные требования, разработаны основные алгоритмы работы, описана конфигурация технических средств.

1.1.1 Постановка задачи

Общая проблематика файловых хранилищ была освещена во введении. Поясню основные принципы работы с файловыми ресурсами в игровых проектах, так сказать, «изнутри».

Большинство модулей, так или иначе нуждающихся в файловом вводе, принимают информацию в виде потока, реализованного силами CRT или STL. В силу широкого использования в индустрии средств STL, решения на базе CRT используются всё реже, поэтому закладываться на их использование я не стал - таким образом, стандартным форматом ввода-вывода де-факто в модуле стали std::basic_istream и std::basic_ostream.

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

Второй задачей модуля является представление в стандартном виде (как потока STL) файловых данных, полученных из различных по внутренней структуре файловых хранилищ. Как правило, приходится сталкиваться с заархивированными, зашифрованными данными, сетевыми устройствами, платформенно-зависимыми хранилищами (например, данные, «зашитые» в исполняемые файлы Win32 как ресурсы). В данном контексте сложность составляют ограничения, налагаемые структурой хранилищ.

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

1.1.2 Предварительные НИР

Из постановки задачи следует, что требуемый модуль является самостоятельной подключаемой библиотекой, поэтому в предварительных НИР следует изучить имеющиеся примеры ПО аналогичного назначения. В силу специфики задачи подобные системы как отдельные программные модули, как правило, реализуются только в стенах софтверных компаний, имеющих в таких модулях необходимость - потенциальный рынок таких систем невелик. Подобные системы были разработаны для внутреннего использования конкурентами MiSTland, а так же самой MiSTland для её ранних проектов. Необходимость в повторной разработке была вызвана недостаточной гибкостью и переносимостью ранее разработанного модуля, а так же трудностью его поддержки. Таким образом, доступными для изучения оказались только свободно распространяемые средства и общеизвестные концепции родом из мира open-source.

BOOST filesystem

Библиотека boost::filesystem является свободно распространяемой в рамках пакета BOOST, продвигаемого комитетом по стандартизации C++. Эта библиотека призвана облегчить «сборку» пути до файла и некоторые операции с путем. Также она делает небольшую обертку (паттерн Wrapper) над стандартными потоками C++, позволяя удобнее указывать путь. Есть возможность обхода файлов внутри директорий при помощи механизма, совместимого с итераторами стандартной библиотеки С++. Работа с различными форматами представления пути успешно инкапсулирована внутри.

Эта библиотека реализует часть требуемого в ТЗ функционала, конкретно - единообразное представление клиентскому коду данных файлов и возможность итерирования файлов. Она никоим образом не решает проблемы распределенных хранилищ, но её код открыт (в соответствии с условиями распространения библиотеки BOOST), поэтому она может служить замечательным образцом внешнего программного интерфейса. Крайне полезны детали её реализации, касающиеся универсального представления пути и конвертирования различных его форматов, а также механизма итерирования веток файловой системы.

UNIX VFS

Задача абстракции от конкретного типа хранилища наиболее полно решена на данный момент в системе Linux (и в большинстве используемых ныне *nix-систем) комплексом под названием VFS (Virtual File System). Аналогичные операции в других доступных для изучения операционных системах - рассматривались системы семейства Windows - производятся не с той гибкостью, которой бы хотелось достичь в проектах MiSTland. Использовать систему VFS непосредственно не представляется возможным, поскольку VFS является неотъемлемой частью ядер операционных систем *nix, однако интерфейс и логика работы VFS, несомненно, являются удачными и проверенными долговременным использованием.

Кратко о VFS в реализации *nix - систем:

Существует виртуальное дерево, каждый «лист» которого является ячейкой, с которой может быть ассоциирован список неких файловых хранилищ - разделов или директорий на диске, съемных носителей, сетевых дисков и т.д. Структура каталогов ассоциированного (в терминологии VFS - замонтированного) хранилища становится для VFS естественным продолжением имен ветки виртуального дерева. Работа с каждым хранилищем на физическом уровне инкапсулирована кодом VFS и может быть изменена или дополнена путем модификации кода, компиляции модуля VFS и интеграции системой патчей в ядро ОС.

Каждому файлу в VFS соответствует идентификатор (в терминологии VFS - «dentry»), по которому VFS определяет, к какому физическому хранилищу принадлежит файл и из которого, соответственно, можно запросить атрибуты, содержимое файла, права доступа, число активных копий и т.п. VFS ведет учет идентификаторов файлов, организуя их в хэш-очереди. Файлом, кстати, в зависимости от реализации хранилища, может быть представлен специальный файл устройства, канал («pipe»), сетевой сокет.

Все файловые операции - чтение, запись, удаление - реализованы на уровне VFS, которая «спускает» вызовы реализациям, предназначенным для работы с конкретными типами хранилищ.

1.1.3 Информационные потребности пользователя

Основные требования, которые предъявлялись к модулю:

1) Открытие файлов на чтение и запись. Имена должны быть единообразными согласно принятой в проекте относительной системе именования.

2) Возможность перебора (итерирования) файлов внутри директории безотносительно их реального местонахождения.

3) Возможность подключать хранилища из сети, зип-архивов с шифрацией и без, из исполняемых файлов Win32.

4) Возможность существования нескольких файлов с одинаковым именем (в разных подсистемах) с возможностью ручного или автоматического выбора вариантов.

5) Контроль исключительных ситуаций в процессе работы. Недопущение аварийного прекращения работы из-за некорректных данных или действий пользовательского кода.

6) Как можно более простой и функциональный программный интерфейс.

1.1.4 Требования, предъявляемые системе

Исходя из поставленной задачи и проведенных предварительных НИР, были сформулированы требования к разрабатываемому модулю.

Состав выполняемых функций

Создаваемый модуль должен обеспечивать «прозрачное» выполнение файловых операций, проводимых клиентским кодом, в том числе:

1) Открытие файла на чтение и предоставление входного потока на него стандартной библиотеки С++, согласно относительной системе именования, принятой в структуре данных проекта.

2) Открытие файла на запись или его создание с предоставлением исходящего потока стандартной библиотеки С++, согласно той же системе именования.

3) Перебор файлов внутри директории безотносительно типу файлового хранилища. Механизм должен быть построен по идеологии итераторов стандартной библиотеки С++.

Для эффективного управление списком файловых хранилищ (в дальнейшем - «подсистем») модуль должен обеспечивать следующие операции:

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

2) Возможность монтирования подсистемы (дисковой директории, зип-архива, сетевого диска, ресурсного exe/dll) в виртуальную директорию.

3) Ручное и автоматическое («сборка мусора») демонтирование и удаление подсистем.

Для обеспечения гибкости работы модуля необходимо наличие следующих особенностей:

1) Система должна позволять сосуществовать нескольким файлам, доступным по одинаковому имени (в разных подсистемах).

2) Необходимо предоставить инструмент в compile-time для выбора варианта файла по естественным критериям (дата файла, размер и т.п.).

3) Необходимо наличие механизма автоматического выбора варианта файла.

Требования к надежности

Для обеспечения стабильности работы модуля необходимо:

1) Максимальное применение отработанных стандартных средств и библиотек.

2) Анализ действий пользователя на корректность перед совершением операций в ядре модуля.

Требования к информационной и программной совместимости

Реализовывать модуль следует на языке С++. Модуль должен максимально основываться на стандартной библиотеке языка С++ и использовать идеологию STL. В качестве входных и выходных типов данных должны использоваться базовые и стандартные библиотечные типы С++. Для обработки наборов данных необходимо пользоваться алгоритмами стандартной библиотеки С++.

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

1.2 Конструкторская часть

1.2.1 Структура входных и выходных данных

Рис. 1.1. Структура входных и выходных данных

На рисунке 1.1. поясняется, что является входными и выходными данными модуля.

Для начальной инициализации VFS клиентский код монтирует подсистемы. Происходит это так: создается объект подсистемы, например, дисковой, инициализируется каталогом на диске, который должен быть представлен этой подсистемой, а также указанием необходимых атрибутов доступа (пароли, права), а потом передается ядру VFS вместе с желаемым расположением в дереве виртуальных директорий. После монтирования одной и более подсистем VFS становится работоспособной. Дальнейшие действия клиентского кода могут быть такими:

1) Запрос на чтение или запись по имени. Передаваемые данные - имя и желаемые параметры потока. Ядро VFS запустит алгоритм поиска файла с таким именем в виртуальном дереве. Подробно алгоритм будет рассмотрен ниже.

2) Запрос на перебор файлов внутри виртуальной директории. Передаваемые данные - путь и маска поиска. Ядро VFS идентифицирует все файлы, чьё имя соответствует маске поиска (считается, что все такие файлы лежат в одной виртуальной директории) и выдаст наружу объект, позволяющий вести перебор аналогично контейнеру STL. Подробно этот алгоритм также будет рассмотрен ниже.

3) Получение информации о файле: существование, атрибуты. В отдельную ветвь на рисунке это не выделено, поскольку реализуется силами кода, получающего файловый поток.

1.2.2 Общая схема работы модуля

Рис. 1.2. Диаграмма прецедентов работы модуля

Общая схема работы модуля выглядит следующим образом:

1) В случае работы со списком подсистем клиентский код должен создать подсистему, определиться с её виртуальным путем и передать её ядру. Происходит монтирование - включение подсистемы в виртуальное дерево. Работу по «сборке мусора» при завершении работы ядро берет на себя.

2) В случае запросов к общему интерфейсу - собственно, основному интерфейсу VFS - во всех случаях запускается универсальный механизм создания итератора, он формирует специфической структуры список дескрипторов, подходящих под маску поиска и путь (маской поиска может выступать конкретное имя файла), с которым далее может происходить следующее:

а) В случае запроса на итерирование виртуальной директории список дескрипторов возвращается клиентскому коду.

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

в) В случае запроса параметров файла так же применяется механизм отбора дескриптора, он так же передается подсистеме, к которой он принадлежит, и она обеспечивает выдачу требуемых данных.

г) В случае попытки удаления файла запускается механизм поиска дескриптора, после чего файл, соответствующий дескриптору, удаляется.

1.2.3 Выбор платформы проектирования и его обоснование

В силу специфики разработки, выбор как операционной системы, так и инструментальных средств был продиктован принятыми в MiSTland техпроцессами.

В качестве операционной системы для платформенно-зависимого кода была выбрана Windows 98/Me/XP, т.к. именно под эти ОС создаются проекты MiSTland. Код дисковой подсистемы базируется на библиотеке CRT, реализация которой существует на большинстве известных программных платформ. На весь остальной код налагалось такое ограничение: модуль должен быть выполнен на языке С++ (ISO/IEC 14882) с использованием идеологии STL, для удобства подключения (весь остальной код также пишется на С++) и для удобства сопровождения любым программистом MiSTland.

В качестве сред разработки были выбраны:

1) Microsoft Visual Studio .NET 7.1 - одна из объективно лучших интегрированных сред для Win32, имеющая такие сильные стороны, как:

а) Удобство и гибкость при создании различных приложений под Win32.

б) Её компилятор практически стопроцентно поддерживает стандарт С++, чем не отличаются многие другие компиляторы.

в) Наличие мощного и удобного интегрированного отладчика, поддерживающего, в том числе, удаленную отладку.

г) Наличие удобного и всеобъемлющего справочного руководства - MSDN.

д) Удобный интерфейс, использующий большинство известных наработок в области Win32 GUI.

2) Rational Rose - также одна из объективно лучших интегрированных сред по проектированию на языке UML. Её сильные стороны таковы:

а) Полная поддержка стандарта UML 1.3, на котором и велось проектирование.

б) Удобные графические инструменты.

в) Возможность проверять семантическую целостность диаграмм - своего рода компилятор.

г) Возможность экспортировать проекты на UML в код целого ряда целевых ООП - языков, в том числе, конечно же, и в код С++.

Как дополнительные инструменты были задействованы Microsoft Visio - для не-UML схем - и следующие программные библиотеки:

1) STLport - одна из реализаций STL, наиболее распространенная в индустрии, свободно распространяемая. Поддерживается силами членов Комитета по стандартизации С++. Платформенно независима. В отличие от реализации Microsoft, поставляемой вместе с MSVS, полностью соответствует стандарту С++.

2) BOOST - открытая библиотека, в которую вошли те инструменты, которые не были включены в стандартную библиотеку С++. Аналоги найти сложно. Отличается стилем кодирования, очень близким к интерфейсу STL, а также использованием идиом STL, таких, например, как «итератор».

3) zlib - свободно распространяемая библиотека, реализующая архивацию и распаковку архивов формата ZIP алгоритмами deflate и inflate (наиболее распространенные, другие сейчас практически не встречаются). Отличается простотой использования и гибким программным интерфейсом.

1.2.4 Проектирование архитектуры модуля

Ниже описаны архитектурные решения, примененные в модуле.

Рис. 1.3. Диаграмма классов VFS

На рисунке 1.3 изображена общая диаграмма классов VFS. На диаграмме не указаны детально члены классов во избежание нагромождения. Здесь прослежены инкапсулированность основных членов классов, основные зависимости с указанием типа связей, их мощности и направлением раскрытия. Интерфейсные классы выделены стереотипом interface. На диаграмме не присутствует никаких вспомогательных классов, прямо не участвующих в основных алгоритмах работы. Ниже детально рассматриваются особенности проектирования каждого из классов-участников диаграммы.

Рис. 1.4. Диаграмма класса fs

На рисунке 1.4 изображена диаграмма интерфейсного класса fs, который является основным программным интерфейсом модуля. Строго говоря, классом fs не является. Реализован он как пространство имен с вложенными в него самостоятельными функциями. Никаких данных за ненадобностью fs не хранит, поэтому его внешние связи ограничены такой их разновидностью, как «зависимость» (dependency).

Зависимость от класса system обусловлена работой алгоритмов get_files, get_descriptor и mount, которым требуется доступ к дереву виртуальных директорий, хранящемуся в system.

Зависимость от классов file_id, io_file и iterator обусловлена использованием их как аргументов и возвращаемых значений.

Рис. 1.5. Диаграмма класса system

На рисунке 1.5 изображена диаграмма класса system, который является ядром VFS. Класс использует несколько модернизированный паттерн «синглетон», или «одиночка». Этот паттерн (устоявшаяся схема проектирования) характерен тем, что на весь проект гарантированно существует только один экземпляр класса. В С++ это реализуется как статический член класса - экземпляр объекта, доступ к которому осуществляется статической же функцией. Инициализация и уничтожение экземпляра происходят как у статической переменной. Конструктор и деструктор такого класса объявляются приватными. Для большей гибкости существует разновидность этой схемы, когда есть открытые статические функции создания и уничтожения единственного объекта, а функция доступа снабжается идентификацией попытки доступа к неинициализированному объекту.

Для облегчения возможной замены реализации класса system, а также для сокрытия некоторой части интерфейса, основные функции-члены system сделаны абстрактными, а реализует их скрытый наследник system_impl.

В качестве контейнера, реализующего дерево, используется шаблонный класс lcrn_tree из внутренней библиотеки MiSTland. В нем определен итератор, используемый в интерфейсе system.

В узлах дерева содержатся объекты типа directory, поэтому system связан с этим классом агрегированием. Также system непосредственно агрегирует объект класса сache.

В программном интерфейсе system реализованы функции создания, уничтожения и доступа для объекта-одиночки, функции доступа к дереву (итераторы на начало и конец), а также функции mount() и unmount(), реализующие одноименные алгоритмы.

Рис. 1.6. Диаграмма класса directory

На рисунке 1.6 изображена диаграмма класса directory, представляющего виртуальную директорию. Класс предназначен для хранения списка подсистем и манипулирования им. Директория хранит своё имя и собственно список, реализованный контейнером std::list, это обуславливает агрегирование классов std::string, std::list и sub_fs. Как было сказано выше, директория физически хранится в дереве system, поэтому класс directory агрегирован классом system.

Рис. 1.7. Диаграмма класса sub_fs

На рисунке 1.7 изображена диаграмма класса sub_fs, который является интерфейсом хранилища (подсистемы). Класс предоставляет следующий интерфейс:

1) Получение физического расположения подсистемы.

2) Доступность подсистемы на запись.

3) Существование файла в подсистеме.

4) Атрибуты файла: размер, доступ, время создания, директория ли он.

5) Создать итератор по пути и маске поиска.

6) Открыть файл на запись или на чтение.

7) Удалить файл.

8) Создать реальные директории по пути.

9) Как было сказано выше, подсистему агрегирует класс directory. Создаваемый объект sub_fs_iter имеет ссылку на sub_fs.

Рис. 1.8. Диаграмма класса sub_fs_iter

На рисунке 1.8 изображена диаграмма класса sub_fs_iter, который является итератором подсистемы. Объекты данного класса, используются алгоритмами get_files и get_descriptor. Основные методы - получение текущего дескриптора и оператор инкремента. Содержит ссылку на «свою» подсистему.

Рис. 1.9. Диаграмма класса file_id

На рисунке 1.9 изображена диаграмма класса file_id, который является дескриптором файла в VFS. Класс содержит своё полное имя и ссылку на подсистему. В текущей реализации в интерфейсе file_id есть набор методов для получения атрибутов файла, которые являются делегированием соответствующих из подсистемы.

Для полноты картины стоит упомянуть вспомогательные классы io_file и iterator, диаграммы которых помещены на рисунки 1.10 и 1.11. Класс io_file служит для предоставления потока на файл, хранит в себе исходящий либо входящий поток и экземпляр file_id. Класс iterator служит для построения контейнеров дескрипторов в реализации алгоритмов get_files и get_descriptor.

Рис. 1.10. Диаграмма класса io_file

Рис. 1.11. Диаграмма класса iterator

1.2.5 Конфигурация технических средств

Ограничения на конфигурацию технических средств накладываются проектом в целом. В проектах MiSTland, согласно тестированию, VFS никогда не занимала сколько-нибудь значительного объема памяти (это связано с небольшими объемами её собственных данных) и не отбирала сколько-нибудь заметное количество процессорного времени. Наиболее серьезно, как показали тесты, машину нагружает распаковка архива.

Требования к программному обеспечению - наличие Windows 98 и старше в случае использования подсистемы ресурсов исполняемых файлов Win32.

1.2.6 Алгоритмы работы модуля

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

Рис.1.12. Схема алгоритма get_files

Необходимо пояснить работу алгоритма. Клиентский код предоставляет путь до искомой последовательности и маску поиска (допустимо конкретное имя файла), а также осуществляет выбор механизма сортировки дескрипторов файлов с одинаковыми виртуальными именами. Используется идиома «предикат», заимствованная из STL. Может использоваться предикат по умолчанию, либо же предоставленный пользовательским кодом. Потом запрашивается ядро VFS, у ядра запрашивается дерево виртуальных директорий. Дерево обходится начиная от корня, при каждой итерации выделяется текущая директория, в соответствии с ней корректируется путь поиска: путь директории считается виртуальной частью пути поиска, остальная часть - реальной, соответственно реальная часть пути должна быть передана подсистемам. Из директории берется список замонтированных в неё подсистем, к каждой подсистеме формируется запрос на существование файла с именем «реальная часть + маска поиска». Для этого используется собственный итератор подсистемы. Все найденные таким образом дескрипторы сохраняются в контейнер со структурой, несколько напоминающей графический эквалайзер (рис.1.13).

Рис. 1.13. Схема контейнера файлов виртуальной директории

Контейнер является списком, элементами которого являются списки дескрипторов с одинаковыми виртуальными именами, отсортированных в соответствии с переданным предикатом.

На рисунке 1.14 изображен алгоритм работы кода, получающего дескриптор файла по введенному имени. Работа аналогична алгоритму get_files, за исключением таких деталей:

1) В системе существует кэш для одиночных запросов, поэтому перед запуском сканирования дерева проверяется он - в случае совпадения имен искомого и закешированного дескрипторов выдается закешированный.

2) Наружу вместо контейнера выдается первый его элемент.

На рисунке 1.15 изображен алгоритм работы кода, монтирующего и демонтирующего подсистемы.

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

Рис. 1.14. Схема алгоритма get_descriptor

Рис. 1.15. Схема алгоритма mount

1.2.7 Методика тестирования

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

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

1) проведено разбиение равнозначности для диапазона входных значений параметров, на основе логических выкладок;

2) определены граничные условия входных параметров;

3) проведен поиск утверждений и составлен набор входных значений, проверяющий утверждение;

4) проведен анализ алгоритма и составлены наборы входных значений, позволяющие «пройти» по каждой ветке алгоритма.

При тестировании выбор значений из диапазонов равнозначности производился с помощью генератора псевдослучайных последовательностей.

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

Модульные тесты были спланированы заранее с помощью предлагаемых методикой модульного тестирования шаблонов:

1) поскольку людские ресурсы были сильно ограничены, тестирование выполнялось разработчиком модуля;

2) документирование тестов проводилось в индивидуальном неформализованном порядке без применения автоматических средств и не включалось в официальную отчетность по работам;

3) приоритеты, как сказано выше, были отданы операциям с контейнерами и динамической памятью в срезе наиболее частых сценариев использования классов;

4) для получения входных данных была организована модельная среда с различными хранилищами по образу системы предыдущего проекта, и её данные использовались в зависимости от тестируемых частей модуля;

5) время для тестирования было выделено из расчета примерно половины времени разработки;

6) в качестве системы учета времени и баг-трекера использовались сначала Microsoft Outlook, потом Sirid.

Высокоуровневое тестирование проводилось при помощи полного воссоздания среды работы VFS, то есть окружения, в котором работает модуль. Поэтому для тестирования был собран каркас приложения, к нему был подключен модуль (так же, как и планировалось подключать к проектам - статически присоединенной dll), и было проверен каждый прецедент использования в соответствии с предоставленным программным интерфейсом. Для проверки работоспособности в многопоточной среде запускалось большое количество потоков, одновременно выполняющих различные комбинации действий. Каждое действие выполнялось как в указанных в требованиях условиях, так и с нарушениями - проверялась передача заведомо неверных параметров, специально организовывались ошибки доступа к данным в хранилищах. Проводился контроль утечек памяти.

Формально в рамках тестирования системы были проведены следующие проверки.

1) Общий интерфейс VFS:

а) Открытие файла на чтение и предоставление входного потока на него стандартной библиотеки С++, согласно относительной системе именования, принятой в структуре данных проекта.

б) Открытие файла на запись или его создание с предоставлением исходящего потока стандартной библиотеки С++, согласно той же системе именования.

в) Перебор файлов внутри директории безотносительно типу файлового хранилища.

2) Ядро VFS:

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

б) Монтирование/демонтирование подсистем различных типов в директории.

в) Проверка механизма "сборки мусора".

3) Особенности:

а) Проверка на возможность существования нескольких файлов с одинаковым именем в рамках всей VFS.

б) Проверка работы критериев сортировки и выбора предпочтительных вариантов файлов.

4) Специализированные модули:

а) Проверка корректности работы шифрования/дешифрования средствами разработанной библиотеки ml_encrypted_zip при заведомо внедренных ошибках шифрации.

5) Надежность:

а) Проверка на недопустимые имена файлов.

б) Проверка на отсутствие запрашиваемых файлов.

в) Проверка на ошибки физического доступа к подсистемам.

Интегральные тесты не проводились, поскольку в них не было необходимости.

Системные тесты были выполнены в ограниченном объеме, поскольку в команде системным тестированием специально занимались отдельные люди. Из системных были выполнены тесты на использование ресурсов компьютера, на надежность (с вычислением средней наработки на отказ) и стресс-тесты.

Тестирование проводилось как при помощи встроенного отладчика Visual Studio, так и при помощи методов «грубой силы», заключавшихся в выводе интересующей информации на консоль окна тестового приложения или в окно output студии.

1.2.8 Результаты экспериментальной проверки

Результаты тестирования показали:

1) Модуль полностью удовлетворяет функциональным требованиям, перечисленным в ТЗ.

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

3) Модуль защищен от ошибок, связанных с многопоточной средой выполнения.

4) Утечек памяти зафиксировано не было, механизм «сборки мусора» функционирует должным образом.

Раздел 2. Технологический раздел

2.1 Проектирование на языке UML

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

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

С повсеместным распространением ООП проектировщики всё чаще стали обращаться к понятию «класс» как к универсальной единице структуры программы и как к актеру поведения. Одновременно несколько видных исследователей - Буч, Джекобсон, Румбах и другие - начали разрабатывать концепции языков, которые могли бы описать все аспекты работы программы, и были бы столь же эффективными, как языки программирования. Их работа вылилась в принятый консорциумом OMG стандарт Unified Modeling Language, который на сегодняшний день используется множеством разработчиков по всему миру и продолжает совершенствоваться.

2.1.1 Концепция Unified Modeling Language

UML представляет собой общецелевой язык визуального моделирования, разработанный для спецификации, визуализации, проектирования и документирования компонентов программного обеспечения. Основные принципы языка UML:

1) абстрагирование - в модель включаются только те аспекты проектируемой системы, которые имеют непосредственное отношение к выполнению системой описываемых функций;

2) многомодельность - достаточно полная модель сложной системы представляет собой некоторое число взаимосвязанных представлений (views), каждое из которых отражает некоторый аспект поведения или структуры системы;

3) иерархическое построение - модель строится на разных уровнях абстракции в зависимости от необходимости раскрытия деталей на данном этапе проектирования.

UML предназначен для описания двух основных видов моделей: статических (модели, описывающие структуру) и динамических (модели, описывающие поведение). Нотация UML, как языка визуального проектирования, включает в себя большое количество компонентов графической нотации. Основными понятиями UML являются «класс», «атрибут», «операция» и «отношение». Основное представление понятий - диаграмма.

2.1.2 Виды диаграмм UML

Диаграммы UML, как и было сказано выше, являются основным способом представления моделей. Последовательность рассмотрения диаграмм в данном подразделе обусловлена рекомендациями Rational Unified Process и наглядно показывает последовательность моделирования системы.

Диаграмма прецедентов (use case diagram)

Разработка диаграммы прецедентов преследует целью сформулировать общие требования к функциональному поведению системы. Суть данной диаграммы состоит в следующем: проектируемая система представляется в виде так называемых прецедентов использования, с которыми взаимодействуют внешние сущности или актеры. Диаграмма служит для описания сервисов, предоставляемых системой актеру. На данном этапе система представляется разработчиками как «черный ящик».

Каждый прецедент характеризует некую предполагаемую последовательность действий. Актер представляет некую внешнюю по отношению к модели сущность. Существует несколько стандартных видов отношений между актерами и прецедентами:

1) ассоциация - указание на семантическую связь актера и прецедента (например, актер инициирует прецедент);

2) включение - указание на то, что заданное поведение одного прецедента включается как составная часть в поведение другого;

3) расширение - указание на то, что заданное поведение прецедента может использоваться другим в случае выполнения неких условий;

4) обобщение - указание на то, что прецедент или актер является специальным случаем другого прецедента или актера соответственно.

Рис. 2.1. Диаграмма прецедентов

Диаграмма классов (class diagram)

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

Класс в UML является абстрактным описанием свойств множества объектов, обладающих одинаковой структурой, поведением и отношениями с объектами других классов. Класс обладает набором атрибутов или свойств, а также набором операций или методов. Выделяется понятие шаблонного класса, аналогичное одноименному из языка С++.

Между классами могут быть следующие отношения:

1) ассоциация - произвольная семантическая связь классов, может иметь кратность (аналогично ER-диаграммам);

2) обобщение - отношение между предком и потомком, интерпретация наследования;

3) агрегация - включение одной сущностью других как составных частей, взаимосвязь между частью и целым;

4) композиция - более сильный вариант агрегации, при котором часть не может существовать отдельно от целого;

5) зависимость - семантическая связь, не являющаяся какой-либо из вышеперечисленных.

Интерфейс в UML - это специальный случай класса, когда специфицируется только его поведение.

Рис. 2.2. Диаграмма классов

Диаграмма кооперации (collaboration diagram)

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

Кооперация - множество взаимодействующих с определенной целью объектов в общем контексте моделируемой системы. Различают два уровня представления кооперации: на уровне спецификации (показывает роли спецификаторов и роли ассоциаций во взаимодействии) и на уровне экземпляров (объекты и связи, образующие отдельные роли).

Рис. 2.3. Диаграмма коопераций уровня спецификации

Рис. 2.4. Диаграмма коопераций уровня экземпляров

Диаграмма последовательности (sequence diagram)

Диаграммы последовательности родственны диаграммам кооперации - они преобразуются друг в друга без потери информации. Диаграмма последовательности показывает взаимодействие в срезе времени.

Рис. 2.5. Диаграмма последовательности

Диаграмма состояний (statechart diagram)

Эти диаграммы описывают возможные последовательности состояний и переходов, которые в совокупности характеризуют поведение моделируемой системы в течение её жизненного цикла. Диаграмма состояний представляет динамическое поведение сущностей на основе спецификаций их реакции на восприятие некоторых конкретных событий.

Диаграмма состояний по существу является графом, представляющим некий конечный автомат.

Рис. 2.6. Диаграмма состояний

Диаграмма деятельности (activity diagram)

Диаграмма деятельности является самым низкоуровневым видом диаграмм UML. Её семантика сродни многократно стандартизованным правилам записи блок-схем алгоритмов.

Рис. 2.7. Диаграмма активностей

Диаграмма компонентов (component diagram)

Диаграмма компонентов служит для представления физической организации системы. В случае проектирования программного обеспечения компонентами являются файлы исходного, исполняемого, бинарного кода. Каждый компонент может предоставлять другим строго определенные интерфейсы. Данные диаграммы предполагают широкое использование собственных символов.

Диаграмма развертывания (deployment diagram)

Данный вид диаграмм показывает, как будет развернута система на физическом уровне. Узлами этих диаграмм являются физические объекты, такие как компьютер, сеть, принтер, камера, датчик. Допустимо частично совмещать информацию из диаграмм компонентов и развертывания, чтобы отобразить, какой компонент и где будет физически развернут. Данные диаграммы предполагают широкое использование собственных символов.

2.1.3 Связь с объектно-ориентированными языками

Некоторые виды диаграмм UML, например диаграммы классов, очень хорошо поддаются обработке генераторами кода. Соответствующие инструментальные средства встроены в большинство мощных CASE-средств, таких как, например, Rational Rose. По данным диаграммы классов такое инструментальное средство способно создать набор файлов со сгенерированными определениями классов, включая их свойства и методы согласно спецификациям диаграммы.

2.2 Идеология STL в применении к архитектуре модуля

Как и многие другие объектно-ориентированные языки, С++ предоставляет широкие возможности по созданию новых типов данных. Библиотека STL призвана предоставить возможность свободно оперировать этими типами.

Библиотека STL (Standard Template Library - стандартная библиотека шаблонов) вводит широкий набор контейнеров для хранения объектов и большое число алгоритмов для манипулирования ими. Благодаря использованию шаблонов, библиотека STL является строго типизированной, что позволяет ей быть крайне гибкой и дает возможность обнаруживать многие ошибки ещё на этапе компиляции.

Библиотека STL является частью стандартной библиотеки языка С++, как это определено в стандарте ISO / IEC 14882 от 1998 года.

Под идеологией STL в архитектуре программного модуля понимают использование структур данных, алгоритмов и типов STL не только в реализации, но и в программном интерфейсе. Это упрощает задачу программистам, использующим модуль, поскольку позволяет оперировать с модулем в рамках привычных и удобных средств STL.

2.2.1 Шаблоны в C++

Шаблон (template) - параметризированная часть C++-кода. Шаблонными параметрами, как правило, являются имена типов (малоиспользуемый вариант - константы). Единожды описанный в программе шаблон можно инстанцировать (создавать на его основе новые классы или функции) неограниченное число раз. Фактически, шаблон является способом метапрограммирования - для каждого объявления параметризованного класса или функции с известным набором аргументов шаблона компилятором генерируется новый класс или функция обычного вида. Можно сказать, что такое метапрограммирование является вариантом умных макроподстановок. С одной стороны, это увеличивает объем компилируемого кода (часто в разы), но с другой, дает очень большую гибкость в использовании.

Перед макросами с параметрами препроцессора языка C (которые сохраняют в полном объёме свой функционал и в C++) шаблоны имеют следующие преимущества:

1) область видимости шаблонов подчиняется стандартным правилам языка C++, в то время как макрос всегда является объектом с глобальной видимостью;

2) компилятор осуществляет синтаксическую проверку шаблона до его первого использования, макрос никогда не проверяется, что делает даже простейшие ошибки (например, пропущенную точку с запятой в конце оператора) трудно обнаруживаемыми;

3) препроцессор рассматривает параметры макроса как текстовые строки без дополнительных проверок; это может привести к замаскированным ошибкам; параметры шаблона по использованию во многом аналогичны параметрам функции.

В качестве иллюстрации, рассмотрим довольно распространенным пример использования макроса и шаблона для определения возведения в квадрат (листинг 2.1). Легко видеть, что оба подхода позволяют определить функцию возведения в квадрат, однако использование шаблона позволяет избежать нежелательного многократного выполнения операций с побочным эффектом (в примере - двукратного выполнения операции инкремента). Замечу, что для шаблонных функций аргумент шаблона может быть вычислен из типа переданного аргумента, что во многих случаях позволяет избежать явного указания аргументов шаблона.

Листинг 2.1. Макрос и шаблон

#define SQR(x) ((x) * (x))

template<typename T> T sqr(const T& x) { return (x * x); }

void test()

{

int x = 2;

int y = 2;

x = sqr(y);

y = SQR(x);

x = sqr(++y);

y = SQR(++x); // ошибка! Инкремент x произойдет дважды.

}

На текущий момент можно с уверенностью сказать, что век макроподстановок заканчивается. Они используются только там, где необходимо обеспечивать межплатформенную совместимость на уровне исходного кода (пример - заголовочные файлы STLport).

2.2.2 Контейнеры STL

Контейнеры STL - это шаблонные классы, которые предназначены для хранения ряда однотипных объектов. К объектам, хранимым в контейнере, предъявляются следующие требования:


Подобные документы

  • Структурная диаграмма программного модуля. Разработка схемы программного модуля и пользовательского интерфейса. Реализация программного модуля: код программы; описание использованных операторов и функций. Вид пользовательской формы с заполненной матрицей.

    курсовая работа [215,3 K], добавлен 01.09.2010

  • Структурная диаграмма программного модуля. Нахождение суммы элементов, находящихся над главной диагональю. Реализация программного модуля: код программы; описание использованных операторов и функций. Особенности тестирования программного модуля.

    курсовая работа [146,6 K], добавлен 01.09.2010

  • Принципы разработки программы для хранения информации о клиентах, которым предоставляются услуги проката автомобилей, а так же для осуществления оперативного поиска необходимой информации. Структура программного модуля. Описание руководства программиста.

    курсовая работа [872,3 K], добавлен 10.06.2014

  • Сравнительный анализ технологий тестирования. Разработка программного модуля "Интеллектуальная обучающая система для широкого перечня курсов". Обоснование необходимости и важности этапа отладки в процессе разработки данного программного обеспечения.

    дипломная работа [101,2 K], добавлен 17.06.2011

  • Реализация программного средства "Действия над матрицами". Разработка кода программного продукта на основе готовой спецификации на уровне модуля. Использование инструментальных средств на этапе отладки программного модуля. Выбор стратегии тестирования.

    отчет по практике [296,1 K], добавлен 19.04.2015

  • Методика разработки программного модуля для нахождения методом хорд корня уравнения x3-x-0,3=0 с точностью до 0,001 на языке программирования Visual Basic for Application. Схема программного модуля и описание процедуры обработки кнопки "Найти корни".

    курсовая работа [394,0 K], добавлен 08.09.2010

  • Создание программного модуля, выполненного на языке программирования VBA (Visual Basic for Applications) и позволяющего во введенном массиве символов удалить все повторные вхождения этих символов. Разработка пользовательского интерфейса. Код программы.

    курсовая работа [317,4 K], добавлен 11.10.2012

  • Анализ правил выбора хода на шахматной доске К. Шеннона. Характеристика программного модуля искусственного интеллекта для игры в шахматы. Контроль времени, поиск лучшего хода в шахматных алгоритмах. Разработка программы для игры с компьютерным оппонентом.

    дипломная работа [3,7 M], добавлен 07.07.2012

  • Разработка структурной диаграммы программного модуля. Представление схемы для основных расчетов выбранного приложения для создания прямоугольной матрицы. Особенности создания пользовательского интерфейса. Тестирование и отладка спроектированного модуля.

    курсовая работа [648,4 K], добавлен 27.05.2015

  • Особенности разработки кода программного модуля на современных языках программирования. Отладка и тестирование программы, оформление документации на программные средства. Применение инструментальных средств для автоматизации оформления документации.

    отчет по практике [203,8 K], добавлен 12.04.2015

Работы в архивах красиво оформлены согласно требованиям ВУЗов и содержат рисунки, диаграммы, формулы и т.д.
PPT, PPTX и PDF-файлы представлены только в архивах.
Рекомендуем скачать работу.