Перечитывая «Чистую архитектуру» (2)
Парадигма программирования — это модель написания программ. Она подсказывает, какую структуру кода и в каких обстоятельствах следует использовать.
К настоящему времени появились три парадигмы: структурное, объектно-ориентированное и функциональное программирование.
По мнению автора, каждая парадигма не расширяет арсенал архитектора. Напротив, у архитекторов и программистов уже достаточно средств, а эти три парадигмы вводят ограничения на их применение. Именно поэтому они и называются парадигмами.
Структурное программирование
Структурное программирование ограничивает и упорядочивает прямую передачу управления; в частности, оно ограничивает произвольное использование goto.
Декомпозиция программы
Dijkstra стремился доказывать правильность программ с помощью математических рассуждений: сделать программу евклидовой структурой, чтобы соединять уже доказанные конструкции в новые программы и затем выводить корректность всей программы.
Он также обнаружил, что большое количество goto затрудняет декомпозицию программы, хотя все задачи оператора goto можно решать с помощью ветвлений и циклов.
Структурная парадигма показывает, что программы можно декомпозировать: большую задачу разбивают на сочетание высокоуровневых функций, каждую из которых далее делят на низкоуровневые функции, рекурсивно продолжая этот процесс. Кроме того, каждую полученную функцию также можно написать в рамках структурной парадигмы.
Однако программирование с такими формальными доказательствами не стало основным подходом. Чаще применяется научный метод проверки.
Научный метод проверки
Научные теории и законы можно опровергнуть, но нельзя окончательно доказать. Аналогично, тесты могут опровергнуть корректность программы, но не доказать её: «Тестирование может показать наличие ошибок, но не доказать их отсутствие». Применяя научный метод, структурная парадигма побуждает сначала рекурсивно разложить программу на небольшие проверяемые функции, а затем написать соответствующие тесты, пытаясь показать, что функции ошибочны. Если тесты не опровергают их корректность, функции можно считать достаточно правильными, а на этом основании — и всю программу.
Объектно-ориентированное программирование
Объектно-ориентированное программирование ограничивает и упорядочивает косвенную передачу управления.
В частности, объектно-ориентированное программирование использует полиморфизм, чтобы ограничить применение указателей на функции. Их цели можно понимать как ограниченное множество: например, полиморфизм в java обычно возникает между объектами двух классов, связанных наследованием.
Автор также считает полиморфизм главным свойством OOP. С его помощью достигается инверсия зависимостей:
Инверсия зависимостей означает введение интерфейсов, позволяющих полностью управлять зависимостями исходного кода системы независимо от потока управления.
Она позволяет разрабатывать систему как набор плагинов. Например, инвертировав зависимости между web UI, базой данных и бизнес-логикой, можно отделить основную бизнес-логику от UI и базы данных. Тогда UI и база данных становятся её плагинами.
Объектно-ориентированное программирование — это способность управлять зависимостями исходного кода посредством полиморфизма. Она позволяет архитектору создавать плагинную архитектуру, отделяя высокоуровневые правила от низкоуровневых реализаций. Низкоуровневые компоненты можно компилировать в плагины, разрабатывать и развёртывать независимо от высокоуровневых.
Функциональное программирование
Функциональное программирование ограничивает и упорядочивает присваивание в программе.
Иными словами, переменные в функциональном языке неизменяемы.
Автор высказывает следующую мысль; это цитата из оригинала:
«Все гонки, взаимные блокировки и проблемы параллельного обновления обусловлены изменяемыми переменными. Если переменные никогда не изменяются, гонки и конфликты параллельного обновления невозможны. Если состояние блокировки неизменно, взаимная блокировка никогда не возникнет».
Если не учитывать ограничения памяти и быстродействия процессора, неизменяемость осуществима. Но на практике эти факторы учитывать необходимо, поэтому реализовать её можно лишь в определённой мере. Для этого требуется изоляция изменяемости.
Изоляция изменяемости
Распространённый способ изолировать изменяемость — разделить приложение или его внутренние службы, выделив изменяемые и неизменяемые компоненты. Неизменяемые компоненты выполняют задачи чистыми функциями, не меняя никакого состояния. Для изменения состояния переменных они взаимодействуют с одним или несколькими нефункциональными, то есть изменяемыми, компонентами.
Пример — система управления версиями GIT. Она фиксирует добавление, удаление и изменение файлов с помощью перемещения указателей. При этом сами файлы фактически не изменяются и не удаляются: остаются только добавление и поиск. Разве это не пример неизменяемости?
Эта же идея используется в управлении транзакциями mysql и в транзакционной памяти.
Итоги
Три парадигмы тесно связаны с архитектурой ПО:
- Полиморфизм позволяет пересекать границы.
- Функциональное программирование упорядочивает и ограничивает размещение данных и права доступа к ним.
- Структурное программирование служит основой реализации модулей.
Эти три парадигмы совпадают с тремя главными заботами архитектуры ПО: независимостью компонентов (объектно-ориентированная парадигма), управлением данными (функциональная парадигма) и функциональностью (структурная парадигма).
Ещё одно размышление
Все три парадигмы вводят для программиста новые ограничения. Каждая ограничивает какой-либо способ написания кода; ни одна не добавляет новых возможностей. Иными словами, парадигмы говорят нам, чего делать не следует.
(Конец второй статьи)