Том Кайт о сервере Oracle Database 11g - Если WHEN OTHERS, то ничего не возвращается

ОГЛАВЛЕНИЕ


Если WHEN OTHERS, то ничего не возвращается

В выпуске этой колонки за июль-август 2007 (русский перевод: "Том Кайт: об игнорировании, блокировании и разборах") я писал о программной конструкции языка PL/SQL ¬- обработчике исключительных ситуаций WHEN OTHERS. Я писал: "В языке PL/SQL есть крайне нежелательная для меня программная конструкция WHEN OTHERS."

Я знаю, что корпорация Oracle никогда не будет устранять эту особенность языка, но сейчас она сделана более безопасной. Только чтобы напомнить, почему я хочу, чтобы эта программная конструкция не существовала, я снова процитирую себя:
Проблема заключается в том, что многие используют предложение WHEN OTHERS без последующего инициирования исключительных ситуаций (вызовы RAISE или RAISE_APPLICATION_ERROR). Это фактически скрывает ошибку. На самом деле ошибка происходит, но она не обрабатывается каким-то осмысленным образом, а просто игнорируется - молча. Вызывающий вашего кода не имеет никакого понятия, что случилось нечто чрезвычайное и ваш код сбился, обычно он думает, что на самом деле все работает успешно.

Много раз, очень много раз, нежели я могу вспомнить или сосчитать, причиной "странного" поведения программных модулей PL/SQL оказывалось ненадлежащее использование предложения WHEN OTHERS - за ним не следовал вызов RAISE. Ошибка скрывается, обработка ошибок фактически отключается, а в результате данные обычно логически разрушаются (приложение не делает свою работу) или получается неверный ответ.

Теперь в сервере Oracle Database 11g я легко могу найти кусок проблемного кода. Когда я полагаю, что была инициирована и скрыта исключительная ситуация, я быстро могу проверить мое подозрение. Рассмотрим следующую безобидную процедуру:
SQL> create table t( x varchar2(4000) );
Table created.

SQL> create or replace
  2  procedure maintain_t
  3  ( p_str in varchar2 )
  4  as
  5  begin
  6    insert into t
  7    ( x ) values
  8    ( p_str );
  9  exception
 10    when others
 11    then
 12      -- call some log_error() routine
         -- вызов некоторой подпрограммы протоколирования ошибок
 13      null;
 14  end;
 15  /
Procedure created.
Она настолько проста, насколько это возможно. Ничего не должно работать неправильно, но для случаев, когда происходит ошибка, я протоколирую ее, используя написанную мною вспомогательную процедуру. Где-то она пишет хорошее сообщение об ошибке (будем надеяться!), а, случается, что кто бы не вызвал эту подпрограмму, он не будет иметь понятия, что произошла непредвиденная исключительная ситуация, поэтому она не может быть локализована. (Я постоянно встречаю подобный код.) Теперь, когда кто-то вызовет эту подпрограмму:

SQL> exec maintain_t( rpad( 'x', 4001,
'x' ) );
PL/SQL procedure successfully completed.
Ее выполнение кажется успешным, но это не так:
SQL> select * from t;
no rows selected
Здесь и начинается неразбериха: пользователи "обрывают" телефон, пишут электронные письма, они говорят: "Сломался сервер Oracle Database, транзакция завершилась успешно, однако данные - некорректные". На самом деле, проблема заключается в скрытой ошибке. Теперь в среде сервера Oracle Database 11g для обнаружения таких ошибок я просто буду просить людей сначала выполнить над их кодом следующие действия:
SQL> alter procedure maintain_t compile
  2  PLSQL_Warnings = 'enable:all'
  3  reuse settings
  4  /

SP2-0805: Procedure altered with
compilation warnings

SQL> show errors procedure maintain_t
Errors for PROCEDURE MAINTAIN_T:

LINE/COL     ERROR
----------   ---------------------------------------
9/8          PLW-06009: procedure
             "MAINTAIN_T" OTHERS handler
             does not end in RAISE or
             RAISE_APPLICATION_ERROR
И тотчас же вы извлечете список приложений (и строки исходного кода), чтобы незамедлительно добавить простые вызовы RAISE, которые позволят увидеть, откуда поступает эта скрытая ошибка. Работа сделана.