Пытаясь понять исключения в C # - PullRequest
4 голосов
/ 30 августа 2011

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

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

private void Parse(XDocument xReader)
{
    IEnumerable<XElement> person = xReader.Descendants("Person").Elements();

    foreach (XElement e in person)
        personDic[e.Name.ToString()] = e.Value;

    if (personDic["Name"] == null || personDic["Job"] == null || personDic["HairColor"] == null)
        throw new KeyNotFoundException("Person element not found.");
}

Но я не уверен, правильно ли это. У меня есть это для обработки:

try
{
    personsReader.Read(filename, persons);
}
catch (KeyNotFoundException e)
{
    MessageBox.Show(e.Message);

    return;
}

// Do stuff after reading in the file..

Тем не менее, при отображении e.Message он просто показывает общее сообщение об ошибке KeyNotFoundException, а не пользовательское сообщение об ошибке. Кроме того, я не уверен, что в общем и целом я правильно разбираюсь во всей этой «обработке исключений». Я возвращаюсь в ловушке, потому что, если файл не читается успешно, очевидно, я просто хочу притворяться, будто пользователь никогда не пытался открыть файл, и позволить ему повторить попытку с другим файлом.

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

Кроме того, почему люди говорят не делать catch (Exception e)? Кажется, что в этом случае я хотел бы сделать это, потому что независимо от того, какая ошибка возникает при чтении в файле, если есть ошибка, я хочу прекратить чтение файла, отобразить сообщение об ошибке и вернуться. Разве это не всегда так? Я могу понять, что не хочу обрабатывать исключение e, если вы хотите обрабатывать что-то по-другому в зависимости от исключения, но в этом случае я бы не хотел просто обрабатывать базовый класс исключений в случае, если что-то пойдет не так? *

Ответы [ 7 ]

5 голосов
/ 30 августа 2011

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

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

throw new MyGoodExceptionType ("Could not read file", e);  // e is caught inner root cause.

Теперь пользовательский интерфейс показывает хорошую ошибку, и, возможно, внутренняя причина находится в журнале и т. Д. *

Некоторые типичные ошибки:

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

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

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

Надеюсь, это поможет.

3 голосов
/ 30 августа 2011

Хорошо, сначала ...

... Это не KeynotFoundException, это должно быть ArgumentException .... Предоставленный аргумент недействителен.

Документация четко гласит:

Исключение, которое выдается, когда ключ, указанный для доступа к элементу в коллекции, не соответствует ни одному ключу в коллекции.

Сравните это с:

Исключение, которое выдается, когда один из аргументов, предоставленных методу, недопустим

Сейчас:

Кроме того, почему люди говорят, что непоймать (исключение е)?

Поскольку это поглощает исключение и делает невозможным централизованную обработку ошибок / регистрацию на месте.Обрабатывайте ТОЛЬКО исключение, которое вы ожидаете, ЕСЛИ НЕ это что-то перехватывает / закрывает или регистрирует / перебрасывает (т.е. throw;).Затем есть центральный обработчик приложения, который получает все незаписанные данные, кроме inin, и регистрирует их;) Он не может ничего обработать - потому что исключения на этом уровне являются неожиданными.Он должен в основном записать исключение в файл и сделать это, возможно, с помощью пользовательского интерфейса (приложение должно быть перезапущено).

1 голос
/ 30 августа 2011

Большую часть времени вы можете не заботиться о типе получаемого вами исключения, поэтому перехват универсального Exception вполне подходит, однако существуют определенные ситуации, в которых вы на самом деле хотите перехватить соответствующее исключение (а не только универсальное *)1002 *).

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

Рассмотрим этот пример: у вас есть поток, который запускает Read каждую минуту в течение 5 минут (это не очень реалистичный пример, но он должен дать вам представление о том, почему вы хотите обрабатывать различные исключения).Вы должны остановить поток через 5 минут, потому что ваше приложение будет закрыто, и вы не хотите ждать еще минуту, пока не будет прочитан флаг running ... в конце концов, вы не хотите, чтобы ваш пользовательждать целую минуту, чтобы просто закрыть приложение.Чтобы сразу же остановить поток, вы устанавливаете флаг в false и вызываете Interrupt в своей теме.В этом случае вам специально нужно перехватить исключение ThreadInterrupted, поскольку оно говорит вам, что вы должны выйти из цикла.Если вы поймали другое исключение, значит, вам не удалось выполнить задачу, но вы не хотите отказываться от задания все вместе, и вы хотели бы попытаться прочитать снова в следующую минуту.Это показывает, как ваши требования определяют тип исключений, которые вы должны обработать.Вот пример в коде:

bool running = true;

Thread t = new Thread(()=>
{
    while(running)
    {
        try
        {
            // Block for 1 minute
            Thread.Sleep(60*1000); 

            // Perform one read per minute
            personsReader.Read(filename, persons);
        }
        catch (KeyNotFoundException e)
        {
            // Perform a specific exception handling when the key is not found
            // but do not exit the thread since this is not a fatal exception
            MessageBox.Show(e.Message);
        }
        catch(InterruptException)
        {
            // Eat the interrupt exception and exit the thread, because the user
            // has signalled that the thread should be interrupted.
            return;
        }
        catch(Exception e)
        {
            // Perform a genetic exception handling when another exception occurs
            // but do not exit the thread since this is not a fatal error.
            MessageBox.Show("A generic message exception: " + e.Message);
        }
    }
});

t.IsBackground = true;
t.Start();

// Let the thread run for 5 minutes
Thread.Sleep(60*5000);

running = false;
// Interrupt the thread
t.Interrupt();

// Wait for the thread to exit
t.Join();

Теперь перейдем к другой проблеме, за исключением того, что она не отображается: обратите внимание, что вы получаете доступ к person[e.Name.ToString()] = e.Value, который требует поиска ключа, и если ключ не находится вкарта, то вы можете получить KeyNotFoundException.Это будет общее исключение, которое вы перехватываете, и ваше пользовательское исключение никогда не будет выдано, потому что person[e.Name.ToString()] может выдать даже прежде, чем вы доберетесь до своего кода.

foreach (XElement e in person)
    person[e.Name.ToString()] = e.Value; // <-- May be throwing the KeyNotFoundException

if (person["Name"] == null || person["Job"] == null || person["HairColor"] == null)
    throw new KeyNotFoundException("Person element not found.");

Кроме того, вы не хотите бросать KeyNotFoundException, когда вы действительно нашли ключ, но не нашли соответствующего значения: если person["Name"] == null оценивается как true, тогдаКлюч «Имя» фактически был найден в словаре person, поэтому бросание KeyNotFoundException может ввести в заблуждение любого, кто поймает это исключение.В случае, если ваше значение равно null, тогда, вероятно, не рекомендуется создавать исключение в любом случае ... это действительно не исключительный случай.Вы можете вернуть флаг, указывающий, что ключ не был найден:

public bool PerformRead(/*... parameters ...*/)
{
    foreach (XElement e in person)
    {
        // Avoid getting the KeyNotFoundException
        if(!person.ContainsKey(e.Name.ToString()))
        {
            person.Add(e.Name.ToString(), "some default value");
        }
        person[e.Name.ToString()] = e.Value;
    }

    if (person["Name"] == null || person["Job"] == null || person["HairColor"] == null)
    {
        return false;
    }
    else
    {
        return true;
    }
}
1 голос
/ 30 августа 2011

Что касается того, что вы делаете, это выглядит в основном нормально.Я не могу сказать, должны ли вы генерировать исключения в этой конкретной точке, но бросание и отлов выполнены правильно.Что касается сообщения, оно должно работать так, как оно есть.Попробуйте отобразить e.ToString(), чтобы увидеть стек вызовов.Может случиться так, что простое выполнение person["Name"] - это сначала выбросить KeyNotFoundException.

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

Например, если вы получите KeyNotFoundException, вы можете упомянуть кое-что о том, как файл неправильно отформатирован, и, возможно, отобразить файл пользователюна экране.Если вы получите FileNotfoundException, вы можете указать им путь и открыть OpenFileDialog, чтобы они выбрали новый файл.Исключения из-за разрешений безопасности вы можете отобразить инструкции, чтобы они повысили ваши разрешения.Некоторые исключения могут быть восстановлены (возможно, один элемент плохо отформатирован, но с остальными все в порядке; может ли все это провалиться?)

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

0 голосов
/ 30 августа 2011

В вашем коде вы запускаете xxx при оценке условия внутри оператора if.

Запрашивать person["Name"] == null || person["Job"] == null || person["HairColor"] == null не удастся, если какой-либо из этих ключей отсутствует в вашем словаре.

Вам нужно сделать это вместо:

if (!person.ContainsKey("Name"] ||
    !person.ContainsKey("Job"] ||
    !person.ContainsKey("HairColor"))

Итак, ваш вызов для исключения не выполняется! И вот почему вы никогда не видите свое сообщение.

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

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

0 голосов
/ 30 августа 2011

«Исключения - для исключительных обстоятельств» - неизвестно

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

0 голосов
/ 30 августа 2011

Я не совсем уверен, почему вы не получаете свое сообщение об ошибке. Это должно происходить (если это не что-то иначе , выбрасывающее KeyNotFoundException, а не то, которое вы явно выбрасываете).

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

Пример:

public static void Main()
{
    var filename = "whatever";

    try
    {
        personsReader.Read(filename, persons);
        var result = personsReader.DoSomethingAfterReading();
        result.DoSomethingElse();
    }
    catch (KeyNotFoundException e)
    {
        MessageBox.Show(e.Message);
    }
    finally
    {
        personsReader.CloseIfYouNeedTo();
    }

    DoSomeUnrelatedCodeHere();
}

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

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

Вот пример нового исключения. Что если что-то идет не так, как надо, и в этой строке:

IEnumerable<XElement> person = xReader.Descendants("Person").Elements();

... вы получаете OutOfMemoryException? Если вы действительно просто отобразите всплывающее окно для пользователя и дадите своей программе попытаться продолжить, как обычно, даже если просто не сможете это сделать? А что, если , потому что вы молча потерпели неудачу на OutOfMemoryException, вы позже пытаетесь разыменовать нулевую ссылку и получаете NullReferenceException, который вызывает сбой вашей программы? У вас будет чертовское время, пытаясь отследить причину, по которой эта ссылка была нулевой.

Лучший способ выявить ошибку - быстро потерпеть неудачу и шумно потерпеть неудачу.

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