Файл: Основы проектирования программы. Этапы создания программного обеспечения.pdf
Добавлен: 24.04.2023
Просмотров: 240
Скачиваний: 1
СОДЕРЖАНИЕ
1. Необходимость разработки моделей жизненного цикла
1.1 Увеличение числа разработчиков
1.2 Уменьшение времени тестирования и сборки
1.3 Увеличение сложности приложений
1.4 Проблема эмоционального выгорания
2. Часто используемые модели жизненного цикла программ
2.2 Модель водопада с промежуточным контролем
2.4 Гибкие методологии разработки
2.4.1 Разработка, основанная на тестах
2.4.2 Разработка, основанная на функциях
Введение
После появления первых компьютеров, довольно долго их программирование оставалось уделом энтузиастов-одиночек. Чтобы написать программу для компьютера нужно было довольно долго в нем разбираться. Однако со временем компьютеры все упрощались, программирование становилось все легче, а программистов все больше. Довольно часто возникала ситуация, когда компания нанимала довольно большое число программистов для разработки какой-то нужной для данной фирмы системы. Как правило, эти программисты были мало знакомы друг с другом, и имели разный опыт работы.
Говоря короче, программирование из чего-то элитарного, и доступного лишь избранным стало ремеслом, делом, поставленным на поток, превратилось в некое подобие конвейера. Как Генри Форд в свое время изложил основные принципы конвейерной разработки, что привело к огромному увеличению его скорости, так и в программировании, люди попытались описать необходимые этапы разработки программного обеспечения, и некоторые «практики», которые можно применять на каждом из этих этапов.
В разработке программ появился новый термин, «жизненный цикл программного обеспечения». Изначально он был взят из кибернетики (там это называлось «жизненный цикл системы», и под ним понимались этапы процесса, охватывающие различные состояния системы, начиная с момента возникновения необходимости в такой системе, и заканчивая ее полным выводом из эксплуатации). Применительно к программному обеспечению определение можно перефразировать следующим образом: «жизненный цикл программного обеспечения – это этапы, через которые проходит программа (комплекс программ), начиная с момента возникновения необходимости этого программного обеспечения заканчивая его полным выводом из эксплуатации».
Например, сюда можно отнести следующие этапы создания программы: этап ее проектирования, этап отладки, этап тестирования, этап использования, и много какие еще.
После возникновения данной концепции, она постоянно подвергалась пересмотру и расширению, что связано с тем, что полностью адекватной модели, пригодной для любой программы, пока создать не удается. Как и любая другая модель, «жизненный цикл» это абстракция реальных процессов, а следовательно, она несколько упрощает ситуацию. Обилие моделей возникает как раз из-за того, что различные модели фокусируются на различных сторонах данного процесса.
В данной работе будут рассмотрены несколько моделей жизненного цикла программы, которые чаще всего используются при разработке в настоящее время. Конечно, на самом деле, данных моделей намного больше.
1. Необходимость разработки моделей жизненного цикла
Как уже говорилось выше, модели жизненного цикла возникли тогда, когда потребовалось существенное увеличение скорости разработки программ. Если мы сравниваем современный процесс разработки программ с конвейером, то как и в конвейерном производстве, нам необходимо найти узкие места, задерживающие итоговый выпуск продукта, и постараться ускорить их, либо вовсе избежать. Ниже мы рассмотрим несколько таких узких мест в программировании, а также методы, которые позволяют снизить время, необходимое на их выполнение.
1.1 Увеличение числа разработчиков
Ранее, после возникновения первых компьютеров, программы, как правило, создавались одиночками. В этом были свои плюсы, так как одному человеку гораздо проще понять свое собственное детище, и при необходимости исправлять в нем ошибки или модернизировать. Однако со временем сложность программ нарастала, и довольно быстро подошла к критической точке, когда силами одного человека написать любую мало-мальски сложную и полезную программу стало практически невозможно.
Возьмем какую-нибудь самую простейшую программу, например игру в «крестики-нолики». Если когда-то можно было изобразить крестики и нолики в виде нескольких отрезков и окружностей (а то и вовсе в виде текста), то сейчас очевидно, что придется рисовать их в графическом редакторе, возможно даже в трехмерном, с тенями (а следовательно, нужен будет художник). Однако потом встанет проблема их вывода на экран. Если программист один (и он не пользуется результатами труда других программистов), ему придется разработать свой формат графических файлов. Если же он возьмет стандартный формат, например, PNG, то ему нужно будет реализовывать довольно сложную библиотеку для вывода таких изображений на экран (а ведь внутри формата PNG применяются и другие технологии, например, формат сжатия ZLIB, который тоже придется реализовать). Чтобы вывести текст (например, «выиграли крестики») в таком случае потребуется реализовывать поддержку формата TTF (TrueType Fonts), в котором определена целая виртуальная машина, а также библиотеку для работы с форматом кодирования символов UTF-8.
Естественно, никто в настоящее время не программирует все с нуля. Каждый программист использует множество библиотек. Однако второй проблемой является то, что и в команде разработки программистов может быть много, и работать они должны одновременно.
Представим себе, что у нас есть восемь программистов, мы выбрали язык программирования, необходимые библиотеки, и начали процесс разработки. Мы практически реализовали графический интерфейс пользователя, и программист №5 работает над его финальной доработкой. В это время программист №2 заметил, что если нажать на одну из кнопок в данном интерфейсе, то вся программа ломается. Он берет файл с определением графического интерфейса, меняет строки с ошибками, и загружает его в итоговое дерево кодов. В это время программист №5 доделывает интерфейс, и сбрасывает в итоговое дерево все свои файлы, в том числе и тот, который изменил программист №2. В итоге, несмотря на работу программиста №2 ошибка так и останется не исправленной. В более сложных случаях это и вовсе может привести к полной поломке программы.
Исходя из этого понятно, что требуется какой-то механизм, который позволил бы разработчикам «синхронизировать» свои усилия, не мешая друг другу. Например, данный инструмент должен позволить программисту №2 сохранить свои наработки, а затем, когда программист №5 отправит свои итоговые изменения, он должен объединить два программных кода, создав итоговый.
Такие программные продукты называются «Системами контроля версий», (Version Control Systems, VCS), некоторыми представителями которых являются Git, Mercurial и SVN.
1.2 Уменьшение времени тестирования и сборки
Если ранее, программист мог написать какую-то программу, или новую функциональность, несколько дней, или даже недель тестировать ее вручную, а затем отдать клиенту, то сейчас этот срок существенно сократился.
Если создатель бухгалтерской программы будет по месяцу тестировать каждое изменение, то бухгалтер не сможет работать, так как каждые несколько месяцев выходят новые законы, и он должен постоянно перестраивать процесс своей работы.
Если программист системы учета товаров на складе будет месяц добавлять поддержку нового вида товаров, фирма потерпит огромные убытки.
В современном мире изменения необходимо создавать, тестировать и передавать программу заказчику очень быстро. Это должно занимать максимум несколько дней, а лучше несколько часов. И если процесс программирования из-за появления новых языков постепенно ускоряется, то как быть с тестированием и сборкой? Ведь как уже говорилось выше, как правило, современные программы используют множество библиотек, и перед передачей программы заказчику, все их нужно правильно «упаковать» в один установочный файл.
На самом деле, процесс тестирования и сборки также поддается автоматизации. Для этого существует такой класс программ, который в разных версиях называется «Постоянная интеграция» или «Постоянная доставка» (Continuous Integration/Continuous Delivery, CI/CD). Данные программы позволяют после каждого, даже самого малого изменения автоматически провести тестирование программы (по заранее созданным тестам), а также собрать ее, и (возможно) сразу же выложить на сервер для заказчика.
Примерами таких программ являются Jenkins и TeamCity. Их использование существенно ускоряет процесс создания и изменения программ, а также доставку этих изменений до заказчика.
1.3 Увеличение сложности приложений
Если еще несколько десятков лет назад структура программ была довольно простой, то сейчас она является очень сложной, и все время увеличивается. Количество объектов, которые необходимо постоянно держать в голове также существенно возросло.
Психологами было доказано, что среднестатистический человек может удержать в голове 7 понятий одновременно. Следовательно, при увеличении сложности разработки, будет падать производительность труда. Чтобы этого не произошло, были разработаны средства, позволяющие уменьшить сложность исходного кода, чтобы его было легче понять, и удержать в голове.
Во-первых, это конечно же использование функций и модулей. Это позволяет разбить программу на более короткие блоки, каждый из которых можно будет (теоретически) отлаживать и программировать отдельно. Комментарии же позволяют человеку быстро понять, что именно делает конкретная функция, и как именно.
Однако со временем такого деления на функции и модули стало не хватать. Тогда был разработана технология объектно-ориентированного подхода. Сам этот подход можно описать несколькими положениями:
- Каждый модуль (то есть, набор функций, и относящихся к ним данных) называется классом. Функции класса называются «методы», данные класса – «поля»;
- Любой класс можно «инстанцировать», то есть, «получить экземпляр класса» - копию его методов и полей. Такой «инстанцированный» класс называется «объект». Класс можно инстанцировать сколько угодно раз, каждый раз получая объекты со своими полями;
- Методы любого объекта могут вызывать методы любого другого объекта, но только те, которые им разрешено (в каждом объекте указываются правила доступа к его полям и методам);
- Разрешено создавать класс, опираясь на другой класс (наследоваться от него). При этом все поля и методы старого класса сохраняются, но в классе-потомке можно добавить новые.
Покажем, как данный метод позволяет упростить разработку программного обеспечения на простом примере. Допустим, в нашей программе нужно работать с точками на плоскости. Если бы мы оставались в старой парадигме модулей и функций, нам нужно было бы создать модуль «точка», у которого было бы несколько функций, например «посчитать расстояние от начала координат» или «определить, в какой координатной четверти находится точка».
Однако тут нас поджидает первая сложность – вряд ли во всей программе мы будем использовать лишь одну точку. Точек должно быть много, следовательно, в нашем модуле нужно будет предусмотреть наличие какого-то «пула» (массива) точек. Так как мы не знаем, сколько именно их будет, массив должен быть динамического размера. То есть, еще нам придется реализовать процедуры выделения нужного количества памяти, создания в них структур, описывающих точку, а затем удаления этих структур и очистку памяти (без удаления точек наша память очень быстро переполнится). Все это довольно сложно реализовать, и открывает простор для большого числа ошибок.
Вторая проблема заключается в следующем – допустим, мы взяли на работу начинающего программиста, и он еще не знаком с нашей программой в целом. Ему понадобилось работать с точкой. Он нашел наш модуль, и увидел в нем (например) пятнадцать функций. Не зная, какую функцию положено вызывать в данном случае, он придумал какой-то, как ему показалось, вполне допустимый способ, однако в реальности этот способ приводит к утечке памяти. Пройдет довольно много времени, пока остальные члены команды смогут разобраться с проблемой.
И, наконец, третья проблема состоит в «плохой масштабируемости» данного решения. Допустим, мы решили работать с точками не на плоскости, а в пространстве. Наш модуль совершенно для этого не приспособлен, и нам придется писать новый. При этом, два этих модуля (на плоскости и в пространстве) окажутся никак не связанными, и, если в дальнейшем будут вноситься изменения в какой-то один из них, то другой их не получит, даже если эти изменения одинаковы для точек на плоскости и в пространстве.
Теперь рассмотрим, как объектно-ориентированное программирование позволяет решить данные задачи.
Мы создаем специальный класс «Точка», в котором опишем несколько полей (в данном случае, скорее всего, два – координата X и координата Y), доступ к которым, однако, запрещаем, чтобы никто из других классов не смог их прочитать (если же подобное чтение необходимо, то можно создать два специальных метода, возвращающих эти координаты, они называются геттеры). Теперь, любой программист может средствами языка, поддерживающего объектно-ориентированное программирование, сколько угодно раз «инстанцировать» наш класс – создать объект класса «точка». При этом автоматически будет выделена новая область памяти для данных класса. При создании объекта программист передаст классу некоторые параметры (скорее всего, координаты точки), и они будут запомнены для дальнейшего использования. Таким образом мы решаем первую проблему – нам не нужно работать с памятью, все происходит за нас автоматически.