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

С# обработка исключений, практический пример. Как бы вы это сделали?

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

Мой примерный метод загружает данные из URL-адреса и пытается сериализовать его в заданный тип, а затем возвращает экземпляр, заполненный данными.

Во-первых, без каких-либо обработки исключений:

    private static T LoadAndSerialize<T>(string url)
    {            
        var uri = new Uri(url);
        var request = WebRequest.Create(uri);
        var response = request.GetResponse();
        var stream = response.GetResponseStream();

        var result = Activator.CreateInstance<T>();
        var serializer = new DataContractJsonSerializer(result.GetType());
        return (T)serializer.ReadObject(stream);            
    }

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

Это первая попытка обработать все, что может пойти не так:

    private static T LoadAndSerialize<T>(string url)
    {
        Uri uri;
        WebRequest request;
        WebResponse response;
        Stream stream;
        T instance;
        DataContractJsonSerializer serializer;

        try
        {
            uri = new Uri(url);
        }
        catch (Exception e)
        {
            throw new Exception("LoadAndSerialize : Parameter 'url' is malformed or missing.", e);
        }

        try
        {
            request = WebRequest.Create(uri);
        }
        catch (Exception e)
        {
            throw new Exception("LoadAndSerialize : Unable to create WebRequest.", e);
        }

        try
        {
            response = request.GetResponse();
        }
        catch (Exception e)
        {
            throw new Exception(string.Format("LoadAndSerialize : Error while getting response from host '{0}'.", uri.Host), e);
        }

        if (response == null) throw new Exception(string.Format("LoadAndSerialize : No response from host '{0}'.", uri.Host));

        try
        {
            stream = response.GetResponseStream();
        }
        catch (Exception e)
        {
            throw new Exception("LoadAndSerialize : Unable to get stream from response.", e);
        }

        if (stream == null) throw new Exception("LoadAndSerialize : Unable to get a stream from response.");

        try
        {
            instance = Activator.CreateInstance<T>();
        }
        catch (Exception e)
        {
            throw new Exception(string.Format("LoadAndSerialize : Unable to create and instance of '{0}' (no parameterless constructor?).", typeof(T).Name), e);
        }

        try
        {
            serializer = new DataContractJsonSerializer(instance.GetType());
        }
        catch (Exception e)
        {

            throw new Exception(string.Format("LoadAndSerialize : Unable to create serializer for '{0}' (databinding issues?).", typeof(T).Name), e);
        }


        try
        {
            instance = (T)serializer.ReadObject(stream);
        }
        catch (Exception e)
        {
            throw new Exception(string.Format("LoadAndSerialize : Unable to serialize stream into '{0}'.", typeof(T).Name), e);                   
        }

        return instance;
    }

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

Итак, что, если я зацеплю за ловушку. Моя следующая попытка:

    private static T LoadAndSerialize<T>(string url)
    {
        try
        {
            var uri = new Uri(url);
            var request = WebRequest.Create(uri);
            var response = request.GetResponse();
            var stream = response.GetResponseStream();
            var serializer = new DataContractJsonSerializer(typeof(T));
            return (T)serializer.ReadObject(stream);
        }
        catch (ArgumentNullException e)
        {
            throw new Exception("LoadAndSerialize : Parameter 'url' cannot be null.", e);
        }             
        catch (UriFormatException e)
        {
            throw new Exception("LoadAndSerialize : Parameter 'url' is malformed.", e);
        }
        catch (NotSupportedException e)
        {
            throw new Exception("LoadAndSerialize : Unable to create WebRequest or get response stream, operation not supported.", e);
        }
        catch (System.Security.SecurityException e)
        {
            throw new Exception("LoadAndSerialize : Unable to create WebRequest, operation was prohibited.", e);
        }
        catch (NotImplementedException e)
        {
            throw new Exception("LoadAndSerialize : Unable to get response from WebRequest, method not implemented?!.", e);
        }
        catch(NullReferenceException e)
        {
            throw new Exception("LoadAndSerialize : Response or stream was empty.", e);
        }
    }

В то время как это конечно легче на глазах, я сильно наклоняюсь к intellisense, чтобы предоставить все исключения, которые могут быть выброшены из метода или класса. Я не уверен, что эта документация на 100% точна, и будет еще более скептически, если некоторые из методов приходят из сборки вне рамок .net. В качестве примера в DataContractJsonSerializer нет исключений для intellisense. Означает ли это, что конструктор никогда не потерпит неудачу? Могу ли я быть уверенным?

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

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

Мне бы хотелось увидеть ваши мысли и/или реализации!

4b9b3361

Ответ 1

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

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

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

Нет, вы не можете быть уверены. С# и .NET вообще не похожи на Java, где вы должны объявлять, какие исключения могут быть выбраны.

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

Это действительно лучший вариант.

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

Ответ 2

Сначала прочитайте мою статью об обработке исключений:

http://ericlippert.com/2008/09/10/vexing-exceptions/

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

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

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

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

Ответ 3

Прежде всего, вы должны, для всех практических целей, никогда не бросать тип Exception. Всегда бросайте что-то более конкретное. Даже ApplicationException было бы лучше, незначительно. Во-вторых, используйте отдельные инструкции catch для разных операций, когда и только тогда, когда вызывающий абонент будет иметь причины заботиться о том, какая операция завершилась неудачно. Если исключение InvalidOperationException, которое происходит в одной точке вашей программы, будет означать что-то другое в отношении состояния вашего объекта, чем какое-либо событие, которое происходит в какое-то другое время, и если ваш вызывающий абонент будет заботиться об этом различии, тогда вы должны обернуть первую часть ваша программа в блоке "try/catch", который обернет InvalidOperationException в какой-то другой (возможно, настраиваемый) класс исключений.

Понятие "только исключения catch, которые вы знаете, как обрабатывать", хорошо теоретически, но, к сожалению, большинство типов исключений настолько смутно относятся к состоянию базовых объектов, что почти невозможно узнать, можно ли "обрабатывать" исключение или не. Например, у вас может быть процедура TryLoadDocument, которая должна внутренне использовать методы, которые могут генерировать исключения, если части документа не могут быть загружены. В 99% случаев, когда такое исключение возникает, правильным способом "обработать" такое исключение будет просто отказаться от частично загруженного документа и вернуться, не подвергая его вызывающему. К сожалению, очень трудно определить 1% случаев, когда этого недостаточно. Вы должны попытаться бросить различные исключения в случаях, когда ваша рутина провалилась, ничего не делая, по сравнению с теми, где она могла иметь другие непредсказуемые побочные эффекты; к сожалению, вы, вероятно, застряли в угадывании интерпретации большинства исключений из подпрограмм, которые вы вызываете.

Ответ 4

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

Я бы не разделил его так, что просто беспорядок. Исключения в основном для ВАС. В идеале, если ваш пользователь вызывает исключения, вы поймали бы их раньше.

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

Ответ 5

Посмотрите Unity Interception. В рамках этой структуры вы можете использовать что-то, называемое ICallHandler, которое позволяет перехватывать вызовы и делать все, что вам нужно или нужно делать с перехваченным вызовом.

Например:

public IMethodReturn Invoke(IMethodInvocation input, 
    GetNextHandlerDelegate getNext)
{
    var methodReturn = getNext().Invoke(input, getNext);
    if (methodReturn.Exception != null)
    {
        // exception was encountered... 
        var interceptedException = methodReturn.Exception

        // ... do whatever you need to do, for instance:
        if (interceptedException is ArgumentNullException)
        {
            // ... and so on...
        }             
    }
}

Разумеется, существуют другие рамки перехвата.

Ответ 6

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

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

т.е. для вашего случая вы можете разделить метод на: CreateRequest (здесь обрабатываются неверные аргументы), GetResponse (обрабатывать сетевые ошибки), ParseRespone (обрабатывать ошибки содержимого).

Ответ 7

Я не согласен с @oded, когда он говорит:

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

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

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

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

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