LINUX.ORG.RU

Правильный try-except в Си


1

1

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

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

...
if (!(f = fopen(...))) {
    return 1;
}
...
if (!(g = log_open(...))) {
    fclose(f);
    return 1;
}
...
if (!(h = door_open(...))) {
    log_close(g);
    fclose(f);
    return 1;
}
...
...
...
if (!(z = mind_open(...))) {
    ...
    door_close(h);
    log_close(g);
    fclose(f);
    return 1;
То есть со временем обработчик ошибки становится всё более жирным.

Как нормальные программисты это организуют? Не предлагать писать не на Си, пожалуйста.

★

Не предлагать писать не на Си, пожалуйста.

Под каждую задачу - свой инструмент, как ни крути.

Если хочешь остаться на Сях - изобретай костыли, вроде:

while(true) {
   if(!f()) break;
   if(!f2()) break;
   ...
}

schizoid ★★★
()

Нормальные программисты используют в таких случаях блоки try-catch-finally.

LongLiveUbuntu ★★★★★
()

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

slackwarrior ★★★★★
()

Ненавижу мудаков, которые делают возврат из середины функции, да еще в нескольких местах. А try-except в Си лучше делать так:

{
    FILE *f = NULL;
    void *p = NULL;

    f = fopen(.....);
    if (!f)
        goto err_exit;
    p = malloc(...);
    if (!p)
        goto err_exit;

    /* .... */

    return 0;

    err_exit:
        if (p)
            free(p);
        if (f)
            fclose(f);
}
tailgunner ★★★★★
()

есть несколько вариантов. Я пишу инога такое

#define F(__c)  if(!(__c)) return false;

и далее в коде

F( my_call(...));

возможны различные вариации типа goto в конец с процедурой освобождения ресурсов и т.п.

mashina ★★★★★
()
Ответ на: комментарий от tailgunner

> Ненавижу мудаков
Не надо меня ненавидеть, я хороший.

делают возврат из середины функции, да еще в нескольких местах

Что в этом плохого?!

anonymous
()
Ответ на: комментарий от toady2

> goto рушит всю оптимизацию компилятора.

Вау. Ты, конечно, уже сравнил со своим вариантом, и с другими вариантами тоже?

tailgunner ★★★★★
()
Ответ на: комментарий от anonymous

>> Ненавижу мудаков

Не надо меня ненавидеть, я хороший.

А я буду!!11

делают возврат из середины функции, да еще в нескольких местах

Что в этом плохого?!

Затрудняет понимание, затрудняет отладку.

tailgunner ★★★★★
()
\\ Писал идиот, не читать.
enum ErrorType {
 ...
 ERR_OPENDOOR,
 ...
};

void errorHandler(ErrorType err)
{
 switch(err)
 {
  ...
  case ERR_OPENDOOR:
   close_door();
  case ERR_OPENFILE:
   close_file();
   ...
 }
}

только вот что с параметрами делать - понятия не имею(

frozenix ★★★
()
Ответ на: комментарий от tailgunner

> Ненавижу мудаков, которые делают возврат из середины функции, да еще в нескольких местах.

а мне не нравится туча из if(){}, по вложенности заползающих за правую границу экрана. Возврат из функции в середине выглядит вполне нормально и логично, функцию можно читать линейно, отметая вместе с return «отработанные» варианты, а не возвращаться к условию в if() после очередного else, находящегося где-то в середине пирамиды if'ов.

Slavaz ★★★★★
()
Ответ на: комментарий от Slavaz

> а мне не нравится туча из if(){}, по вложенности заползающих за правую границу экрана

Поддерживаю. Предлагаю сойтись на «вкусовщине».

anonymous
()
Ответ на: комментарий от anonymous

да, о вкусах не спорят.. пока сторонники разных вкусов не пересекаются в одном проекте :)

Slavaz ★★★★★
()
Ответ на: комментарий от Slavaz

>> Ненавижу мудаков, которые делают возврат из середины функции, да еще в нескольких местах.

а мне не нравится туча из if(){}, по вложенности заползающих за правую границу экрана.

В моем примере их нет.

Возврат из функции в середине выглядит вполне нормально и логично

Бугага. Впрочем, если все споры 70-х годов о структурном программировании прошли и мимо тебя, и мимо твоего преподавателя, я тебе не доктор.

P.S. вообще, функция должна иметь одну точку входа и одну точку выхода, но Си тако Си // программирование 101

tailgunner ★★★★★
()
Ответ на: комментарий от anonymous

Что-то мне так не кажется. Ты лично проверял!?

Нет. Но точно где-то читал; несколько раз и в разных местах. Смысл уловил тот, что когда в коде простые if'ы, while'ы... компилятор ещё более менее понимает, что происходит и поэтому может хорошо соптимизировать это. А когда вдруг встечается goto, переходящий на метку, которая непонятно где, компилятор уже не совсем понимает, что происходит и тупо всё оставляет как есть, не пытаясь соптимизировать это.

Ещё мнооого раз слышал про то, что goto вообще нужно избегать везде.

Это всё враки?

toady2 ★
() автор топика
Ответ на: комментарий от anonymous

>> Затрудняет понимание, затрудняет отладку.

Ок.

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

tailgunner ★★★★★
()
Ответ на: комментарий от toady2

> Ещё мнооого раз слышал про то, что goto вообще нужно избегать везде.

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

tailgunner ★★★★★
()
Ответ на: комментарий от tailgunner

Лучше так:

{
    FILE *f = NULL;
    void *p = NULL;

    f = fopen(.....);
    if (!f)
        goto out_return;
    p = malloc(...);
    if (!p)
        goto out_fclose;

    /* .... */

    return 0;

    out_free_p:
                free(p);
    out_fclose:
                fclose(f);
    out_return:
                return -1;
}
ttnl ★★★★★
()
Ответ на: комментарий от mashina

есть несколько вариантов. Я пишу инога такое

>#define F(__c)  if(!(__c)) return false;

и далее в коде

>F( my_call(...));

Плохой стиль. Ничего непонятно. Не надо путать людей на ровном месте.

ttnl ★★★★★
()
Ответ на: комментарий от tailgunner

Раньше всегда писал как ты. Потом попробовал не писать кучу if - else, а делать возвраты из середины функции. Хз что там со структурным программированием, но читабельность действительно повышает, и похрен что это не по дзену, значит этот дзен - говно.

dizza ★★★★★
()
Ответ на: комментарий от dizza

> Раньше всегда писал как ты. Потом попробовал не писать кучу if - else, а делать возвраты из середины функции

У меня нет else. А если ты обошелся даже без if, то явно познал какой-то более вызокий дзен.

и похрен что это не по дзену, значит этот дзен - говно.

Или... ну ты понял.

tailgunner ★★★★★
()
Ответ на: комментарий от tailgunner

>Это тоже подвержено ошибкам.

Ошибкам подвержено все. Но на то и мозг есть :)

ttnl ★★★★★
()
Ответ на: комментарий от ttnl

Плохой стиль. Ничего непонятно. Не надо путать людей на ровном месте.

нормальный стиль. Попробуй, чтоли, приложить немного мозги и понять. Логиа предельно простая, реализация паттерна ( A && B && C ... )

есть ещё такая вещь, как общая читабельность кода. С твоими явными goto, как ниже, более менее удобно работать с небольшими выражениями. А когда вместо ... будут реальные аргументы, то для общей читабельности придётся как-то прятать переходы в простые макросы.

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

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

mashina ★★★★★
()
Ответ на: комментарий от mashina

>нормальный стиль.

Отвратительный. Прятать return'ы в define'ы - хуже этого ничего нет.

есть ещё такая вещь, как общая читабельность кода. С твоими явными goto, как ниже, более менее удобно работать с небольшими выражениями. А когда вместо ... будут реальные аргументы, то для общей читабельности придётся как-то прятать переходы в простые макросы.

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

( A && B && C ... )

Делать вызов трех и более функций в одной строке - это тоже неправильно.

Для интереса, можешь привести пример своего кода, где это реально понадобилось, обсудим, как можно переписать его.

ttnl ★★★★★
()
Ответ на: комментарий от dizza

> То есть нету else?

Ну не вижу я их в коде, который привел.Хотя примерно то же можно написать и с длинной цепочкой if-else

Значит мы о каких то разных вещах говорим.

Значит, ты не читаешь написанного.

tailgunner ★★★★★
()
Ответ на: комментарий от tailgunner

код освобождения ресурсов размазывается по всей функции, вместо того, чтобы быть сосредоточенным в одном месте

тут надо заметить, что далеко не все функции занимаются выделением/освобождением ресурсов, а guard'ы какие-нибудь, например, порой весьма и весьма повышают читаемость кода

//звезду капитана принять готов

shty ★★★★★
()
Ответ на: комментарий от ttnl

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

в говно могут разполстизь и всего 3-5 вызовов ф-ий. Например, какой-нибудь стандартный типовой эпилог проверок уже может убить читабельность.

Делать вызов трех и более функций в одной строке - это тоже неправильно.

это не в одной строке, это линейное выполнение последовательностей вызовов до первого фола, примерно как в sh'ах или аналогично в си. Или всего лишь через максросы у меня в примере

F( !call_1(...));

F( !call_2(...));

и т.п.

Для интереса, можешь привести пример своего кода, где это реально понадобилось,

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

bool my_call_n(my_object obj*, ...);

обсудим, как можно переписать его.

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

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

mashina ★★★★★
()
Ответ на: комментарий от shty

> тут надо заметить, что далеко не все функции занимаются выделением/освобождением ресурсов

Ну как бы в топике обсуждается try-finally, который только для этого и используется.

а guard'ы какие-нибудь, например, порой весьма и весьма повышают читаемость кода

Я не знаю, что ты называешь guard'ами, и причем тут возвраты из середины функции.

tailgunner ★★★★★
()
Ответ на: комментарий от dizza

>> Значит, ты не читаешь написанного

Или...

В данном случае никаких «или» нет.

tailgunner ★★★★★
()
Ответ на: комментарий от mashina

> Мой подход к юзабилити проверен и отточен множествами сишных проектов.

Дас ист фантастиш!!11

tailgunner ★★★★★
()
Ответ на: комментарий от mashina

>Мой подход к юзабилити проверен и отточен множествами сишных проектов.

почему-то напомнило: «я автор статей, текстов и постов»

lazyklimm ★★★★★
()

оставь как есть. или вот идея.

    #define MAX_FILES
    FILE my_opened_files[MAX_FILES]; 
    /* или можно использовать динамические контейнеры, например что-нибудь из GLib */
    memset (my_opened_files, 0, sizeof(my_opened_files)); /* может, можно взять для этого что-то другое */

    bool file_error = false;

    if (!(f = fopen(...))) {
        file_error = true;
    }
    else {
        my_opened_files[0] = f;
    }
    ...
    if (!(g = log_open(...))) {
        file_error = true;
    }
    ...
    if (!(h = door_open(...))) {
        file_error = true;
    }
    else {
        my_opened_files[1] = f;
    }
    ...
    ...
    ...
    if (!(z = mind_open(...))) {
        file_error = true;
    }
    else {
        my_opened_files[2] = f;
    }
    ...
    /* и уже в конце */
    for (int i = 0; i < MAX_FILES; i++) {
        if (my_opened_files[i]) {
            fclose(my_opened_files[i]);
        }
    }

и лучше конечно с контейнером каким-нибудь адекватным

jeuta ★★★★
()
Ответ на: комментарий от dizza

>и похрен что это не по дзену, значит этот дзен - говно

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

slackwarrior ★★★★★
()
Ответ на: комментарий от tailgunner

> Ненавижу мудаков, которые делают возврат из середины функции, да еще в нескольких местах.

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

LamerOk ★★★★★
()

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

Если открытие ресурса зависит от открытия предыдущего, то можно использовать goto:

TYPE1 *name1 = type1_init();

if (!name1) goto first_fail;

...

TYPE2 *name2 = type2_init();

if (!name2) goto second_fail;

...

TYPE3 *name3 = type3_init();

if (!name3) goto third_fail;

...

third_fail:

type2_free(name2);

second_fail:

type1_free(name1);

....

sched ★
()
Ответ на: комментарий от LamerOk

> Возврат из функции должен быть ровно тогда, когда дальше делать нечего.

С философской точки зрения - безусловно.

tailgunner ★★★★★
()

> Как нормальные программисты это организуют?

Сначала разрабатывают стратегию обработки ошибок. Надо понять, где и когда аккуратно надо делать «откаты», а когда можно упасть с громким воплем. Потом реализуем аллокацию ресурсов малыми функциями. В идеале, надо постараться разбить аллокацию по логическим уровням не более одного - двух ресурсов за функцию.

В конкретно твоём коде достаточно обычных goto как тут: Правильный try-except в Си (комментарий)

LamerOk ★★★★★
()
Ответ на: комментарий от dizza

> читабельность действительно повышает, и похрен что это не по дзену, значит этот дзен - говно.

Это как раз по дзену. Не по дзену это чего-то не делать, потому что богородица не велит.

LamerOk ★★★★★
()
Ответ на: комментарий от tailgunner

> P.S. вообще, функция должна иметь одну точку входа и одну точку выхода

Был о тебе более высокого мнения.

slovazap ★★★★★
()
Ответ на: комментарий от slovazap

>> P.S. вообще, функция должна иметь одну точку входа и одну точку выхода

Был о тебе более высокого мнения.

Да, первое впечатление обо мне обычно обманчиво.

tailgunner ★★★★★
()
Ответ на: комментарий от tailgunner

>А try-except в Си лучше делать так:

А не проще вместо goto засовывать безошибочный вариант в блок else {} ?

dn2010 ★★★★★
()
Вы не можете добавлять комментарии в эту тему: топик перемещен в архив.