- Основные подходы к процессу программирования
- Введение
- Основные подходы к процессу программирования.
- Чистая архитектура. Часть II — Парадигмы программирования
- Парадигмы программирования
- Обзор парадигм
- Структурное программирование
- Объектно-ориентированное программирование
- Функциональное программирование
- Заключение
Основные подходы к процессу программирования
Основные подходы к процессу программирования — это основные идеи и понятия, которые определяют стиль написания компьютерных программ.
Введение
Программой является завершенный продукт, который пригоден для запуска на системе, для которой он был разработан. Программным продуктом является программа, которую любой пользователь может запустить, тестировать, корректировать и модифицировать в плане его развития. Подобная программа должна быть написанной в обобщенном стиле, тщательно оттестированной и сопровождаться набором подробной документации.
Программным комплексом является совокупность взаимодействующих программ, которые согласованы по функциям и форматам, четко заданным интерфейсам, и в целом составляют законченное средство для решения больших задач.
Жизненным циклом программного обеспечения является общий период его проектирования и эксплуатации, включая момент появления проекта и оканчивая завершением ее применения.
Основные подходы к процессу программирования.
Методологическую основу программирования составляет набор методик, которые используются в жизненном цикле программного обеспечения и объединены общим философским подходом. Известны следующие широко распространенные методологии программирования:
- Императивная методология программирования.
- Объектно-ориентированная методология программирования.
- Логическая методология программирования.
- Функциональная методология программирования.
Технология программирования исследует технологические процессы и очередность их использования, то есть, стадии, с применением имеющихся знаний, методов и средств. Процессом является набор связанных взаимно операций, которые способны преобразовать заданные исходные данные в выходные. Процессы включают в свой состав совокупность действий, а все действия состоят из совокупности задач. Вертикальное измерение способно отражать статические моменты процессов, и использует такие понятия, как рабочие процессы, действия, задачи, результаты деятельности и исполнители.
Стадией является часть действий по формированию программного обеспечения, которая ограничена определенными временными рамками и заканчивается выдачей конкретного продукта, определяемого при помощи заданных для этой стадии требований. Стадии могут состоять из этапов, как правило, имеющих итерационный характер. Часто стадии могут объединяться в более значительные временные рамки, именуемые фазами. Таким образом, горизонтальное измерение представляет собой время, отображает динамические особенности процессов и использует такие понятия, как фаза, стадия, этап, итерация и контрольная точка.
Технологический подход должен определяться спецификой используемых стадий и процессов, которая ориентирована на различные классы программного обеспечения и на особенности коллектива специалистов по программированию.
Императивное программирование является исторически первой методологией программирования, которую использовали практически все программисты, формировавшие программы на одном из популярных языков программирования, таких как, Basic, Pascal, Cи. Данная методология ориентируется на классическую фон Неймановскую модель, которая оставалась в течение длительного времени единственной аппаратной архитектурой. Методология императивного программирования базируется на принципе последовательного изменения состояния вычислительного устройства при помощи последовательности шагов. Причем управление изменениями является полностью определенным и контролируемым.
Основными методами и концепциями императивного программирования считаются:
- Метод изменения состояний, заключающийся в поочередном изменении состояний. Метод обладает поддержкой концепции алгоритма.
- Метод управления потоком исполнения, заключающийся в осуществлении пошагового контроля управления. Метод обладает поддержкой концепции потока исполнения.
Если вычислителем является современное компьютерное оборудование, то его состоянием могут считаться значения всех ячеек памяти, состояние процессора и всех других связанных с ним устройств. Единственной структурой данных считается последовательность ячеек, то есть, пар «адрес» — «значение», с упорядоченными линейно адресами.
Модульное программирование — это такой метод программирования, при котором всю программу разбивают на группы элементов, именуемых модулями, при этом все они имеют свои контролируемые размеры, четкое предназначение и подробно проработанный интерфейс с внешней средой. Единственной альтернативой модульности может считаться монолитная программа, что, естественно, не всегда может быть удобно.
Структурное программирование появилось в качестве варианта, призванного понизить сложность формирования программного обеспечения. В начале эры программирования работа программистов не имела никаких регламентов. Совокупность решаемых задач не обладала размахом и масштабностью, применялись главным образом машинно-ориентированные языки и близкие к ним языки типа Ассемблера. Формируемые программы не имели больших размеров, не задавалось жестких ограничений на время их проектирования.
Метод структурного программирования был достаточно эффективным при формировании программ, имеющих ограниченную сложность. Но, с ростом сложности разрабатываемых программных проектов, возможности метода структурного программирования оказались недостаточными. В результате появились основные принципы Объектно-Ориентированного Программирования (ООП), которое вобрало в себя все самые лучшие идеи, имеющиеся в структурном программировании. В сочетании с новыми мощными концепциями это позволило по-новому организовывать создаваемые программы.
Необходимо подчеркнуть, что теоретические основы ООП были заложены еще в семидесятых годах двадцатого века, но практическое воплощение они получили только к середине восьмидесятых, то есть, с разработкой соответствующих технических средств.
Чистая архитектура. Часть II — Парадигмы программирования
Эта серия статей – вольный и очень краткий пересказ книги Роберта Мартина (Дяди Боба) «Чистая Архитектура», выпущенной в 2018 году. Начало тут.
Парадигмы программирования
Дисциплину, которая в дальнейшем стала называться программированием, зародил Алан Тьюринг в 1938 году. В 1945 он уже писал полноценные программы, которые работали на реальном железе.
Первый компилятор был придуман в 1951 году Грейс Хоппер (бабушка с татуировкой Кобола). Потом начали создаваться языки программирования.
Обзор парадигм
Существует три основных парадигмы: структурное, объектно-ориентированное и функциональное. Интересно, что сначала было открыто функциональное, потом объектно-ориентированное, и только потом структурное программирование, но применяться повсеместно на практике они стали в обратном порядке.
Структурное программирование было открыто Дейкстрой в 1968 году. Он понял, что goto – это зло, и программы должны строиться из трёх базовых структур: последовательности, ветвления и цикла.
Объектно-ориентированное программирование было открыто в 1966 году.
Функциональное программирование открыто в 1936 году, когда Чёрч придумал лямбда-исчисление. Первый функциональный язык LISP был создан в 1958 году Джоном МакКарти.
Каждая из этих парадигм убирает возможности у программиста, а не добавляет. Они говорят нам скорее, что нам не нужно делать, чем то, что нам нужно делать.
Все эти парадигмы очень связаны с архитектурой. Полиморфизм в ООП нужен, чтобы наладить связь через границы модулей. Функциональное программирование диктует нам, где хранить данные и как к ним доступаться. Структурное программирование помогает в реализации алгоритмов внутри модулей.
Структурное программирование
Дейкстра понял, что программирование – это сложно. Большие программы имеют слишком большую сложность, которую человеческий мозг не способен контролировать.
Чтобы решить эту проблему, Дейсктра решил сделать написание программ подобно математическим доказательствам, которые также организованы в иерархии. Он понял, что если в программах использовать только if, do, while, то тогда такие программы можно легко рекурсивно разделять на более мелкие единицы, которые в свою очередь уже легко доказуемы.
С тех пор оператора goto не стало практически ни в одном языке программирования.
Таким образом, структурное программирование позволяет делать функциональную декомпозицию.
Однако на практике мало кто реально применял аналогию с теоремами для доказательства корректности программ, потому что это слишком накладно. В реальном программировании стал популярным более «лёгкий» вариант: тесты. Тесты не могут доказать корректности программ, но могут доказать их некорректность. Однако на практике, если использовать достаточно большое количество тестов, этого может быть вполне достаточно.
Объектно-ориентированное программирование
ООП – это парадигма, которая характеризуется наличием инкапсуляции, наследования и полиморфизма.
Инкапсуляция позволяет открыть только ту часть функций и данных, которая нужна для внешних пользователей, а остальное спрятать внутри класса.
Однако в современных языках инкапсуляция наоборот слабее, чем была даже в C. В Java, например, вообще нельзя разделить объявление класса и его определение. Поэтому сказать, что современные объектно-ориентированные языки предоставляют инкапсуляцию можно с очень большой натяжкой.
Наследование позволяет делать производные структуры на основе базовых, тем самым давая возможность осуществлять повторное использование этих структур. Наследование было реально сделать в языках до ООП, но в объектно-ориентированных языках оно стало значительно удобнее.
Наконец, полиморфизм позволяет программировать на основе интерфейсов, у которых могут быть множество реализаций. Полиморфизм осуществляется в ОО-языках путём использования виртуальных методов, что является очень удобным и безопасным.
Полиморфизм – это ключевое свойство ООП для построения грамотной архитектуры. Он позволяет сделать модуль независимым от конкретной реализации (реализаций) интерфейса. Этот принцип называется инверсией зависимостей, на котором основаны все плагинные системы.
Инверсия зависимостей так называется, что она позволяет изменить направление зависимостей. Сначала мы начинаем писать в простом стиле, когда высокоуровневые функции зависят от низкоуровневых. Однако, когда программа начинает становиться слишком сложной, мы инвертируем эти зависимости в противоположную сторону: высокоуровневые функции теперь зависят не от конкретных реализаций, а от интерфейсов, а реализации теперь лежат в своих модулях.
Любая зависимость всегда может быть инвертирована. В этом и есть мощь ООП.
Таким образом, между различными компонентами становится меньше точек соприкосновения, и их легче разрабатывать. Мы даже можем не перекомпилировать базовые модули, потому что мы меняем только свой компонент.
Функциональное программирование
В основе функционального программирования лежит запрет на изменение переменных. Если переменная однажды проинициализирована, её значение так и остаётся неизменным.
Какой профит это имеет для архитектуры? Неизменяемые данные исключают гонки, дедлоки и прочие проблемы конкурентных программ. Однако это может потребовать больших ресурсов процессора и памяти.
Применяя функциональный подход, мы разделяем компоненты на изменяемые и неизменяемые. Причём как можно больше функциональности нужно положить именно в неизменяемые компоненты и как можно меньше в изменяемые. В изменяемых же компонентах приходится работать с изменяемыми данными, которые можно защитить с помощью транзакционной памяти.
Интересным подходом для уменьшения изменяемых данных является Event Sourcing. В нём мы храним не сами данные, а историю событий, которые привели к изменениям этих данных. Так как в лог событий можно только дописывать, это означает, что все старые события уже нельзя изменить. Чтобы получить текущее состояние данных, нужно просто воспроизвести весь лог. Для оптимизации можно использовать снапшоты, которые делаются, допустим, раз в день.
Заключение
Таким образом, каждая из трёх парадигм ограничивает нас в чём-то:
- Структурное отнимает у нас возможность вставить goto где угодно.
- ООП не позволяет нам доступаться до скрытых членов классов и навязывает нам инверсию зависимостей.
- ФП запрещает изменять переменные.