1629295403-b876e2087bddebea4bc9666fb2377a02 (846199), страница 78
Текст из файла (страница 78)
Типы ошибок для MyMathClass могут быть определены еледующим образом:enum MathErrorsNegativeNumber,NonlntegerValueФункция Factorial () может возвращать значение MathErrors, и вы можете проверить его в своей программе следующим образом (как можно часто увидеть в классах.NET Framework):MathErrors meResult = MyMathFunctions.Factorial(6);if(meResult == MathErrors.NegativeNumber) ...Теперь функция Factorial () сообщает об ошибках функции Main ( ) , которая выводит соответствующее сообщение на экран и завершает на этом свою работу:i = 6, факториал = 720i = 5, факториал = 12 0i = 4, факториал = 24i = 3, факториал = 6i = 2, факториал = 21 = 1 , факториал = 1i = 0, факториал = 0Factorial() получила отрицательный параметрНажмите <Enter> для завершения программы...(Здесь я предпочел прекращать работу при обнаружении ошибки.) Указание о происшедшей ошибке посредством возвращаемого функцией значения повсеместно используется еще со времен FORTRAN.
Зачем же менять этот механизм?Чем плохи коды ошибокЧто же не так с кодами ошибок? Они были достаточно хороши даже для FORTRAN!Да, но в те времена компьютеры были ламповыми. Увы, но коды ошибок приводят к ряду проблем.Этот метод основан на том факте, что у функции имеются значения, которые онане может вернуть при корректной работе. Однако существуют функции, у которых398ЧастьVII. Дополнительные главылюбые возвращаемые значения корректны.
Не всегда везет поработатьс функцией, которая должна возвращать только положительные значения.Например, вы не можете получить логарифм отрицательного числа, но самозначение логарифма может быть как положительным, так и отрицательным.Вы можете предложить справиться с этой проблемой, возвращая кодошибки как значение функции, а необходимые данные — посредством аргумента, описанного как out, но такое решение менее интуитивно и теряет выразительную силу функции. Сперва познакомьтесь с исключениями,и вы убедитесь, что они предоставляют гораздо более красивый путь решения проблемы.В целом числе не удается разместить большое количество информации. Так,рассматриваемая функция Factorial О возвращает -1, если ее аргумент отрицателен. Локализовать ошибку было бы проще, если бы был известен самаргумент, но в возвращаемом функцией типе для него просто нет места.Обработка ошибок является необязательной.
Вы не получите никаких преимуществ от проверок в функции Factorial ( ) , если вызывающая функция не будет в свою очередь проверять возвращаемое значение. Конечно,руководитель группы может просто сказать: "Парни, или вы проверяете коды ошибок, или занимаете очередь на бирже труда", но совсем иное дело,когда проверку заставляет выполнить сам язык программирования.Зачастую при проверке кода ошибки, возвращаемого функцией Factorial () или любой другой функцией, практически вся вызывающая функция оказывается заполненнойпроверками всех возможных кодов ошибок от всех вызванных функций, при этом простоне остается ни сил, ни места сделать в функции хоть что-то полезное.
Судите сами:// Вызов SomeFuncO, проверка кода ошибки, его обработка и// возврат из функцииerrRtn = SomeFunc () ;if (errRtn == SF_ERRORl)(Console .WriteLine ("Ошибка типа 1 при вызове SomeFuncO");return MY ERROR 1;if (errRtn == SF ERROR2)Console .WriteLine ("Ошибка типа 2 при вызове SomeFuncO");return My_ERROR 2;}// Вызов другой функции, проверка кода ошибки и так далее...errRtn = SomeOtherFuncО;if (errRtn == SOF_ERRORl){Console.WriteLine("Ошибка типа 1 при вызове " +"SomeOtherFunc () ") ;return MY ERROR 3;)if (errRtn == SOF ERROR2){Console.WriteLine("Ошибка типа 2 при вызове " +Глава 18.
Эти исключительные исключения399"SomeOtherFunc()");return MY_ERR0R_4;}Такой механизм имеет ряд проблем.В нем очень много повторов. Дублирование кода обычно очень неприятно по-|пахивает...Он заставляет пользователя функции поддерживать проверку массы кодов ошибокКод обработки ошибок оказывается перемешан с обычным кодом, что затеняетосновную работу программы и делает исходный текст неудобочитаемым.Все эти проблемы кажутся мелкими в простых примерах, но все становится гораздохуже с ростом сложности вызываемых функций. В конечном итоге код обработки ошибок не перехватывает все ошибки, которые могут возникнуть.К счастью, описанный далее механизм исключений решает указанные проблемы.В С# для перехвата и обработки ошибок используется совершенно иной механизм,называемый исключениями.
Он основан на ключевых словах try, catch, throw и finally. Набросать схему его работы можно следующим образом. Функция пытается(try) пробраться через кусок кода. Если в нем обнаружена проблема, она бросает(throw) индикатор ошибки, который функции могут поймать (catch), и независимо оттого, что именно произошло, в конце (finally) выполнить специальный блок кода, какпоказано в следующем наброске исходного текста:public class MyClass{public void SomeFunction(){// Настройка для перехвата ошибкиtry{// Вызов функции или выполнение каких-то иных// действий, которые могут генерировать исключениеSomeOtherFunction();// .
. . Какие-то иные действия . . .}catch(Exception е){// Сюда управление передается в случае, когда в блоке// try сгенерировано исключение — в самом ли блоке, в// функции, которая в нем вызывается, в функции,// которая вызывается функцией, вызванной в try-блоке// и так далее — словом, где угодно. Объект Exception// описывает ошибку' Далее будет использоваться выражение "генерирует исключение". — Примеч.
ред.400Часть VII. Дополнительные главы}finally{// Выполнение всех завершающих действий: закрытие// файлов, освобождение ресурсов и т.п. Этот блок// выполняется независимо от того, было ли// сгенерировано исключение.}}public void SomeOtherFunction(){I I . . . Ошибка произошла где-то в теле функции . .
.11 . . . Ж "пузырек" исключения "всплывает" вверх по// всей цепочке вызовов, пока не будет перехвачен в// блоке catchthrow new Exception("Описание ошибки");I I . . . Продолжение функции . . .Функция Some Function () помещает некоторую часть кода в блок, помеченныйключевым словом try. Любая функция, вызываемая в этом блоке (или функция, вызываемая функцией, вызываемой в этом блоке — и так далее...), рассматривается как вызванная в данном try-блоке.Непосредственно за блоком try следует ключевое слово catch с блоком, которомупередается управление в случае, если где-то в try-блоке произошла ошибка. Аргументcatch-блока — объект класса Exception (или некоторого подкласса Exception).Однако catch-блок не обязан иметь аргументы: пустой catch перехватывает всеисключения, как и catch (Exception):catchЕсли вам не нужен доступ к информации из объекта перехваченного исключения, выможете указать в блоке только тип исключения:catch (MyException){// Действия, которые не требуют обращения к объекту// исключения}Блок finally— если таковой имеется в вашем исходном т е к с т е — выполняетсядаже в случае перехвата исключения, не говоря уже о том, что он выполняется при нормальной работе.
Обычно он предназначается для "уборки" — закрытия открытых файлов, освобождения ресурсов и т.п.В отличие от исключений С++, в которых аргументом catch может быть объект произвольного типа, исключения С# требуют, чтобы он был объектом класса Exception или производного от него.Итак, где-то в дебрях кода на неизвестно каком уровне вызовов в функции SomeOt h e r F u n c t i o n ( ) случилась ошибка... Функция сообщает об этом, генерируя исклю-Глава18.Эти исключительные исключения401чение в виде объекта Exception и передает его с помощью оператора throw вверхцепочке вызовов в первый же блок, который в состоянии перехватить его и обработан!Иногда обработчик try/catch располагается в той же функции, в которойнерировано исключение.
Первая же функция, владеющая достаточным количесвом контекстуальной информации для выполнения действий по его обработкеможет перехватить и обработать его; если данная функция не в состоянии этогосделать, исключение передается дальше вверх по цепочке вызовов.В демонстрационной программе FactorialExceptionприведены ключевые элементы механизма исключений.// FactorialException - создание функции вычисления// факториала, которая сообщает о некорректном аргументе с//использованиемисключенийusing System;namespace FactorialException{// MyMathFunctions - набор созданных мною математических// функцийpublic class MyMathFunctions{// Factorial - возвращает факториал переданного// аргументаpublic static double Factorial(int nValue){// Проверка: отрицательные значения запрещеныif (nValue < 0){// Сообщение об отрицательном аргументеstring s = String.Format("Отрицательный аргумент в вызове Factorial { о } " ,nValue);throw new Exception(s); // Генерация исключения...}// начинаем со значения аккумулятора,// равного 1double dFactorial = 1.0;// Цикл со счетчиком nValue, уменьшающимся до 1, с// умножением на каждой итерации значения аккумулятора// на величину счетчикаdo{dFactorial *= nValue,} while(--nValue > 1 ) ;// Возвращаем вычисленное значениеreturn dFactorial;402Часть VII.