Про винятки і правилах, Інші СУБД, Бази даних, статті

Автор: Стівен Фернстайн, член колегії Oracle ACE


Де, коли і як краще всього обробляти виключення


Питання: Нещодавно я дізнався, що якщо виключення виникає в розділі оголошень блоку, то розділ обробки виключень цього блоку не обробляє його. Це здається неправильним. Чому PL / SQL працює саме так, і що це означає для моєї практики програмування?


Відповідь: Якщо виключення виникає в розділі оголошень блоку, то воно поширюється в зовнішнє середовище необробленим.


PL / SQL веде себе таким чином (або, якщо бути більш точним, команда розробників PL / SQL вирішила реалізувати обробку винятків саме таким чином) тому, що до тих пір поки локальні змінні і константи повністю не оброблені, програма не є життєздатною. Припустимо, що виключення, що виникло в розділі оголошень, буде оброблятися всередині цієї підпрограми. На що можна буде послатися всередині розділу винятків? Адже не можна бути впевненим, що локальні змінні були проініціалізувати.


Ключове питання: як це поведінка впливає на спосіб написання коду? Перед тим, як відповісти на це питання, давайте подивимося, коли ми можемо зіткнутися з цією проблемою.


Винятки в розділі оголошень відбуваються, коли ви намагаєтеся ініціалізувати змінну, оголошену в цьому розділі, таким способом, який викликає виняток. Найбільш часто виникають винятком є, звичайно, ORA -06502 або VALUE _ ERROR, яке виникає (назвемо два сценарії) при спробі привласнити символьної змінної значення, яке занадто велике для неї, або коли ви намагаєтеся привласнити числової змінної нечислові значення. Наприклад:


DECLARE


   l_name VARCHAR2(5) := “STEVEN”;


   l_age NUMBER := “49 Years Old”;


BEGIN


Те ж саме правило для винятків застосовується при ініціалізації змінних, оголошених в пакеті (поза будь підпрограми). Якщо виключення виникає при спробі ініціалізувати змінну рівня пакета, то це виняток буде поширюватися необробленим за межі пакету, навіть якщо розділ ініціалізації містить розділ обробки винятків. У такій ситуації машина PL / SQL все ж реєструє пакет як ініціалізований і дозволяє посилатися на підпрограми і змінні пакета.


Щоб зрозуміти це, розглянемо наступну послідовність кроків і PL / SQL операторів:


1. Я компілюю пакет valerr, який присвоює занадто довге значення пакетної символьної змінної. Тіло пакета включає розділ обробки винятків (див. лістинг 1).


2. Лістинг 1: У пакеті присвоюється занадто довге значення пакетної символьної змінної.


3.     PACKAGE valerr


4.     IS


5.         FUNCTION little_name RETURN VARCHAR2;


6.     END valerr;


7.      


8.     PACKAGE BODY valerr


9.     IS


10.      g_name       VARCHAR2 (1)       :=           “Liu”;


11.   


12.      FUNCTION little_name RETURN VARCHAR2


13.      IS


14.      BEGIN


15.                     RETURN g_name;


16.      END little_name;


17.  BEGIN


18.      DBMS_OUTPUT.put_line (“Before I show you the name… “);


19.  EXCEPTION


20.      WHEN OTHERS


21.      THEN


22.                     DBMS_OUTPUT.put_line (          “Trapped the error: ” //             DBMS_UTILITY.format_error_stack ()


23.                                                                                                                                                );


24.                     RAISE;


25.  END valerr;


26. Тепер я намагаюся запустити функцію valerr. little _ name, виняток не обробляється


[У автора: “the exception goes unhandled “, По моєму, якраз навпаки. А.Бачін]:


SQL>           BEGIN


  2                              DBMS_OUTPUT.put_line


(“Name: ” // valerr.little_name);


  3               END;


  4               /


BEGIN


*


ERROR at line 1:


ORA-06502: PL/SQL: numeric or value


error: character string buffer too small


ORA-06512: at “HR.VALERR”, line 3


ORA-06512: at line 2


Цього і слід було очікувати.


27. Але коли я намагаюся викликати цю функцію вдруге, виняток не порушується:


28.  SQL>           BEGIN


29.    2                                DBMS_OUTPUT.put_line


30.  (“Name: ” // Valerr.little_name);


31.    3               END;


32.    4               /


33.   


34.  Name:


35.  PL/SQL procedure successfully completed.


Пакет був помічений як ініціалізований, і виконавчий механізм PL / SQL не намагається знову ініціалізувати його, тому код в розділі ініціалізації ніколи не виконується. Проте все ж таки є можливість викликати всі програми цього пакета, що може привести до неабиякою плутанини і враженню, що помилки не можна відтворити.


Якщо ви коли-небудь зіткнетеся з такою проблемою, просто відключіться і знову приєднаєтеся до своєї схемою. Тоді можна буде відтворити помилку, тому що база даних Oracle тепер змушена спробувати ініціалізувати пакет для нової сесії.


Зверніть увагу, що Oracle розглядає це поведінка як баг (номер 5658561). Якщо ви зіткнулися з такою поведінкою і хочете, щоб Oracle змінив його, пропоную вам зайти на Oracle MetaLink і додати до цього багу своє повідомлення про те, як поточне поведінка завдає шкоди вашому додатком.


Чи повинна ця інформація про обробку винятків змінити ваш спосіб написання коду? Я вважаю, так. Може бути іноді необхідно, щоб виключення з розділу оголошень поширювалося необробленим, хоча, ймовірно, в більшості випадків, краще перехопити виключення всередині блоку і зареєструвати інформацію про помилку.


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


PROCEDURE process_data


IS


    l_name   VARCHAR2 (10) :=


                   “Steven Feuerstein”;


BEGIN


    DBMS_OUTPUT.put_line (l_name);


EXCEPTION


    WHEN OTHERS


    THEN


                   DBMS_OUTPUT.put_line


                                  (  “Trapped the error: “


                                   // DBMS_UTILITY.format_


                                   error_stack ()


                                  );


                   RAISE;


END process_data;


напишіть наступне:


PROCEDURE process_data


IS


    l_name   VARCHAR2 (10);


    PROCEDURE initialize


    IS


    BEGIN


                   l_name := “Steven Feuerstein”;


    END initialize;


BEGIN


    initialize;


    DBMS_OUTPUT.put_line (l_name);


EXCEPTION


    WHEN OTHERS


    THEN


                   DBMS_OUTPUT.put_line


                                  (  “Trapped the error: “


                                   // DBMS_UTILITY.format_


                                   error_stack ()


                                  );


    RAISE;


END process_data;


Тепер, коли я запущу виправлену процедуру process_data, помилка буде перехоплена і оброблена, а потім знову збуджена:


SQL>           BEGIN


    2                             process_data;


    3              END;


    4              /


Trapped the error: ORA-06502:


PL/SQL: numeric or value error:


character string buffer too small


BEGIN


*


ERROR at line 1:


ORA-06502: PL/SQL: numeric or value


error: character string buffer too small


ORA-06512: at “HR.PROCESS_DATA”,


line 19


ORA-06512: at line 2


Те ж саме вірно і для пакетів. У виправленої процедурі valerr в Лістингу 2, в розділі ініціалізації просто викликається процедура ініціалізації.


Лістинг 2: В розділі ініціалізації викликається процедура ініціалізації


  1               PACKAGE BODY valerr


 2 IS


 3               g_name    VARCHAR2 (1);


 4


 5               FUNCTION little_name


 6                               RETURN VARCHAR2


 7               IS


 8               BEGIN


 9                               RETURN g_name;


10                             END little_name;


11


12                             PROCEDURE initialize


13                             IS


14                             BEGIN


15                                             g_name := “Lu”;


16                             END initialize;


17               BEGIN


18                               initialize;


19               EXCEPTION


20                               WHEN OTHERS


21                               THEN


22                                              DBMS_OUTPUT.put_line ( “Trapped the error: ” // DBMS_UTILITY.format_error_stack ()


23                                                                                                                                                             );


24         RAISE;


25               END valerr;


Тепер я повинен зізнатися, що в мене є два зауваження щодо даного мною ради (перемістити присвоювання значень за замовчуванням в окрему підпрограму ініціалізації). По-перше, цьому раді не можна слідувати по відношенню до константам. Значення за замовчуванням повинні бути присвоєні їм під час оголошення. По-друге, у виправленому пакеті valerr (в Лістингу 2) змінна g _ name оголошена в рядку 3, а значення їй присвоюється тільки в рядку 15. У більшості звичайних пакетів, змінні будуть також оголошені в перших рядках пакета, а код ініціалізації буде відстояти від них на сотні, можливо, навіть тисячі рядків. Особисто я не люблю такі відстані.


Присвоєння значень за замовчуванням в тому ж рядку, що й оголошення змінної, здається більш простим для розуміння. Вся інформація в одному місці. Чи повинен я поступитися цим зручністю для читання, щоб поліпшити обробку помилок? Це обгрунтована поступка, але тут розумно використовувати компроміс.


Ось що я пропоную: якщо значення за замовчуванням для змінної або константи є літерали, надайте його при оголошенні, а якщо значення за умовчанням є результатом обчислення виразу, перемістіть це присвоювання в процедуру ініціалізації.


Цей підхід поліпшить читабельність коду, зменшивши ризик виникнення необроблених винятків. Ризик буде мінімальним, оскільки ми звертаємо велику увагу на свій код, коли пишемо його, щоб побачити, що ми присвоюємо значення неправильного типу або неправильного розміру. Наприклад, якщо всі змінні ініціалізувалися літералами, то підпрограма ініціалізації не знадобиться (див. лістинг 3).


Лістинг 3: Змінні ініціалізувалися літеральнимі значеннями.


PROCEDURE process_data


IS


    l_name                                                   VARCHAR2 (100) := “Steven Feuerstein”;


    l_books_sold                           PLS_INTEGER;


    PROCEDURE initialize


    IS


    BEGIN


                   l_books_sold := book_counter.in_circulation (“Oracle PL/SQL Programming”);


    END initialize;


BEGIN


    initialize;


    DBMS_OUTPUT.put_line (


                                   l_name


      //           ” sold “


      //           l_books_sold


      //           ” books.”);


EXCEPTION


    WHEN OTHERS


    THEN


                   q$error_manager.raise_unanticipated;


                   RAISE;


END process_data;


А щоб перехопити виключення, пов’язані з оголошенням, можна використовувати оператор блоку. Оператор блоку – це розділ DECLARE – BEGIN – END, який розміщується всередині розділу винятків. Оскільки цей блок може мати свій власний розділ обробки винятків, можна негайно перехопити виключення, і або обробити помилку, або зареєструвати її, і знову порушити виняток.


Оператори блоку дозволяють відкласти оголошення змінних до тих пір, поки вони не будуть використовуватися в програмі. Оголошення “як раз вчасно” допомагають уникнути довгого одноразового розділу оголошень на початку великих програм, які містять оголошення для кожної змінної, що використовується в програмі.


Припустимо, наприклад, що в процедурі process _ data я не працюю зі змінною 1_ books _ sold до рядка 245 цієї процедури. Замість того, щоб оголошувати цю змінну поруч з l _ name, яка використовується в процедурі негайно, я почекаю до того моменту, коли вона буде потрібно в програмі, і використовую оператор блоку. Тоді я зможу перехопити виключення, яке може виникнути в розділі оголошень. Лістинг 4 містить виправлену процедуру process _ data, яка ілюструє використання оператора блоку.


Лістинг 4: виправлена ​​процедура PROCESS_DATA з оператором блоку


PROCEDURE process_data


IS


    l_name     VARCHAR2 (100) := “Steven Feuerstein”;


BEGIN


    /*


Негайне використання l_name


    */


    IF l_name IS NOT NULL


    THEN


… багато коду тут …


    END IF;


    /*


ще більше коду …


Потім я використовую оператор блоку, щоб оголосити l _ books _ sold


прямо в тій області програми, в якій вона потрібна.


*/


    <>


    DECLARE


                   l_books_sold                           PLS_INTEGER := book_counter.in_circulation (“Oracle PL/SQL Programming”);


    BEGIN


                   IF l_books_sold > 1000


                   THEN


… багато коду тут …


                   END IF;


    EXCEPTION


                   WHEN VALUE_ERROR


                   THEN


                                  q$error_manager.raise_unanticipated


                                                   (“Problem initializing l_books_sold!”);


                                  RAISE;


    END check_books_sold;


   


… і багато коду тут …


END process_data;


Один заключний момент: починаючи з Oracle Database 10 g Release 1, компілятор PL / SQL може видавати рекомендації щодо якості коду. Наприклад, він буде попереджати нас, що деякі рядки коду в підпрограмі ніколи не будуть виконані або є “недосяжними” (PLW -6002). Було б чудово, якби Oracle додав рекомендацію для коду подібного цьому:


DECLARE


    l_name VARCHAR2(5) := “STEVEN”;


    l_age NUMBER := “49 Years Old”;


BEGIN


Тоді не доведеться чекати до запуску програми, щоб виявити цю проблему.


Вихідний код програми в обробнику винятку.


Я переконався, що розміщувати вихідний код програми всередині обробника виключення – поганий стиль програмування. Ми повинні мати можливість видалити всі розділи обробки винятку, і, при відсутності помилок, наш код повинен працювати також. Але бувають ситуації, коли потрібно виконати SELECT INTO (явний однорядковий запит), очікуючи, що він не поверне жодного рядка (інакше кажучи, це правильний результат). Однак база даних Oracle в цьому випадку повертає виняток NO _ DATA _ FOUND, і потім доводиться писати логіку додатка в розділі винятків. Що ж тепер ніколи не використовувати явний оператор SELECT INTO в своєму коді?!


Абсолютно вірно : Вважається поганою практикою розміщувати будь-що в обробнику винятків, окрім коду обробки виключення. Якщо ви ставите код програми в пропозицію WHEN, інші розробники повинні знати, що потрібно дивитися логіку програми в цьому розділі. Оскільки це не є нормою, ця логіка часто залишається непоміченою.


Тому давайте погодимося, що розміщувати код програми в реченні WHEN можна тільки для того, щоб обробити помилку (зазвичай такий код включає реєстрацію і повторний виклик виключення). Справедливо зауважити, що при використанні оператора SELECT INTO в виконуваному розділі, реалізація цього підходу є загадкою. Чи означає це, що ніколи не можна писати SELECT INTO у своїх програмах? Давайте розглянемо це питання.


Явний оператор SELECT INTO викликає NO _ DATA _ FOUND, якщо жоден рядок не обрана і TOO _ MANY _ ROWS, якщо знайдено більше одного рядка. Ці два винятки потребують різної обробки. Ллевеллін пропонує розділити всі виключення на три групи:


· Навмисні,


· Недоречні і


· Несподівані.


Навмисними є винятки, які навмисно порушуються в програмі, як частина нормального поведінки. Прекрасним прикладом програми, яка викликає навмисне виняток, є UTL _ FILE. GET _ LINE , Яка викликає NO _ DATA _ FOUND, коли читання досягло кінця файлу.


Недоречними називаються винятки, які, виникаючи, не є помилкою в логіці програми. Це можуть бути, наприклад, різні умови, пов’язані з даними. Виняток NO _ DATA _ FOUND, викликане SELECT INTO, є недоречним.


Виниклі “важкі помилки”, яких ви не очікували і які можуть вказувати на серйозну проблему в додатку, є несподіваними . TOO _ MANY _ ROWS – це класична несподівана помилка, вона означає, що для первинного або унікального ключа існує дублююче значення.


Перед тим як почати створювати своє Таку програму, визначте стратегію обробки винятків для кожного з цих трьох типів. Коли ви зіткнетеся з конкретним винятком, з’ясуйте, в яку категорію воно потрапляє, і виконайте відповідну дію. Ось керівні принципи, яким я слідую для цих трьох типів виключень:


A. Навмисні. Змініть свою програму так, щоб уникнути розміщення логіки програми в розділі винятків. Наприклад, один із способів застосувати це правило до UTL _ FILE. GET _ LINE показаний в процедурі process _ file на Лістингу 5, яка читає вміст файлу і потім обробляє кожну прочитану рядок. Зверніть увагу на цикл у рядках з 16 по 18: цей цикл виглядає як нескінченний (він не містить оператора EXIT), але насправді він зупиняється, коли UTL _ FILE викликає NO _ DATA _ FOUND.


Лістинг 5: Процедура PROCESS_FILE викликає UTL_FILE.GET_LINE безпосередньо.


 1 PROCEDURE process_file (dir_in IN VARCHAR2, file_in IN VARCHAR2)


 2 IS


 3               TYPE line_t IS TABLE OF VARCHAR2 (32767)


 4                               INDEX BY PLS_INTEGER;


 5


 6               l_file                            UTL_FILE.file_type;


 7               l_lines                      line_t;


 8 BEGIN


 9               l_file :=


10                                             UTL_FILE.fopen         (LOCATION               => dir_in


11                                                 , filename                                                           => file_in


12                                                 , open_mode                                        => “R”


13                                                 , max_linesize                                      => 32767


14                                                                                                                              ) ;


15


16                             LOOP


17                               UTL_FILE.get_line (l_file, l_lines (l_lines.COUNT + 1));


18                               END LOOP;


19               EXCEPTION


20                             WHEN NO_DATA_FOUND


21                             THEN


22 / * Обробка кожного рядка * /


23                                              FOR indx IN 1 .. l_lines.COUNT


24                                              LOOP


25                                                             do_stuff_with_line (l_lines (indx));


26                                              END LOOP;


27


28                                              UTL_FILE.fclose (l_file);


29  END process_file;


Таким чином, розділ обробки виключень перехоплює цю помилку і потім обробляє кожен рядок. На жаль, цей код, що обробляє рядки, знаходиться в розділі обробки винятків. Що робити програмісту?


Ніколи не викликати UTL _ FILE. GET _ LINE безпосередньо! Лістинг 6 показує, як переписати процедуру, щоб вирішити цю проблему. Я створюю локальний модуль get _ next _ line, який викликає UTL _ FILE . GET _ LINE. Він перехоплює NO _ DATA _ FOUND і повертає TRUE у вихідному (OUT) булеве аргументі, якщо досягнуто кінець файлу.


Лістинг 6: виправлена ​​процедура PROCESS_FILE викликає локальний модуль


 1 PROCEDURE process_file (dir_in IN VARCHAR2, file_in IN VARCHAR2)


 2 IS


 3               TYPE line_t IS TABLE OF VARCHAR2 (32767)


 4                               INDEX BY PLS_INTEGER;


 5


 6               l_file                                       UTL_FILE.file_type;


 7               l_lines                      line_t;


 8               l_eof                                       BOOLEAN                                                := FALSE;


 9


10                             PROCEDURE get_next_line (line_out OUT VARCHAR2, eof_out OUT BOOLEAN)


11                             IS


12                             BEGIN


13                                             UTL_FILE.get_line (l_file, line_out);


14                                             eof_out := FALSE;


15                             EXCEPTION


16                                             WHEN NO_DATA_FOUND


17                                             THEN


18                                                            line_out := NULL;


19                                                            eof_out  := TRUE;


20                             END get_next_line;


21               BEGIN


22                             l_file :=


23                                             UTL_FILE.fopen (LOCATION                                     => dir_in


24                                                             , filename                                                             => file_in


25                                                             , open_mode                                          => “R”


26                                                             , max_linesize                         => 32767


27                                                                                                                                        );


28


29                             WHILE (NOT l_eof)


30                             LOOP


31                                             get_next_line (l_lines (l_lines.COUNT + 1), l_eof);


32                                             EXIT WHEN l_eof;


33                             END LOOP;


34


35 / * Обробка кожного рядка * /


36                             FOR indx IN 1 .. l_lines.COUNT


37                             LOOP


38                                             do_stuff_with_line (l_lines (indx));


39                             END LOOP;


40


41                             UTL_FILE.fclose (l_file);


42 END process_file;


Потім я пишу цикл WHILE, який показує, за яких обставин цикл буде завершуватися. Відразу після циклу я завершую іншу логіку програми та закриваю файл.


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


B. Несподівані . Керівні принципи для обробки несподіваних винятків дуже прості. У загальному випадку, необхідно зареєструвати помилку, разом з усією конкретною інформацією про програму, яка допоможе зрозуміти, що її викликало. І потім потрібно знову викликати помилку, щоб запобігти подальшому виконання зовнішнього блоку.


Краще уникати жорстко закодованих операторів INSERT, вставляти дані в журнальні таблиці, натомість покладіться на один загальний повторно використовуваний пакет управління помилками, який буде дбати про всі деталі реалізації за вас.


C. Недоречні . Тепер давайте обговоримо, що робити з недоречними винятками, такими як NO _ DATA _ FOUND. Як і у випадку з навмисними винятками, основне правило – уникати розміщення логіки програми в розділі винятків. Реалізувати це при роботі з недоречними винятками можна, давши програмісту можливість вибрати: чи повинно виняток порушуватися чи ні.


Щоб продемонструвати цей підхід у випадку з NO _ DATA _ FOUND, припустимо, що Сем написав програму, яка повертає ID для відділу з заданим ім’ям:


FUNCTION id_for_name (


    department_name_in IN departments


                   .department_name%TYPE


)


    RETURN departments.department_id%TYPE


IS


    l_return departments


    .department_id%TYPE;


BEGIN


    SELECT department_id


                   INTO l_return


                   FROM departments


    WHERE department_name =


                   department_name_in;


    RETURN l_return;


END id_for_name;


Сандра повинна написати процес пакетної обробки, який читає з проміжної таблиці, яка містить інформацію про відділи. Якщо відділ вже існує, вона повинна підтвердити запит на оновлення цього відділу через Oracle Advanced Queuing. Якщо відділ не існує, потрібно підтвердити запит на додавання нового відділу. Вона пише програму, використовуючи переваги існуючої програми Сема, що зручно для них обох:


PROCEDURE load_from_staging_table


IS


    l_id departments.department_id%TYPE;


BEGIN


    FOR dept_rec IN (SELECT *


                   FROM dept_staging_table)


    LOOP


                   BEGIN


                                  l_id := id_for_name


                                                 (dept_rec.department_name);


                                  submit_update_request (dept_rec);


                   EXCEPTION


                                  WHEN NO_DATA_FOUND


                                  THEN


                                                 submit_add_request (dept_rec);


                   END;


    END LOOP;


END load_from_staging_table;


Якщо назва відділу не знайдено в таблиці, функція id _ for _ name викликає NO _ DATA _ FOUND. Тому Сандра створює анонімний блок всередині циклу, перехоплюючи виняток, поміщаючи логіку “запиту на додавання нового відділу “(submit _ add _ request) у розділі виключень, і продовжує працювати.


Однак це саме те, чого ми хотіли уникнути: логіка програми в розділі обробки винятків. І знову, що робити програмісту?


Щоб усунути цей недолік, перепишіть функцію id _ for _ name і, заодно, все однорядкові запити і функції пошуку (дивися лістинг 7). Цей підхід має декілька основних рис. Перше , Новий параметр propagate _ if _ ndf _ in вказує, чи виключення NO _ DATA _ FOUND (коли воно викликане оператором SELECT INTO) поширюватися за межі функції.


Лістинг 7: Виправлена ​​функція ID_FOR_NAME


 1 FUNCTION id_for_name (


 2               department_name_in               IN                           departments.department_name%TYPE


 3 ,             propagate_if_ndf_in  IN                           BOOLEAN := FALSE


 4 ,             ndf_value_in                                                          IN                departments.department_id%TYPE := NULL


 5 )


 6               RETURN departments.department_id%TYPE


 7 IS


 8               l_return    departments.department_id%TYPE;


 9 BEGIN


10                             SELECT department_id


11                                INTO l_return


12                               FROM departments


13                             WHERE department_name = department_name_in;


14


15                             RETURN l_return;


16               EXCEPTION


17                             WHEN NO_DATA_FOUND


18                             THEN


19                                             IF propagate_if_ndf_in


20                                             THEN


21                                                            RAISE;


22                                             ELSE


23                                                            RETURN ndf_value_in;


24                                             END IF;


25                             WHEN TOO_MANY_ROWS


26                             THEN


27                                             q$error_manager.raise_unanticipated


28                                                 (text_in   =>  “Multiple rows found for department name”


29                                             ,    name1_in  =>  “DEPARTMENT_NAME”


30                                             ,    value1_in =>  department_name_in


31                                            );


32  END id_for_name;


Друге , Новий параметр ndf _ value _ in надає значення, яке буде використовуватися, щоб вказати, що дані не знайдені, якщо виняток не поширюється. Можливо, ви захочете просто повертати NULL, щоб вказати “дані не знайдені” (“no data found”), але це значення (або, точніше, відсутність значення) іноді може бути допустимим значенням стовпця. Навіщо ж жорстко кодувати його?


Третє , Якщо виключення NO _ DATA _ FOUND виникло, то воно розповсюджується за межі функції шляхом повторного виклику (RAISE; в рядку 21), тільки якщо користувач запросив таку поведінку. Інакше функція повертає значення індикатора “дані не знайдені” (“no data found”).


І останнє, якщо виникло TOO _ MANY _ ROWS, утиліта управління помилками реєструє помилку, включаючи ID відділу, який викликав проблему і поширює виняток за межі функції необробленим.


Тепер, використовуючи нову версію функції id _ for _ name, Сандра може переписати свою програму завантаження (дивись Лістинг 8). Вона вирішила використовувати -1 для вказівки, що відділ не знайдений. Вона також “сховала” -1 В константу, щоб код був більш зрозумілим. Вся логіка програми розміщується в виконуваному розділі, а код став більш ясним і простим для розуміння і управління.


Лістинг 8: Виклик зміненої функції ID_FOR_NAME


PROCEDURE load_from_staging_table


IS


    c_no_such_dept        CONSTANT PLS_INTEGER := -1;


    l_id departments.department_id%TYPE;


BEGIN


    FOR dept_rec IN (SELECT * FROM dept_staging_table)


    LOOP


                   BEGIN


                                  l_id :=


                                                 id_for_name (dept_rec.department_name


                                                                                                                , raise_if_ndf_in => FALSE


                                                                                                                , ndf_value_in         => c_no_such_dept


                                                                                                                );


                                  IF l_id = c_no_such_dept


                                  THEN


                                                 submit_update_request (dept_rec);


                                  ELSE


                                                 submit_add_request (dept_rec);


                                  END IF;


                   END;


    END LOOP;


END load_from_staging_table;


Не сподівайтеся, що ви зможете видалити всі розділи винятків в PL / SQL коді, і він буде добре працювати, коли ніякі винятки не виникають. Існування навмисних і недоречних винятків робить це неможливим.


Однак можна заховати ці типи винятків з коду верхнього рівня двома способами. Можна інкапсулювати програму, яка навмисно викликає виключення, і перетворити це виняток у прапор або інший тип даних, або можна дати розробникові можливість вибору: поширити недоречне виключення або повернути значення, що означає, що виключення виникало.


Який би підхід ви не вибрали, найголовніше – це обговорити ці питання перед початком створення наступного додатка.

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*