Вы находитесь на странице: 1из 7

Порядок оформления кода

Данный документ является выдержкой из репозитория Embedded Linux курса КПИ

Общие сведения
Ниже приведено краткое резюме правил оформления кода. Более подробно все описано в
официальном Coding Style ядра, которым будем руководствоваться. В случае противоречий,
приоритет имеют приведенные ниже правила. Список правил будет пополняться по мере
необходимости.

Краткое резюме
1. Максимальная длина строки равна:

1. 79 символов для модулей ядра и всего, что относится к дереву исходников ядра
2. 99 сиволов для всего, что не относится к ядру
2. Пустые строки должны быть пустыми и не содержать других символов кроме \n. Все
строки заканчиваются на \n. Каждый исходник обязательно заканчивается пустой
строкой
3. Отступы:

1. Для всего, что относится к ядру, в коде для отступа используется символ TAB. 1 отступ
= 1 TAB. 1 TAB эквивалентен 8 пробелам, что необходимо выставить в редакторе
2. Для всего, что не относится к ядру, для отступов используются символы пробела. 1
отступ = 1 TAB = 4 пробела
4. Фигурные скобки { }:

1. В определениях функций открывающая скобка ставится со следующей строки


inline unsigned int invert(unsigned int val)
{
return ~val;
}
2. В случае с if-else, используется следующий стиль:
if (a == b) {
do_first();
} else if (c == a) {
do_next();
} else {
do_nothing();
}
3. В случае с switch, case должны размещаться на том же уровне идентации, что и
switch. Если используются сквозные(не содержащие break, кроме default) case, они
должны быть обозначены как fall-through. Не рекомендуется злоупотреблять
использованием сквозных case. Пример стиля:
switch (state) {
case STATE_INIT:
do_first();
break;
case STATE_RUN:
do_run();
/* fall through */
case STATE_NEXT:
do_next()
break;
default:
return EUNKNOWN;
}
4. Во всех остальных случаях, открывающая скобка ставится через пробел в той же
строке, а закрывающая – на уровне идентации блока открывающей. Пример:
if (a == b) {
for (int i = 0; i < sz; i++) {
do_smthng();
}

a = sz;
}
5. Круглые скобки ( ):

1. В выражениях (statements), отделяются пробелами. Пример:


if (state) {
...
}

for (i = k; i >= 0; i--) {


...
}

while (!ret) {
...
}

do {
...
} while (i < cnt);
2. В определении функций и их вызовах пробелами не отделяются:
bool is_last(struct item *it)
{
...
}

bool tst = is_last(item_ptr);

while (!is_last(ptr)) {
...
}
6. Компаундные конструкции переносятся следующим образом:

1. В выражениях с if логический оператор переносится на следующую строку, которая


начинается с двойной идентации. Пример:
if ((LAST_ITEM == a) && (b != a) && (NOT_FIRST == c)
&& (k == p) && (NOT_FIRST != k)
&& (g == r) && ((a == r) || (b == r)) {
do_something();
}

Таким образом, при чтении кода не нужно искать оператор на предыдущей строке, а
за счет идентации часть тестируемого выражения не перепутать с выражениями
внутри if-блока.
2. В длинных вызовах функций, при переносе аргументы находятся на уровне первого
символа за открывающей скобкой. Закрывающая скобка ставится за последним
аргументом. Если это невозможно, первый аргумент ставится на уровне идентации
плюс один от уровня идентации вызова. Пример:
// first variant - aligned with opening delimiter
prn("Here we have some long call"
"\nThis string literal is concatenated."
"\nFinally we have params: %d, %d, %d\n",
some_really_really_really_long_long_param,
short_param_a, short_param_b);

// second variant - add an extra level of indentation to distinguish arguments


some_very_very_very_long_function_call(
one_indented_arg,
second_indented_arg);
7. Бинарные и тернарные операторы (+, -, *, /, %, =, <, >, <=, >=, ==, !=, <<, >>, |, &, ^, ?, :)
отделяются слева и справа пробелами. Например: a + b вместо a+b. Унарные операторы
пробелом от аргумента не отделяются. Например: ++a, b->c, k = -a
8. McCabe complexity не должна превышать 8 для простых функций и 10 для сложных.
Сложные функции, McCabe complexity которых превышает 8 должны содержать локально
задокументированное описание, доказывающее, что именно такой вариант реализации
является оптимальным.
9. Код должен быть задокументирован согласно требованиям kernel-doc. Комментарии
делятся на "внешние" и "внутренние". Внешние затем используются для автоматической
сброрки документации. Внутренние же предназначены для разработчика, что бы повысить
читабельность кода, объяснить какие-то неочевидные моменты.
При этом, следует избегать как недостаточной, так и излишней документированности.
Например:
Излишне. Тут и так понятно, как расшифровывается cnt и где используется.
int cnt = 0; // counter
while (cnt++ < k) {
...
}

Недостаточно. Тут не мешало бы описать, зачем мы трактуем somevar как


unsigned long long и какие возможны сайд-эффекты. А также откуда взялась эта
магическая константа. В большинстве случаев, константы лучше определить как const и
использовать в коде, обращаясь по имени.
tmp = *((unsigned long long *)&somevar) & 0xD0BF00AA00000000LLU;

Более подробно о необходимой степени документированности можно почитать в Coding


Style.
10 Использование include guards, в большинстве случаев, считается плохой практикой и
. недопустимо. То же относится и к #pragma once. Необходимость в данных конструкциях
свидетельствует о неправильной декомпозиции и наличии циклических зависимостей.
Ниже проиллюстрирован пример такой зависимости и способ борьбы с ней:
Стоит заметить, что циклические зависимости могут возникать и в пределах одного
файла. В этом случае, для борьбы с ними используют forward declaration. Пример
структуры, содержащей в качестве одного из полей указатель на функцию, принимающую
аргументом объект типа этой структуры:
/* Forward declaration */
struct niceobj;

/*
* Pointer named comfunc to function of the form
* void name(struct niceobj *)
* defined as type
*/
typedef void (*comfunc)(struct niceobj *);

/* Here we finally declare it */


struct niceobj {
/* Use the defined type */
comfunc comfuncptr;

/* Simply pointer named otherfunc without type definition


* This one can be used without forward declaration
*/
void (*otherfunc)(struct niceobj *, int);

...
};

/* Here we have some function pointers used */


void comcom(struct niceobj *obj)
{
...
}

void otherother(struct niceobj *obj, int somearg)


{
...
}

static const struct niceobj nice = {


.comfuncptr = &comcom,
.otherfunc = &otherother
};

...

/* Use it */
a = nice.comcom(&nice);
b = nice.otherfunc(&nice, 0);
/* Or explicitly like this */
a = (*nice.comcom)(&nice);
b = (*nice.otherfunc)(&nice, 0);
11 typedef используется для opaque-объектов, внутренняя структура которых частично
. (лучше – полностью) сокрыта от конечного пользователя. Подразумевается, что
пользователю не нужно знать как объект устроен внутри. Для работы с таким объектом
пользователь использует методы. Пример такого объекта:
typedef struct unit_s {
void *ptr;
size_t size;
} defunit_t;
size_t defunit_getsize(defunit_t *unit);
defunit_t *defunit_create(void *ptr, size_t size);
...

Стоит заметить, что даже у структуры, объявленной типом, есть имя


(typedef struct unit_s {... вместо просто typedef struct {...). Это необходимо для
упрощения отладки. Следует по-возможности избегать анонимного, особенно структур и
перечислений (кроме случая, когда перечисления хранят константы).
Пример transparent-объекта, который не скрывает структуру от пользователя:
struct vecpoint {
long long x;
long long y;
};

struct vecpoint to_vector(struct vecpoint *p1, struct vecpoint *p2)


{
struct vecpoint ret = {
.x = p2->x - p1->x,
.y = p2->y - p1->y
};
return ret;
}

Также возможны объекты смешанного вида, часть полей которых сокрыта от


пользователя, а часть является открытой. Это очень популярно в ядре. Например,
пользователь создает структуру и заполняет определенные поля. Далее вызывает метод
init(), который дозаполняет остальное. В таких случаях не стоит использовать typedef,
вместо этого, для сокрытия полей используется mangling-схема (когда, например, к имени
приватных полей добавляется префикс или суфикс, указывающий на приватность: \_, \__,
pv_, ...
12 Для всех имен используется стиль наименования lowercase_underscored_style, кроме:
.
• констант и макроопределений, для них используется UPPERCASE_UNDERSCORED_STYLE
• имен псеводо-ООП типов, для которых используется CamelCaseStyle
Пример псеводо-ООП:
/* Compiled with -fms-extensions */

typedef struct SomeAnimal_s {


double kgweight;
char *name;
} SomeAnimal_t;

/* Inherits Animal */
typedef struct HumanPerson_s {
SomeAnimal;
char *surname;
} HumanPerson_t;

/* ! NOTICE ! SomeAnimal is in CamelCase and is prefixed, so that all


* methods belonging to it start with SomeAnimal_
* while the right part stays in lowercase_underscored_style
*/
void SomeAnimal_print(SomeAnimal_t *s)
{
printf("name: \"%s\", weight: %.2f\n", s->name, s->kgweight);
}

...
HumanPerson_t j = {
.kgweight = 80.0,
.name = "John",
.surname = "Sins"
};

SomeAnimal_t bee = {
.kgweight = 0.25,
.name = "Queen Bee"
};

SomeAnimal_t *ptrs[] = {(SomeAnimal_t *)&j, (SomeAnimal_t *)&bee};


for (int i = 0; i < sizeof(ptrs)/sizeof(*ptrs); i++) {
SomeAnimal_print(ptrs[i]);
}

/* Outputs
* name: "John", weight: 55.00
* name: "Queen Bee", weight: 0.25
*/
13 В коде, комментариях к нему, названиях переменных, а также документации не
. допускается использование других языков, кроме английского. Испьзование
транслита также не допускается. Исключения составляют:

• строки локализации, например:


struct lang lang_UA = {
.exit_label = "Вийти";
.create_label = "Створити";
...
};
• собственные имена. В данном случае используются правила транслитерации языка
оригинала. Например:
В коде драйвера датчика 强光, он может быть записан как qiangquang_sensor
14 char ≠ byte. В соответствии со стандартом С:
.
• sizeof(short) ≤ sizeof(int) ≤ sizeof(long) ≤ sizeof(long long)
• char может быть равен или signed char или unsigned char, должен иметь ширину,
как минимум 8 бит. При этом его ширина должна быть достаточной, для того, что бы
уместить все символы из execution character set
• short и int должны иметь ширину, как минимум 16 бит
• long должен иметь ширину, как минимум 32 бита
• long long должен иметь ширину, как минимум 64 бита
Соответственно все вышеприведенные типы могут иметь одинаковую ширину. Если нужны
байты, или другие типы фиксированной ширины, стоит использовать uint8_t, u8 и другие
портативные типы.

• float – тип числа с плавающей запятой одинарной точности. Обычно это 32-битный
IEEE 754 single-precision формат.
Литерал для определения float это F или f, например: 3.14F. Тогда как 3.14 это
double
• double– тип числа с плавающей запятой двойной точности. Как правило это 64-битный
IEEE 754 double-precision формат.
• long double – тип числа с плавающей запятой расширенной точности. Реализации
существенно отличаются
Литерал для определения long double это L или l, например: 3.14L.
15 Если функция не принимает аргументов, в декларации и имплементации используется
. somefunc(void), а не просто somefunc(). somefunc() означает, что на этапе компиляции
ничего не известно об аргументах функции. При вызовах все так же используется
somefunc();