Желательно-ориентированное программирование

Материал из Eludia
Версия от 22:42, 12 марта 2010; Do (обсуждение | вклад)

(разн.) ← Предыдущая | Текущая версия (разн.) | Следующая → (разн.)
Перейти к: навигация, поиск
— Как засунуть верблюда в холодильник в 4 приёма?
— Очень просто:
  1. открыть холодильник;
  2. достать оттуда слона;
  3. засунуть на его место верблюда;
  4. закрыть холодильник.

Школьный фольклор, «Загадка».


В рамках Eludia.pm реализован мини-framework, позволяющий программировать в терминах желаний.

Описание

Желание — это требование к состоянию системы (как правило, к содержимому БД), характеризующееся следующими свойствами:

  • на момент формулировки желание может быть частично или полностью исполненным;
  • выяснить, исполнено ли желание, проще, чем пытаться выполнить его вслепую.

Кроме того, характерным (хотя необязательным) свойством желаний является возможность их группировки: исполнять несколько однотипных желаний вместе проще, чем последовательно.

Для исполнения желаний предусмотрена процедура

wish ($type, $items, $options);

где

$type
тип желаний (строка);
$items
(ссылка на) список желаний;
$options
(ссылка на) хэш с разнообразными опциями.

Алгоритм

Код процедуры в основном сводится к вызовам других процедур, имена которых зависят от $type. Таким образом, framework сам по себе весьма абстрактный, хотя предлагает достаточно удобную декомпозицию подобных задач (как можно убедиться на примере желаний типа table_data). Ниже описаны шаги реализуемого алгоритма.

Уточнение опций

Прежде всего, вызывается

&{"wish_to_adjust_options_for_$type"} ($options);

этот шаг предусмотрен для подстановки всевозможных значений по умолчанию, которые в дальнейшем должны считаться обязательными.

В частности, после этого вызова $options -> {key} должен быть ссылкой на список строк, являющихся именами некоторых компонент в хэшах, из которых состоит @$items. В дальнейшем этот набор полей рассматривается как первичный ключ при сопоставлении желаемого с действительным.

Прояснение требований

Далее аналогичная операция производится с каждым из желаний

&{"wish_to_clarify_demands_for_$type"} ($i, $options)

уже в контексте уточнённых опций. Характерный пример: приведение имени типа данных к каноническому виду в wish table_columns: замена NUMERIC на DECIMAL для MySQL, INT на INT4 для PostgreSQL и т. п.

Исследование текущего состояния

Теперь система выясняет, что из затребованного (или относящегося к нему) уже имеет место в реальности:

$existing = &{"wish_to_explore_existing_$type"} ($options);

Результирующий хэш $existing должен быть проиндексирован по $options -> {key}.

Составление плана действий

Имея в руках текущее и желаемое, система для определяет, какие (минимальные) операции требуется произвести.

Не было ли такого ранее?

Вначале для каждого требования $new ищется его прототип в прошлом:

my $old = delete $existing -> {@$new {@{$options -> {key}}}};

Если такового не находится, то планируется операция с фиксированным именем create. "Планирование операции" означает прописывание в список действий: хэш $todo, о котором рассказано ниже.

push @{$todo -> {create}}, $new

Если было, нужно ли его менять?

Если же прототип найден, то сначала желание уточняется в его контексте:

&{"wish_to_update_demands_for_$type"} ($old, $new, $options);

Необходимость этого шага обусловлена тем, что существующее состояние может отличаться от требуемого в бОльшую сторону — тогда необходимо подправить $new так, чтобы он содержал бОльшие значения параметров из $old. Скажем, если в wish table_columns требуется тип VARCHAR(255) при имеющемся VARCHAR(4000), то $new можно приравнять к $old — тогда никаких действий не потребуется.

Если менять, то как именно?

Когда уточнённое желание всё-таки отличается от прототипа (сравниваются значения функции Dumper от $old и $new), то производится планирование необходимых операций:

&{"wish_to_schedule_modifications_for_$type"} ($i, $existing, $todo, $options);

При отладке функций, связанных с wish, часто возникают ситуации, когда производятся лишние действия. Это связано с тем, что $old и $new, в принципе обозначая одно и то же, имеют небольшие отличия: лишние компоненты, пустые строки или 0 вместо undef и т. п. Выявляются такие ситуации вставкой отладочной печати в wish_to_schedule_modifications_for_$type. Определив различие, обычно следует модифицировать либо wish_to_explore_existing_$type (которая обеспечивает значения $old), либо wish_to_clarify_demands_for_$type (соответственно, $new).

Бывают также различия, обусловленные наличием лишних компонент в $new — они могут вообще не иметь смысла в рамках текущего диалекта SQL или вообще содержать какую-либо информацию, специфичную для приложения. В этой связи бывает полезно обеспечить совпадение наборов компонент уже на выходе из wish_to_update_demands_for_$type:

foreach my $i ($old, $new) {

 %$i = map {$_ => $i -> {$_}} qw (name field_1 field_2 ... field_n);

}

Возвращаясь к wish_to_schedule_modifications_for_$type, отметим, что хэш $todo как на входе в процедуру, так и на выходе имеет следующий вид:

{
 action1 => [args11, args12,... ],
 action2 => [args21, args22,... ],
 ...
}

Процедура wish_to_schedule_modifications_for_$type может приписывать элементы в список и для действия с предопределённым именем create. Это, в частности, имеет смысл в ситуациях, когда соответствующие DDL-команды для создания и изменения имеют одинаковый вид: CREATE OR REPLACE ..., COMMENT ON... и т. п.

То, что не запрошено — может, удалить?

Далее в раках этого же этапа вызывается

&{"wish_to_schedule_cleanup_for_$type"} ($existing, $todo, $options);

которая может спланировать дополнительные действия по "сборке мусора" (типичный пример из wish table_data: удаление того, что было в $existing, но не упомянуто ни в одном $i). Здесь следует проявлять большую осторожность: в большинстве случаев каждое желание покрывают только часть общего требуемого состояния, так что удалять не следует вообще ничего.

Реализация плана

И, наконец, содержимое $todo реализуется в виде вызовов

&{"wish_to_actually_${action}_${type}"} ($todo -> {$action}, $options)

Порядок действий не определён, всегда следует предполагать возможность асинхронного исполнения.

Разное

Идея "Желательно-ориентированного программирования" не претендует на всеобщность и даже на принципиальную новизну. Весьма похожими категориями приходится мыслить, скажем, при использовании утилиты make (типичный Makefile — огромное желание) и её суррогатов. Однако там всё в основном привязано к файловой системе, а wish получился из нескольких процедур для работы с СУБД.

В области же БД wish можно условно считать далеко идущим обобщением инструкций типа ALTER и CREATE OR REPLACE с одной стороны и REPLACE INTO / MERGE INTO — с другой.