Роберт Мартин - Чистая архитектура. Искусство разработки программного обеспечения
- Название:Чистая архитектура. Искусство разработки программного обеспечения
- Автор:
- Жанр:
- Издательство:Питер
- Год:2018
- Город:СПб.
- ISBN:978-5-4461-0772-8
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
Роберт Мартин - Чистая архитектура. Искусство разработки программного обеспечения краткое содержание
Роберт Мартин дает прямые и лаконичные ответы на ключевые вопросы архитектуры и дизайна. «Чистую архитектуру» обязаны прочитать разработчики всех уровней, системные аналитики, архитекторы и каждый программист, который желает подняться по карьерной лестнице или хотя бы повлиять на людей, которые занимаются данной работой.
Чистая архитектура. Искусство разработки программного обеспечения - читать онлайн бесплатно ознакомительный отрывок
Интервал:
Закладка:

Многие идеи функционального программирования появились задолго до появления самого программирования. Эта парадигма в значительной мере основана на -исчислении, изобретенном Алонзо Чёрчем в 1930-х годах.
Квадраты целых чисел
Суть функционального программирования проще объяснить на примерах. С этой целью исследуем решение простой задачи: вывод квадратов первых 25 целых чисел (то есть от 0 до 24).
В языках, подобных Java, эту задачу можно решить так:
public class Squint {
public static void main(String args[]) {
for (int i=0; i<25; i++)
System.out.println(i*i);
}
}
В Clojure, функциональном языке и производном от языка Lisp, аналогичную программу можно записать так:
(println (take 25 (map (fn [x] (* x x)) (range))))
Этот код может показаться немного странным, если вы не знакомы с Lisp. Поэтому я, с вашего позволения, немного переоформлю его и добавлю несколько комментариев.
(println ;___________________ Вывести
(take 25 ;_________________ первые 25
(map (fn [x] (* x x)) ;__ квадратов
(range)))) ;___________ целых чисел
Совершенно понятно, что println, take, map и range — это функции. В языке Lisp вызов функции производится помещением ее имени в круглые скобки. Например, (range) — это вызов функции range.
Выражение (fn [x] (* x x)) — это анонимная функция, которая, в свою очередь, вызывает функцию умножения и передает ей две копии входного аргумента. Иными словами, она вычисляет квадрат заданного числа.
Взглянем на эту программу еще раз, начав с самого внутреннего вызова функции:
• функция range возвращает бесконечный список целых чисел, начиная с 0;
• этот список передается функции map, которая вызывает анонимную функцию для вычисления квадрата каждого элемента и производит бесконечный список квадратов;
• список квадратов передается функции take, которая возвращает новый список, содержащий только первые 25 элементов;
• функция println выводит этот самый список квадратов первых 25 целых чисел.
Если вас напугало упоминание бесконечных списков, не волнуйтесь. В действительности программа создаст только первые 25 элементов этих бесконечных списков. Дело в том, что новые элементы бесконечных списков не создаются, пока программа не обратится к ним.
Если все вышесказанное показалось вам запутанным и непонятным, тогда можете отложить эту книгу и прекрасно провести время, изучая функциональное программирование и язык Clojure. В этой книге я не буду рассматривать эти темы, так как не это является моей целью.
Моя цель — показать важнейшее отличие между программами на Clojure и Java. В программе на Java используется изменяемая переменная — переменная, состояние которой изменяется в ходе выполнения программы. Это переменная i — переменная цикла. В программе на Clojure, напротив, нет изменяемых переменных. В ней присутствуют инициализируемые переменные, такие как x, но они никогда не изменяются.
В результате мы пришли к удивительному утверждению: переменные в функциональных языках не изменяются .
Неизменяемость и архитектура
Почему этот аспект важен с архитектурной точки зрения? Почему архитектора должна волновать изменчивость переменных? Ответ на этот вопрос до нелепого прост: все состояния гонки (race condition), взаимоблокировки (deadlocks) и проблемы параллельного обновления обусловлены изменяемостью переменных. Если в программе нет изменяемых переменных, она никогда не окажется в состоянии гонки и никогда не столкнется с проблемами одновременного изменения. В отсутствие изменяемых блокировок программа не может попасть в состояние взаимоблокировки.
Иными словами, все проблемы, характерные для приложений с конкурирующими вычислениями, — с которыми нам приходится сталкиваться, когда требуется организовать многопоточное выполнение и задействовать вычислительную мощность нескольких процессоров, исчезают сами собой в отсутствие изменяемых переменных.
Вы, как архитектор, обязаны интересоваться проблемами конкуренции. Вы должны гарантировать надежность и устойчивость проектируемых вами систем в многопоточном и многопроцессорном окружении. И один из главных вопросов, которые вы должны задать себе: достижима ли неизменяемость на практике?
Ответ на этот вопрос таков: да, если у вас есть неограниченный объем памяти и процессор с неограниченной скоростью вычислений. В отсутствие этих бесконечных ресурсов ответ выглядит не так однозначно. Да, неизменяемость достижима, но при определенных компромиссах.
Рассмотрим некоторые из этих компромиссов.
Ограничение изменяемости
Один из самых общих компромиссов, на которые приходится идти ради неизменяемости, — деление приложения или служб внутри приложения на изменяемые и неизменяемые компоненты. Неизменяемые компоненты решают свои задачи исключительно функциональным способом, без использования изменяемых переменных. Они взаимодействуют с другими компонентами, не являющимися чисто функциональными и допускающими изменение состояний переменных (рис. 6.1).
Изменяемое состояние этих других компонентов открыто всем проблемам многопоточного выполнения, поэтому для защиты изменяемых переменных от конкурирующих обновлений и состояния гонки часто используется некоторая разновидность транзакционной памяти .
Транзакционная память интерпретирует переменные в оперативной памяти подобно тому, как база данных интерпретирует записи на диске. Она защищает переменные, применяя механизм транзакций с повторениями.
Простым примером могут служить атомы в Clojure:
(def counter (atom 0)) ; инициализировать счетчик нулем
(swap! counter inc) ; безопасно увеличить счетчик counter.

Рис. 6.1.Изменяемое состояние и транзакционная память
В этом фрагменте переменная counter определяется как атом (с помощью функции atom). Атом в языке Clojure — это особая переменная, способная изменяться при очень ограниченных условиях, соблюдение которых гарантирует функция swap!.
Функция swap!, показанная в предыдущем фрагменте, принимает два аргумента: атом, подлежащий изменению, и функцию, вычисляющую новое значение для сохранения в атоме. В данном примере атом counter получит значение, вычисленное функцией inc, которая просто увеличивает свой аргумент на единицу.
Функция swap! реализует традиционный алгоритм сравнить и присвоить . Она читает значение counter и передает его функции inc. Когда inc вернет управление, доступ к переменной counter блокируется и ее значение сравнивается со значением, переданным в inc. Если они равны, в counter записывается значение, которое вернула функция inc, и блокировка освобождается. Иначе блокировка освобождается и попытка повторяется.
Читать дальшеИнтервал:
Закладка: