Code Style

В руководство включены рекомендации из различных источников, рекомендуется ознакомиться с оригиналами. Здесь собраны правила, нарушения которых наиболее часто встречаются в коде студентов.

Вертикальные отступы

Рекомендуется использовать пустые строки вокруг определений функций, типов и логических блоков кода. Допускается использование не более одной пустой строки подряд.

double calculate_distance(...)
{
    ...
}
void circle_print(...)
{
    ...
}
void triangle_print(...)
{
    ...
}
double calculate_distance(...)
{
    ...
}

void circle_print(...)
{
    ...
}

void triangle_print(...)
{
    ...
}

Имена идентификаторов

  • lower_case_with_underscores: переменные, функции;

  • UpperCamelCase: структуры, объединения, перечисления;

  • UPPER_CASE_WITH_UNDERSCORES: макросы.

Следует давать переменным содержательные имена. Различные типы объявлений подчиняются следующим правилам:

  • Имена типов и переменных должны быть существительными.

  • Имена функций должны содержать глаголы.

Неудачные имена:

// Слишком общее имя.
bool flag = false;

// Непонятно назначение буфера.
char buf[BUFSIZE];

// Герундий в имени функции.
int* finding_element(const int* begin, const int* end);

// Что конкретно проверяет эта функция?
bool check(const Triangle* triangle);

Лучше:

// Название флага отражает смысл.
bool found = false;

// Понятно, какие данные хранятся в буфере.
char error_message[MAX_MESSAGE_LENGTH];

// Ок - глагол.
int* find_element(const int* begin, const int* end);

// Семантика функции понятна без документации.
// Для предиката используется префикс is_, в точке вызова
// будет очевиден тип возвращаемого значения.
bool is_equilateral(const Triangle* triangle);

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

Правильно:

typedef struct {
    double x;
    double y;
} Point;

Point point;

Исключение — рекурсивные структуры:

typedef struct List {
    void* data;
    struct List* next;
} List;

List* list;

Неправильно:

struct Point {
    double x;
    double y;
};

struct Point point;

Для элементов перечислений используется префикс имени перечисления:

typedef enum {
    HttpStatusOk = 200,
    HttpStatusBadRequest = 400,
    HttpStatusNotFound = 404,
    ...
} HttpStatus;

Не используйте магические константы

Неправильно:

for (int i = 0; i < 12; ++i) {
    ...
}

Правильно:

enum { MONTHS_IN_YEAR = 12 };

for (int i = 0; i < MONTHS_IN_YEAR; ++i) {
    ...
}

Иногда арифметическое выражение понятнее, чем значение этого выражения.

const int seconds_in_day = 86400;

Такую запись проще проверить на корректность:

const int seconds_in_day = 24 * 60 * 60;

Wiki: Magic number

Не пишите user-specific код

Подобный код не является переносимым:

FILE* dict = fopen("/home/v.pupkin/myproject/dict.txt", "r");

Лучше:

// Получение пути к файлу из внешнего источника:
// из старнадрного потока ввода, аргументов командной строки,
// конфигурационного файла, и т. д.
const char* dict_file_path = ... ;

FILE* dict = fopen(dict_file_path, "r");

Не используйте глобальные переменные

В подавляющем большинстве случаев глобальные переменные усложняют поддержку кода.

Некоторые последствия использования глобальных переменных:

  1. Нарушение локальности. Чем меньше область видимости отдельных элементов, тем проще отлаживать код.

  2. Неявные зависимости. Глобальные переменные могут неявно связывать любые части части исходного кода.

  3. Усложнение тестирования. Каждый тест должен быть независим. При наличии глобальных переменных каждый тест вынужден явно присваивать всем переменным некоторые значения для настройки стартового окружения. Наличие разделяемого между тестами состояния может привести к тому, что результат их выполнения зависит от порядка запуска.

Использование глобальных констант допустимо.

C2 Wiki: Global Variables Are Bad

Использование const

Используйте const везде, где это имеет смысл. Неизменяемые объекты упрощают понимание программы. Рассматривайте const в сигнатурах функций как часть контракта.

Правильно:

// Сигнатура гарантирует, что переданный объект не будет изменен.
void print_circle(const Circle* circle);

// Вычисленное расстояние не меняется в последующем коде.
const double distance = calculate_distance(...);

Избыточно:

// Результат может быть сохранен в изменяемую переменную.
const double calculate_distance(...);

// Невозможно форсировать контракт.
double distance = calculate_distance(...);
// Параметры копируются и гарантированно не будут изменены в вызывающем коде
// даже без использования const.
double calculate_distance(const Point point_a, const Point point_b);

Tip of the Week #109: Meaningful const in Function Declarations

Пишите короткие функции

Функции должны быть короткими и решать ровно одну задачу, очевидную из имени. По возможности старайтесь писать чистые функции.

Можно ориентироваться на следующие эвристики:

  1. Функция должна умещаться на экране.

  2. В функции должно быть не более 5−10 локальных переменных.

  3. В функции должно быть не более 3 уровней вложенности.

Linux kernel coding style: Functions

Указывайте имена параметров в объявлениях функций

Имена параметров в объявлениях функций не являются обязательными для компилятора, но значительно улучшают читаемость.

// Кто кого захватывает?
bool can_capture(Piece, Piece);
bool can_capture(Piece attacker, Piece victim);

Избегайте избыточных ветвлений (if true return true)

Избыточно:

bool can_capture(Piece attacker, Piece victim)
{
    if (attacker.color != victim.color) {
        return true;
    } else {
        return false;
    }
}

Лаконично:

bool can_capture(Piece attacker, Piece victim)
{
    return attacker.color != victim.color;
}

Используйте ранние возвраты

if (ok) {
    call_a()
    if (a_ok) {
        call_b()
        call_c()
        if (b_ok && c_ok) {
            call_d()
            call_e()
            call_f()
            call_g()
        } else {
            throw_exception("Method A failed")
    } else {
        throw_exception("Method B or C failed")
} else {
    throw_exception()
}
call_a()
if (a_error) {
   throw_exception()
}

call_b()
call_c()
if (c_error || c_error) {
    throw_exception()
}

call_d()
call_e()
call_f()
call_g()

LLVM Coding Standards: Use Early Exits and continue to Simplify Code C2 Wiki: If Ok

Не используйте else после return, break, continue

Не используйте else или else if после ключевых слов, прерывающих поток выполнения команд.

void foo(int value)
{
    int local = 0;
    for (int i = 0; i < 42; i++) {
        if (value == 1) {
            return;
        } else {
            local++;
        }

        if (value == 2) {
            continue;
        } else {
            local++;
        }
    }
}
void foo(int value) {
    int local = 0;
    for (int i = 0; i < 42; i++) {
        if (value == 1) {
            return;
        }
        local++;

        if (value == 2) {
            continue;
        }
        local++;
    }
}

LLVM Coding Standards: Don’t use else after a return

Выносите предикаты в функции

Часто в коде встречаются циклы, вычисляющие единственное булево значение. Пример:

bool found_foo = false;
for (size_t i = 0; i  n; ++i) {
    if (is_foo(bar_list[i])) {
        found_foo = true;
        break;
    }
}

if (found_foo) {
    ...
}

Вместо таких циклов рекомендуется писать функции с использованием ранних выходов:

static bool contains_foo(Bar* bars, size_t n)
{
    for (size_t i = 0; i <n; ++i) {
        if (is_foo(bars[i])) {
            return true;
        }
    }
    return false;
}
...

if (contains_foo(bars, n)) {
    ...
}

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

Гораздо важнее, что в этом случае вы вынуждены выбрать имя для предиката. Польза не так очевидна на синтетическом примере, но если условие становится более сложным, то именованный предикат позволяет понять смысл условия, не погружаясь в детали реализации.

LLVM Coding Standards: Turn Predicate Loops into Predicate Functions:

Порядок заголовочных файлов

Предпочительный порядок заголовочных файлов:

  1. Главный заголовочный файл модуля.

  2. Локальные/приватные заголовочные файлы.

  3. Заголовочные файлы сторонних библиотек.

  4. Системные заголовочные файлы.

Блоки следует отделять пустой строкой. Файлы в каждом блоке должны быть отсортированы лексикографически.

Абстрактный пример для circle.c:

#include "circle.h"

#include "wkt_reader.h"

#include <json.h>
#include <svg.h>

#include <math.h>
#include <stdio.h>
#include <stdlib.h>

LLVM Coding Standards: #include Style

Подключайте как можно меньше

Избыточное подключение заголовочных файлов замедляет компиляцию, особенно при подключении в другие заголовочные файлы.

Распространенная ошибка: в заголовочный файл circle.h подключается stdio.h, при этом в circle.h не используются определения из stdio.h:

circle.h:

#pragma once

#include <stdio.h>

typedef struct {
    ...
} Circle;

void circle_print(const Circle* circle);

circle.c:

#include "circle.h"

void circle_print(const Circle* circle)
{
    printf(...);
}

В этом случае stdio.h следует подключать в circle.c:

circle.h:

#pragma once

typedef struct {
    ...
} Circle;

void circle_print(const Circle* circle);

circle.c:

#include "circle.h"

#include <stdio.h>

void circle_print(const Circle* circle)
{
    printf(...);
}

В этом примере в circle.h нужно подключить stdio.h, поскольку используется тип FILE:

circle.h:

#pragma once

#include <stdio.h>

typedef struct {
    ...
} Circle;

void circle_print(const Circle* circle, FILE* stream);

LLVM Coding Standards: #include as Little as Possible

Скрывайте детали реализации

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

circle.h:

#pragma once

typedef struct {
    ...
} Circle;

// Функция calculate_distance - деталь реализации функции circle_intersects,
// пользователь модуля не будет вызывать ее напрямую.
double calculate_distance(const Point* point_a, const Point* point_b);
bool circle_intersects(const Circle* circle_a, const Circle* circle_b);

circle.c:

#include "circle.h"

// Функцию следует определить только в файле реализации и добавить
// спецификатор static.
static double calculate_distance(const Point* point_a, const Point* point_b)
{
    ...
}

Кодировка файлов

Файлы с исходным кодом должны быть в кодировке UTF-8. Преобразование из кодировки Windows-1251 можно выполнить с помощью утилиты iconv:

iconv -f cp1251 -t utf8 input.c > output.c

Символ перевода строки — Line Feed (LF). Для преобразования файла из формата DOS в формат Unix используется утилита dos2unix:

dos2unix input.c

.clang-format

Код следует форматировать утилитой clang-format с конфигурационным файлом .clang-format:

---
Language:        Cpp
AccessModifierOffset: -4
AlignAfterOpenBracket: AlwaysBreak
AlignConsecutiveAssignments: false
AlignConsecutiveDeclarations: false
AlignEscapedNewlines: Left
AlignOperands:   false
AlignTrailingComments: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortBlocksOnASingleLine: false
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: None
AllowShortIfStatementsOnASingleLine: false
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: true
AlwaysBreakTemplateDeclarations: true
BinPackArguments: false
BinPackParameters: false
BraceWrapping:   
  AfterClass:      false
  AfterControlStatement: false
  AfterEnum:       false
  AfterFunction:   true
  AfterNamespace:  false
  AfterStruct:     false
  AfterUnion:      false
  AfterExternBlock: false
  BeforeCatch:     false
  BeforeElse:      false
  IndentBraces:    false
  SplitEmptyFunction: true
  SplitEmptyRecord: true
  SplitEmptyNamespace: true
BreakBeforeBinaryOperators: All
BreakBeforeBraces: Custom
BreakBeforeInheritanceComma: false
BreakBeforeTernaryOperators: true
BreakConstructorInitializersBeforeComma: false
BreakConstructorInitializers: BeforeColon
BreakStringLiterals: true
ColumnLimit:     80
CompactNamespaces: true
ConstructorInitializerAllOnOneLineOrOnePerLine: true
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 8
Cpp11BracedListStyle: true
DerivePointerAlignment: false
ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: false
IncludeBlocks:   Preserve
IndentCaseLabels: false
IndentPPDirectives: None
IndentWidth:     4
IndentWrappedFunctionNames: false
KeepEmptyLinesAtTheStartOfBlocks: false
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
PenaltyBreakAssignment: 2
PenaltyBreakBeforeFirstCallParameter: 19
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakString: 1000
PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 60
PointerAlignment: Left
ReflowComments:  true
SortIncludes:    true
SortUsingDeclarations: true
SpaceAfterCStyleCast: false
SpaceAfterTemplateKeyword: true
SpaceBeforeAssignmentOperators: true
SpaceBeforeParens: ControlStatements
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1
SpacesInAngles:  false
SpacesInContainerLiterals: false
SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
Standard:        Cpp11
TabWidth:        8
UseTab:          Never
...