Как отличить неожиданные (с головой) исключения от ожидаемых (экзогенных)? - PullRequest
2 голосов
/ 09 июля 2019

В этом посте я использую классификацию исключений по @Eric Lippert, которую вы можете найти здесь: Исчезающие исключения

Самые важные в этом случае:

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

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

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

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

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

Способ, которым я в настоящее время достигаю этого, заключается в том, чтобы явно отловить ТОЛЬКО экзогенных исключений в компонентах низкого уровня и обернуть ихв пользовательские исключения.В верхнем слое (в моем случае ViewModel приложения MVFM MVVM) я затем явно перехватываю пользовательское исключение, чтобы показать предупреждение.Во втором блоке catch я перехватываю общие исключения, чтобы показать ошибку.

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

После прочтения этой статьи dotnetpro - Implementierungsausnahmen Мне также интересно, , следует ли мне обернуть все (в том числе с костью) исключения в обычайисключения для предоставления дополнительной контекстной информации при их регистрации?

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

1048 *Пример высокоуровневого обработчика перехвата во ViewModel:
public class MainWindowViewModel
{
    private readonly ICustomerRepository _customerRepository;

    public MainWindowViewModel(ICustomerRepository customerRepository)
    {
        _customerRepository = customerRepository;
        PromoteCustomerCommand = new DelegateCommand(PromoteCustomer);
    }

    public ICommand PromoteCustomerCommand { get; }

    private void PromoteCustomer()
    {
        try
        {
            Customer customer = _customerRepository.GetById(1);
            customer.Promote();
        }
        catch (DataStoreLoadException ex)
        {
            // A expected exogenous exception. Show a localized message with some hints and log as warning.
            Log(LogLevel.Warning, ex);
            ShowMessage("Unable to promote customer. It could not be loaded. Try to...", ex);
        }
        catch (Exception ex)
        {
            // A unexpected boneheaded exception. Show a localized message, so that the users contacts the support and log as error.
            Log(LogLevel.Error, ex);
            ShowMessage("Unable to promote customer because of an unknown error. Please contact support@example.com", ex);
        }
    }
}

Пример низкоуровневой упаковки исключений:

public class SqlCustomerRepository : ICustomerRepository
{
    public Customer GetById(long id)
    {
        try
        {
            return GetFromDatabase(id);
        }
        catch (SqlException ex)
        {
            // Wrap the exogenous SqlException in a custom exception. The caller of ICustomerRepository should not depend on any implementation details, like that the data is stored in a SQL database.
            throw new DataStoreLoadException($"Unable to get the customer with id {id} from SQL database.", ex);
        }

        // All other exceptions bubble up the stack without beeing wrapped. Is it a good idea, or should I do something like this to provide additional context? (Like the id in this case)
        /*catch (Exception ex)
        {
            throw new DataStoreException($"Unknown error while loading customer with id {id} from SQL database.", ex);
        }*/
    }
}

1 Ответ

1 голос
/ 09 июля 2019

Хотя в нашем коде нет такой точной классификации, наша обработка исключений часто неявно указывает, считали ли мы возможным (экзогенным) конкретное исключение или мы просто учитываем возможные ошибки.

Используя пример Эрика, если мы получим доступ к файлу, поместим его в try/catch и явно поймем FileNotFoundException, то должно указать, что мы поняли, что FileNotFoundException был возможным результатом, даже если мы проверили миллисекунду ранее, чтобы увидеть, существует ли она.

Если, с другой стороны, наш код включает это:

try
{
    // do some stuff
}
catch(Exception ex)
{
    // maybe log it
}

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

Что (как бы) отличает их, так это то, что один указывает на то, что мы поняли, что это возможно, и объяснил это, а другой говорит: «Будем надеяться, что здесь все идет не так, как надо».

Даже это различие не совсем ясно. Наш код доступа к файлу может быть в «неопределенном» try/catch(Exception ex) блоке. Мы знаем, что удаленно возможно, что из-за состояния гонки файл может не существовать. В этом случае мы просто позволим этой расплывчатой ​​обработке исключений его поймать. Это может зависеть от того, что должно произойти. Если мы удаляли файл и оказалось, что он не существует, нам ничего не нужно делать. Если нам нужно было прочитать это, и теперь оно ушло, это всего лишь исключение. Возможно, нам не поможет поймать это конкретное исключение, если результат будет таким же, как и для любого другого исключения.

Точно так же, то, что мы явно отлавливаем исключение, не гарантирует, что оно не является "тупым". Может быть, я что-то не так делаю, и иногда мой код выдает ObjectDisposedException. Я понятия не имею, почему это так, поэтому я добавляю catch(ObjectExposedException ex). На первый взгляд может показаться, что я знаю, что происходит в моем коде, но я действительно не знаю. Я должен был выяснить проблему и исправить ее вместо того, чтобы поймать исключение, не имея ни малейшего представления, почему это происходит. Если приложение не работает время от времени, и я не знаю почему, тот факт, что я поймал исключение, в лучшем случае бесполезен или в худшем случае вреден, поскольку скрывает то, что происходит на самом деле.


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


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

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

try
{
    _otherLayer.DoSomeStuff();
}
catch(Exception ex)
{
    _logger.Log(ex);       
}

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

Если цель заключенного в исключение исключения состоит в добавлении контекста, например

throw new DataStoreLoadException($"Unable to get the customer with id {id} from SQL database.", ex);

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

Но возможно ли, что мы получим точно такой же пробег от этого?

throw new Exception($"Unable to get the customer with id {id} from SQL database.", ex);

Если нигде нет строки кода, в которой говорится, что catch(DataStoreLoadException ex) и делают что-то иное, чем это было бы с любым другим исключением , то, скорее всего, мы от этого не получим никакой выгоды.

Стоит отметить, что несколько лет назад Microsoft рекомендовала наследовать наши пользовательские исключения от ApplicationException вместо Exception.Это будет отличать пользовательские и системные исключения.Но стало очевидно, что это различие не добавило ценности.Мы не говорили: «Если это ApplicationException, сделайте это, иначе сделайте это».То же самое часто верно и для других пользовательских исключений, которые мы определяем.

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