Методические указания к выполнению курсовой работы по дисциплине «Основы конструирования программ». 1_Меженная_ОКП_Курсовое_проектирование_Пособие. Курсовое проектирование
![]()
|
Требования к программе включают: полный текст варианта задания; исходные данные для курсовой работы (см. подраздел 1.1); функциональные требования к курсовой работе (рекомендуется взять за основу материал подраздела 1.2 и конкретизировать его для определенного вари- анта, например, прописать индивидуальное задание, разновидности поиска и сор- тировки, возможные исключительные ситуации); требования к программной реализации (см. подраздел 1.3). 13 Не следует в разделе требований писать то, что точно не будет реализовано в действительности. Рекомендуется: после завершения работы над программой вернуться к требованиям и проверить соответствие вашей программы тому, что вы заявляли. Разработка модульной структуры программы подразумевает графическое представление структуры программы с указанием модулей, подмодулей и их функциональных возможностей (рисунок 2.1). Для объектно-ориентированного программирования в данном подразделе приводится UML-диаграмма классов. Рисунок 2.1 – Пример модульной структуры программы 14 Важно: рисунок 2.1 с примером модульной структуры программы носит обучающий характер и не предназначен для копирования в пояс- нительную записку! Данный пример нуждается в доработке, а именно: конкретизации индивидуального задания, детализации вариантов сорти- ровки, поиска. Целесооразно также отразить в модульной структуре про- граммы подмодули, отвечающие за чтение и запись информации, с ука- занием моментов их вызова в процессе использования программы. Выбор способов организации данных: 1. В качестве выбора способа описания входных данных приводится описа- ние следующих типов struct (с указанием конкретных полей): для учетных записей пользователей; для данных. В случае объектно-ориентированного программирования приводятся назва- ния предполагаемых классов и содержащихся в них полей. При работе с базой данных дополнительно приводится структура таблиц. 2. В качестве способа объединения входных данных указывается: использование массивов (статически или динамически создаваемых) или векторов; их выбранная область видимости (локальные или глобальные). Разработка перечня пользовательских функций программы подразумева- ет перечисление прототипов функций, необходимых для реализации программы, и краткие комментарии к ним. Код функций не приводится, так как на текущем эта- пе проектирования он еще не существует. Рекомендуется: прототипы функций разбивать на тематические группы в соответствии с модульной структурой программы. При программной реализации целесообразно тематические группы функций реализовать в отдельных cpp-файлах и подключить их к основному файлу с помощью одноименых заголовочных h-файлов. В случае использования объектно-ориентированного программирования при- водятся методы для классов. В сущности, второй раздел пояснительной записки представляет собой про- граммный интерфейс курсовой работы: пользовательские типы данных (структу- ры), переменные массивов или векторов, прототипы всех предполагаемых функ- ций. В дальнейшем необходимо будет к интерфейсу добавить реализацию. 15 Разработка алгоритмов работы программы включает блок-схемы алго- ритмов (с кратким словесным описанием их работы в тексте пояснительной за- писки) для функции main и двух любых других пользовательских функций. В случае использования объектно-ориентированного программирования раз- рабатываются алгоритмы для двух любых методов классов, а также алгоритм функции main. Важно: так как алгоритмы разрабатываются до непосредственного коди- рования, то они не могут содержать просто копии строчек кода.Алго- ритм функции должен отражать логику решения задачи; может содер- жать словесные инструкции с упоминанием имен структур, масси- вов/векторов, других функций. Критерий качественного алгоритма:если разработанный алгоритм без допол- нительных пояснений понятен другому человеку, владеющему основами про- граммирования, и может быть однозначно реализован в коде, значит алгоритм до- стиг своей цели. Признаком хорошего алгоритма также является его относитель- ная компактность (занимает не более одной страницы формата А4); иная ситуация свидетельствует либо о чрезмерно сложной логике решения проблемы, либо о не- достаточной функциональной декомпозиции решаемой задачи (т. е. содержимое функции следовало разбить на несколько функций). Алгоритм должен быть оформлен согласно ГОСТ 19.701-90 Схемы алгорит- мов, программ, данных и систем. Пояснения и примеры разработки алгоритмов приведены в разделе 5 данного пособия. Рекомендуется: при разработке алгоритмовиспользовать профессио- нальные графические редакторы диаграмм и блок-схем, например Microsoft Visio. Описание работы программы подразумевает краткое словесное описание работы программы со скриншотами консоли. Важно: объем пояснительной записки не регламентируется. Основным критерием выступает качество работы. 16 3 Порядок защиты и критерии оценки курсовой работы Защита курсовой работы включает три этапа: 1. Демонстрация функциональных возможностей программы студентом (во избежание проблем совместимости версий и подключения внешних файлов реко- мендуется проводить демонстрацию программы на собственных ноутбуках). От- дельный носитель (диск) с программой не требуется. 2. Анализ содержимого и качества оформления пояснительной записки (обя- зательно: распечатать пояснительную записку и вложить лист-задание). 3. Аудит кода (code review) и вопросы по коду. Итоговая отметка за курсовую работу формируется в результате суммиро- вания баллов за каждый из перечисленных выше этапов защиты. Критерии получения минимальной удовлетворительной оценки за курсовую работу изложены в таблице 3.1. Таблица 3.1 – Критерии получения минимальной удовлетворительной отметки за курсовую работу Результи- рующая отметка Этап защиты, который подлежит оценке Критерии оценки 4 балла 1. Демонстрация функ- циональных возможно- стей программы Реализация базового функционала, а имен- но работа только с данными (без реализа- ции работы с учетными записями пользова- телей): просмотр, добавление, удаление, ре- дактирование данных, выполнение индиви- дуального задания, поиск, сортировка 2. Анализ содержимого и качества оформления пояснительной записки Оформленная в соответствии с требования- ми записка, но без алгоритмов 3. Аудит кода (code review) Владение кодом (исчерпывающие ответы на вопросы по собственному коду) Если все изложенные в таблице 3.1 требования соблюдены, то за каждый этап защиты к минимальной удовлетворительной отметке (4 балла) прибавляются до- полнительные баллы. Это происходит при выполнении описанных в таблице 3.2 критериев. Следует отметить, что за каждый из трех этапов защиты максимально можно набрать по два балла; итого максимальная результирующая отметка за кур- совую работу составит 10 баллов. 17 Таблица 3.2 – Критерии начисления дополнительных баллов за каждый из этапов защиты (баллы прибавляются к исходной отметке, равной 4) Этап защиты, который подле- жит оценке Результирующая отметка Критерии оценки 1. Демонстрация функциональ- ных возможно- стей программы + 1 балл (итог: 5 баллов) Реализация полного функционала (авториза- ция, работа с учетными записями: просмотр, добавление, удаление, редактирование, рабо- та с данными) + 2 балла (итог: 6 баллов) Реализация продвинутого функционала, а именно: обратная связь с пользователем (за- прос на подтверждение удаления, сообщения об успешности выполнения действий), обра- ботка исключительных ситуаций (проверка форматов вводимых данных, проверка суще- ствования номера записи для редактирова- ния/удаления, проверка на уникальность но- вого логина и т. д.); любые другие подходы по усовершенствованию программы (напри- мер, хеширование паролей с «солью») 2. Анализ со- держимого и ка- чества оформ- ления поясни- тельной записки + 1 балл (итог: 7 баллов) Частичная реализация алгоритмов (т. е. один или два вместо трех) или наличие в алгорит- мах ошибок + 2 балла (итог: 8 баллов) Реализация трех алгоритмов без ошибок 3. Аудит кода (code review) + 1 балл (итог: 9 баллов) Качество кода: хорошее (нет дублирования одного и того же кода: вместо этого – функ- ции; каждая функция решает одну задачу; осмысленные имена переменных, констант, функций) + 2 балла (итог: 10 баллов) Качество кода: отличное (функция main не перегружена кодом; сведены к минимуму операции чтения и записи в файл; код снаб- жен комментариями; нет «хардкода»: вместо этого – константы; код разбит на модули в виде отдельных cpp-файлов, которые под- ключаются посредством заголовочных h- файлов; все действия логичны и понятны) 18 4 Рекомендации по проектированию программы 4.1 Принципы и уровни проектирования программы в курсовой работе Для создания качественных (надежных, производительных, легко модифици- руемых) программ необходимо следовать ключевым принципам проектирования: 1. Минимизация сложности. Управление сложностью – самый важный тех- нический аспект разработки программного обеспечения. На уровне архитектуры сложность проблемы можно снизить, разделив систему на подсистемы. В разделе- нии сложной проблемы на простые фрагменты и заключается цель всех методик проектирования программного обеспечения. Чем более независимы подсистемы, тем безопаснее сосредоточиться на одном аспекте сложности в конкретный мо- мент времени. Технологически процесс разделения системы на подсистемы целесообразно выполнять путем декомпозиции исходной задачи на подзадачи. Данный подход получил название нисходящего проектирования и представляет собой процесс дробления задачи на подзадачи, установления логических связей между ними. По- сле этого переходят к уточнению выделенных подзадач. Этот процесс детализации продолжается до уровня, позволяющего достаточно легко реализовать подзадачу на выбранном языке программирования, т. е. до уровня очевидно реализуемых модулей. 2. Простота сопровождения. Проектируя приложение, не забывайте о про- граммистах, которые будут его сопровождать. Постоянно представляйте себе во- просы, которые могут возникать у них при взгляде на создаваемый вами код. Про- ектируйте систему так, чтобы ее работа была очевидной. 3. Слабое сопряжение. Слабое сопряжение предполагает сведение к миниму- му числа соединений между разными частями программы. Это позволит макси- мально облегчить интеграцию, тестирование и сопровождение программы. 4. Расширяемость. Расширяемостью системы называют свойство, позволяю- щее улучшать систему, не нарушая ее основной структуры. Изменение одного фрагмента системы не должно влиять на ее другие фрагменты. Внесение наиболее вероятных изменений должно требовать наименьших усилий. 5. Возможность повторного использования. Проектируйте систему так, что- бы ее фрагменты можно было повторно использовать в других системах. 6. Минимальная, но полная функциональность. Этот аспект подразумевает от- сутствие в системе лишних частей: дополнительный код необходимо разработать, проанализировать, протестировать, а также пересматривать при изменении других фрагментов программы. 19 7. Соответствие стандартам. Попытайтесь придать всей системе привыч- ный для разработчиков облик, применяя стандартные подходы и следуя соглаше- ниям о коде (Code Convention). Применительно к данной курсовой работе, предполагающей реализацию за- дач в процедурном стиле (без использования объектно-ориентированного про- граммирования), процесс проектирования программы целесообразно представить в виде последовательности уровней (рисунок 4.1): 1. Уровень програмной системы: анализ исходной задачи, подлежащей про- граммной реализации. 2. Выбор способов описания и хранения данных: выбор способов хранения информации (файлы), продумывание пользовательских типов данных (структур), выбор способов объединения объектов структур в наборы данных (массивы или контейнеры, например векторы), определение области видимости для выбранных наборов данных (глобальные, локальные). 3. Разделение системы на физически независимые модули (отдельные cpp- файлы) и их подключение с помощью одноименных заголовочных h-файлов. 4. Выделение в рамках каждого модуля прототипов функций (записываются в заголовочные h-файлы). 5. Реализация (описание) функций в соответствующих cpp-файлах. Рисунок 4.1 – Уровни проектирования программы в курсовой работе 20 4.2 Способы организации работы по чтению/записи в файл Выполнение операций чтения/записи в файл должно быть сведено к миниму- му (т. е. после однократной выгрузки данных из файла в массив/вектор дальней- шая работа ведется с этим массивом/вектором, а не происходит многократное счи- тывание данных из файла в каждой функции). Реализовать данный принцип можно двумя способами (рисунки 4.2, 4.3). Рисунок 4.2 – Первый способ организации работы с файлами Рисунок 4.3 – Второй способ организации работы с файлами При первом способе (см. рисунок 4.2) в начале программы осуществляется чтение (импорт) информации из файла в массив или вектор, далее все операции с 21 данными производятся посредством массива/вектора, в конце программы (непо- средственно перед ее закрытием) происходит запись (экспорт) информации из массива или вектора в файл. При втором способе (см. рисунок 4.3) в начале программы осуществляется чтение (импорт) информации из файла в массив или вектор, далее все операции с данными производятся посредством массива/вектора, однако при каждом измене- нии информации дополнительно происходит запись (экспорт) информации из мас- сива или вектора в файл. Важно: первый способ сводит операции перезаписи файла к минимуму (данная операция производитя только один раз – в конце программы). Однако в случае некорректного прерывания сессии все промежуточные изменения будут утеряны. В этом отношении второй способ более надежный, так как осуществляет динамическую выгрузку информации в файл, а потому и более предпочтительный. Следует, однако, отметить, что второй способ при этом уступает первому по временным затратам. 4.3 Принципы организации работы с массивами и векторами В качестве способа объединения входных данных рекомендуется использо- вать массивы (динамически создаваемые) или векторы. Массивы представляют собой набор однотипных пронумерованных данных. Общие особенности работы с массивами: 1. Индексация с 0; индексы массива – только целые положительные числа. 2. Имя массива является указателем на первый байт нулевого элемента мас- сива и хранит соответствующий адрес. 3. Не производится проверка выхода за границы массива (любая попытка проверки делает язык программирования более медленным), в связи с чем требу- ется анализ кода на предотвращение ситуаций, потенциально приводящих к выхо- ду за пределы массива. 4. Массив в функцию всегда передается по указателю. Вместе с массивом в функцию необходимо передавать его размер, так как внутри функции узнать раз- мер массива программным способом будет невозможно: // Вывод содержимого массива на экран void showStudentArray( Student * arr_of_students , int number_of_students ); // Удаление студента из массива void delStudentFromArray( Student * arr_of_students , int & number_of_students ); 22 Рекомендации по работе со статически создаваемыми массивами: 1. Размер памяти, выделяемой для статического массива, необходимо указы- вать как глобальную константу (например, RESERVE_SIZE): struct Student { string name; string surname; int age; }; const int RESERVE_SIZE = 100; void main() { Student arr_of_students[RESERVE_SIZE]; int number_of_students=0; // работа программы } В примере кода, приведенном выше, RESERVE_SIZE имет смысл физическо- го размера массива, а переменная number_of_students – логического размера мас- сива. 2. Способ увеличения размера статически созданного массива (для добавле- ния нового элемента в массив):изначально резервируем память физического раз- мера RESERVE_SIZE, а используем логический размер n<= RESERVE_SIZE. При необходимости n увеличиваем, но при этом контролируем, чтобы n<= RE- SERVE_SIZE. Важно: при недостатке памяти для статически созданного массива по- требуется внести правки в программный код (в части увеличения кон- станты RESERVE_SIZE, отвечающей за размер массива) и перекомпили- ровать программу. В этом смысле программа в рамках курсовой работы, основанная на статических массивах, является ненадежной в практиче- ском использовании; а применение динамически создаваемых массивов или векторов является более целесообразным. 3. При выполнении операции чтения информации из файла в статически со- зданный массив контролируем выход за пределы массива (далее в качестве логи- ческого размера массива фигурирует переменная number_of_students): 23 void readFileStudents( Student * arr_of_students , int & number_of_students ) { ifstream fin(FILE_OF_DATA, ios ::in); // Открыли файл для чтения if (!fin.is_open()) cout << "Указанный файл не существует!" << endl; else { int i = 0; while (!fin.eof()) { if (i < RESERVE_SIZE) { fin >> arr_of_students [i].name >> arr_of_students [i].surname >> arr_of_students [i].age; i++; } else { cout << "Недостаточно памяти для чтения всех данных!" << endl; break ; } } number_of_students = i; } fin.close(); //Закрыли файл } 4. При добавлении новых элементов в статически созданный массив контро- лируем выход за пределы массива (в качестве логического размера массива фигу- рирует переменная number_of_students): void addStudentInArray( Student * arr_of_students , int & number_of_students ) { //добавление студента, если не происходит выход за пределы массива if ( number_of_students + 1 <= RESERVE_SIZE) { number_of_students ++; cout << "Введите имя студента: " ; cin >> arr_of_students [ number_of_students - 1].name; cout << "Введите фамилию студента: " ; 24 cin >> arr_of_students [ number_of_students - 1].surname; cout << "Введите возраст студента: " ; cin >> arr_of_students [ number_of_students - 1].age; writeEndFileStudents( arr_of_students [ number_of_students - 1]); } else cout << "Недостаточно памяти для добавления нового элемента!" << endl; } 5. Для удаления элемента из статически созданного массива необходимо сдвинуть все элементы на одну позицию, начиная с номера удаляемого элемента, а затем уменьшить логический размер массива: void delStudentFromArray( Student * arr_of_students , int & number_of_students ) { int number_of_deleted_item; cout << "Введите номер удаляемой записи: " ; cin >> number_of_deleted_item; number_of_deleted_item--; // пользователь мыслит с 1, но индексы нумеруются с 0 if (number_of_deleted_item >= 0 && number_of_deleted_item < number_of_students ) { for ( int i = number_of_deleted_item; i < number_of_students - 1; i++) { arr_of_students [i] = arr_of_students [i + 1]; } number_of_students --; writeFileStudents( arr_of_students , number_of_students ); } else cout << "Введен некорректный номер удаляемой записи!" << endl; } |