Допустимое использование goto для управления обработкой ошибок в C?

data = [5, 7, 10, 2, 7, 8, 1, 3,2]
def func(data):
    result =[]
    temp =[]
    data.append(data[-1])
    for i in range(1,len(data)):
        if data[i]>=data[i-1]:
            temp.append(data[i-1])
        else:
            temp.append(data[i-1])
            temp.reverse()
            result.extend(temp)
            temp=[]
    if len(temp)!=0:
        temp.reverse()
        result.extend(temp)
    temp.clear()
    return result

print(func(data))

# output [10, 7, 5, 8, 7, 2, 3, 1, 2] 
88
задан Arnaud 21 April 2016 в 09:28
поделиться

8 ответов

FWIF, I find the error handling idiom you gave in the question's example to be more readable and easier to understand than any of the alternatives given in the answers so far. While goto is a bad idea in general, it can be useful for error handling when done in a simple and uniform manner. In this situation, even though it's a goto, it's being used in well-defined and more or less structured manner.

55
ответ дан 24 November 2019 в 07:37
поделиться

Мне кажется, что cleanup_3 должен выполнить его очистку, а затем вызвать cleanup_2 . Аналогично, cleanup_2 должен выполнить очистку, а затем вызвать cleanup_1. Похоже, что каждый раз, когда вы делаете cleanup_ [n] , что cleanup_ [n-1] требуется, таким образом, это должно быть ответственностью метода (так что, например, cleanup_3 никогда нельзя вызвать, не вызвав cleanup_2 и, возможно, вызвав утечку.)

Учитывая такой подход, вместо gotos, вы просто вызовете процедуру очистки, а затем вернетесь.

Подход goto не является неправильным или плохим , однако, просто стоит отметить, что это не обязательно «самый чистый» подход (ИМХО).

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

0
ответ дан 24 November 2019 в 07:37
поделиться

Я лично являюсь последователем «Сила десяти - 10 правил написания критического кода безопасности» .

Я включу небольшой фрагмент этого текста это иллюстрирует то, что я считаю хорошей идеей о goto.


Правило: ограничивайте весь код очень простыми конструкциями потока управления - не используйте goto операторы, конструкции setjmp или longjmp, а также прямая или косвенная рекурсия.

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


Изгнать использование goto кажется плохим , но:

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

1
ответ дан 24 November 2019 в 07:37
поделиться

GOTO полезен. Это то, что может сделать ваш процессор, и именно поэтому у вас должен быть к нему доступ.

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

2
ответ дан 24 November 2019 в 07:37
поделиться

В выражении goto нет ничего морально неправильного, кроме как что-то морально неправильное с (void) * указателями.

Все дело в том, как вы используете инструмент. В представленном вами (тривиальном) случае оператор case может реализовать ту же логику, хотя и с большими накладными расходами. На самом деле вопрос в том, каково мое требование к скорости?

goto довольно быстр, особенно если вы тщательно следите за тем, чтобы оно компилировалось в короткий прыжок. Идеально подходит для приложений, где скорость является премией. Для других приложений, вероятно, имеет смысл использовать накладные расходы в случае поддержки if / else +.

Помните: goto не убивает приложения, разработчики убивают приложения.

ОБНОВЛЕНИЕ: Вот пример случая

int foo(int bar) { 
     int return_value = 0 ; 
     int failure_value = 0 ;

     if (!do_something(bar)) { 
          failure_value = 1; 
      } else if (!init_stuff(bar)) { 
          failure_value = 2; 
      } else if (prepare_stuff(bar)) { 
          return_value = do_the_thing(bar); 
          cleanup_3(); 
      } 

      switch (failure_value) { 
          case 2: cleanup_2(); 
          case 1: cleanup_1(); 
          default: break ; 
      } 
} 
4
ответ дан 24 November 2019 в 07:37
поделиться

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

FWIW, если вы посмотрите учебники developer.apple.com, они используют подход goto к обработке ошибок.

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

8
ответ дан 24 November 2019 в 07:37
поделиться

Как правило, хорошая идея - избегать goto, но злоупотребления, которые были распространены, когда Дейкстра впервые написал «GOTO рассмотрено как вредное», в наши дни даже не приходят в голову большинству людей как вариант. .

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

Ваш конкретный пример можно упростить следующим образом (шаг 1):

int foo(int bar)
{
    int return_value = 0;
    if (!do_something(bar)) {
        goto error_1;
    }
    if (!init_stuff(bar)) {
        goto error_2;
    }
    if (prepare_stuff(bar))
    {
        return_value = do_the_thing(bar);
        cleanup_3();
    }
error_2:
    cleanup_2();
error_1:
    cleanup_1();
    return return_value;
}

Продолжение процесс:

int foo(int bar)
{
    int return_value = 0;
    if (do_something(bar))
    {   
        if (init_stuff(bar))
        {
            if (prepare_stuff(bar))
            {
                return_value = do_the_thing(bar);
                cleanup_3();
            }
            cleanup_2();
        }
        cleanup_1();
    }
    return return_value;
}

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

16
ответ дан 24 November 2019 в 07:37
поделиться

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

Учтите:

  1. do_something (), init_stuff () и prepare_stuff (), похоже, знают, потерпели ли они неудачу, поскольку в этом случае они возвращают либо false, либо nil.
  2. Ответственность за настройку состояния, по-видимому, лежит на этих функциях, поскольку состояние не устанавливается непосредственно в foo ().

Следовательно: do_something (), init_stuff () и prepare_stuff () должны выполнять свою собственную очистку . Наличие отдельной функции cleanup_1 (), которая очищает после do_something (), нарушает философию инкапсуляции. Плохой дизайн.

Если они производили очистку самостоятельно, то foo () становится довольно простым.

С другой стороны. Если foo () действительно создает свое собственное состояние, которое необходимо удалить, тогда подойдет goto.

0
ответ дан 24 November 2019 в 07:37
поделиться
Другие вопросы по тегам:

Похожие вопросы: