ПРОЦЕДУРЫ И ФУНКЦИИ


ВНЕШНИЕ И ВНУТРЕННИЕ ПРОЦЕДУРЫ


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

В ABL выделяют два вида процедур: внутренние и внешние.

Внешние процедуры представляют собой отдельные текстовые файлы с расширением .p. Именем внешней процедуры является имя ее файла. Таким образом, создание внешней процедуры сводится к созданию ее файла.

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

Внешние и внутренние процедуры могут быть как с параметрами, так и без них.


ПРОЦЕДУРЫ БЕЗ ПАРАМЕТРОВ


Внутренняя процедура без параметров описывается следующим образом: 

PROCEDURE proc-name [PRIVATE]:
    ... (тело процедуры)
END [PROCEDURE].

Вызов любой процедуры без параметров осуществляется следующим образом:

RUN proc-name.

где, proc-name - имя вызываемой процедуры, для внешней процедуры это соответственно имя ее файла.

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

Если файл находится в каталоге не перечисленном в системной переменной PROPATH, то в операторе вызова процедуры в качестве proc-name необходимо указать полный путь к ее файлу. В том случае, если файл находится в одном из подкаталогов каталога объявленного в PROPATH, то достаточно указать только недостающую часть пути.

Поиск файла процедуры в каталоге осуществляется в следующей последовательности: proc-name.r (файл скомпилированной процедуры proc-name.p), если скомпилированный файл не найден в каталоге, то AVM осуществляет поиск исходного файла proc-name.p.

Создадим две внешние процедуры hello-word.p и test-proc1.p, в которой осуществим вызов hello-word.p.

hello-word.p:

DISPLAY "Hello, Word".

test-proc1.p:

RUN hello-word.p.

Создадим внешнюю процедуру test-proc2.p, в которой опишем и вызовем внутреннюю процедуру hello-word:

test-proc2.p:

RUN hello-word.
PROCEDURE hello-word:
DISPLAY "Hello, Word".
END PROCEDURE.

Если вызываемая внутренняя процедура hello-word не будет описана в test-proc2.p, то AVM попытается найти соответствующую внешнюю процедуру с именем вызываемой внутренней (hello-word.r, hello-word.p).


ПРОЦЕДУРЫ С ПАРАМЕТРАМИ


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

Объявление параметров осуществляется в теле процедуры, как правило в самом ее начале, и очень схоже с объявлением переменных:

DEFINE {INPUT | OUTPUT | INPUT-OUTPUT} PARAMETER parameter
 
{{AS data-type | LIKE field}
[EXTENT [constant]]}
  [[NOT] CASE-SENSITIVE]
  [FORMAT string]
  [DECIMALS n]
  [INITIAL
    {constant | {[constant [, constant] ... ]}}]
  [COLUMN-LABEL label]
  [LABEL string]
  [NO-UNDO]

parameter – идентификатор параметра;

data-type – тип данных параметра.

Существует три основных типа параметра:

  • INPUT – входной параметр, используется для передачи начальных данных в процедуру при ее вызове;
  • OUTPUT – выходной параметр, возвращаемый процедурой в результате ее выполнения значение;
  • INPUT-OUTPUT смешанный тип, когда один и тот же параметр используется как входной и выходной одновременно.

Вызов процедуры с параметрами осуществляется следующим образом:

RUN proc-name ([parameter-type] parameter-value,...).

parameter-type – тип параметра;

parameter-value – значение параметра.

Последовательность передачи значений параметров при вызове процедуры должна быть точно такой же, в какой они были объявлены в процедуре.

В случае если тип параметра не указан явно, то по умолчанию он является INPUT параметром.

DEF VAR vName1 AS CHAR NO-UNDO.
DEF VAR vName2 AS CHAR NO-UNDO.
DEF VAR vName3 AS CHAR NO-UNDO.

RUN parsing-name("Иванов Иван Иванович",
OUTPUT vName1,
OUTPUT vName2,
OUTPUT vName3).

DISPLAY "Имя: " vName1  "Фамилия: " vName2  "Отчество: " vName3.

PROCEDURE parsing-name:
   DEF INPUT  PARAM vFIO        AS CHAR NO-UNDO.
   DEF OUTPUT PARAM vFirstName  AS CHAR NO-UNDO. /* Имя      */
   DEF OUTPUT PARAM vSecondName AS CHAR NO-UNDO. /* Фамилия  */
   DEF OUTPUT PARAM vThirdName  AS CHAR NO-UNDO. /* Отчество */
   DEF VAR vIO AS CHAR NO-UNDO.
   vSecondName = ENTRY(1,vFIO, " ").
   vFirstName  = ENTRY(2,vFIO, " ").
   vThirdName  = ENTRY(3,vFIO, " ").
END PROCEDURE.

В качестве параметра процедуры так может выступать буфер записи таблицы:

DEFINE PARAMETER BUFFER buffer-name FOR [TEMP-TABLE] table-name
[PRESELECT]

buffer-name - идентификатор буфер записи;

table-name - имя таблицы.

Параметр BUFFER всегда по умолчанию является INPUT-OUTPUT.

Параметром процедуры также могут быть временная таблица (TEMP-TABLE) и ProDataSet:

DEFINE {INPUT | OUTPUT | INPUT-OUTPUT} PARAMETER
{   TABLE FOR temp-table-name [APPEND] [BIND] [BY-VALUE]
  | TABLE-HANDLE temp-table-handle [BIND] [BY-VALUE]
  | DATASET FOR dataset-name [APPEND] [BIND] [BY-VALUE]
  | DATASET-HANDLE dataset-handle [BIND] [BY-VALUE]
}

temp-table-name - идентификатор временной таблицы;

temp-table-handle - указатель временной таблицы, тип HANDLE;

dataset - идентификатор ProDataSet;

dataset-handle - указатель ProDataSet, тип HANDLE.


ВОЗВРАЩАЕМОЕ ЗНАЧЕНИЕ


Помимо выходных параметров, процедуры дополнительно могут возвращать текстовое значение. Определение данного значения осуществляется с помощью оператора RETURN в теле процедуры.

RETURN [return-value |
        ERROR [return-value | error-object-expression] |
        NO-APPLY]

return-value - возвращаемое значение, тип CHARACTER.

Получение возвращаемого процедурой значения в вызывающем ее блоке осуществляется с помощью функции RETURN-VALUE.

Одним из предназначений данной возможности является возврат ошибки, возникшей в теле процедуры. В этом случае оператор RETURN применяется с опцией ERROR.

Задание значений OUTPUT и INPUT-OUTPUT параметров процедуры должны осуществляться до оператора RETURN ERROR.


ОБЛАСТЬ ВИДИМОСТИ КОНТЕНТА ПРОЦЕДУР


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

DEF VAR vText AS CHAR NO-UNDO.

vText = "Hello, Word".
RUN hello-word.


PROCEDURE hello-word:

   DISPLAY vText.
END. 

Внутренние процедуры не могут содержать в себе объявления временных таблиц. Если во внутренней процедуре предполагается работа с временной таблицей, то она должна быть объявлена во внешней процедуре.

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

DEF VAR vText AS CHAR NO-UNDO.
vText = "Hello, Word".
RUN hello-word.
DISPL vText.

PROCEDURE hello-word:
   DEF VAR vText AS CHAR NO-UNDO.
   vText = "Привет, Мир".
   DISPLAY vText.
END. 

Внешние процедуры могут обращаться к одним и тем же переменным находящимся в памяти. Для этого переменная в процедуре, где она создается должна быть объявлена как NEW [GLOBAL] SHARED, а в процедурах, которым она должна быть доступна как SHARED.

По завершению процедуры весь ее контент удаляется.


ИНКЛЮД-ФАЙЛЫ


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

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

Расширение .i является общепринятым для инклюд-файлов, но допускаются указания других расширений с целью группировки инклюд-файлов по типу. Примеры таких расширений представлены в таблице.

 

Расширение
Назначение
.def объявление переменных, временных таблиц и т.д.
.pro описание внутренних процедур
.fun описание функций

 

Использование инклюд-файлов подобно «матрешке», т.е. один инклюд-файл может включать в себя другой инклюд-файл и т.д.

Использование инклюд-файла в процедуре при компиляции увеличивает размер r-файла включая в него сам инклюд-файл. При этом r-файл уже не обращается к исходному инклюд-файлу. В таком случае, при внесении изменений в инклюд-файл необходимо перекомпилировать все процедуры использующих его.


АРГУМЕНТЫ


Процедурам и инклюд-файлам могут быть переданы аргументы. Существует два способа передачи аргументов: по имени аргумента и по его порядковому номеру.

RUN my-proc1.p "loan" "open-date".

my-proc1.p:

FOR EACH {1} WHERE {2} >= 01.01.2011 AND {2} < 01.01.2012:
   DISPLAY loan.cust-corp.
END.

При вызове my-proc1.p ей передаются два аргумента 1 - "loan" и 2 - "open-date". В соответствии со своими порядковыми номерами значения аргументов будут подставлены в код процедуры. Таким образом, my-proc.p будет иметь следующий вид при выполнении:

FOR EACH loan WHERE open-date >= 01.01.2011 AND open-date < 01.01.2012:
   DISPLAY loan.cust-corp.
END.

my-proc2.p:

DEF VAR a AS DECIMAL NO-UNDO.
DEF VAR b AS DECIMAL NO-UNDO.
ASSIGN
  a = 5.5
  b = 3.0.
{my-include.i
&par1 = a
&par2 = b
}

my-iclude.i:

DEF VAR c AS DECIMAL NO-UNDO.
c = {&par1} * {&par2}.
DISPLAY "Результат = " с.

Имя аргумента должно начинаться с символа &. В процедуре my-proc2.p мы подключили инклюд-файл my-include.i с передаваемыми ему аргументами &par1 и &par2, которым присваиваем значения переменных a и b соответственно.


ПРОЦЕДУРНЫЙ УКАЗАТЕЛЬ


Каждой вызываемой процедуре сопоставляется создаваемый автоматически и добавляемый в стек процедур процедурный указатель. Стек представляет собой набор процедурных указателей активных в клиентской сессии процедур. Процедурный указатель в свою очередь представляет собой значение типа HANDLE.

В ABL существует системная переменная THIS-PROCEDURE типа HANDLE, значением которой является указатель процедуры в которой она используется.

Процедурный указатель является ссылкой на процедуру как на объект ООП, обладающий набором атрибутов и методов.

procedure-handle [:attribute | :method]

Атрибуты:

Атрибут Тип данных Доступ Описание
FILE-NAME CHARACTER чтение/запись файл процедуры
PERSISTENT LOGICAL чтение логическое значение TRUE для персистетной процедуры 
HANDLE HANDLE чтение процедурный указатель
NAME CHARACTER  чтение/запись имя процедуры
TYPE CHARACTER  чтение тип указателя, значение "PROCEDURE"
INTERNAL-ENTRIES  CHARACTER чтение список внутренних процедур и функций с разделителем - запятая

Методы:

Метод Описание
ADD-SUPER-PROCEDURE()
GET-SIGNATURE()  возвращает сигнатуру процедуры или функции
REMOVE-SUPER-PROCEDURE()
SET-CALLBACK-PROCEDURE()

GET-SIGNATURE(int-proc-name) - возвращает сигнатуру, указываемого в качестве входного параметра метода, процедуры или функции. Сигнатура представляет собой описание процедуры (функции) и всех ее параметров: идентификатор параметра, тип параметра и тип данных параметра.

Возвращаемая методом сигнатура представляет строку следующего вида:

type, return-type, [mode name p-type [, mode name p-type] ... ]

type - тип процедуры, может принимать следующие значения:

  • PROCEDURE - внутренняя процедура;
  • FUNCTION - внутренняя функция;
  • EXTERNS - внешняя функция;
  • DLL-ENTRY - точка входа DLL;
  • MAIN - основная процедура.

return-type - тип данных возвращаемого функцией значения;

mode - тип параметра (INPUT, OUTPUT, INPUT-OUTPUT и т.д.);

name - идентификатор параметра;

p-type - тип данных параметра.


PERSISTENT ПРОЦЕДУРЫ


Рассматриваемый до этого способ вызова внешней процедуры являлся NON-PERSISTENT. Создаваемый при NON-PERSISTENT вызове процедурный указатель существует исключительно до конца выполнения процедуры и по завершению ее удаляется из стека.

Допустим у нас имеется две внешние процедуры test1.p, в которой объявлена внутренняя процедура hello-word и test2.p, в которой нам необходимо осуществить вызов hello-word из test1.p.

Такой вызов внутренней процедуры из сторонней внешней процедуры имеет следующий синтаксис:

RUN intern-proc IN h_extern [(parametrs)].

intern-proc - вызываемая внутренняя процедура;

h_extern - указатель внешней процедуры содержащей intern-proc;

parametrs - параметры вызываемой внутренней процедуры.

Осуществить такой вызов возможно только в том случае, если в момент вызова в test2.p процедуры hello-word в стеке процедур имеется указатель на test1.p. Таким образом, нам необходимо, чтобы test1.p была вызвана ранее чем test2.p и продолжала быть активной, т.е. ожидала завершения test2.p.

Следовательно решение данной задачи будет выглядеть следующим образом:

test1.p:

RUN test2.p (THIS-PROCEDURE).
PROCEDURE hello-word:
   DISPLAY "Hello, Word".
END.  

test2.p:

DEF INPUT PARAM h_proc AS HANDLE NO-UNDO.
RUN hello-word IN h_proc.

Первой вызываемой процедурой является test1.p, в которой уже осуществляется вызов test2.p с передачей ей в качестве входного параметра процедурного указателя test1.p. Процедура test1.p ожидает завершения test2.p и тем самым является активной на протяжении выполнения test2.p.

В отличие от NON-PERSISTENT, процедурный указатель на PERSISTENT процедуру существует в стеке до завершения клиентской сессии, а не самой процедуры, либо до тех пор, пока он не будет принудительно удален из стека.

RUN extern-proc [ PERSISTENT [SET h_extern] [(parametrs)].

extern-proc - имя внешней процедуры;

h_extern - процедурный указатель, тип HANDLE;

parametrs - параметры процедуры.

Таким образом, решение нашей задачи может быть реализовано несколько иначе:

test1.p:

PROCEDURE hello-word:
   DISPLAY "Hello, Word".
END.

test2.p:

DEF VAR h_proc AS HANDLE NO-UNDO.

RUN test1.p PERSISTENT SET h_proc.
RUN hello-word IN h_proc.

Теперь первой вызываемой процедурой является test2.p и уже в ней, осуществляется вызов test1.p, указатель которой сохраняется в переменной h_proc и продолжает существовать до конца клиентской сессии.

Внутренняя процедура объявленная как PRIVATE не может быть вызвана из сторонних внешних процедур. Ее вызов возможен только из процедуры в которой она объявлена.


УДАЛЕНИЕ PERSISTENT ПРОЦЕДУР


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

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

Удаление персистентной процедуры осуществляется следующим образом:

DELETE PROCEDURE h_extern [NO-ERROR].

При удалении PERSISTENT процедуры могут возникнуть ошибки, например, когда удаляемый указатель h_extern отсутствует в стеке. Для отключения реакции AVM на возникающую ошибку удаления можно использовать NO-ERROR.

Перед удалением рекомендуется предварительно проверять существование процедурного указателя.

IF VALID-HANDLE(h_extern) THEN
    DELETE PROCEDURE h_extern.


ШИФРОВАНИЕ ВНЕШНИХ ПРОЦЕДУР (УТИЛИТА XCODE)


Для защиты исходного кода в PROGRESS предусмотрено его шифрование, для осуществление которого применяется утилита XCODE. Найти ее можно в каталоге $DLC/bin.

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

xcode [ -k key ] -d directory [files] [ - ]

-k key - ключ шифрования. Максимально допустимая длина ключа составляет 8 символов. Если ключа не задан, то для шифрования применяется так называемый default-key (ключ по умолчанию).

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

files - относительный путь файлов, которые необходимо зашифровать.


ФУНКЦИИ


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

Описание функции осуществляется во внешней процедуре.

FUNCTION function-name [RETURNS] return-type [PRIVATE]
   [(parameter [, parameter] ... )]:

   ... (тело функции)

   [RETURN result].
END [FUNCTION].

function-name – имя функции по которому она в дальнейшем будет вызываться;

return-type – тип данных возвращаемого функцией значения;

parametr – параметр функции;

result – возвращаемое функцией значение.

Основным требованием является то, что функция должна быть объявлена до момента обращения к ней. Как правило, объявление функции осуществляется в начале файла внешней процедуры.

Вызов функции осуществляется по ее имени с передачей ей всех параметров:

function-name(param,...).

В отличие от других языков программирования в ABL нет тригонометрических функции. Данные функции (SIN, COS, TG, CTG) можно реализовать самостоятельно, воспользовавшись разложением в ряды Тейлора.

ФУНКЦИЯ SIN(x):

FUNCTION SIN RETURNS DEC (x AS DEC):
   DEF VAR xRad AS DEC  NO-UNDO. /* Радианное значение угла  */
   DEF VAR e    AS DEC  NO-UNDO. /* Точность                  */
   DEF VAR fact AS DEC  NO-UNDO. /* Значение факториала        */
   DEF VAR iTel AS DEC  NO-UNDO. /* Значение i-го элемента ряда */
   DEF VAR iRes AS DEC  NO-UNDO. /* i-результат          */
   DEF VAR n    AS INT  NO-UNDO.

   ASSIGN
      n    = 1
      fact = 1
      xRad = x * 3.14 / 180
      iTel = 1
      e = 0.0000001
   .
   DO WHILE ABS(iTel) > e:
      iTel = (EXP(- 1, (n - 1)) * EXP(xRad, (2 * n - 1))) / fact.
      n = n + 1.
      fact = fact * (2 * n - 1) * ( 2 * n - 2).
      iRes = iRes + iTel.
   END.
   RETURN iRes.
END FUNCTION.

ФУНКЦИЯ cos(x):

FUNCTION COS RETURNS DEC (x AS DEC):
   DEF VAR xRad AS DEC NO-UNDO.  /* Радианное значение угла      */
   DEF VAR e    AS DEC NO-UNDO.  /* Точность                     */
   DEF VAR fact AS DEC NO-UNDO.  /* Значение факториала          */
   DEF VAR iTel AS DEC NO-UNDO.  /* Значение i-го элемента ряда  */
   DEF VAR iRes AS DEC NO-UNDO.  /* i-результат                  */
   DEF VAR n    AS INT NO-UNDO.

   ASSIGN
      n    = 0
      fact = 1
      xRad = x * 3.14 / 180
      iTel = 1
      e = 0.0000001
   .
   DO WHILE ABS(iTel) > e:
      iTel = EXP(- 1, n) * EXP(xRad, (2 * n)) / fact.
      n = n + 1.
      fact = fact * (2 * n - 1) * (2 * n).
      iRes = iRes + iTel.
   END.
   RETURN iRes.
END FUNCTION.

Разместим данные функции в отдельном инклюд-файле trigonometry.fun. Для того чтобы воспользоваться данными функциями нам необходимо будет подключать данный инклюд-файл в процедуре, в которой мы будем их использовать.

DEF VAR x AS DEC NO-UNDO.
{trigonometry.fun}
x = sin(30).
DISPL x.

Важно, что подключение инклюд-файла осуществляется до использования функций в коде процедуры. Если бы мы подключили инклюд-файл с функциями в конце процедуры, то встретив применение функции SIN() в коде, AVM еще бы ничего не знала о ней, что привело бы к ошибке.

Обойти данное требование можно разместив в начале файла процедуры объявление прототипа функции, тогда ее реализация может быть размещена в конце процедуры.

Объявление прототипа функции осуществляется следующим образом:

FUNCTION function-name [RETURNS] return-type
   [(parameter [, parameter  ... )]
   { FORWARD
    | [MAP [TO] actual-name] IN proc-handle
    | IN SUPER
   }. 

Применим объявление прототипа функции SIN():

DEF VAR x AS DEC NO-UNDO.
FUNCTION SIN RETURNS DEC (x AS DEC) FORWARD.
x = SIN(30).
DISPL x.
{trigonometry.fun}

Опция FORWARD указывает на то, что реализация данной функции находится в этом же файле процедуры. Объявление прототипа функции позволило нам осуществить подключение инклюд-файл с ее реализацией в конце процедуры, а не в ее начале.

Объявление прототипа функции дает AVM необходимую информацию о ее сигнатуре. Тем самым, встретив функцию в коде AVM уже знает о ней.

Объявление прототипа функции также позволяет использовать функции из сторонних внешних процедур. Для этого в опции IN необходимо указать handle процедуры, содержащей реализацию функции, которую мы собираемся использовать.

Создадим процедуру pp-lib.p, в которой разместим реализацию наших тригонометрических функций:

pp-lib.p:

{trigonometry.fun}

При объявлении прототипа функций в сторонних процедурах, нам необходимо указать handle процедуры pp-lib.p, следовательно она должна быть активной. Для этого осуществим персистентный вызов процедуры pp-lib.p.

DEF VAR h_lib AS HANDLE NO-UNDO.
RUN lib-fun.p PERSISTENT SET h_lib.
FUNCTION COS RETURNS DEC (x AS DEC)
IN h_lib


DEF VAR x AS DEC NO-UNDO.
x = COS(60).

DISPL x.

DELETE PROCEDURE h_lib.

Что если подключаемая персистентная процедура содержит функцию с именем уже использующимся в процедуре, в которой она подключается? 

При объявлении прототипа функции мы можем указать альтернативное имя функции, а в опции MAP [TO] актуальное имя функции в персистентной процедуре.

DEF VAR h_lib AS HANDLE NO-UNDO.
RUN lib-fun.p PERSISTENT SET h_lib.
FUNCTION FCOS RETURNS DEC (x AS DEC)
MAP TO COS IN h_lib

DEF VAR x AS DEC NO-UNDO.
x = FCOS(60).

DISPL x.

DELETE PROCEDURE h_lib.

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

В случае если тип параметра не указан явно, то по умолчанию он является INPUT параметром.

Вы здесь: Главная Основы ABL ПРОЦЕДУРЫ И ФУНКЦИИ