Всегда интересовал вопрос, как правильно сделать оформление блоков кода, когда блоков среднее кол-во, допустим 5. У каждого человека своё мнение и каждый любит писать по своему. Конечно всё зависит от ситуации, но всё же хотелось бы услышать ответ. Вот пример, псевдокод который что-то делает и возвращает 1 в случае успеха и 0 в случае ошибки. В коде только 1 выделение памяти, но по факту может быть и больше + использование других ресурсов, которые необходимо освобождать 1) Всё вложено, результат заносится в переменную, первоначально инициализирован Плюсы: + у функции одна точка входа и одна точка выхода (что является вроде как алгоритмически правильным) + никогда не забываем освободить память. Минусы: + большая вложенность кода + требует первоначальную инициализацию переменной + использование дополнительной переменной для результата Код (Text): int MainFunct(int Param) { char* Mem; int x; int Status = 1; if (Param != MAGIC_VAL) { Mem = malloc(MEM_SIZE); if (Mem) { for (x = 0; x < MEM_SIZE; x++) { if (Funct(Mem, x, Param)) { Status = 0; break; } } Proc1(Mem); free(Mem); } } return Status; } 2) Всё вложено, результат заносится в переменную, первоначально НЕ инициализирован Плюсы: + у функции одна точка входа и одна точка выхода (что является вроде как алгоритмически правильным) + никогда не забываем освободить память. + не требует инициализировать переменную Минусы: + большая вложенность кода + использование дополнительной переменной для результата + увеличение объема кода Код (Text): int MainFunct(int Param) { char* Mem; int x; int Status; if (Param != MAGIC_VAL) { Mem = malloc(MEM_SIZE); if (Mem) { Status = 1; for (x = 0; x < MEM_SIZE; x++) { if (Funct(Mem, x, Param)) { Status = 0; break; } } Proc1(Mem); free(Mem); } else { Status = 0; } } else { Status = 0; } return Status; } 3) Всё вложено, результат по ходу действия Плюсы: + не требует дополнительная переменная Минусы: + у функции одна точка входа и множество точек выхода что затрудняет отладку + большая вложенность кода + можно забыть освободить память Код (Text): int MainFunct(int Param) { char* Mem; int x; if (Param != MAGIC_VAL) { Mem = malloc(MEM_SIZE); if (Mem) { for (x = 0; x < MEM_SIZE; x++) { if (Funct(Mem, x, Param)) { free(Mem); return 0; } } Proc1(Mem); free(Mem); return 1; } } return 0; } 4) Всё разделено на отдельные блоки, результат по ходу дела Плюсы: + нет большой вложенности + не требует дополнительная переменная + более понятный код Минусы: + у функции одна точка входа и множество точек выхода что затрудняет отладку + можно забыть освободить память Код (Text): int MainFunct(int Param) { char* Mem; int x; if (Param == MAGIC_VAL) { return 0; } Mem = malloc(MEM_SIZE); if (!Mem) { return 0; } for (x = 0; x < MEM_SIZE; x++) { if (Funct(Mem, x, Param)) { free(Mem); return 0; } } Proc1(Mem); free(Mem); return 1; } 5) Всё разделено на отдельные блоки, для выхода используется метка Плюсы: + нет большой вложенности + не требует дополнительная переменная + более понятный код + у функции одна точка входа и только 2 точки выхода расположенные рядом Минусы: + можно забыть освободить память + использование меток (хотя это не минус, но всё же многие не любят), хотя Microsoft довольно часто использует метки Код (Text): int MainFunct(int Param) { char* Mem; int x; if (Param == MAGIC_VAL) { goto _Error; } Mem = malloc(MEM_SIZE); if (!Mem) { goto _Error; } for (x = 0; x < MEM_SIZE; x++) { if (Funct(Mem, x, Param)) { free(Mem); goto _Error; } } Proc1(Mem); free(Mem); return 1; _Error: return 0; } 6) Всё разделено на отдельные блоки, для выхода используется метка и переменная с результатом Плюсы: + нет большой вложенности + более понятный код + у функции одна точка входа и и одна точка выхода Минусы: + можно забыть освободить память + использование меток + требуется дополнительная переменная Код (Text): int MainFunct(int Param) { char* Mem; int x; int Status = 0; if (Param == MAGIC_VAL) { goto _Exit; } Mem = malloc(MEM_SIZE); if (!Mem) { goto _Exit; } for (x = 0; x < MEM_SIZE; x++) { if (Funct(Mem, x, Param)) { free(Mem); goto _Exit; } } Proc1(Mem); free(Mem); Status = 1; _Exit: return Status; } Ну и так далее и тому подобные варианты. Вот и хочется услышать что лучше использовать? Смотрел на исходники от MS - там везде всё по разному, бывает и вложенность больше 5, и метки используются, и выход по среди кода через return и выход в конце с использованием переменной для результата, т.е. как видно - каждый разработчик писал так, как ему больше нравится.
Тогда маразм получается. Каждую мелочь в отдельную функцию/процедуру помещать и потом тоннами передавать параметры. Может ты не так выразился? Я имею в виду 5 блоков - это 5 конструкций вида Код (Text): XXX { } а не их глубина вложенности одного в другова
Ой, я вот был любителем 1/2-го вариантов, сейчас у меня смесь 1/2 + 4-го, в зависимости от ситуации, но 4-й преобладает, и вообще стараюсь избегать вложений больше 2-3х, делю на функции. Проще код читать так... Или старею...
У меня такие приоритеты (от -10 до 10): понятность кода 10 отсутствие утечек памяти 10 быстродействие 8 количество кода 1 малая вложенность 1 уменьшение количества переменных 0 одна точка выхода 0 использование меток -1 -10, ну например, использование глючных и тормозных сторонних библиотек
1. goto не использовать. 2. если блок содержит одну инструкцию, избавиться от {} 3. Использовать по возможности return value. 4. { и } всегда "одиноки" на строчке для блоков, исключение: } while (...) 5. Несколько последовательных присваиваний выравниваются так, чтобы операторы присваивания были строго друг под другом, а их позиция была кратна величине пробела по tab. У меня вот такой какой-то стиль.
Меня интересует именно написание кода, а не стиль оформления блоков. Каждый человек привык к своему стилю оформления, но тут чуть другая вещь идет.
2slesh сильная вложенность сильно ппц. Множественные точки выхода не проблема в принципе, особенно если используется cpp + RAII и ресурсы сами будут освобождаться при выходе за область видимости. Если мы говорим о plain C, то это проблема решается довольно просто и без goto, что-то типа Код (Text): do { if(!a) break; if(!b) break; } while(FALSE) // здесь освобождение ресурсов do-while не рассматривается как отдельный блок.
2 izl3sa подобное как раз и делаю когда очень много условий и нет возможности всё раскидать по функциям
2slesh Да имхо это оптимальный подход в данном случае. Но я все же советую вам потихоньку присматриваться к cpp, тк он, при должном уровне владения, позволит вам избегать глупых ошибок уровня кодирования (и это не единственный плюс). Впрочем каждой задаче свой язык =)
В C у меня иногда такой паттерн используется: Код (Text): NTSTATUS HighlyNestedFun() { NTSTATUS ntStatus = STATUS_SUCCESS; enum failure_stage {success, res1_failure, res2_failure, res3_failure, res4_failure, res5_failure} failureStage = success; HANDLE hRes1, hRes2, hRes3, hRes4, hRes5; hRes1 = AcquireResource1(); if (!hRes1) ntStatus = STATUS_INSUFFICIENT_RESOURCES1, failureStage = res1_failure; else { hRes2 = AcquireResource2(); if (!hRes2) ntStatus = STATUS_INSUFFICIENT_RESOURCES2, failureStage = res2_failure; else { hRes3 = AcquireResource3(); if (!hRes3) ntStatus = STATUS_INSUFFICIENT_RESOURCES3, failureStage = res3_failure; else { hRes4 = AcquireResource4(); if (!hRes4) ntStatus = STATUS_INSUFFICIENT_RESOURCES4, failureStage = res4_failure; else { hRes5 = AcquireResource5(); if (!hRes5) ntStatus = STATUS_INSUFFICIENT_RESOURCES5, failureStage = res5_failure; else { //using acquired resources } } } } } switch (failureStage) { case success: FreeResource5(hRes5); case res5_failure: FreeResource4(hRes4); case res4_failure: FreeResource3(hRes3); case res3_failure: FreeResource2(hRes2); case res2_failure: FreeResource1(hRes1); case res1_failure: } return ntStatus; } Преимущества: - унифицированное освобождение ресурсов (всё в одном месте, трудно забыть освободить что-то) - единственная точка выхода (повышает наглядность. Множественные goto к точке выхода точно также неудобны, как и множественные точки выхода) - вложенность не мешает, а наоборот наглядно отражает успешность выполнения функции
2l_inc Куча минусов у этого подхода, кроме того, что это имхо некрасиво, так ещё и куча дополнительной работы. Вложенность мешает при добавлении нового кода другим человеком.
Если все дело на C++, то наверное самым красивым решением будет делать классы-обертки для выделяемых ресурсов. Обертки должны освобождать ресурс в деструкторе, если он не были освобожден принудительно. Ну а сами обертки, естественно, создавать как автоматические переменные.
МОжно этим путем пойти, только вместо тонн параметров использовать структуру с набором всех параметров, необходимых самой вложенной функции, постепенно заполняя ее и передавая по цепочке указатель на нее.
slesh Погугли насчёт CodingStyle ядра linux. Там чётко написано, что уровней вложенности допускается три штуки. То есть, например, функция, в ней цикл, в цикле условие. Если программисту надо большее количество уровней вложенности, значит он не имеет достаточной квалификации для написания ядерного кода. Кстати с аргументами та же фишка: если аргументов у функции пять и больше, значит функция написана кривыми руками. Ещё там написано, что в функции должен быть ровно один return. Конечно, не стоит считать linux истиной в последней инстанции, но тот факт, что linux при таких ограничениях на код существует, всё же о чём-то говорит. izl3sa Нет, не решается. Если мы в функции выполняем штук пять операций, которые могут обломаться, и соответственно требуют обработки ошибок, сводящихся к освобождению ресурсов и возврату из функции, то предложенный вами do{}while(0); нисколько не выход, он лишь затрудняет чтение кода, и порождает запутанный эпилог функции, в которой будет стоять куча if'ов, в простейшей ситуации они будут выглядеть примерно так: if(file) fclose(file);. В более же сложных ситуациях придётся писать и вложенные условия и кучу другой гадости. goto finish1; goto finish2; и тп. гораздо естественнее и понятнее. Кстати заменяет комментарии поясняющие, что данными строками преследуется единственная цель -- обработка ошибочных ситуаций. Избавляться от goto в такой ситуации можно лишь если начальство боготворит Дейкстру и считает все его высказывания непререкаемой истиной, и, соответственно, использование готу считает ересью, за которую сжигают на костре. Если же начальство более вдумчиво, то готу лучше для всех.
r90 - читал этот документик, но там не всё так однозначно. В частности данный код выглядит просто убийственным и тут бы уже лучше бы была бы вложенность Код (Text): int fun(int) { int result = -1; char *buf1; char *buf2; char *buf3; buf1 = malloc(SIZE); if (NULL == buf1) goto end1; buf2 = malloc(SIZE); if (NULL == buf2) goto end2; buf3 = malloc(SIZE); if (NULL == buf3) goto end3; . . . do_something . . . result = 0; free(buf3); end3: free(buf2); end2: free(buf1); end1: return result; }