Почему бы не использовать исключения в качестве регулярного потока управления? - 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 ]

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

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

Например, когда я пытаюсь отследить ошибку в VS, я обычно включаю «разбить все исключения». Если вы используете исключения для управления потоком, то я буду регулярно ломать отладчик и буду вынужден игнорировать эти неисключительные исключения, пока не доберусь до реальной проблемы. Это может свести кого-то с ума !!

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

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

4 голосов
/ 27 февраля 2015

Вот рекомендации, которые я описал в своем блоге :

  • Создайте исключение, чтобы указать непредвиденную ситуацию в вашем программном обеспечении.
  • Использовать возвращаемые значения для проверки ввода .
  • Если вы знаете, как обращаться с исключениями, которые выдает библиотека, ловите их на минимально возможном уровне .
  • Если у вас неожиданное исключение, полностью отмените текущую операцию. Не притворяйся, что знаешь, как с ними бороться .
3 голосов
/ 08 апреля 2009

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

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

Я помню, что из моих дней C многие функции возвращали коды ошибок, например:

-1 - x lesser then MinX
-2 - x greater then MaxX
-3 - y lesser then MinY

и т.д.

Действительно ли он менее читабелен, чем бросать и ловить исключение?

3 голосов
/ 21 августа 2009

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

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

Сказал по-другому: то, что вы можете , не означает, что вы должны .

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

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

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

Пример: Microsoft Volta - это проект, позволяющий писать веб-приложения в прямом .NET и позволяющий платформе позаботиться о том, какие биты должны выполняться и где. Одним из следствий этого является то, что Volta должна иметь возможность компилировать CIL в JavaScript, чтобы вы могли запускать код на клиенте. Однако есть проблема: .NET имеет многопоточность, а JavaScript нет. Итак, Volta реализует продолжения в JavaScript, используя исключения JavaScript, а затем реализует потоки .NET, используя эти продолжения. Таким образом, приложения Volta, использующие потоки, могут быть скомпилированы для запуска в неизмененном браузере - Silverlight не требуется.

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

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

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

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

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

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

Наконец, все предварительные проверки показывают, что вы знаете или ожидаете, и делают это явным. Код должен быть понятен в намерениях. Что бы вы предпочли прочитать?

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

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

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

В вашем случае я недостаточно знаком с библиотекой C #, чтобы знать, есть ли у даты и времени простой способ проверить, не вышла ли временная метка за пределы. Если это так, просто позвоните if (.isvalid (ts)) в противном случае ваш код в основном в порядке.

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

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

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

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

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

2 голосов
/ 11 января 2015

Как уже неоднократно упоминали другие, принцип наименьшего удивления запрещает чрезмерное использование исключений только для целей потока управления. С другой стороны, ни одно правило не является на 100% правильным, и всегда есть случаи, когда исключением является «просто правильный инструмент» - кстати, очень похожий на goto, который поставляется в виде break и continue в таких языках, как Java, которые часто являются идеальным способом выпрыгнуть из сильно вложенных циклов, которых не всегда можно избежать.

Следующий пост в блоге объясняет довольно сложный, но также довольно интересный вариант использования для нелокального ControlFlowException:

Это объясняет, как внутри jOOQ (библиотеки абстракций SQL для Java) такие исключения иногда используются для прерывания процесса рендеринга SQL на ранних этапах, когда выполняется какое-то "редкое" условие.

Примеры таких условий:

  • Слишком много значений связывания. Некоторые базы данных не поддерживают произвольное количество значений связывания в своих операторах SQL (SQLite: 999, Ingres 10.1.0: 1024, Sybase ASE 15.5: 2000, SQL Server 2008: 2100). В этих случаях jOOQ прерывает фазу рендеринга SQL и повторно отображает инструкцию SQL с встроенными значениями связывания. Пример: * * тысяча двадцать-шесть

    // Pseudo-code attaching a "handler" that will
    // abort query rendering once the maximum number
    // of bind values was exceeded:
    context.attachBindValueCounter();
    String sql;
    try {
    
      // In most cases, this will succeed:
      sql = query.render();
    }
    catch (ReRenderWithInlinedVariables e) {
      sql = query.renderWithInlinedBindValues();
    }
    

    Если бы мы явно извлекали значения связывания из запроса AST для их подсчета каждый раз, мы бы тратили ценные циклы ЦП на те 99,9% запросов, которые не страдают от этой проблемы.

  • Некоторая логика доступна только косвенно через API, который мы хотим выполнить только «частично». Метод UpdatableRecord.store() генерирует оператор INSERT или UPDATE в зависимости от внутренних флагов Record. Со стороны мы не знаем, какая логика содержится в store() (например, оптимистическая блокировка, обработка прослушивателя событий и т. Д.), Поэтому мы не хотим повторять эту логику, когда храним несколько записей в пакетный оператор, где мы бы хотели, чтобы store() только генерировал оператор SQL, а не выполнял его на самом деле. Пример:

    // Pseudo-code attaching a "handler" that will
    // prevent query execution and throw exceptions
    // instead:
    context.attachQueryCollector();
    
    // Collect the SQL for every store operation
    for (int i = 0; i < records.length; i++) {
      try {
        records[i].store();
      }
    
      // The attached handler will result in this
      // exception being thrown rather than actually
      // storing records to the database
      catch (QueryCollectorException e) {
    
        // The exception is thrown after the rendered
        // SQL statement is available
        queries.add(e.query());                
      }
    }
    

    Если бы мы вывели логику store() в «повторно используемый» API, который можно настроить для необязательного , а не выполнения SQL, мы бы искали создание довольно сложного в обслуживании, вряд ли повторно используемый API.

Заключение * * тысяча пятьдесят-одна В сущности, наше использование этих нелокальных goto s точно соответствует тому, что [Мейсон Уилер] [5] сказал в своем ответе: "Я только что столкнулся с ситуацией, с которой я не могу должным образом разобраться на данном этапе, потому что у меня недостаточно контекста для его обработки, но подпрограмма, которая вызвала меня (или что-то еще выше в стеке вызовов), должна знать, как справиться с этим. " Оба варианта использования ControlFlowExceptions были довольно просты в реализации по сравнению с их альтернативами, что позволило нам повторно использовать широкий спектр логики без рефакторинга ее из соответствующих внутренних компонентов. Но ощущение того, что это будет сюрпризом для будущих сопровождающих, остается. Код кажется довольно деликатным, и, хотя в данном случае это был правильный выбор, мы всегда предпочитали бы не использовать исключения для локального потока управления, где легко избежать обычного ветвления через if - else.

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