**Руководство по стилистическому оформлению исходных файлов ядра (по мотивам style(9))** В этой статье описывается предпочтительный стиль оформления исходных файлов ядра FreeBSD. Также ее можно использовать, как руководство по предпочтительному оформлению кода пользовательских программ. Многие правила описываются на примерах. Смотрите примеры прежде чем думать, что в данном руководстве ничего не говорится о данной проблеме. /* * Руководство по стилистическому оформлению кода во FreeBSD. Основано на работе "Нормальная форма ядра" (KNF, Kernel Normal Form) группы CSRG. * * @(#)style 1.14 (Berkeley) 4/28/95 * $FreeBSD: src/share/man/man9/style.9,v 1.121 2005/06/28 20:15:18 hmp Exp $ */ /* * ОЧЕНЬ важные одно-строчные комментарии. */ /* Большинство одно-строчных комментариев выглядит так. */ /* * Многострочные комментарии. Составляйте их, как полноценные предложения. Старайтесь оформлять их так, * чтобы они выглядели, как настоящие параграфы. */ Заголовок, в котором указываются авторские права должен располагаться на нескольких строчках. В первой строке комментария находится дефис, идущий после звёздочки, как здесь: /*- * Copyright (c) 1984-2025 John Q. Public. All Rights Reserved. * * Дальше идёт длинная, скучная лицензия, отредактировано для краткости */ Автоматический скрипт собирает информацию о лицензиях из дерева исходного кода со всех комментариях, которые начинаются с первого столбца и с ``/*-''. Если вы хотите уведомить indent(1) не переформатировать комментарии, которые начинаются с первого столбца, которые не являются лицензией или копирайт замечанием, то в них замените дефис на звездочку. Комментарии, начинающиеся не с первой колонки никогда не рассматриваются, как условия лицензии. Вначале, после заголовка с лицензией следует пустая строка и строка с ID тегом $FreeBSD$ для файлов, написанных на не C/C++ языках. ID тэги системы контроля версий должны встречаться только один раз в файле (не так как в данном руководстве). Исходные файлы, написанные не на C/C++ должны придерживаться вышеприведённого примера , а файлы на C/C++ нижнего примера. Вся идентификация ревизий системы контроля версий (VCS, Version Control System), полученная со стороны, должна поддерживаться, включаться в исходные файлы, где применимо. Несколько идентификаторов (ID) показывают историю файла. В вкратце, не редактируйте чужие ID или их структуру. Если иначе не свёрнуты (например, как ``if defined(LIBC_SCCS)''), заключены в директивы ``#if 0 ... #endif'', чтобы скрыть любые не компилируемые биты и убрать идентификаторы из объектных файлов. Если файл переименовывается, то просто добавьте ``From: '' перед чужими идентификаторами VCS. #if 0 #ifndef lint static char sccsid[] = "@(#)style 1.14 (Berkeley) 4/28/95"; #endif /* not lint */ #endif #include __FBSDID("$FreeBSD: src/share/man/man9/style.9,v 1.121 2005/06/28 20:15:18 hmp Exp $"); Перед перечислением заголовочных файлов, оставьте пустую строчку. Сначала включаются заголовочные файлы ядра (sys/*.h). Обычно включают либо , ЛИБО , но не оба. В файле присутствует включение , и будет нормальным основываться на этой зависимости. #include /* Имена системных заголовочных файлов заключаются в угловые скобки */ Если программа работает с сетью, то следующими включаются файлы, обеспечивающие работу с сетью. #include #include #include #include #include Не включайте файлы из /usr/include в фрагменте, где включаются заголовочные файлы ядра. Оставьте пустую строчку перед включением следующей группы файлов, файлов из каталога /usr/include, которые должны быть перечислены в алфавитном порядке. #include Пути к различным файлам системы объявляются в файле . Пути, локальные для вашей программы описываются в файле "pathnames.h", располагаемом в вашем каталоге. #include Оставьте пустую строчку перед включением пользовательских заголовочных файлов. #include "pathnames.h" /* Имена локальных заголовочных файлов помещаются в двойные кавычки. */ Имена ``небезопасных'' макросов (имеющих сторонние эффекты) и имена макросов для именованных (manifest) констант пишутся заглавными буквами. Поставьте одиночный символ табуляции между #define и именем макроса. В случае, если макрос развёртывается в функцию, то имя функции пишется маленькими буквами, а имя макроса, такое же, как и у функции, пишется большими буквами. Выравнивайте бэкслеши по правому краю, для облегчения чтения. В случае, если макрос представляет собой составную конструкцию, то для безопасного использования в if конструкциях заключайте её в цикл do. Последнюю точку с запятой, определяющую конец составной конструкции ставьте после вызова макроса, нежели в конце определения макроса, чтобы легче сделать грамматический разбор для хороших принтеров и редакторов. #define MACRO(x, y) do { \ variable = (x) + (y); \ (y) += 2; \ } while (0) При условном компилировании кода с помощью #ifdef и #if, комментарий может быть добавлен после соответствующих #endif или #else, чтобы позволить пользователю легко различать участки условно-компилируемого кода. Этот комментарий должен использоваться для (субъективно) больших участков кода, участков размером более 20 строк или в местах, где часто встречающиеся #ifdef могут смутить читателя. Исключения могут быть сделаны для случаев, когда код условно не компилируется для целей lint(1), если даже некомпилируеммый участок довольно мал. Комментарий должен быть отделён от #endif или #else одиночным пробелом. Для небольших условно-компилируемых участков, завершающий комментарий не используется. Комментарий для #endif должен соответствовать выражению, расположенному между соответствующим #if или #ifdef. Комментарии для #else и #elif, должны указывать на инверсию выражению(ям), используемым в предыдущих #if и/или #elif конструкциях. В комментариях подвыражение ``defined(FOO)'' укорачивается до FOO. Для целей комментариев, ``#ifndef FOO'' расценивается, как ``#if !defined(FOO)''. #ifdef KTRACE #include #endif #ifdef COMPAT_43 /* Большой фрагмент кода или какой-то условно компилируемый код. */ #else /* !COMPAT_43 */ /* Или здесь. */ #endif /* COMPAT_43 */ #ifndef COMPAT_43 /* Другой большой кусок кода или другой условно компилируемый код. */ #else /* COMPAT_43 */ /* Или здесь. */ #endif /* !COMPAT_43*/ Проект медленно продвигается к использованию беззнаковых целых описателей формы uintXX_t из спецификации ISO/IEC 9899:1999 (``ISO C99'') вместо более старых описателей BSD-типа u_intXX_t. В новом коде должен использоваться первый вариант, а старый код должен быть преобразован к новой форме записи, в случае если другая важная работа в той области закончена и нет других причин для использования старой формы записи. Как и коммитты, производящих манипуляции с пробелами, нужно с осторожностью делать изменения, внедряющие использование uintXX_t. Идентификаторы в списке перечисления пишутся заглавными буквами. enum enumtype { ONE, TWO } et; В объявлениях не ставьте пробелов между звездочкой (asterisk) и соседним словом, кроме слов, являющихся идентификаторами, относящиеся к типам. (Эти идентификаторы могут быть именами любого из основных типов, либо классификаторами типов, либо typedef-именами, другими, чем который объявляется.). Отделяйте данные идентификаторы от звездочек одиночным пробелом. При объявлении переменных в структурах, сортируйте их сначала в порядке частоты использования, затем по размеру (от большого к маленькому), а потом уже в алфавитном порядке. Первая категория обычно не применяется, но есть исключения. Каждая компонента занимает свою собственную строчку. Постарайтесь сделать структуру более читабельной, выравнивая имена компонент структуры, используя один или два символа табуляции. Используйте только один символ табуляции, если этого достаточно, по крайней мере для выравнивания 90% имен компонент структуры. Имена, следующие за сверх длинными именами типов должны быть отделены одиночным пробелом. Основные структуры должны быть объявлены в начале файла, в котором они используются, или в начале отдельных заголовочных файлов, если они используются в нескольких исходных файлах. Использование структур должно сопровождаться отдельными объявлениями или иметь класс памяти extern, в случае объявления в заголовочном файле. struct foo { struct foo *next; /* List of active foo. */ struct mumble amumble; /* Комментарий для mumble */ int bar; /* Старайтесь выравнивать комментарии. */ struct verylongtypename *baz; /* Не поместится в 2 символа табуляции. */ }; struct foo *foohead; /* Первый элемент глобального списка foo */ Используйте макросы queue(3), всегда, когда возможно, вместо создания своих собственных списков. Исходя из этого, предыдущий пример можно записать, как: #include struct foo { LIST_ENTRY(foo) link; /* Используйте queue макросы для foo списков. */ struct mumble amumble; /* Комментарий для mumble. */ int bar; /* Старайтесь выравнивать комментарии. */ struct verylongtypename *baz; /* Не поместится в 2 символа табуляции. */ }; LIST_HEAD(, foo) foohead; /* Первый элемент глобального списка foo */ Избегайте определения типов (typedef) для структур. Определения типов проблематичны, т.к. они некорректно скрывают нижележащий тип; к примеру, вам нужно узнать является ли определенный тип самой структурой или указателем на структуру. К тому же они должны определятся только один раз, тогда как, неполный структурный тип (прим. перев. struct foo) может встречаться столько раз, сколько необходимо. Определения типов проблемно использовать в отдельных заголовочных файлах. Файл, где определяется тип (typedef) должен быть включен до включения файла, где он используется, или определение должно присутствовать в том же файле, где он используется, что ведет к "загрязнению" области имен, или должен существовать !!обходной (back-door) механизм для доступа к typedef. Когда требуется использовать typedef, используйте имя, совпадающее с именем тега структуры. Избегайте имен для typedef, оканчивающихся на ``_t'', кроме случае оговоренных в стандарте C или в POSIX. /* Старайтесь, чтобы имя структуры совпадало с именем типа. */ typedef struct bar { int level; } BAR; typedef int foo; /* Это тип foo. */ typedef const long baz; /* А это тип baz. */ Все функции имеют прототипы где-либо. Прототипы для частных функций, т.е, не используемые где либо ещё, объявляются в начале первого модуля. Функции, локальные для одного модуля объявляются со спецификатором класса static. Прототипы для функций из других частей ядра располагаются в соответствующем заголовочном файле. Прототипы должны быть перечислены в логическом порядке, желательно по алфавиту если нет очевидных причин для использования другого порядка сортировки. Функции, используемые локально в нескольких модулях выносите в отдельный заголовочный файл (например, "extern.h"). Не используйте __P макроc. В общем, код может считаться `'новым кодом'', если он составляет более 50% изменяемых(ого) файлов(а). Этого достаточно для того, чтобы использовать текущие правила из данного руководства. В заголовочных файлах, видимых для пользовательских программ, прототипы должны использовать либо ``защищённые'' имена формальных параметров (начинающиеся с символа подчёркивания), либо просто типы без имён. Использование ``защищённых'' имён предпочтительнее. К примеру, используйте: void function(int); или: void function(int _fd); В прототипах, после символа табуляции может быть использован дополнительный пробел для выравнивания имён функций. static char *function(int _arg, const char *_arg2, struct foo *_arg3, struct bar *_arg4); static void usage(void); /* * Все значимые функции должны иметь комментарии, коротко * описывающие основные действия подпрограммы. Комментарий * перед "main" описывает действия самой программы. */ int main(int argc, char *argv[]) { char *ep; long num; int ch; В целях согласованности, для разбора аргументов, переданных из командной строки, должен использоваться getopt(3). Аргументы должны быть отсортированы и в вызове getopt(3) и в операторе switch, за исключением частей switch, выполняющихся каскадом. Элементы в операторе switch, выполняющиеся каскадом, помечаются комментарием FALLTHROUGH. Числовые аргументы должны проверяться на корректность. Части кода, которые не могут быть достигнуты помечаются комментарием NOTREACHED. while ((ch = getopt(argc, argv, "abn:")) != -1) switch (ch) { /* Используйте отступы для switch. */ case 'a': /* Не используйте отступов для case. */ aflag = 1; /* FALLTHROUGH */ case 'b': bflag = 1; break; case 'n': num = strtol(optarg, &ep, 10); if (num <= 0 || *ep != '\0') { warnx("illegal number, -n argument -- %s", optarg); usage(); } break; case '?': default: usage(); /* NOTREACHED */ } argc -= optind; argv += optind; После ключевых слов (if, while, for, return, switch) ставьте пробел. Для операторов управления только с одним выражением или вообще с их отсутствием фигурные скобки ( `{' и `}') опускаются. Бесконечные циклы задаются с помощью for, а не while. for (p = buf; *p != '\0'; ++p) ; /* ничего не выполняется */ for (;;) stmt; for (;;) { z = a + really + long + statement + that + needs + two + lines + gets + indented + four + spaces + on + the + second + and + subsequent + lines; } for (;;) { if (cond) stmt; } if (val != NULL) val = realloc(val, newsize); Части цикла for могут отсутствовать. Не используйте объявления в теле цикла, если только подпрограмма не является особенно сложной. for (; cnt < 15; cnt++) { stmt1; stmt2; } Отступ - это 8 символьный . Отступы второго уровня составляют 4 пробела. Если вам надо сократить длинное выражение, пишите оператор последним в строке. while (cnt < 20 && this_variable_name_is_too_long && ep != NULL) z = a + really + long + statement + that + needs + two + lines + gets + indented + four + spaces + on + the + second + and + subsequent + lines; Не вставляйте пробелов/табуляцию в конце строки. Для формирования отступов используйте только символы табуляции, с последующими пробелами. Ширина суммы пробелов не должна превышать ширины символа табуляции. Не используйте пробелы перед символами табуляции. Закрывающиеся и открывающиеся фигурные скобки должны находиться на одной строке с else. Фигурные скобки, являющиеся не обязательными могут быть пропущены. if (test) stmt; else if (bar) { stmt; stmt; } else stmt; После имён функций не должно быть никаких пробелов. После запятых ставьте пробел. После скобок `(' `[' или перед `]' `)' не должно находится лишних пробелов. error = function(a1, a2); if (error != 0) exit(error); Унарные операторы не требуют пробелов, в отличии от бинарных. Не используйте круглые скобки, если только они не требуются для указания приоритета или в случае, если без них выражение выглядит запутанным. Помните, что другие могут легче запутаться, чем вы. ВЫ понимаете следующее? a = b->c[0] + ~d == (e || f) || g && h ? i : j >> 1; k = !(l & FLAGS); Коды выхода, в случае удачного завершения, должны быть нулём, или в соответствии с предустановленными значениями из sysexits(3). exit(EX_OK); /* * Избегайте смешных комментариев, типа * "В случае успеха, выходим с кодом 0" */ } Возвращаемый тип функции должен находиться на отдельной строчке, предшествуя самой функции. Открывающаяся скобка тела функции также должна быть на отдельной строке. static char * function(int a1, int a2, float fl, int a4) { Объявления переменных в функциях описывайте в следующем порядке: по размеру, затем по алфавиту. Допускается объявлять несколько переменных в одной строке, если при этом строка становится большой, то заново используйте ключевое слово типа. Будьте осторожны, чтобы не "затемнить" код инициализацией переменных в их объявлениях. Используйте данную возможность очень внимательно. НЕ используйте вызовы функций в инициализациях. struct foo one, *two; double three; int *four, five; char *six, seven, eight, nine, ten, eleven, twelve; four = myfunction(); Не определяйте функции внутри других функций; стандарт ANSI C по этому поводу говорит, что цель таких объявлений - область действия файла в независимости от вложенности определений. "Прятание" объявлений, имеющих область действия файл, в том что считается локальной областью действия - нежелательно и вызовет жалобы со стороны хорошего компилятора. Для указателя на null используйте константу NULL. В местах, где компилятору точно известен тип, например в присваивании, используйте NULL, а не (type *)0 или (type *)NULL. В других контекстах, особенно для аргументов функции, используйте (type *)NULL. (Приведение типа существенно для переменных (variadic) аргументов и необходимо для других, в случае, если прототип функции не находится в области видимости (scope).) При сравнении указателей с NULL старайтесь писать: (p = f()) == NULL а не: !(p = f()) Не используйте ! для проверок, если только она не логического типа, например: if (*p == '\0') а не: if (!*p) Подпрограммы, возвращающие void * не должны иметь приведения типов их возвращаемых значений к указателю на любой другой тип. Возвращаемые значения в операторе return должны быть заключены в скобки. Для обработки ошибок используйте err(3) и warn(3). if ((four = malloc(sizeof(struct foo))) == NULL) err(1, (char *)NULL); if ((six = (int *)overflow()) == NULL) errx(1, "number overflowed"); return (eight); } Объявление функций в старом стиле, выглядит так: static char * function(a1, a2, fl, a4) int a1, a2; /* Переменные типа int объявляйте тоже */ float fl; /* Остерегайтесь double vs. float различий в прототипах. */ int a4; /* Описывайте переменные в порядке, указанном в списке формальных параметров. */ { Используйте объявления функций в ANSI стиле, если только не требуется совместимости со стилем K&R. Длинные списки параметров сворачиваются с использованием обычного четырех-символьного отступа. Функции с переменным числом параметров должны выглядеть так: #include void vaf(const char *fmt, ...) { va_list ap; va_start(ap, fmt); STUFF; va_end(ap); /* В функциях с возвращаемым значением void не должно быть оператора return. */ } static void usage() { /* Вставляйте пустую строчку, если у функции нет локальных переменных */ Используйте printf(3), а не fputs(3), puts(3), putchar(3) или что-либо ещё. Эта функция и быстрее и более прозрачна, не говоря уж об отсутствии глупых ошибок. Usage выражения должны выглядеть, как раздел SYNOPSIS в страницах справочника. Опции должны следовать в следующем порядке: 1. Опции без операндов объявляются первыми,в алфавитном порядке и внутри скобок `[' `]'. 2. Далее следуют опции с операндами, также в алфавитном порядке. Каждая опция и её аргумент заключаются в свою собственную пару квадратных скобок. 3. Далее - требуемые аргументы (если есть), расположенные в таком же порядке, в каком они должны указываться в командной строке. 4. И наконец, опциональные аргументы, описанные в порядке передачи в программу и все в скобках. Вертикальная черта (`|') разделяет ``либо-либо'' опции или аргументы, а несколько опций/аргументов, которые указываются вместе, помещаются в один набор квадратных скобок. "usage: f [-aDde] [-b b_arg] [-m m_arg] req1 req2 [opt1 [opt2]]\n" "usage: f [-a | -b] [-c [-dEe] [-n number]]\n" (void)fprintf(stderr, "usage: f [-ab]\n"); exit(EX_USAGE); } Заметьте, что описание опций в страницах справочника должно следовать в алфавитном порядке, независимо от того, есть аргументы у опции или нет. Сортировка по алфавиту должна принимать в расчёт случай сортировки, описанный выше Стилистические изменения, включая изменения, связанные с пробельными символами являются нежелательными и должны избегаться при отсутствии серьезной причины. Код, который приблизительно соответствует стилю FreeBSD KNF в репозитории не должен отклоняться от этого соответствия. Всегда, когда возможно, код должен проверяться программой проверки кода (к примеру, lint(1) или gcc -Wall) и при этом производить минимальное количество предупреждений. Last modified: $Date: 2006/02/28 17:03:52 $