Новости

20.08.2021

Книга «Программируем на C# 8.0. Разработка приложений»

Множество примеров кода научат работать с шаблонами, LINQ и асинхронными возможностями языка. Вы разберетесь с асинхронными потоками, ссылочными типами, допускающими значение NULL, сопоставлениями с образцом, реализациями по умолчанию для метода интерфейса, диапазонами и синтаксисом индексации и многим другим.

Исключения


Некоторые операции могут завершиться неудачей. Если ваша программа читает данные из файла, хранящегося на внешнем диске, кто-то может отключить диск. Приложение может попытаться создать массив и обнаружить, что в системе недостаточно свободной памяти. Ненадежное подключение к беспроводной сети может привести к сбою в работе сетевых запросов. Один из широко используемых способов обнаружения подобного рода ошибок в API — возвращать значение, указывающее, было ли выполненное действие успешным. Это требует бдительности от разработчиков относительно того, все ли ошибки они отслеживают, потому что программе нужно проверять возвращаемое значение каждой операции. Это, безусловно, жизнеспособная стратегия, но она может запутать код; логическая последовательность задач, выполняемых в нормальных условиях, может оказаться похороненной под всеми этими проверками на ошибки, что усложняет сопровождение кода. C# поддерживает другой популярный механизм обработки ошибок, который способен сгладить эту проблему, — исключения.

Когда API сообщает об ошибке с исключением, это нарушает нормальный ход выполнения, что приводит к переходу к ближайшему подходящему коду обработки ошибок. Механизм обеспечивает определенный уровень разделения между логикой обработки ошибок и кодом, который пытается выполнить поставленную задачу. Он может облегчить чтение и обслуживание кода, хотя, с другой стороны, способен затруднить просмотр всех возможных путей выполнения кода.

Исключения также могут сообщать о проблемах в операциях в тех случаях, когда код возврата оказывается неприменим. Например, среда выполнения может обнаруживать и сообщать о проблемах основных операций, даже таких простых, как использование ссылки. Переменные ссылочного типа могут содержать null, и, если вы попытаетесь вызвать метод с такой ссылкой, произойдет сбой. Среда выполнения сообщает об этом исключением.

Большинство ошибок в .NET представлены в виде исключений. Однако некоторые API предлагают выбор между кодами возврата и исключениями. Например, тип int имеет метод Parse, принимающий строку и пытающийся интерпретировать ее содержимое как число. Если вы передадите ему какой-то нечисловой текст (например, «Hello»), он укажет на ошибку, вызвав исключение FormatException. Если вас это не устраивает, то вместо этого можно вызывать TryParse, который выполняет точно такую же работу, но если ввод не числовой, он возвращает false вместо вызова исключения. (Поскольку возвращаемое значение метода призвано сообщать об успехе или неудаче, метод предоставляет целочисленный результат через выходной параметр.) Числовой синтаксический анализ — не единственная операция, использующая эту схему, при которой используется пара методов (Parse и TryParse, в данном случае) и предоставляется выбор между исключениями и возвращаемыми значениями. Как вы видели в главе 5, аналогичный выбор предоставляют словари. Индексатор выдает исключение, если вы используете ключ, которого нет в словаре, но вы также можете искать значения с помощью TryGetValue, который возвращает false при сбое, подобно TryParse. Хотя эта схема и встречается в ряде мест, для большинства API исключения остаются единственным выбором.

Если вы разрабатываете API, который способен вызвать сбой, как он должен об этом сбое сообщать? Использовать ли в этом случае исключения, возвращаемое значение или и то и другое? Рекомендации Microsoft по разработке библиотеки классов содержат инструкции, которые кажутся однозначными:

Не возвращайте коды ошибок. Исключения являются основным средством сообщения об ошибках в библиотеках классов.

 Рекомендации по разработке .NET Framework

Но как это согласуется с существованием int.TryParse? В руководстве есть раздел, посвященный вопросам производительности исключений, в котором говорится следующее:

Рассмотрите использование TryParse для элементов, которые могут генерировать исключения в общих сценариях, чтобы избежать проблем с производительностью, связанных с исключениями.

Рекомендации по разработке .NET Framework

Невозможность проанализировать число не обязательно является ошибкой. Например, нужно, чтобы ваше приложение позволяло указывать месяц как в виде числа, так и в виде текста. Так что, безусловно, есть распространенные сценарии, в которых операция может завершиться неудачей, но у руководства есть и другой критерий: предлагается использовать его для «чрезвычайно чувствительных к производительности API». Так что предлагать подход TryParse следует только тогда, когда операция выполняется быстро по сравнению со временем, необходимым для вызова и обработки исключения.

Исключения, как правило, могут создаваться и обрабатываться за доли миллисекунды, поэтому они не такие уж и медленные — во всяком случае, не такие, как чтение данных по сетевому соединению, — но и не слишком быстрые. Я выяснил, что на моем компьютере один поток может анализировать пятизначные числовые строки со скоростью примерно 65 миллионов строк в секунду в .NET Core 3.0 и он способен отклонять нечисловые строки с такой же скоростью при использовании TryParse. Метод Parse обрабатывает числовые строки так же быстро, но он примерно в 1000 раз медленнее отклоняет нечисловые строки, чем TryParse, благодаря затратам на исключения. Конечно, преобразование строк в целые числа — это довольно быстрая операция, поэтому это делает исключения неудачным выбором, но именно поэтому эта схема наиболее распространена для операций, быстрых по своей природе.

Особенно медленно исключения могут работать при отладке. Отчасти это связано с тем, что отладчику необходимо решить, куда ему влезать, но это особенно заметно при первом необработанном исключении, которое вызывает ваша программа. Может создаться впечатление, что исключения значительно дороже, чем они есть на самом деле. Числа в предыдущем абзаце основаны на наблюдаемом поведении во время выполнения без отладки. Тем не менее эти цифры несколько занижают затраты, поскольку обработка исключения приводит к тому, что CLR запускает кусочки кода и получает доступ к структурам данных, которые в противном случае не нужны, а это может привести к вытеснению полезных данных из кэша ЦП. Это может привести к тому, что код будет работать медленнее в течение короткого времени после обработки исключения, пока не относящийся к исключению код и данные не вернутся в кэш. Простота теста уменьшает этот эффект.

Большинство API не предоставляют вариант TryXxx и сообщают обо всех сбоях как об исключениях, даже в случаях, когда сбой может быть вполне ожидаемым. Например, файловые API не содержат способа открыть для чтения существующий файл без исключения в случае его отсутствия. (Вы можете сначала использовать другой API для проверки наличия файла, но это не гарантия успеха. Другой процесс всегда может удалить файл между вашим запросом касательно его существования и попыткой его открыть.) Поскольку операции с файловой системой по своей природе медленные, шаблон TryXxx не обеспечит здесь существенного повышения производительности, даже если добавит логики.

Источники исключений


API библиотеки классов — не единственный источник исключений. Они могут возникнуть в любом из следующих сценариев:

  • Проблему обнаруживает ваш собственный код.
  • Ваша программа использует API библиотеки классов, где возникает проблема.
  • Среда выполнения обнаруживает сбой операции (например, арифметическое переполнение в проверяемом контексте, попытку использовать нулевую ссылку или разместить объект, для которого недостаточно памяти).
  • Среда выполнения обнаруживает сбой вне вашего контроля, который влияет на ваш код (например, среда выполнения пытается выделить память для какой-то внутренней цели и обнаруживает, что свободной памяти недостаточно).


Хотя все они используют одни и те же механизмы обработки исключений, места возникновения исключений отличаются. Когда ваш собственный код вызовет исключение (позже я покажу вам, как это сделать), вы будете знать, какие условия привели к его возникновению, но что происходит, когда исключения вызывают другие сценарии? В следующих разделах я опишу, где ожидать каждый вид исключений.

Исключения от API

При вызове API есть несколько видов проблем, способных привести к исключениям. Возможно, вы предоставили аргументы, которые не имеют смысла, например пустую ссылку вместо рабочей или пустую строку вместо имени файла. Или аргументы могут выглядеть хорошо по отдельности, но не все вместе. Например, вы можете вызвать API, который копирует данные в массив, и попросить его скопировать больше данных, чем в него умещается. Их можно описать как ошибки в стиле «никогда не сработает», и обычно они являются результатом ошибок в коде. (Один разработчик, который раньше работал в команде компилятора C#, называет их тупоголовыми исключениями (boneheaded exceptions).)

Другой класс проблем возникает, когда все аргументы выглядят правдоподобно, но операция оказывается невозможной при текущем состоянии среды. Например, вы можете попросить открыть определенный файл, но файл может отсутствовать; или, возможно, он существует, но какая-то другая программа уже открыла его и требует монопольного доступа. Еще один вариант заключается в том, что все может начаться хорошо, но поменяться в будущем. Например, вы успешно открыли файл и некоторое время читали данные, но затем файл стал недоступным. Как предлагалось ранее, кто-то мог отключить диск или диск мог выйти из строя из-за перегрева или возраста.

Программное обеспечение, которое обменивается данными с внешними службами по сети, должно учитывать, что исключение не обязательно указывает, что что-то действительно не так — иногда запросы не выполняются из-за какого-то временного обстоятельства, и просто требуется повторить операцию. Это особенно распространено в облачных средах, где отдельные серверы обычно включаются и выключаются в рамках балансировки нагрузки, которую, как правило, предлагают облачные платформы. Это нормально, когда несколько операций не дают результатов по какой-либо конкретной причине.

При использовании служб через библиотеку вы должны выяснить, обрабатывает ли она это за вас. Например, библиотеки хранилища Azure по умолчанию выполняют повторные попытки и выдают исключение только в том случае, если вы отключите это поведение или если проблемы не исчезнут после ряда попыток. Как правило, не нужно добавлять собственные обработки исключений и повторные циклы для такого рода ошибок в случае библиотек, которые делают это для вас.

Асинхронное программирование добавляет еще один вариант. В главах 16 и 17 я покажу различные асинхронные API, в которых работа может продолжаться после возвращения из метода, который ее запустил. Работа, которая выполняется асинхронно, также асинхронно сбоит, и в этом случае библиотеке может потребоваться дождаться следующего вызова вашего кода, прежде чем она сможет сообщить об ошибке.

Несмотря на различия, во всех этих случаях исключение будет исходить из некоторого API, который вызывается вашим кодом. (Даже в случае сбоя асинхронных операций исключения возникают либо при попытке получить результат операции, либо когда вы явно спрашиваете, была ли ошибка.) В листинге 8.1 показан код, в котором могут возникать исключения такого рода.

Листинг 8.1. Получение исключения из библиотечного вызова

В этой программе нет ничего категорически неправильного, поэтому мы не получим никаких исключений относительно того, что аргументы изначально неверные. (В неофициальной терминологии он не допускает тупоголовых ошибок.) Если на диске C: вашего компьютера есть папка Temp, и если она содержит файл File.txt, и если пользователь, запускающий программу, имеет разрешение на чтение файла, и если на компьютере никто не получил монопольного доступа к файлу, и если нет проблем — таких, как повреждение диска, — которые могут сделать любую часть файла недоступной, и если нет новых проблем (таких, как воспламенение диска) во время работы программы, этот код будет работать замечательно: он покажет каждую строку текста в файле. Но тут очень много если.

С полным содержанием статьи можно ознакомиться на сайте "Хабрахабр":

https://habr.com/ru/company/piter/blog/573716/


Комментарии: 0

Пока нет комментариев


Оставить комментарий






CAPTCHAОбновить изображение

Наберите текст, изображённый на картинке

Все поля обязательны к заполнению.

Перед публикацией комментарии проходят модерацию.