Наилучшая практика обработки исключений в приложении Windows Forms? - PullRequest
117 голосов
/ 08 октября 2008

В настоящее время я пишу свое первое приложение для Windows Forms. Сейчас я прочитал несколько книг по C #, поэтому у меня есть относительно хорошее понимание того, какие функции языка C имеют дело с исключениями. Однако все они довольно теоретичны, поэтому у меня пока нет ощущения, как преобразовать базовые концепции в хорошую модель обработки исключений в моем приложении.

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

Основные вещи, которые я сейчас пытаюсь решить:

  • Когда мне повторно выдать исключение?
  • Должен ли я попытаться иметь какой-то центральный механизм обработки ошибок?
  • Имеет ли обработка исключений, которые могут быть сгенерированы, снижение производительности по сравнению с упреждающим тестированием, например, существует ли файл на диске?
  • Должен ли весь исполняемый код быть заключен в блоки try-catch-finally?
  • Есть ли случаи, когда пустой блок catch может быть приемлемым?

Все советы с благодарностью получены!

Ответы [ 16 ]

79 голосов
/ 08 октября 2008

Еще несколько битов ...

У вас обязательно должна быть централизованная политика обработки исключений. Это может быть так же просто, как завершение Main() в try / catch, быстрое завершение работы с изящным сообщением об ошибке пользователю. Это обработчик исключений «последней инстанции».

Упреждающие проверки всегда правильны, если это возможно, но не всегда совершенны. Например, между кодом, в котором вы проверяете существование файла, и следующей строкой, в которой вы его открываете, файл мог быть удален, или какая-то другая проблема может препятствовать вашему доступу. Вам все еще нужно попробовать / поймать / наконец в этом мире. Используйте как упреждающую проверку, так и try / catch / finally при необходимости.

Никогда не «глотайте» исключение, за исключением самых хорошо документированных случаев, когда вы абсолютно уверены в том, что выбрасываемое исключение пригодно для жизни. Это почти никогда не будет так. (И если это так, убедитесь, что вы глотаете только определенный класс исключений - не когда-либо глотайте System.Exception.)

При создании библиотек (используемых вашим приложением) не проглатывайте исключения и не бойтесь, чтобы исключения всплыли. Не перебрасывайте, если у вас есть что-то полезное, чтобы добавить. Никогда (в C #) не делайте так:

throw ex;

Как вы сотрете стек вызовов. Если вам необходимо повторно сгенерировать (что иногда необходимо, например, при использовании блока обработки исключений в Enterprise Library), используйте следующее:

throw;

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

Обработка исключений в .NET - это больше искусство, чем наука. Каждый будет иметь свои любимые, чтобы поделиться здесь. Это лишь некоторые из советов, которые я использовал в .NET с первого дня, методы, которые спасли мой бекон не один раз. Ваш пробег может отличаться.

61 голосов
/ 08 октября 2008

Отличный код Статья CodeProject здесь . Вот несколько основных моментов:

  • План на худшее *
  • Проверьте это рано
  • Не доверяйте внешним данным
  • Единственные надежные устройства: видео, мышь и клавиатура.
  • Запись может завершиться ошибкой
  • Код безопасно
  • Не выбрасывайте новое исключение ()
  • Не помещайте важную информацию об исключениях в поле сообщения
  • Поместите один улов (исключение ex) для потока
  • Обнаруженные общие исключения должны быть опубликованы
  • Log Exception.ToString (); никогда не входите только Exception.Message!
  • Не перехватывать (исключение) более одного раза для потока
  • Никогда не глотай исключения
  • Код очистки должен быть помещен в блоки finally
  • Использовать "везде"
  • Не возвращать специальные значения в случае ошибки
  • Не использовать исключения для указания отсутствия ресурса
  • Не использовать обработку исключений как средство возврата информации из метода
  • Использовать исключения для ошибок, которые не следует игнорировать
  • Не очищать трассировку стека при повторном генерировании исключения
  • Избегайте изменения исключений без добавления семантического значения
  • Исключения должны быть помечены как [Сериализуемые]
  • Если сомневаешься, не утверждай, брось исключение
  • Каждый класс исключений должен иметь как минимум три исходных конструктора
  • Будьте осторожны при использовании события AppDomain.UnhandledException
  • Не изобретай велосипед
  • Не использовать неструктурированную обработку ошибок (VB.Net)
15 голосов
/ 25 апреля 2009

Обратите внимание, что в Windows Forms есть собственный механизм обработки исключений. Если кнопка в форме нажата, а ее обработчик выдает исключение, которое не перехватывается в обработчике, Windows Forms отобразит свой собственный диалог необработанных исключений.

Чтобы предотвратить отображение диалогового окна «Необработанные исключения» и перехватывать такие исключения для ведения журнала и / или для предоставления собственного диалога об ошибках, вы можете прикрепить его к событию Application.ThreadException перед вызовом Application.Run () в функции Main (). способ.

14 голосов
/ 08 октября 2008

Все советы, размещенные здесь до сих пор, хороши и заслуживают внимания.

Одна вещь, на которой я хотел бы остановиться, это ваш вопрос: «Имеет ли обработка исключений, которые могут быть выброшены, снижение производительности по сравнению с упреждающим тестированием, например, существует ли файл на диске?»

Наивное эмпирическое правило гласит: «блоки try / catch стоят дорого». Это не совсем так. Попытка не дорогая. Это ловушка, когда система должна создать объект Exception и загрузить его с трассировкой стека, это дорого. Есть много случаев, когда исключение, ну, в общем-то, достаточно исключительное, так что вполне можно заключить код в блок try / catch.

Например, если вы заполняете словарь, это:

try
{
   dict.Add(key, value);
}
catch(KeyException)
{
}

часто быстрее, чем это:

if (!dict.ContainsKey(key))
{
   dict.Add(key, value);
}

для каждого добавляемого элемента, поскольку исключение выдается только при добавлении дублирующего ключа. (Сводные запросы LINQ делают это.)

В приведенном вами примере я бы использовал try / catch почти не задумываясь. Во-первых, тот факт, что файл существует, когда вы проверяете его, не означает, что он будет существовать, когда вы его откроете, поэтому вы все равно должны действительно обработать исключение.

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

9 голосов
/ 08 октября 2008

Вот несколько рекомендаций, которым я следую

  1. Fail-Fast: Это скорее правило генерации исключений. Для каждого сделанного вами предположения и каждого входящего в функцию параметра выполните проверку, чтобы убедиться, что вы начинаете с правильного данные и что ваши предположения верны. Типичные проверки включают: аргумент не нулевой, аргумент в ожидаемом диапазоне и т. Д.

  2. При повторном броске сохранить трассировку стека - это просто переводит использование броска при повторном броске вместо броска new Exception (). В качестве альтернативы, если вы чувствуете, что можете добавить больше информации, оберните исходное исключение как внутреннее исключение. Но если вы ловите исключение только для регистрации, обязательно используйте throw;

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

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

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

  6. Определенно бывают моменты, когда требуются пустые блоки catch, я думаю, что люди, которые говорят иначе, не работали с базами кода, которые развивались в нескольких выпусках. Но они должны быть прокомментированы и рассмотрены, чтобы убедиться, что они действительно необходимы. Наиболее типичным примером являются разработчики, использующие try / catch для преобразования строки в целое число вместо использования ParseInt ().

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

4 голосов
/ 08 октября 2008

Золотое правило, которое пытались придерживаться, - обрабатывать исключение как можно ближе к источнику.

Если вам нужно перебросить исключение, попробуйте добавить к нему, повторное выбрасывание FileNotFoundException мало поможет, но выкидывание ConfigurationFileNotFoundException позволит его перехватить и обработать где-нибудь в цепочке.

Другое правило, которому я стараюсь следовать, - это не использовать try / catch как форму выполнения программы, поэтому я проверяю файлы / соединения, проверяю, что объекты были инициированы и т. Д. Перед их использованием. Try / catch должен быть для исключений, вещей, которые вы не можете контролировать.

Что касается пустого блока catch, если вы делаете что-то важное в коде, сгенерировавшем исключение, вы должны перебросить исключение как минимум. Если нет никакого кода в коде, который вызвал исключение, выполняемое, почему вы написали его в первую очередь.

4 голосов
/ 08 октября 2008

Я только ухожу, но кратко расскажу, как использовать обработку исключений. Я попытаюсь обратиться к другим вашим пунктам, когда я вернусь:)

  1. Явная проверка всех известных состояний ошибки *
  2. Добавьте пробную версию кода, если вы не уверены, что вам удалось обработать все случаи
  3. Добавьте попытку / перехват вокруг кода, если вызываемый вами интерфейс .NET выдает исключение
  4. Добавить пробную версию кода, если он пересекает порог сложности для вас
  5. Добавьте пробу / уловку вокруг кода, если для проверки работоспособности: вы утверждаете, что ЭТО НЕ ДОЛЖНО БЫТЬ
  6. Как правило, я не использую исключения в качестве замены кодов возврата. Это хорошо для .NET, но не для меня. У меня есть исключения (хе-хе) к этому правилу, хотя это зависит от архитектуры приложения, над которым вы работаете.

* В пределах разумного. Нет необходимости проверять, попадет ли, скажем, космический луч в ваши данные, что приведет к перевороту пары битов. Понимание того, что является «разумным», является приобретенным навыком для инженера. Это трудно определить количественно, но легко интуитивно. То есть, я могу легко объяснить, почему я использую попытку / ловлю в каком-то конкретном случае, но мне трудно наделить другого такими же знаниями.

Я, например, стараюсь держаться подальше от архитектур, основанных исключительно на исключениях. try / catch не имеет снижения производительности как такового, оно возникает, когда генерируется исключение, и код, возможно, должен пройти несколько уровней стека вызовов, прежде чем что-то его обработает.

4 голосов
/ 08 октября 2008

Мне нравится философия не ловить ничего, что я не собираюсь обрабатывать, что бы ни значит обработка в моем конкретном контексте.

Я ненавижу, когда вижу такой код:

try
{
   // some stuff is done here
}
catch
{
}

Я видел это время от времени, и довольно трудно найти проблемы, когда кто-то «ест» исключения. Это сделал мой коллега, и он, как правило, приводит к постоянному потоку проблем.

Я перебрасываю, если есть что-то, что мой конкретный класс должен сделать в ответ на исключение, но проблема должна быть устранена, чтобы она называлась методом, где это произошло.

Я думаю, что код должен быть написан с упреждением, и что исключения должны быть в исключительных ситуациях, а не во избежание проверки условий.

2 голосов
/ 08 октября 2008

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

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

пример:

void Main()
{
  try {
    DoStuff();
  }
  catch(Exception ex) {
    LogStuff(ex.ToString());
  }

void DoStuff() {
... Stuff ...
}

Если DoStuff пойдет не так, вы все равно захотите, чтобы он освободился. Исключение будет выдано main, и вы увидите последовательность событий в трассировке стека ex.

1 голос
/ 15 января 2019

вы можете перехватить событие ThreadException.

  1. Выберите проект приложения Windows в обозревателе решений.

  2. Откройте созданный файл Program.cs, дважды щелкнув по нему.

  3. Добавьте следующую строку кода в начало файла кода:

    using System.Threading;
    
  4. В методе Main () в качестве первой строки метода добавьте следующее:

    Application.ThreadException += new ThreadExceptionEventHandler(Application_ThreadException);
    
  5. Добавьте следующий метод Main () ниже:

    static void Application_ThreadException(object sender, ThreadExceptionEventArgs e)
    {
        // Do logging or whatever here
        Application.Exit();
    }
    
  6. Добавить код для обработки необработанного исключения в обработчике событий. Любое исключение, которое не обрабатывается нигде в приложении, обрабатывается приведенным выше кодом. Чаще всего этот код должен регистрировать ошибку и отображать сообщение для пользователя.

ссылка: https://blogs.msmvps.com/deborahk/global-exception-handler-winforms/

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...