|
|
|||||||||||||||||||||||||||||
|
Об исключениях и правилахИсточник: oracle Стивен Фернстайн
Автор: Стивен Фернстайн, член коллегии 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 коде, и он будет хорошо работать, когда никакие исключения не возникают. Существование намеренных и неуместных исключений делает это невозможным. Однако можно спрятать эти типы исключений из кода верхнего уровня двумя способами. Можно инкапсулировать программу, которая намеренно вызывает исключение, и преобразовать это исключение во флаг или другой тип данных, либо можно дать разработчику возможность выбора: распространить неуместное исключение или вернуть значение, означающее, что исключение возникало. Какой бы подход вы не выбрали, самое главное - это обсудить эти вопросы перед началом создания следующего приложения. Ссылки по теме
|
|