|
|
|||||||||||||||||||||||||||||
|
Правильное размещение PL/SQLИсточник: oracle Стивен Ферстайн
Лучшая практика размещения, размещения и ещё раз размещения PL / SQL Я пишу пакеты и процедуры как в Oracle Database , так и в Oracle Developer ( Oracle Forms ). Как мне выбрать , где разместить код ? По другому этот вопрос звучит так: "В каком контексте должна быть программа?" То есть, из каких программ приложения она может вызываться? Только из единственной формы? Любой формы? Из единственной программы на сервере? Из любой схемы экземпляра БД? По этим вопросам я принимаю решение, руководствуясь принципом: "Включу программу как можно ближе к тому месту, где она будет использоваться (вызываться)" . Демонстрация размещения и перемещения Для демонстрации различных возможных и подходящих способов определения кода я в этой статье использую следующие бизнес-условия: Моя команда создает приложение для call-центра. Продавцы продукта моей компании звонят нам, когда у них возникают проблемы, а мы помещаем их звонки в очередь, если они не могут быть обработаны сразу. И вот я должен написать программу, которая распределяет необработанные звонки по членам группы поддержки. Пакет, который будет содержать всю логику, называется call_manager. Процедура для распределения необработанных звонков называется distribute_calls. Листинг 1 показывает спецификацию и тело этой программы. Листинг 1: процедура distribute_calls PROCEDURE distribute_calls ( department_id_in IN departments.department_id%TYPE) IS BEGIN WHILE ( calls_are_unhandled ( ) ) LOOP FOR emp_rec IN emps_in_dept_cur (department_id_in) LOOP IF current_caseload (emp_rec.employee_id) < avg_caseload_for_dept (department_id_in) THEN assign_next_open_call (emp_rec.employee_id); END IF; END LOOP; END LOOP; END distribute_calls; Как видите, этот выполняемый раздел легко читается: Пока есть хотя бы один необработанный вызов для каждого сотрудника заданного отдела, проверяем, меньше ли текущая загрузка средней по отделу, и связываем очередной вызов с этим сотрудником. Продолжаем обработку, если остались другие необработанные вызовы. Выполняемый раздел вызывает несколько подпрограмм для выполнения этой работы:
Один момент: ни одна из этих программ ещё не реализована. Я использую проектирование верхнего уровня, которое называется также пошаговым уточнением ( refinement ), чтобы сосредоточиться на программе с обобщенной, высокоуровневой логикой. Этот способ помогает избежать затрат на всякие мелкие подробности . Теперь можно перейти к следующему уровню детализации и обозначить место, откуда должны будут вызываться эти подпрограммы. Как я уже говорил, мое правило следующее: Описание подпрограммы должно быть как можно ближе к месту ее использования. Если следовать этому правилу без какого-либо дополнительного анализа, мне следовало бы описать каждую программу как локальную подпрограмму в самой distribute _ calls , как показано на Листинге 2 (троеточием [...] показано место реализации этих подпрограмм). Листинг 2: Четыре локальные подпрограммы в distribute _ calls PROCEDURE distribute_calls ( department_id_in IN departments.department_id%TYPE) IS FUNCTION calls_are_handled RETURN BOOLEAN IS BEGIN ... END calls_are_handled; FUNCTION current_caseload ( employee_id_in IN employees.employee_id%TYPE) RETURN PLS_INTEGER IS BEGIN ... END current_caseload; FUNCTION avg_caseload_for_dept ( employee_id_in IN employees.employee_id%TYPE) RETURN PLS_INTEGER IS BEGIN ... END current_caseload; PROCEDURE assign_next_open_call ( employee_id_in IN employees.employee_id%TYPE) IS BEGIN ... END assign_next_open_call; BEGIN Процедуры и функции, определенные непосредственно в декларационной секции любого PL / SQL -блока, называются локальными, или вложенными, подпрограммами. В этом примере они могут вызываться только из процедуры distribute _ calls , и это, конечно же, соответствует определению наиближайшего их использования. Но, как только я это сделал, сразу же поймал себя на мысли о других программах, которые уже написаны в этом пакете и о том, что могу захотеть использовать некоторые из этих подпрограмм в программах, которые напишу в будущем. Я вспомнил, например, что на последней неделе написал другую функцию, которая очень похожа на current _ caseload . Она сейчас "живет" в процедуре show _ caseload . Прежде чем реализовывать дважды одну и ту же логику (а значит, необходимо отлаживать и поддерживать код в обоих местах), лучше вытащить функцию current _ caseload и из distribute _ calls , и из show _ caseload . Итак, после небольшой перегруппировки кода, я закончил тело пакета, показанное на Листинге 3. Листинг 3: Перемещение функции current_caseload CREATE OR REPLACE PACKAGE BODY call_manager IS FUNCTION current_caseload ( employee_id_in IN employees.employee_id%TYPE) RETURN PLS_INTEGER IS BEGIN ... END current_caseload; PROCEDURE show_caseload ( department_id_in IN departments.department_id%TYPE) IS BEGIN ... END show_caseload; PROCEDURE distribute_calls ( department_id_in IN departments.department_id%TYPE ) IS BEGIN ... END distribute_calls; END; / И вот я переместил функцию current _ caseload дальше от distribute _ calls , но сделал это, потому что она используется двумя подпрограммами пакета. Поэтому теперь она близка насколько возможно обоим ее использованиям . Однако я ещё не рассмотрел и не вижу необходимости использования current _ caseload извне пакета distribute _ calls , поэтому я не помещаю заголовок current _ caseload в спецификацию пакета. Теперь мое внимание переключилось на avg _ caseload _ for _ dept . Что-то в этой программе выглядит знакомым. Что же это , что же это ? Да ! Моя коллега Сандра прислала на последней неделе письмо по электронной почте, извещающее всех нас, что она сложила в один пакет call _ util несколько полезных утилит, включающих функцию, которая возвращает среднюю загрузку сотрудника . Я хлопнул себя по лбу, откопал письмо, и нашел, что эта функция называется dept _ avg _ caseload . Я проверил, что call _ util у меня есть, и - глядите-ка - функция call _ util . dept _ avg _ caseload вообще-то уже прекрасно реализована в нем, и ждет, когда ее будут использовать. Тогда я возвращаюсь к процедуре distribute _ calls , удаляю функцию avg _ caseload _ for _ dept и заменяю мою выполняемую часть как показано на Листинге 4. Листинг 4: Переделанная исполняемая часть distribute _ calls BEGIN WHILE ( calls_are_unhandled ( ) ) LOOP FOR emp_rec IN emps_in_dept_cur (department_id_in) LOOP IF current_caseload (emp_rec.employee_id) < call_util.dept_avg_caseload (department_id_in) THEN assign_next_open_call (emp_rec.employee_id); END IF; END LOOP; END LOOP; END distribute_calls; Теперь одна из подпрограмм, используемых в моей процедуре, объявлена так далеко, что я не могу даже контролировать ее реализацию, и могу даже никогда не увидеть эту реализацию. Является ли это проблемой ? Нет. Мне больше ничего не надо делать и беспокоиться о ней! Функция call _ util . dept _ avg _ caseload реализована вне моего использования, но она наиболее близка ко всем использованиям из различных пакетов, и поэтому объявлена в спецификации пакета call _ utils . Вау. Я думаю, что закончил оптимизацию расположения определений моих подпрограмм. Я остался с двумя локальными подпрограммами ( calls _ are _ unhandled и assign _ next _ open _ call ), одной программой ( current _ caseload ), определенной непосредственно на уровне пакета (без включения в спецификацию пакета), и еще одной функцией ( call _ util . dept _ avg _ caseload ), которая написана кем-то другим и которая доступна всем схемам с правом на выполнение пакета call _ util . Я надеюсь, что шаги, которые я выполнил, создавая distribute _ calls , помогут вам принимать собственные решения в том, где лучше реализовать ваши сложные, многоуровневые программы. Размещение кода Oracle Developer В этой статье уделялось внимание тому, где и как описать код в Oracle Database , однако такие же правила и логика применимы к Oracle Developer . Я предлагаю делать так:
Если клиентская программа весьма специфичная форма или отчет, опишите ее в этом же модуле. Ссылки по теме
|
|