Подтвердить что ты не робот

Лучшая практика для обработки исключений в приложении Windows Forms?

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

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

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

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

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

4b9b3361

Ответ 1

Несколько бит...

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

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

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

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

throw ex;

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

throw;

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

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

Ответ 2

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

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

Ответ 3

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

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

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

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

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

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

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

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

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

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

Ответ 4

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

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

Ответ 5

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

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

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

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

  • Захватывайте глобальные обработчики исключений и следите за тем, чтобы регистрировать как можно больше информации. Для winforms зацепите как события, связанные с процессом appdomain, так и thread.

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

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

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

Ответ 6

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

Я ненавижу, когда вижу код, например:

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

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

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

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

Ответ 7

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

  • Явная проверка всех известных условий ошибки *
  • Добавьте попытку/уловить код, если вы не уверены, можете ли вы обрабатывать все случаи.
  • Добавить try/catch вокруг кода, если интерфейс .NET, который вы вызываете, генерирует исключение
  • Добавьте попытку/поймать код, если он пересекает порог сложности для вас.
  • Добавьте попытку/поймать код, если для проверки работоспособности: вы утверждаете, ЭТО ДОЛЖНО НИКОГДА НЕ ПРОИЗОЙТИ
  • Как правило, я не использую исключения в качестве замены для кодов возврата. Это нормально для .NET, но не для меня. У меня есть исключения (хе-хе) для этого правила, хотя это зависит от архитектуры приложения, над которым вы тоже работаете.

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

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

Ответ 8

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

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

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

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

Ответ 9

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

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

Пример:

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

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

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

Ответ 10

Когда мне нужно перебросить исключение?

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

Должен ли я попытаться использовать какой-то центральный механизм обработки ошибок?

Я пишу файл журнала... довольно легко для приложения WinForm.

Выполняют ли исключения обработки, которые могут быть выбраны, по сравнению с предварительным тестированием таких вещей, как наличие файла на диске?

Я не уверен в этом, но я считаю, что это хорошая практика, чтобы исключить исключения... Я имею в виду, что вы можете спросить, существует ли файл, и если он не бросает FileNotFoundException

Должен ли весь исполняемый код быть заключен в блоки try-catch-finally?

Yeap

Есть ли случаи, когда пустой блок catch может быть приемлемым?

Да, скажем, вы хотите показать дату, но вы не знаете, как эта дата была в магазинах (dd/mm/yyyy, mm/dd/yyyy и т.д.), вы пытаетесь выполнить синтаксический анализ, но если это не удается,... если это не относится к вам... Я бы сказал, да, есть

Ответ 11

Единственное, что я узнал очень быстро, заключалось в том, чтобы заключить абсолютно каждый фрагмент кода, который взаимодействует с чем-либо вне потока моей программы (то есть файловая система, вызовы базы данных, User Input) с блоками try-catch. Try-catch может нанести удар производительности, но обычно в этих местах вашего кода он не будет заметен, и он будет платить за себя с безопасностью.

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

Ответ 12

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

Try
{
int a = 10 / 0;
}
catch(exception e){
//error logging
throw;
}

это приведет к тому, что трассировка стека закончится в инструкции catch. (избегайте этого)

catch(Exception e)
// logging
throw e;
}

Ответ 13

n мой опыт, который я видел, чтобы уловить исключения, когда я знаю, что я собираюсь их создавать. Для случаев, когда я вхожу в веб-приложение, и я выполняю Response.Redirect, я знаю, что получаю исключение System.ThreadAbortException. Поскольку он преднамерен, у меня есть только улов для определенного типа и просто усвоить его.

try
{
/*Doing stuff that may cause an exception*/
Response.Redirect("http:\\www.somewhereelse.com");
}
catch (ThreadAbortException tex){/*Ignore*/}
catch (Exception ex){/*HandleException*/}

Ответ 14

Я глубоко согласен с правилом:

  • Никогда не пропускайте ошибки незаметно.

Причина в том, что:

  • Когда вы сначала записываете код, скорее всего, у вас не будет полного знания о 3-стороннем коде,.NET FCL lirary или ваших последних сотрудниках. На самом деле вы не можете отказаться писать код, пока не узнаете все возможности исключения. Так
  • Я постоянно обнаруживаю, что использую try/catch (Exception ex) только потому, что хочу защитить себя от неизвестных вещей, и, как вы заметили, я перехватываю Exception, а не более конкретный, такой как OutOfMemoryException и т.д. И я всегда сделайте исключение для меня (или QA) ForceAssert.AlwaysAssert(false, ex.ToString());

ForceAssert.AlwaysAssert - мой личный способ Trace.Assert, независимо от того, Определен макрос DEBUG/TRACE.

Возможно, цикл разработки: я заметил, что уродливое диалоговое окно Assert или кто-то еще жалуется мне на это, затем я возвращаюсь к коду и выясняю причину, чтобы поднять исключение и решить, как его обрабатывать.

Таким образом, я могу записать MY-код за короткое время и защитить меня от неизвестного домена, но всегда замечая, что если произошли ненормальные вещи, система стала безопаснее и безопаснее.

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

Для кода WinForms я всегда подчиняюсь золотому правилу:

  • Всегда пытайтесь/улавливать (исключение) код вашего обработчика событий

это защитит ваш пользовательский интерфейс, который будет всегда использоваться.

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

Исключение должно происходить с небольшой вероятностью, иначе это не исключение.

Ответ 16

Вы должны думать о пользователе. Крушение приложения - это последняя вещь, которую хочет пользователь. Поэтому любая операция, которая может выйти из строя, должна иметь блок catch try на уровне ui. Нет необходимости использовать try catch в каждом методе, но каждый раз, когда пользователь что-то делает, он должен иметь возможность обрабатывать общие исключения. Это отнюдь не освобождает вас от проверки всего, чтобы исключить исключения в первом случае, но нет сложного приложения без ошибок, и ОС может легко добавить неожиданные проблемы, поэтому вы должны ожидать неожиданного и убедиться, что пользователь хочет использовать один операция не будет потери данных, так как приложение аварийно завершает работу. Нет необходимости когда-либо позволять вашему краху приложения, если вы поймаете исключения, он никогда не будет находиться в неопределенном состоянии, и пользователь ВСЕГДА не может быть вызван сбоем. Даже если исключение находится на самом верхнем уровне, а не сбой, пользователь может быстро воспроизвести исключение или, по крайней мере, записать сообщение об ошибке, и, следовательно, поможет вам решить проблему. Конечно, гораздо больше, чем простое сообщение об ошибке, а затем видя только диалоговое окно ошибок Windows или что-то в этом роде.

Вот почему вы НИКОГДА не должны быть тщеславными и думаете, что у вашего приложения нет ошибок, что не гарантируется. И очень небольшое усилие состоит в том, чтобы обернуть несколько блоков catch catch из соответствующего кода и показать сообщение об ошибке/занести в журнал ошибку.

Как пользователь, я, конечно, серьезно разозлился всякий раз, когда браузер или офисное приложение или всевозможные сбои. Если исключение настолько велико, что приложение не может продолжить его лучше отображать это сообщение и сообщать пользователю, что делать (перезапустить, исправить некоторые настройки os, сообщить об ошибке и т.д.), Чем просто сбой и что это.

Ответ 17

Вы можете перехватить событие 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/