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 ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.