slesh А по-моему, очень даже пристойно выглядит. И вложенность здесь абсолютно ни к чему. Это в lisp'е такого рода вещи удачно укладываются во многоступенчатую вложенность, но лисп вообще древовидный язык, и в нём движение вглубь дерева кода к детям, внукам и пра-пра-пра-...внукам текущего нода столь же просто и естественно как движение вдоль, перебирающее братьев-сестёр. Но за это лисп и не любят: завершения блоков кода вида ))))))) многих раздражает. В C же получается ещё хуже. Я как-то разглядывал программку, ещё досовскую, в которой было много-много вложенных блоков, именно с целью обработки ошибок, там десяток уровней вложенности был нормой. Это был просто абзац. Разобраться в коде я так и не смог. Но если вам кажется иначе... В такой ситуации я ничем не могу помочь. Вообще же, я разглядывал массу различных программ написанных на C с разными стилями оформления кода. И ядерный K&R стиль, по-моему, самый адекватный. Конечно же, вопросы выбора стиля очень субъективны, но вот на мой взгляд создатели языка С предложили самый адекватный стиль, всё последующие потуги что-то исправить либо сводятся к дополнениям к K&R (CodingStyle@Linux), либо просто ужасны и неудобны. Скажем, сорцы gcc вроде вполне читаемы, несмотря на гнутый стиль, но стоит только подольше в них поковыряться, как начинается отрыжка от функций длиной в двадцать экранов. Найти нужную строку в программе гораздо сложнее. Хотя, с другой стороны, такие функции нисколько не вредят удобочитаемости gtk+.
+1 linux kernel coding style +1 goto -- это не зло, а необходимость в случае, если не используются исключения
Функции с большой вложенностью - предпочитаю принцип do{}while(0);. Только через макро-расширения, чтобы читалось нормально: Код (Text): #define BLOCK_BEGIN do{ #define BLOCK_END }while(0); #define BLOCK_EXEC(s) if((s)==0)break; ... BLOCK_BEGIN BLOCK_EXEC ((hFile = CreateFile (...)) != -1) BLOCK_EXEC ((nBytes = GetFileSize (hFile, 0)) > 0) BLOCK_EXEC ((p = VirtualAlloc (0, nBytes, ...)) != 0) // ... etc. BLOCK_END // // Release resources here ... // Если мешает if(hfile)fclose(hfile);, то можно написать небольшие функции для этого. P.S. Если есть несколько аллокаций подряд, то лучше делать одним блоком: Код (Text): char* buf1 = malloc (SIZE1 + SIZE2 + SIZE3); char* buf2 = buf1 + SIZE1; char* buf3 = buf2 + SIZE2; ... free (buf1);
izl3sa Всего лишь неаргументированное ИМХО? А ИМХО красиво. Нет там никакой кучи. Если для Вас основной работой при написании кода является собственно набор, а не продумывание его построения, то либо Вы очень крутой программист, либо очень хреновый. "Это бредятина". © Если вложенность задействована так, что одного взгляда на индентацию достаточно, чтобы понять ход работы кода (как в посте #14), это хорошо. Конечно, вложенностью часто злоупотребляют, как в следующем примере: Код (Text): VOID HighlyNestedFun(BOOL flag1, BOOL flag2, BOOL flag3) { if (flag1) { if (flag2) { if (flag3) { //process flag1 = TRUE, flag2 = TRUE, flag3 = TRUE } else { //process flag1 = TRUE, flag2 = TRUE, flag3 = FALSE } } else { if (flag3) { //process flag1 = TRUE, flag2 = FALSE, flag3 = TRUE } else { //process flag1 = TRUE, flag2 = FALSE, flag3 = FALSE } } } else { if (flag2) { if (flag3) { //process flag1 = FALSE, flag2 = TRUE, flag3 = TRUE } else { //process flag1 = FALSE, flag2 = TRUE, flag3 = FALSE } } else { if (flag3) { //process flag1 = FALSE, flag2 = FALSE, flag3 = TRUE } else { //process flag1 = FALSE, flag2 = FALSE, flag3 = FALSE } } } } В таких случаях от вложенности надо избавляться. Например, таким паттерном: Код (Text): VOID UnrolledHighlyNestedFun(BOOL flag1, BOOL flag2, BOOL flag3) { enum {y = -1, n = 0, flag1_set = 1, flag2_set = 2, flag3_set = 4}; DWORD state = (flag1 ? flag1_set : 0) | (flag2 ? flag2_set : 0) | (flag3 ? flag3_set : 0); switch (state) { case ((flag1_set & y) | (flag2_set & y) | (flag3_set & y)): break; case ((flag1_set & y) | (flag2_set & y) | (flag3_set & n)): break; case ((flag1_set & y) | (flag2_set & n) | (flag3_set & y)): break; case ((flag1_set & y) | (flag2_set & n) | (flag3_set & n)): break; case ((flag1_set & n) | (flag2_set & y) | (flag3_set & y)): break; case ((flag1_set & n) | (flag2_set & y) | (flag3_set & n)): break; case ((flag1_set & n) | (flag2_set & n) | (flag3_set & y)): break; case ((flag1_set & n) | (flag2_set & n) | (flag3_set & n)): break; } } Преимущество такого паттерна ещё и в том, что одинаковую обработку для каких-то сочетаний очень легко организовать (в отличие от примера с избыточной вложенностью). r90 То же самое можно сказать про использование goto. Есть разница между необходимостью с целью реализации алгоритма и необходимостью с целью повышения читабельности кода.
2 l_inc, ну незнаю, то что ты продемонстрировал только усложняет понимание кода. Первый вариант очень хорошо читаемый и логика очень понятная. А вот второй вариант довольно сложный для прочтения, а также в первом варианте до любой операции можно добраться за 3 условия, то в твоем потребуется намного больше проверок, что может сказать на скорости выполнения кода (пускай и не сильно изменится, но как гласит народная мудрость - копейка рубль бережет).
slesh Когда Вы смотрите на следующий фрагмент кода (не весь ведь код помещается на экран, особенно с учётом полезной нагрузки), Вы можете быстро определить, при каких условиях он выполняется? Код (Text): else { //вот например, этот блок } } } else { if (flag2) Думаю, что нет, т.к. нужно пролистать наверх к началу вложенных условий и проанализировать, в каком именно месте находится рассматриваемый блок кода. Здесь же: Код (Text): case ((flag1_set & y) | (flag2_set & n) | (flag3_set & n)): break; достаточно просто взглянуть на заголовок блока, т.к. этот код является хорошим комментарием к самому себе. Это насчёт читаемости. Теперь по поводу быстродействия. В следующей строке (как и во всей функции) вообще никаких проверок не будет. Код (Text): DWORD state = (flag1 ? flag1_set : 0) | (flag2 ? flag2_set : 0) | (flag3 ? flag3_set : 0); Любой компилятор догадается сделать этот код без ветвлений комбинацией neg+sbb+and. Единственный безусловный переход во всей функции будет в точке switch (state), где адрес назначения прыжка будет выбираться из таблицы переходов, сформированной компилятором. P.S. Ах да. Ещё безусловные переходы по break. Аналогичные ставятся в любом if-then-else перед началом else-блока.
l_inc Для этого комментарии есть. А вот от этого: Код (Text): ((flag1_set & y) | (flag2_set & n) | (flag3_set & n)): в глазах рябит.
qwe8013 От чего? От двух подряд идущих скобок? Мне не рябит. Я показал, как я делаю и как считаю правильным делать. Кому нравится, возьмёт на заметку. Если Вам не нравится, делайте по-своему.
l_inc Не совсем понял мысль вашу. Вообще весь CodingStyle -- это повышение читабельности кода и ничего больше. Сочинители linux'ового CodingStyle сочли, что всё что угодно можно написать следуя правилам перечисленным в CodingStyle. А если у кого-то не получается, значит он просто не умеет писать на C. Вот и всё. Если ко всем ограничениям из CodingStyle добавить ещё и запрет на goto, то я боюсь, что в мире не найдётся специалиста, который сможет писать программы следуя этому CodingStyle: жёсткие ограничения на количество идентированных блоков и требование иметь ровно один return в функции, при отсутствии goto вынудят дробить код на много очень мелких функций. А это приведёт к тому, что задачи выполняемые каждой функцией будут уже столь мелки и незначительны, что... Короче, по-моему, получится не очень хорошо.
r90 Я так понял, Вы разделяете идею linux CodingStyle. По крайней мере мысль, которую я процитировал: Например, мне часто надо больше уровней. Но это не означает, что я бы то же самое на трёх уровнях не реализовал. Реализовал бы, конечно, но код бы стал менее понятным. Из чего и следует моё замечание о том, что принимать это ограничение в качестве неукоснительного правила — не самая удачная идея. Насчёт goto я не имел в виду, что запрет на него — это дополнительное ограничение. Имелось в виду, что с тем же успехом можно взять какой-нибудь someproject coding style, где, например, нет ограничения на вложенность, но запрещено использовать goto (тем более, что такой вариант гораздо более распространён). При этом аналогично можно сформулировать принцип: "А если программисту всё-таки надо goto, значит хреновый он программист", — согласно которому ядро линукс написано хреновыми программистами.
Мне близки принципы упомянутые by r90 - goto, последовательное "развертывание" функционала и последовательный "откат" и так далее. Несколько замечаний. 0) Код может быть как самой главной процедурой модуля/проекта так и какой-нить низкоуровневой функцией которую часто вызывают но которая меняется очень редко (DebugMsg, OutString, etc). Понятно, что требования к разным участкам кода должны быть также разные - например в центральной функции какого-нить банковского модуля где сосредоточена наиболее верхняя и важная логика - что самое важное? Быстродействие? На уровне логики - безусловно! (как наиболее эффективно строить сложные запросы к БД например). Также важна "масштабируемость" - в условиях часто меняющихся бизнес-требований это намного важнее чем какая-то там "вложенность". В случае функции которую часто вызывают но которая меняется редко конечно самое важное это быстродействие. Если какая-то сортировка то никого вообще не волнует как она оформлена - если на одних goto но в два раза быстрее чем "правильная" - так и нада. Если трудно читать - всегда можно добавить комментариев; 1) Много говорят о том как оформлять процедуры - но это вовсе не самое главное! Каркас программы - который закладывает архитектор в самом начале - вот кто определяет придется ли мне чесать голову чтобы найти все возможные места на которое повлияет мое изменение или нет; 2) Много говорят о том как "надо" писать код но мало говорят о критериях - почему именно так а не иначе? Даже рекомендации от самых крутых прогеров звучат неубедительно для многих если они просто директивы без объяснения (или им слишком тупо следуют личности без критического образа мышления). Что же может быть критерием? Как можно убедить прогера (например) использовать "последовательную развертку" функционала: Код (Text): if ( !(hFile1= CreateFile(...)) ) goto Exit1; if ( !(hFile2= CreateFile(...)) ) goto Exit2; ... Попытки указать ему на то, что код растет направо натолкнется на "резонное" замечание "а я щас поставлю новое супер IDE с отличной подсветкой и все будет ништяк" - и что вы скажете в ответ? Сравнивать размеры болтов? Таким "независимым" (sic!) критерием может быть, например, величина "сжатия" компилером вашего C-кода. Если при разборе ассемблера который нагенерил хороший компилер видно что он спотыкается о ваш код (он не понимает паттернов и получается хуже чем было бы если бы человек писал на асме) - может говорить что вы пишете неправильно (или слишком круто для компилера) - однако вы в любом случае должны понимать почему так было сконпелировано...
Опу ТС и всем негодующим могу посоветовать Макконела "Совершенный код" (the code complete) - хорошо расписано о стилях и вкусах в написании кода.
стиль l_inc имеет свои преимущества и иногда решение будет красивее и изящнее пример такого случая задачка для студентов решение через вложенные условия - ад
2slesh а что мешает подключить #include <boost/scope_exit.hpp> или подключить #include <loki/scopeguard.h> очень удобно они компиляторм разворачиваются в чистый машинный код без зависимостей и берут всю тяжкую рабюоту по слежке на себя например надо выпонить какое то действие прни выходе пишем Код (Text): BOOST_SCOPE_EXIT((&hEvent)) { // ... тут пишем код который выполнится при выходе из блока // ... странная запись & означает передачу аргуметов по ссылке }BOOST_SCOPE_EXIT_END; или вот возмем LOKI_ON_BLOCK_EXIT выделили ресупс и сразу пишем так Код (Text): LOKI_ON_BLOCK_EXIT(CloseHandle, hEvent); или если надо по ссылке передать то LOKI_ON_BLOCK_EXIT(CloseHandle, Loki::ByRef::(hEvent)); выполнится так же при выходе из блока ну и по возможности смотреть на готовые с++ обертки типа ATL чтобы не парится с освобождением ресурсов в крайнем случае я использую если врапперов нет или BOOST_SCOPE_EXIT или LOKI_ON_BLOCK_EXIT
2slesh а что касается оформления то я за вложенные блоки если их немного если много то я за подход с гото вызов проверка прыжок на освобождение или продолжаем дальше но это к большим функциям только относится которые логически делают одно дело а дробить ради того что бы соблюсти кодинг стиль маразм ставьте мне +5 я самые дельные советы дал
2slesh как пишут код си хакеры ? если блоков немного они делаю их вложенными если много юзают гото как отличить новичка от профи ? новичок разделит единое целое на 5+ функций что бы небыло вложенности при этом задачу размажет так что ее вкурить будет трудней хакер использует гото и разместит одну задачу в одной функции
Rockphorr Не. Нисколько. Смотри: Код (Text): int main() { int ns[3]; int imin = 0, imax = 0; int i; scanf("%d%d%d", ns, ns+1, ns+2); printf("%d, %d, %d\n", ns[0], ns[1], ns[2]); for(i = 1; i < 3; i ++) { if(ns[imin] > ns[i]) imin = i; if(ns[imax] < ns[i]) imax = i; } ns[imin] += ns[imax]; ns[imax] = ns[imin] - ns[imax]; ns[imin] -= ns[imax]; printf("%d, %d, %d\n", ns[0], ns[1], ns[2]); return 0; } Если хочешь, я могу ещё и для десяти переменных написать, и нисколько при этом не запутаться во вложенных условиях. 7d407e345f84708 Поубивал бы таких хакеров вместе с их кодом. Разобраться в их мешанине невозможно. Они не C-хакеры, они asm-хакеры, которые влезли в мир C как заурядные нубы, позабыв посмотреть на отличия C от asm.
r90 не, там без циклов и массивов это какбы дальше по программе а щас Код (Text): input(a,b,c) print(a,b,c) ... ; <-- здесь вся кухня по сравнению abc между собой и обменом значениями через d print(a,b,c) и если использовать стиль то хватит трех "ифов"
Rockphorr Что значит без циклов и массивов? У меня они и так, "как бы дальше по программе", в начале стоит "int main()". Циклы и массивы неотъемлимая часть языка, и несколько странно утверждать, что какой-то там паттерн удачен лишь потому, что он позволяет обойтись без циклов и массивов. ps. Я не пытаюсь доказать неудачность паттерна, просто указываю на то, что приведённое "доказательство" его удачности несостоятельно.