Локальные модули PL/SQL: вложенные функции и процедуры

Вложенные процедуры и функции PL/SQLЛокальный (или вложенный) модуль — это процедура или функция PL/SQL, определяемая в разделе объявлений блока PL/SQL (анонимного или именованного). Локальным такой модуль называется из-за того, что он определяется только внутри родительского блока PL/SQL и не может быть вызван из другого блока, определенного вне родительского. Как показано на рис. 1, блоки, внешние по отношению к определению процедуры, не могут непосредственно вызывать ее локальные процедуры или функции.


Оглавление статьи[Показать]


 

Локальные модули недоступны вне процедуры

Рис. 1. Локальные модули недоступны вне процедуры

 

Синтаксис определения процедуры или функции не отличается от синтаксиса созда­ния отдельных модулей. Например, в приведенном ниже анонимном блоке объявлена локальная процедура show_date:

DECLARE
   PROCEDURE show_date (date_in IN DATE) IS 
   BEGIN
      DBMS_OUTPUT.PUT_LINE (TO_CHAR (date_in, 'Month DD, YYYY');
   END show_date;
BEGIN
   ...
END ;

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

 

Преимущества локальных модулей

Использование локальных модулей обеспечивает следующие преимущества:

  •  Уменьшение объема кода за счет исключения повторяющихся фрагментов. Самая распространенная причина для создания локальных модулей. Уменьшение объема кода сопровождается повышением его качества, так как число строк и количество потенциальных ошибок в них уменьшаются, а поддержка кода значительно упро­щается. Изменения вносятся только в локальном модуле, а результаты немедленно проявляются в родительском модуле.
  •  Улучшение удобочитаемости кода. Даже если модуль не содержит повторяющихся блоков кода, взаимосвязанные операторы лучше поместить в локальный модуль. В этом случае вам будет легче отслеживать логику родительского модуля.

 

Уменьшение объема кода

Рассмотрим пример уменьшения объема кода. Процедура calc_percentages получает числовые значения из пакета sales (sales_pkg), вычисляет процент отдельных продаж для общего объема продаж, передаваемого в параметре, после чего форматирует число для вывода в отчете или на форме. В следующем примере задействованы всего три вы­числения, но я извлек его из коммерческого приложения, в котором их было 23!

PROCEDURE calc_percentages (total_sales_in IN NUMBER)
IS
   l_profile sales_descriptors%ROWTYPE;
BEGIN
   l_profile.food_sales_stg :=
      TO_CHAR ((sales_pkg.food_sales / total_sales_in ) * 100, '$999,999');
   l_profile.service_sales_stg :=
      TO_CHAR ((sales_pkg.service_sales / total_sales_in ) * 100,'$999,999');
   l_profile.toy_sales_stg :=
      TO_CHAR ((sales_pkg.toy_sales / total_sales_in ) * 100,'$999,999');
END;

Этот код (относительно) долго пишется, занимает больше места, чем необходимо, и соз­дает проблемы при сопровождении. А если понадобится изменить формат, к которому преобразуются числа? А если изменится формула вычислений? Придется вносить из­менения во все вычисления.

С локальными модулями я могу выделить общий, повторяющийся код в функцию, которая будет многократно вызываться в calc_percentages. Версия локального модуля этой процедуры выглядит так:

PROCEDURE calc_percentages (total_sales_in IN NUMBER)
IS
   l_profile sales_descriptors%ROWTYPE;
   /* Функция определяется прямо в процедуре! */
   FUNCTION pct_stg (val_in IN NUMBER) RETURN VARCHAR2 
   IS
   BEGIN
      RETURN TO_CHAR ((val_in/total_sales_in ) * 100, '$999,999');
   END;
BEGIN
   l_profile.food_sales_stg := pct_stg (sales_pkg.food_sales); 
   l_profile.service_sales_stg := pct_stg (sales_pkg.service_sales); 
   l_profile.toy_sales_stg := pct_stg (sales_pkg.toy_sales);
END;

Все сложности вычисления, от деления на total_sales_in до умножения на 100 и форма­тирования функцией TO_CHAR, были выделены в функцию pct_stg. Эта функция опреде­ляется в разделе объявлений в процедуры. Вызов этой функции из тела calc_percentages упрощает чтение и сопровождение команд процедуры. Если формула вычислений изменится, то изменения достаточно внести всего в одной функции, а они отразятся на всех операциях присваивания.

 

Улучшение удобочитаемости кода

Локальные модули способны кардинально упростить чтение и сопровождение кода. По сути, локальные модули позволяют очень близко воспроизвести методологию нисходяще­го проектирования или пошаговой проработки. Те же приемы могут использоваться для декомпозиции или рефакторинга существующих программ, чтобы упростить их чтение.

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

Я рекомендую включить в ваши стандарты программирования ограничение, со­гласно которому длина исполняемых разделов в блоках PL/SQL не превышает 60 строк (объем текста, помещающегося на экране или странице). Это может по­казаться странным, однако соблюдение этих рекомендаций не только возможно, но и очень полезно.

Предположим, программа содержит серию циклов WHILE (в том числе и вложенных), тела которых содержат серию сложных вычислений и глубоко вложенную условную логику. Даже с обширными комментариями разобраться в логике программы, разнесенной на несколько страниц, будет нелегко — особенно если завершители END IF и END LOOP на­ходятся на разных страницах с открывающими командами IF и LOOP.

Напротив, если извлечь последовательность взаимосвязанных команд, разместить их в одном или нескольких локальных модулях, а затем вызвать эти модули в теле программы, такая программа буквально будет документировать сама себя. Процедура assign_workload предоставляет упрощенную версию этого сценария, которая наглядно демонстрирует преимущества локальных модулей:

PROCEDURE assign_workload (department_in IN emp.deptno%TYPE)
IS
   CURSOR emps_in_dept_cur (department_in IN emp.deptno%TYPE)
   IS
      SELECT * FROM emp WHERE deptno = department_in;

   PROCEDURE assign_next_open_case
      (emp_id_in IN NUMBER, case_out OUT NUMBER)
   IS
   BEGIN ... полная реализация ... END;

   FUNCTION next_appointment (case_id_in IN NUMBER)
      RETURN DATE 
   IS
   BEGIN ... полная реализация ... END;

   PROCEDURE schedule_case
      (case_in IN NUMBER, date_in IN DATE)
   IS
   BEGIN ... полная реализация ... END;

BEGIN /* main */
   FOR emp_rec IN emps_in_dept_cur (department_in)
   LOOP
      IF analysis.caseload (emp_rec.emp_id) < 
         analysis.avg_cases (department_in);
      THEN
         assign_next_open_case (emp_rec.emp_id, case#); 
         schedule case
            (case#, next_appointment (case#));
      END IF;
   END LOOP
END assign_workload;

Процедура assign_workload состоит из трех локальных модулей:

assign_next_open_case
next_appointment
schedule_case

Она также зависит от двух пакетных программ, которые уже существуют и легко под­ключаются к этой программе: analysis.caseload и analysis.avg_cases. Для понимания логики, лежащей в основе assign_workload, неважно, какой код выполняется в каждой из них. Я могу просто положиться на имена этих модулей в процессе чтения основного кода. Даже без комментариев читатель кода будет четко представлять, что делает каждый модуль. Конечно, если вы предпочитаете обеспечивать самодокументирование кода на уровне именованных объектов, вам придется придумать очень содержательные имена для ваших функций и процедур.

 

Область действия локальных модулей

Модульный раздел объявлений похож на тело пакета. Тело пакета тоже содержит определения модулей. Главное различие между локальными и пакетными модулями связано с их областью действия. Локальные модули могут вызываться только из блока, в котором они определяются; пакетные модули могут (как минимум!) вызы­ваться из любой точки пакета. Если пакетные модули также включены в спецификацию пакета, они могут вызываться другими программными единицами из схем, имеющих привилегии EXECUTE для этого пакета.

Следовательно, локальные модули следует применять только для инкапсуляции кода, который не нужно вызывать за пределами текущей программы. А если нужно — соз­дайте пакет!

 

Вложенные подпрограммы

Каждый раз, когда я пишу сколько-нибудь нетривиальную программу длиной более 20 строк, я создаю один или несколько локальных модулей. Это помогает мне следить за логикой решения; я могу рассматривать свой код на более высоком уровне абстракции, присваивая имя целой последовательности команд, а также выполнять нисходящее проектирование и пошаговую проработку требований. Наконец, модуляризация кода даже в рамках одной программы позволяет легко выделить вложенную подпрограмму и создать действительно автономную процедуру или функцию, пригодную для повтор­ного использования.

Конечно, логику также можно вывести из локальной области действия и преобразовать ее в отдельную программу уровня тела пакета (если предположить, что код пишется в составе пакета). Этот подход сокращает глубину вложения локальных процедур, что может быть полезно. Однако он может привести к появлению тел пакетов, содержащих очень длинные списки программ, многие из которых используются только из другой программы. Мой общий принцип заключается в том, что определение элемента должно располагаться как можно ближе к месту его использования, что естественным образом приводит к созданию вложенных подпрограмм.

Надеюсь, сейчас вам вспомнилась какая-нибудь из написанных вами программ. Вер­нитесь к ней и устраните повторяющийся код, почистите логику — в общем, сделайте программу более понятной для другого читателя. Не боритесь с искушением. Проведите модуляризацию своего кода.

Чтобы помочь вам в определении вложенных подпрограмм и работе с ними в ваших приложениях, я создал пакет TopDown. При помощи этого пакета вы тратите немного времени на расстановку в коде «индикаторов» (фактически инструкций относительно того, какие вложенные подпрограммы вы хотите построить и как именно). Затем вы компилируете этот шаблон в базу данных, вызываете TopDown.Refactor для программного модуля — и вложенные подпрограммы строятся автоматически!

Процесс можно повторить на каждом последующем уровне структуры вашей программы. При этом быстро формируется модульная архитектура, которую вы (и ваши коллеги) непременно оцените в будущем.

Более полное описание пакета TopDown, исходный код и примеры сценариев приводятся в файле TopDown.zip.

Вас заинтересует / Intresting for you:

Управление приложениями PL/SQL...
Управление приложениями PL/SQL... 3109 просмотров Stas Belkov Thu, 16 Jul 2020, 06:20:48
Встроенные методы коллекций PL...
Встроенные методы коллекций PL... 7004 просмотров sepia Tue, 29 Oct 2019, 09:54:01
Работа с числами в PL/SQL на п...
Работа с числами в PL/SQL на п... 16431 просмотров Antoniy Mon, 28 May 2018, 16:45:11
Основы языка PL/SQL: использов...
Основы языка PL/SQL: использов... 3031 просмотров Ирина Светлова Tue, 06 Feb 2018, 14:04:03
Войдите чтобы комментировать