Требования
к оформлению программ на языке Си

© М.Л. Цымблер (mzym@susu.ru), Е.В. Аксенова (evaksen@mail.ru)


Содержание

1. Соглашения по идентификаторам

1.1 Подбор идентификаторов
1.2 Написание идентификаторов

2. Соглашения по самодокументируемости программ

2.1 Комментарии
2.2 Спецификация функции и прототипа
2.3 Спецификация программного файла

3. Соглашения по читаемости программ

3.1 Лесенка
3.2 Длина строк программного текста
3.3 Прочие рекомендации


1. Соглашения по идентификаторам

1.1 Подбор идентификаторов

1.1.1 Все идентификаторы должны выбираться из соображений читаемости и максимальной семантической нагрузки. Например:

#define EPS (0.0001) // точность

int sum; // сумма
char *message; // сообщение

Неудачными можно считать идентификаторы:

#define uU (0.0001) // точность

int  uu; // сумма
char *zz; // сообщение

1.1.2 Идентификаторы рекомендуется подбирать из слов английского языка. Например:

/* выдает звуковой сигнал заданной частоты и длительности */
void beep(int hertz, int msec)

/* выдает 1, если файл с именем fname существует */
int exist_file(char *fname)

int done; // признак окончания работы с программой
double width, height; // размеры изделия (ширина, высота)

Не очень удачными можно считать идентификаторы:

/* выдает звуковой сигнал заданной частоты и длительности */
void zvuk(int chast, int dlit) 

/* выдает 1, если файл с именем Im существует */
int est_file(char *Im) 

int konec; // признак окончания работы с программой
double shirina, vysota; // размеры изделия (ширина, высота)

1.2 Написание идентификаторов

1.2.1 Идентификаторы констант и макроопределений рекомендуется писать заглавными буквами. Например:

#define PI (3.14) // значение числа пи

#define MAX(x,y) ((x)>(y))?(x):(y) // максимум двух чисел

1.2.2 Существуют разные подходы к написанию остальных идентификаторов. Например:

а) все буквы идентификатора пишутся маленькими, для разделения слов в идентификаторе используется символ "_"

int cnt_node; // количество звеньев

/* удалить звено c номером i из списка node */
void delete_node(list *node, int i)

б) в идентификаторах каждое слово, входящее в идентификатор, писать, начиная с большой буквы, остальные буквы - маленькие.

int CntNode; // количество звеньев

/* удалить звено c номером I из списка Node */
void DeleteNode(List *Node, int I)

в) аналогично б) за исключением того, что первая буква идентификатора пишется маленькой.

int cntNode; // количество звеньев

/* удалить звено c номером i из списка node */
void deleteNode(list *node, int i)

Принятого подхода нужно придерживаться во всем тексте программы.

2. Соглашения по самодокументируемости программ

2.1 Комментарии

2.1.1 Комментарии в теле программы следует писать на русском языке и по существу так, чтобы программист, не участвовавший в разработке программы (но имеющий опыт работы на языке Си), мог без особого труда разобраться в логике программы, и, при необходимости, сопровождать данный программный продукт.

2.2 Спецификация функции и прототипа

Для каждой пользовательской функции должна быть описана в виде комментария спецификация, содержащая следующую информацию:
а) назначение функции;
б) описание семантики параметров-значений (параметров, передаваемых по значению), если она неочевидна;
в) описание семантики параметров-переменных (параметров, передаваемых по ссылке), если она неочевидна.
г) описание семантики возвращаемого значения, если она неочевидна.
Например:

1) семантика параметров и возвращаемого значения очевидна:

/* возвращает 1, если год year -- високосный */
int is_leap_year(int year)  

2) семантика параметров очевидна, семантика возвращаемого значения неочевидна

/* Возвращает день недели даты d/m/y;
  год y должен быть в отрезке 1582..4902;
  результат: ВСК = 0, ПНД = 1, ВТР = 2, ... СБТ = 6 */
int day_of_week(int d, int m, int y)

3) семантика параметров и возвращаемого значения неочевидна

#define MAXN (10)

typedef double matrix_t[MAXN, MAXN];
typedef double vector[MAXN];

/* Решение системы линейных алгебраических уравнений
  методом Гаусса.
  Входные данные:
  a   -- матрица коэффициентов системы;
  b   -- столбец свободных членов системы;
  eps -- точность вычислений.
  Выходные данные:
  x            -- вектор решения;
  has_solution -- флаг, устанавливаемый в 1, если решение
                  системы существует, и в 0 во всех
                  остальных случаях;
  num_of_roots -- число корней в решении системы, может
                  принимать значения:
                  0      -- если решение системы не существует,
                  MAXN   -- если решение системы существует и
                            единственно,
                  MAXINT -- если существует бесконечное
                            множество решений;
  det          -- значение определителя матрицы a;
  afor_reverse -- нижняя треугольная матрица, полученная из a в
                  в результате выполнения прямого хода алгоритма
                  Гаусса;
  bfor_reverse -- столбец свободных членов, полученный из b в
                  в результате выполнения прямого хода алгоритма
                  Гаусса.                                         */
double gauss(matrix_t a, vector b, double eps,
  vector * x,
  int * has_solution,
  int * num_of_roots,
  double * det,
  matrix_t * afor_reverse,
  vector * bfor_reverse)

Замечание:
Если функция реализует какой-либо вычислительный метод (например: нахождение площади фигуры методом трапеций, поиск минимума функции методом Ньютона и т.п.), рекомендуется в теле функции поместить комментарий с кратким описанием метода, либо ссылку на источник, где описан метод.

Прототипы функций достаточно снабдить кратким комментарием назначения функции. Например:

/* решение системы линейных алгебраических уравнений
  методом Гаусса */
double gauss(matrix_t, vector, double, vector *, int *, int *, double *, matrix_t *, vector *);

2.3 Спецификация программного файла

Программный файл должен начинаться со спецификации в виде комментария, содержащего следующую информацию:
а) идентификация (имя) файла;
б) фамилия и копирайт автора;
в) дата написания файла;
г) версия языка программирования и замечания по компиляции программного файла в других версиях языка (если требуется);
д) назначение программного файла;
Например:

/* primes.c
  --------------------------
  (c)оздал: Иванов И.И.
  группа  : ММ-216
  дата    : 01/09/07
  для     : Borland C++ Builder 6.0
  -------------------------------------------------------
  Подсчет количества простых чисел в промежутке [1..200]. */

[Шаблон программного файла]

3. Соглашения по читаемости программ

3.1 Лесенка

"Лесенка" должна отражать структурную вложенность языковых конструкций. Рекомендуется отступ не менее 2-х и не более 8-и пробелов. Принятого отступа нужно придерживаться во всем тексте программы. Правила написания конструкций (K&R стиль):

а) if - else

if (<условие>) {
  <операторы>
} else {
  <операторы>
}

if (<условие>)
  <оператор>;
else
  <оператор>;

б) while

while (<условие>) {
  <операторы>
}

while (<условие>)
  <оператор>;

в) do - while

do {
  <операторы>
} while (<условие>);

do
  <оператор>;
while (<условие>);

г) for

for (<выражение1>; <выражение2>; <выражение3>) {
  <операторы>
}

for (<выражение1>; <выражение2>; <выражение3>)
  <оператор>;

д) switch

switch (<выражение>) {
case <выражение>:
  <операторы>
  break;
.......
default:
  <операторы>
}

е) определение функции

<тип> <имя_функции>(<список_параметров>)
{
  <операторы>
}

Например:

int sign(double x)
  /* выдает знак числа x */
{
  int result;
  
  if (x > 0)
    result = 1;
  else
    if (x < 0)
      result = -1;
    else
      result = 0;
  return result;
}

/* нахождение действительных корней квадратного уравнения;
  a, b, c -- коэффициенты
  x1, x2  -- корни (если действительного решения нет,
             то полагаются равными 0);
  num     -- число корней (0, 1, или 2)                  */
void equation(double a, double b, double c, double *x1, double *x2, int *num)
{
  double d;
  
  d = pow(b, 2) - 4 * a * c;
  if (d < 0) {
    *num = 0;
    *x1 = 0.0;
    *x2 = 0.0;
  } else {
    *x1 = (- b + sqrt(d)) / (2 * a);
    *x2 = (- b - sqrt(d)) / (2 * a);
    if (*x1 == *x2)
      *num = 1;
    else 
      *num = 2;
  }
}

3.2 Длина строк программного текста

Длина строк программы не должна превышать ширины экрана (80 символов). Инструкции длиннее 80 символов разбиваются на логические части, которые всегда значительно короче, чем изначальная строка, и распологаются со сдвигом вправо. То же самое относится к заголовкам функций с длинным списком аргументов и к длинным строковым константам. Например:

double function(double param1, double param2,
  double param3, double param4)
{
  double result;

  result = param1 + sqrt(param2) - param3 +
    pow(param3, param4) * cos(param4);
  printf("Внимание! Это длинный printf с 5-ью параметрами: "
    "1-й параметр = %f, 2-й параметр = %f, 3-й параметр = %f, "
    "4-й параметр = %f, результат: %f\n", param1, param2, param3,
    param4, result);
  return result;
}

3.3 Прочие рекомендации

3.3.1 Рекомендуется при перечислении идентификаторов после запятой "," ставить один пробел " ". Например:

double a, b;

printf("Сумма: %f. Разность: %f.", a + b, a - b);

3.3.2 Рекомендуется всегда писать символ-разделитель операторов ";" (непосредственно после оператора). Например:

switch (num) {
case 1:
  printf("один...");
  break;
case 2:
  printf("два...");
  break;
case 3:
  printf("три..."); // <-- здесь
  break;
default:
  printf("много!"); // <-- и здесь
}

if (n < 0) {
  printf("Введено неверное значение n, прерываем работу!");
  exit(0); // <-- здесь
}