c # обработка исключений, практический пример.Как бы вы это сделали? - PullRequest
13 голосов
/ 14 марта 2012

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

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

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

    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 framework.В качестве примера, DataContractJsonSerializer не показывает исключений для intellisense.Значит ли это, что конструктор никогда не потерпит неудачу?Могу ли я быть уверен?

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

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

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

Ответы [ 7 ]

20 голосов
/ 14 марта 2012

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

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

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

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

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

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

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

13 голосов
/ 15 марта 2012

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

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

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

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

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

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

1 голос
/ 15 марта 2012

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

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

0 голосов
/ 14 марта 2012

Рассмотрите возможность разделения метода на более мелкие, чтобы можно было обрабатывать ошибки, связанные с ними.

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

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

0 голосов
/ 14 марта 2012

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

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

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

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

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

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

0 голосов
/ 14 марта 2012

Посмотрите на Перехват единства . В этой среде вы можете использовать то, что называется 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...
        }             
    }
}

Конечно, существуют и другие механизмы перехвата.

0 голосов
/ 14 марта 2012

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

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

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

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