Почему бы не использовать исключения в качестве регулярного потока управления? - PullRequest
178 голосов
/ 08 апреля 2009

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

C # и Java (и многие другие) имеют множество типов поведения, которое мне вообще не нравится (например, type.MaxValue + type.SmallestValue == type.MinValue): int.MaxValue + 1 == int.MinValue).

Но, увидев мою порочную природу, я добавлю немного оскорблений к этой травме, расширив это поведение, скажем, до типа Overridden DateTime. (Я знаю, что DateTime запечатан в .NET, но ради этого примера я использую псевдоязык, точно такой же, как C #, за исключением того факта, что DateTime не запечатан).

Переопределенный Add метод:

/// <summary>
/// Increments this date with a timespan, but loops when
/// the maximum value for datetime is exceeded.
/// </summary>
/// <param name="ts">The timespan to (try to) add</param>
/// <returns>The Date, incremented with the given timespan. 
/// If DateTime.MaxValue is exceeded, the sum wil 'overflow' and 
/// continue from DateTime.MinValue. 
/// </returns>
public DateTime override Add(TimeSpan ts) 
{
    try
    {                
        return base.Add(ts);
    }
    catch (ArgumentOutOfRangeException nb)
    {
        // calculate how much the MaxValue is exceeded
        // regular program flow
        TimeSpan saldo = ts - (base.MaxValue - this);
        return DateTime.MinValue.Add(saldo)                         
    }
    catch(Exception anyOther) 
    {
        // 'real' exception handling.
    }
}

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

Я думаю, что во многих случаях они более понятны, чем if-структуры, и не нарушают никаких контрактов, заключаемых методом.

ИМХО реакция "Никогда не используйте их для регулярного выполнения программы", которую, кажется, все имеют, не настолько хорошо развита, как сила этой реакции может оправдать.

Или я ошибаюсь?

Я читал другие посты, посвященные разным особым случаям, но я считаю, что в этом нет ничего плохого, если вы оба:

  1. Очистить
  2. Уважайте контракт вашего метода

Пристрели меня.

Ответы [ 24 ]

2 голосов
/ 25 сентября 2013

Одна эстетическая причина:

Попытка всегда идет с уловом, тогда как if не обязательно должна идти с else.

if (PerformCheckSucceeded())
   DoSomething();

С try / catch она становится намного более многословной.

try
{
   PerformCheckSucceeded();
   DoSomething();
}
catch
{
}

Это 6 строк кода слишком много.

1 голос
/ 08 апреля 2009

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

1 голос
/ 02 сентября 2013

Существует несколько общих механизмов, с помощью которых язык может позволить методу завершать работу, не возвращая значение и не возвращаясь к следующему блоку «catch»:

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

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

  • Пусть вызывающий абонент разместит информацию или код обработки исключений по фиксированному адресу относительно сложенного обратного адреса. Например, с ARM вместо использования инструкции «BL-подпрограмма» можно использовать последовательность:

        adr lr,next_instr
        b subroutine
        b handle_exception
    next_instr:
    

Для нормального выхода подпрограмма просто сделает bx lr или pop {pc}; в случае ненормального выхода подпрограмма либо вычтет 4 из LR перед выполнением возврата, либо использует sub lr,#4,pc (в зависимости от варианта ARM, режима выполнения и т. д.). Этот подход будет работать очень плохо, если вызывающая сторона не предназначена для разместите его.

Для языка или инфраструктуры, в которых используются проверенные исключения, может быть полезно, если они обрабатываются с помощью механизма, подобного # 2 или # 3 выше, в то время как непроверенные исключения обрабатываются с использованием # 1. Хотя реализация проверенных исключений в Java довольно неприятна, они не были бы плохой концепцией, если бы существовал способ, с помощью которого сайт вызова мог бы сказать, по сути, «Этот метод объявлен как выбрасывающий XX, но я не ожидаю этого когда-либо делать это, если это произойдет, перебросить как «непроверенное» исключение. В среде, где проверенные исключения обрабатывались таким образом, они могут быть эффективным средством управления потоком для таких вещей, как методы синтаксического анализа, которые в некоторых контекстах могут иметь высокая вероятность сбоя, но там, где сбой должен возвращать принципиально иную информацию, чем успех. Однако мне неизвестно о каких-либо фреймворках, использующих такой шаблон. Вместо этого, более распространенным является использование первого подхода, приведенного выше (минимальные затраты для - исключение, но высокая стоимость, когда создаются исключения) для всех исключений.

1 голос
/ 08 апреля 2009

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

В JVM создание исключения не так уж и дорого, только создание исключения с новым xyzException (...), поскольку последнее включает в себя обход стека. Поэтому, если у вас есть заранее созданные исключения, вы можете бросить их много раз без затрат. Конечно, таким образом вы не можете передавать данные вместе с исключением, но я думаю, что это все равно плохо.

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