Нэш Трей - C# 2010. Ускоренный курс для профессионалов (2010) (1160865), страница 53
Текст из файла (страница 53)
Со временем сообщество разработчиков осознало, что беспорядочное разбрасывание операторов с ту ухудшает читабельность кода, когда в большинстве случаев единственной целью, которой они пытались достичь, было аккуратно убрать за сгенерированным исключением и позволить ему распространяться далее по стеку.
Хуже того, это затрудняло написание кода и его дальнейшее сопровождение. Код, не обрабатывающий исключений, но от которого ожидается правильное поведение в отношении этих исключений, называется кодом, нейтральным к исключениям. Ясно, что должен был существовать лучший способ написания нейтрального по отношению к исключениям кода, без необходимости полагаться повсюду на операторы сгу. Фактически единственное место, где есть необходимость в операторе ггу — это точка, в которой в ответ на исключение производится любого рода восстановление Безопасность и обработка исключений 201 или протоколирование системы.
Со временем многие начали понимать, что написание операторов сгу было фактичесни наименее значимой частью написания безопасного и нейтрального к исключениям кода. Вообще говоря, единственный код, который должен перехватывать исключения — это код, знающий, как исправить ситуацию. Такой нод может находиться даже в главной точке входа и просто выполнять сброс системы в некоторое известное начальное состояние, по сути, перезапуская ее. Под нейтральным к исключениям кодом подразумевается код, в котором не предусмотрена возможность каким-либо специальным образом обрабатывать исключения, но, тем не менее, который должен изящно справляться с исключительными условиями.
Обычно этот код расположен где-то в стеке — между кодом, сгенерировавшим исключение, и нодом, перехватившим его, — и он не должен зависеть от иснлючения на его пути в стеке. Здесь некоторые могли подумать об операторе Сигом без параметров, который позволяет перехватить исключение, выполнить пакую-то обработку и затем повторно сгенерировать исключение. Однако далее будет представлена намного более ясная техника, которая позволяет писать нейтральный к исключениям код без использования одиночного оператора сгу и получать при агом более устойчивый и читабельный код. Базовая структура нейтрального к исключениям кода Общая идея, лежащая в основе написания нейтрального к исключениям кода, подобна идее, лежащей в основе кода фиксации/отката (поппи(1/го1!Ьас(к).
При этом пишется такой код, который гарантирует, что если его выполнение не завершено, то вся операция отменяется без каких-либо последствий для состояния системы. Изменения в состоянии фиксируются только в том случае, когда код достигает конечной точки выполнения. Методы необходимо кодировать подобным образом для того, чтобы они были нейтральны н исключениям. Если исключение сгенерировано до того, как достигнут конец метода, то состояние системы должно оставаться неизменным. Приведенный ниже шаблон показывает, как необходимо структурировать метод.
чтобы достичь этой цели: гоаб Ехсеротопиецтга1негиоб(1 ( // Весь код, который потенциально может сгенерировать исключение, // находится в первом разделе. Здесь не применяется никаких // изменений в состоянии ни к каким объектам системы, включая данный. // Все изменения фиксируются в этой точке с использованием // операций, гарантирующих отсутствие генерации исключений. // Как видите, эта техника не работает, если нет набора операций, которые гарантированно не генерируют исключений. В противном случае реализовать проиллюстрированное поведение "фиксации/отката" невозможно.
К счастью, исполняющая система .г(ЕТ предоставляет несколько операций, специфинация которых гарантирует, что исключения никогда не будут ими сгенерированы. Начнем с построения примера, описывающего то, что имеется в виду. Предположим, что есть система или приложение по управлению сотрудниками. Пусть как только сотрудник зарегистрирован, для него создается объект Ещр1оуее, который должен храниться в одной и только одной коллекции в системе. В данный момент две коллекции представляют активных сотрудников в системе и одна — уволенных. Вдобавок коллекции существуют внутри объекта Ел1р1оуееРакаЬа ее, как показано в следующем примере: 202 Глава ? ояспч Яузгев.СЬ11есС1опя~ с1аяя Евр1оуеепагаЬаяе ( ргстасе АггяуЕЕяс зссстеЕвр1оуеея; ргстасе Аггяуьсяс сегвспясес(евр1оуеея; ) В примере используются коллекции типа Аггауе гас иа пространства имен я уз сев.
Со11есс).опя. Реальная система, очевидно, должна иметь дело с чем-то более удобным, например, с базой данных. Теперь посмотрим, что произойдет при увольнении сотрудника. Естественно, этот сотрудник должен быть перемещен из коллекции асС1теЕвр1оуеея в коллекцию Сегв1пагес(Евр1оуеея, Наивная реализация такой задачи могла бы выглядеть так: ия1пд Яуясев.Со11есг1опя; с1аяя Евр1оуее ( ) с1яяя Евр1оуеепягаЬяяе ( риьугс теса ТегвгпягеЕвр1оуее( гпг 1пс(ех ) ( оЬ]есс евр1оуее = зссснеЕвр1оуеез(1пяех] асгстеквр1оуеея.кевотеАС( 1пяех ); Сегвспягес(Евр1оуеея.Ас)6( евр1оуее ); ) ргстзсе Аггяуссяг ясг1теквр1оуеея; ргстясе Аггя?11яс сегвспясеяхвр1оуеея> ) Приведенный код выглядит достаточно адекватным.
Метод, выполняющий перемещение, предполагает, что вызывающий его код каким-то образом знает индекс текущего сотрудника в списне ассстеевр1оуеея до вызова тегвгпасеевр1оуее. Он копирует ссылку на унааанного сотрудника, удаляет ее иэ ассстеевр1оуеез и добавляет в коллекцию сегвспасебевр1оуеея. Так что же плохого в этом методе? Присмотритесь к методу внимательнее и подумайте, где в нем могут генерироваться исключения. Фактически исключение может быть сгенерировано при вызове любого метода внутри данного. Если индекс выйдет за пределы допустимого диапазона, можно ожидать генерации исключения АгдпвепсОисОгеапде в первых двух строках.
Рааумеется, если исключение АгдпвепсОпсОтеапде будет сгенерировано в первой строке, то выполнение никогда не дойдет до второй строки, но идея должна быть ясна. Кроме того, если памяти недостаточно, может случиться, что вызов Ас)с) завершиться неудачей с генерацией исключения. Опасность исходит от возможности генерации исключения после модификации состояния системы.
Предположим. что передан правильный индекс. Первые две строки успешно выполнятся. Однако если исключение будет сгенерировано при попытке добавить сотруднина к сегвспасес(евр1оуеея, то сотрудник будет потерян для системы. Что же предпринять для решения проблемы? Начальная попытка может заключаться в использовании операторов сгу для предотвращения повреждения состояния системы. Рассмотрим следующий пример: пзспд Яуясев.Со11ессгопя; с1аяя Евр1оуее ( ) с1аяя Евр1оуееоагаЬяяе ( РЬЬ11с томт Тегв1пагеЕвр1оуее( гпг гпоех ) ( Безопасность и обработка исключений 203 оЪ1есС евр1оуее = пп11) сгу ( евр1оуее = асстчеЕвр1оуеез(1пг(ех]г ) сагсп ( // Индекс находится эа пределами допустимого диапазона.
1г( евр1оуее != пп11 ) ( астгчеЕвр1оуеез.кевочеАС( 1пг(ех )! с у ( сегв1пасес(евр1ауеез.Апп( евр1оуее ) ) сассс ( // Не удалось выделить память. асС1чеввр1оуеез.лг(г)( евр1оуее )г ) ) рггчаге Аггауььзг асггчеЕвр1оуеез; рггчасе Аггауьззт Сегвспатег(Евр1оуеезг ) Посмотрите, насколько быстро код стало трудно читать и понимать — и все "благодаря" операторам сгу. Объявление переменной евр1оуее должно быть вынесено за пределы оператора Сгу, а переменная должна быть инициализирована значением пп11, После получения ссылку на экземпляр евр1оуее потребуется проверить на равенство пп11, чтобы убедиться в ее действительности. Действительную ссылку евр1оуее можно добавить в список СегвгпагебЕвр1оуеез. Однако если по какой-то причине выполнить это не получится.
евр1оуее понадобиться вернуть обратно в список асггчеЕвр1оуеез. Также обратите внимание, что вызов НевочеАС не находится внутри блока Сгу. Это нормально, поскольку если он даст сбой, состояние не будет модифицировано, и перехват не нужен. С описанным подходом связано несколько проблем, которые нетрудно заметить. Во-первых,что произойдет, если не получится добавить евр1оуее обратно в коллекцию ассгчеЕвр1оуеез? Можно ли смириться с таким сбоем? Нет! Это неприемлемо, поскольку состояние системы уже изменилось. Во-вторых, скорее всего, из этого метода понадобится вернуть код ошибки, чтобы сообщить.












