Страницы

Рекомендации по написанию кода на Си

После просмотра кода коллег, с которыми создаю совместный проект, решил составить небольшой список рекомендаций с целью повышения качества нашего кода. Хотя написанное по большей части очевидно, не исключаю, что оно может пригодиться еще кому-то из моих двух - трех читателей.

  1. Придерживаться здравого смысла.
  2. Быть немного идеалистом, но помнить про пункт 1.
  3. Писать программы структурно, что в свою очередь означает:
    1. Для программирования в крупном:
      1. Код программ следует разбивать на относительно небольшие модули. Связи модулей друг с другом не должны быть циклическими.
      2. Код модулей разбивать на относительно небольшие функции с ограниченным количеством параметров.
    2. Программирование в малом:
      1. Не использовать goto и его частные случаи для циклов: break и continue;
      2. Использовать return только в конце тел функций, возвращающих значение.
      3. Не использовать функции, прерывающие нормальный поток исполнения, например exit(); Исключением является вещи, вроде стандартного макроса assert, предназначенного для выявления ошибок программирования во время исполнения. Его использование только поощряется, но требует осторожности.
      4. Использовать switch исключительно как частный случай многоветочного if (таблица 1 пример 5).
  4. Не использовать некоторые конструкции и приёмы, разработанные специально для этого:
    1. Тернарную операцию "expr ? exprTrue : exprFalse" . If - более читаемая и универсальная конструкция.
    2. Операцию присваивания в качестве подвыражения с побочным эффектом даже когда это позволяет сэкономить аж целую строку кода (табл.1 пример 1). Тоже самое касается инкремента ++ и декремента --.
    3. Операцию запятую, позволяющей записывать выражения из плохо связанных подвыражений.
    4. Умножение и деление на степень двойки с помощью побитовых операций. Понятность и переносимость страдает при том, что даже самый захудалый компилятор сам все правильно преобразовывает.
    5. Не надо считать, все в мире циклы, являются частными случаями сишного for. Использовать его исключительно для полного прохода в рамках целочисленных границ, известных до выполнения цикла. "for (i = low(a); i <= high(a); i += step);".
    6. Для обозначения нулевого указателя вместо 0 использовать макрос NULL из файла string.h
    7. Несмотря на то, что в С нет полноценного логического типа, следует разделять логические и целочисленные значения, а заодно и указатели (табл.1 пример 2).
  5. При объявлении не следует группировать произведённые от одного типа разнотипные переменные (табл.1 пример 3).
  6. Глобальные идентификаторы подбирать понятные и по возможности лаконичные. Межмодульные имена должны хранить в себе печать модуля.
  7. Различать массивы и указатели на единичные элементы, используемые как переменные параметры фунцкции  (табл.1 пример 4).
  8. Использовать условную компиляцию с помощью инструкций препроцессора по минимуму (табл.1 пример 6). В случаях, когда используется код для разных веток условной компиляции, который не может быть собран в рамках одной сборки, следует выносить зависимости от условной компиляции за рамки основного кода в отдельные модули/единицы_компиляции.
  9. Уменьшить использование числовых типов, отличающихся друг от друга отсутствием знака или разрядностью. Из целых предпочитать int_least32_t или long, из чисел с плавающей точкой - double.
  10. Сообщения-предупреждения (warnings) от компилятора считать сообщениями об ошибках. Есть редкие исключения.
  11. Много всего, что я забыл либо поленился написать, то, что выходит за рамки коротких рекомендаций, а также то, о чём не ведаю. Эта часть, как водится, самая полезная.

Таблица 1. Примеры плохого и очень плохого кодов
Очень плохо
Плохо (хорошо на Си/++ не бывает)
1
if ((i = get()) > 11) a();
i = get();
if (i > 11) a();
2
int n = 1;
if (n);

bool b = true, a;
if (b == true);

void *p = &v;
if (p);

if (b) {/* капитан Очевидность одобряет*/
    a = true;
} else { 
    a = false;
}
int n = 1;
if (n != 0);

bool b = true, a;
if (b);

void *p = &v;
if (p != NULL);

a = b; /* сестра таланта */
3
int a, *b, c[34], d;
int a, d; /* всё по полочкам */
int *b;
int c[34];
4
void func(char str[], char *сh) {
    *(str + 6) = 'u';
    ch[0] = *str;
}
void func(char str[], char *сh) {
    str[6] = 'u'; /* массиву - массивово */
    *ch = str[0]; /* указателю - указателево*/
}
5
switch (s) {
    case 1:
        b();/* не всегда ясно - забыт break */
    case 2:/*или так задумано*/
        c();
        break;


    сase 3:
        if (condition){
             d();
             break;
        }
        e();
}
switch (s) {
    case 1:
    case 2: /* чтобы не играть в догадки*/
        if (s == 1) {/* всё должно быть явно */
            b();
        }
        c();
        break;
     case 3:
        if (condition) {
            d();
        } else {
            e();
        }/* не забываем явный */
        break; /* хоть это и последний case */
}/* бог экономии строк негодует */
6
#define A
cond = true;
...
#if defined A
    b(c());
#else
    if (cond_/*замаскированная ошибка*/) {
        c(b());
    }
#endif
a = true;
cond = true;
...
if (a) {/* бог экономии наносекунд в шоке */
    b(c());
} else if (cond) {
    c(b());
}

Комментариев нет:

Отправить комментарий