На примере программы «Жизнь» обсудим общий подход к программированию, применяемый повсеместно, можно сказать, технологию — метод декомпозиции (пошаговой детализации). Суть этой технологии заключается в том, что исходную задачу программист сводит к нескольким другим, более частным, специальным задачам (подзадачам). Те, в свою очередь также сводятся нескольким подзадачам и так далее. Каждое такое действие — шаг декомпозиции. Процедурные языки, к которым относится и Perl, очень хорошо поддерживают технику декомпозиции, поскольку для каждой задачи/подзадачи можно запрограммировать процедуру, решающую её.
Сколько должно быть шагов декомпозиции? Это зависит, конечно, от сложности поставленной задачи, от привычек программиста, от требований к эффективности программы. Вряд ли следует оформлять такую мелкую задачу, как сложение двух чисел, как процедуру.
Какие преимущества даёт технология декомпозиции? Во-первых, декомпозиционный подход широко применяется в отраслях человеческой деятельности, даже не связанных с компьютерами и программированием. Представим себе, к примеру, работу художника, писателя, архитектора. Художник прежде набрасывает эскиз будущей картины в самых общих чертах, обводит контуры. Затем закрашивает большие участки. Затем приступает к тонкостям — теням, цветовым переходам и прочим нюансам. Декомпозиция является естественным подходом к выполнению любой более-менее сложной работы.
Во-вторых, разделение задачи на подзадачи позволяет разделить труд между членами коллектива исполнителей. К примеру, руководитель проекта, совершив один шаг декомпозиции, раздаёт задания руководителям отделов. Те, в свою очередь, делают следующий шаг декомпозиции, и распределяют задачи среди своих подчинённых. Рядовые исполнители, призванные реализовать отдельную процедуру, могут и не знать общего замысла. Они получают спецификацию — что подаётся на вход процедуры, и что должно быть на выходе.
В-третьих, программирование с использованием метода декомпозиции приводит
к более ясным для понимания программам. Особенно если программист даёт
переменным и процедурам «говорящие» имена, из которых понятно их
предназначение. Конечно, понять, что делает процедура xxx
,
или для чего создавалась переменная z80
, невозможно. Другое
дело переменная lineCount
или процедура
drawTriangle
. Ясная и выдержанная в едином стиле
номенклатура переменных и процедур — важная обязанность программиста; те, кто
позволяет себе давать имена вроде xxx
или
z80
, профнепригодны.
[Четвёртая выгода — топологическая: команды, относящиеся к решению определённой
программы. Хорошо, если тело процедуры помещается на экране редактора целиком —
пара десятков строчек].
Приступим к разработке программы, применяя метод декомпозиции.
Прежде всего заметим, что все операции, которые будут выполняться в программе,
можно подразделить на два вида. Первые — это те, которые будут выполняться
однократно, либо в начале программы, либо непосредственно перед её завершением.
Это выяснение размера экрана, его очистка, выключение курсора, случайная
расстановка живых/мёртвых клеток в начальном кадре, установка обработчика
сигнала SIGINT
(в начале); очистка, благодарности
и прощание, восстановление курсора, выход (в конце работы). Операции второго
вида включают поочерёдный перебор клеток и выяснение их участи на следующем
кадре (выживет/умрёт/оживёт/останется мёртвой), а также отрисовка кадра.
Последние будут исполняться в бесконечном цикле, выход из которого возможен
только лишь в результате внешнего вмешательства — обработки сигнала
(SIGINT
, к примеру).
Логично было бы действия, выполняемые однократно в начале работы, объединить
в процедуру init
(от initiate, начинать). Однократные действия в конце
работы — в процедуру term
(от terminate, завершить). Процедуру, где перебираются
все клетки поля и выясняется их дальнейшая судьба, назовём
step
(шаг). И, наконец, отрисовкой экрана займётся
процедура frame
(кадр).
Таким образом, первый шаг декомпозиции даёт следующий грубый набросок будущей программы:
Perl# Декларативная часть программы — # объявления и определения процедур sub init { … } sub term { … } sub step { … } sub frame { … } # Исполняемая часть программы init; while(1) { step; frame; }