# Arcadia C++ Style Guide

## Преамбула

![](img/helicopter.jpg)

Этот документ описывает стиль оформления кода на C/C++, принятый в Аркадии.
Единый стиль оформления кода (стандарт кодирования) является необходимостью
при совместной работе над большим проектом, так как существенно сокращает время,
нужное для понимания чужого кода.

**Основная мысль: Программа должна быть написана не для машины («ну оно же работает»),
не для себя («подходи, я тебе объясню как это работает»), а для всех читающих,
модифицирующих и отлаживающих программистов, к которым следует относиться с уважением.**

Обратите внимание, что:

Все читающие и отлаживающие программисты в Яндексе пишут на императивном,
объектно-ориентированном языке C++ времен С++17.

Более детально:
* Мы не пишем на plain/C. Мы используем `new`, а не `malloc`. В нашем языке C++ оператор `new` кидает exception-ы, а не возвращает "0" в случае неуспешного выделения памяти.
* Наш язык C++ является императивным, а не функциональным языком. В нем есть шаблоны, но они не используются для вещей типа compile-time вычислений. Мы используем range-based for, а не `for_each`.
* Мы не используем `boost`, ограниченно используем STL. Если в `util` есть аналог функции или класса из `std::`, следует использовать его.
  Более подробно - см. [Arcadia C++ Library Guide](cpp_library_guide.md)
* Мы не поощряем изобретения своих велосипедов (на самом деле мы уже изобрели все велосипеды и положили их в библиотеку [util][util]).
* Мы используем [ограниченное подмножество нового синтаксиса C++17](#cpp17), которое поддерживается всеми необходимыми нам компиляторами.

В настоящее время незначительная часть кода в дереве исходников `/arcadia`
не соответствует представленному ниже стандарту (в частности потому, что
некоторые блоки кода были написаны около 15 лет назад). Тем не менее,
данный стандарт является обязательным при написании нового кода.

### Политика исправления стиля

* Общий принцип исправления стиля в старом коде — соблюдение принципа «один файл — один стиль». Т.е. если исправляется ошибка, и при этом меняется одна-несколько строк, то следует сохранять оригинальный стиль. А если исправленный код пишется в новом стиле, то к нему следует привести весь код в файле, а если потребуется, то и весь код класса (например, при переименовании полей или методов класса)
* Если автор не согласен со стилем исправления, то он имеет право его переделать, но изменения автора должны соответствовать стилю, описываемому в настоящем документе (в т.ч. предыдущему пункту)
* Коммиты, исправляющие стиль, следует отделять от коммитов, изменяющих функциональность.
* Не следует исправлять стиль в файлах, которые вы не модифицируете, также не следует менять форматирование строк, если они соответствуют настоящим рекомендациям. Помните, что у каждой строчки есть автор, узнать о котором можно, используя VCS blame. Изменив форматирование чужой строчки, вы необоснованно становитесь ее автором.

### Политика исправления этого документа

* Как и любой документ, описывающий вкусовые предпочтения группы людей, данный стандарт получен в результате длительных споров и взаимных уступок. Поэтому прежде чем его исправлять, согласуйте это исправление со всеми заинтересованными сторонами. Если вы не знаете, кто это, воздержитесь от исправлений.
* Составляя данный документ, авторы считали, что чем менее объемен стандарт, тем лучше: специфицировать следует лишь то, на чем сильно спотыкается глаз при чтении кода, ведь любая дополнительная спецификация приводит к недовольству тех, кому приходится переучиваться. Поэтому, предлагая исправление или дополнение, всегда используйте следующий алгоритм - а) мотивируйте, что данное место мозолит глаза большинству, и что поэтому его надо специфицировать; б) только после этого предложите, как именно надо специфицировать это место; в) обоснуйте, что предложенная спецификация минимизирует суммарные усилия коллектива по переучиванию.

### Инструменты

Для автоматического форматирования C++ файлов можно использовать команду `ya style`.
Она в своей основе использует clang-format с правильным конфигом, который лежит здесь:
[devtools/ya/handlers/style/style_config][style_config] —
им можно пользоваться отдельно, если например Ваш редактор использует clang-format напрямую.

## Имена

Имя должно отражать суть тех данных, того типа или того действия, которое этим именем названо.
При формировании имен допускаются только общеупотребительные сокращения.
Общепринятые однобуквенные имена (`i`, `j`, `k`) допускается использовать только для счётчиков циклов и итераторов.
Структуры — тоже классы, и все что относится к классам, относится и к структурам (если явно не сказано обратное).

### Переменные

#### Капитализация, префиксы, подчеркивания

* Локальные и глобальные переменные пишутся с маленькой буквы.
* Названия функций пишутся с большой буквы.
* Переменные-указатели на функции, как и обычные переменные, пишутся с маленькой буквы:
  ```cpp
  auto localFunction = [&]() { ... };
  ```
* Аргументы функций пишутся с маленькой буквы.
* Члены классов пишутся с большой буквы; private и protected члены классов заканчиваются подчёркиванием, а публичные члены не имеют никаких суффиксов.
* Методы классов пишутся с большой буквы и не содержат суффиксов независимо от области видимости.
* Названия классов и определения типов (`typedef`-ы) предваряются префиксом '`T`', за которым следует название класса, начинающееся с большой буквы.
  Названия виртуальных интерфейсов начинаются с '`I`'.
  Виртуальный интерфейс — это класс, содержащий хотя бы один чисто виртуальный метод (включая унаследованные) и не содержащий полей данных (включая унаследованные).
* Все глобальные константы и `define`-ы полностью капитализированы.
* Токены в сложных именах переменных и функций выделяются капитализацией первой буквы токена (без вставки подчеркивания). Токены в полностью капитализированных именах констант отделяются подчеркиванием.
* Использовать подчеркивание в качестве первого символа имени запрещено.
* Использовать венгерскую нотацию запрещено.

```cpp
class TClass {
public:
    int Size;
    int GetSize() {
        return Size;
    }
};
TClass object;
int GetValue();
void SetValue(int val);
```

*Исключение*: для interop с внешним, по отношению к Аркадии, кодом,
можно и нужно добавлять методы (или внешние функции), требующиеся этому interop. Например:
* протокол итераторов stl (`.begin()`, `.end()`),
* протокол для доступа к непрерывному буферу (`.data()`, `.size()` или внешние функции),
* протокол обмена объектов (внешняя функция `swap()`).

#### Макросы

Если вам всё же пришлось использовать макросы, то необходимо позаботиться о том, чтобы он был уникальным
(например соответствовал иерархии директорий в пути до этого файла).
Если макрос предполагается использовать как часть API вашей библиотеки, то макрос должен иметь префикс `Y_`
(см. для примера макросы в [`util/system/compiler.h`](https://a.yandex-team.ru/arc/trunk/arcadia/util/system/compiler.h)).

#### Перечисления

Глобальные enum-ы следует называть по тем же правилам, что и классы, но с заглавной буквы '`E`'.
Члены этих enum-ов надо называть с использованием только заглавных букв, так же как и глобальные константы, которыми они по сути являются.
Имена должны иметь префикс, образованный по первым буквам названия enum-а.

```cpp
enum EFetchType {
    FT_SIMPLE,
    FT_RELFORM_DEBUG,
    FT_ATR_DEBUG,
    FT_SELECTED,
    FT_ATR_SELECTED,
    FT_LARGE_YA
};
```

Для enum-членов класса следует использовать те же правила, что и для других членов класса, так как это аналог константных членов:

```cpp
class TIndicator {
public:
    enum EStatus {
        Created,
        Running,
        Suspended,
        Aborted,
        Failed,
        Finished
    };
    // ...
};
```

Неименованные enum-ы допускаются только в членах класса:

```
class TFile {
public:
    enum {
        Invalid = -1
    };
    //...
};
```

C++11-перечисления следует оформлять так же, как и enum-члены класса, так как они обладают похожим scope.

```
enum class EStatus {
    Created,
    Running,
    Suspended,
    Aborted,
    Failed,
    Finished
};
```

##### Преобразования перечисления в строку

Нельзя делать свои конструкции для преобразования enum в `TString` и обратно.
Нужно использовать [`GENERATE_ENUM_SERIALIZATION`][enum_serialization].

Вместо последнего поля с количеством полей в enum можно использовать `GENERATE_ENUM_SERIALIZATION_WITH_HEADER` (см. в том же документе).

## Форматирование

### Табуляция

В текстовом редакторе нельзя использовать символ табуляции.
Причина: это единственный способ обеспечить читаемость Вашей программы на любом устройстве вывода.
Убедитесь что в Вашем текстовом редакторе есть опция по замене символа табуляции на пробелы.
Например, в редакторе textpad надо отметить checkbox "Convert new tabs to spaces".

### Отступы

Наш нормативный сдвиг = 4. Сдвиг должен заполняться пробелами, даже если Вы и нажимаете на кнопку tab.

### Стиль блоков

Для блочных операторов применяем стиль 1TBS

```cpp
if (something) { // K&R style
    One();
    Two();
} else {
    Three();
    Four();
}
for (int i = 0; i < N; ++i) { // K&R style
    // do something...
}
```

Многострочные условия являются исключением (если условие не помещается в одну строчку - это повод разбить его на несколько), они записываются так:

```cpp
if (a && b && c &&
    d && e)
{
    Op();
}
```

Для функций и методов можно применять любой из двух стилей:

```
Func1(a, b, c)
{
}
```

```
Func1(a, b, c) {
}
```

В пределах одного файла стиль расстановки фигурных скобок должен быть один.

#### Короткие блоки

Однострочные тела операторов, а также inline-функций должны начинаться с новой строки. Тела операторов и функции, объявленные в одну строку, доставляют неудобства при отладке.
```
if (something)
    A();
```

Подчиненный оператор не должен быть пустым. Запрещено:
```cpp
for (int i = 0; i < 100; i++);
```

Причина: такой текст не отличим от опечатки, невылавливаемой на этапе компиляции.

#### Операторы

Запрещается использовать на одной строке несколько операторов.

#### Пустые строки

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

### Пробелы

#### Знаки операций

Все знаки операций, за исключением унарных и операторов доступа к членам структур, отбиваются с обеих сторон пробелом:
```cpp
a = b;
x += 3;
z = a / 6;
```

В том числе и знак присваивания. То есть надо писать:
```cpp
if (!x.a || ~(b->c - e::d) == 0)
    z = 0;
```

```cpp
void F() noexcept {
}
```

```cpp
struct T {
    void F() const noexcept {
    }
};
```

#### Ссылки и указатели

Для ссылочных типов и указателей амперсанд (`&`) и звёздочка (`*`) приклеиваются к типу данных.
```cpp
const T& value = Foo();
void Bar(int* p);
```

#### Скобки

После имени функции, после открывающей скобки и перед закрывающей скобкой пробел не ставится:
Func(a, b, c);
между оператором и скобкой пробел ставится:

```cpp
if ()
for ()
while ()
```

Пробелы внутри скобок выглядят так:

```cpp
Func(a, b, c);
(a + b)
```

Внутри range-based for:
```cpp
for (auto& x : c) {
}
```

Асимметричные пробелы не допускаются.

При инстанцировании шаблонов треугольные скобки расставляются без пробелов.
```cpp
vector<vector<int>> matrix;
```

#### Конец строки

Пробелов в конце строки быть не должно. Для этого можно использовать опции вашего текстового редактора.
Настройки редакторов

Textpad: "strip trailing spaces from lines when saving".

Vim:
```vim
    augroup vimrc
      " Automatically delete trailing DOS-returns and whitespace on file open and
      " write.
      autocmd BufRead,BufWritePre,FileWritePre * silent! %s/[\r \t]\+$//
    augroup END
```

Emacs:
```emacs
    (add-hook 'c-mode-common-hook
    	  (lambda () (add-to-list 'write-file-functions 'delete-trailing-whitespace)))
```

## Lambda-функции

Использование однострочных lambda допускается только в одном случае: определение в месте использования, при этом код lambda-функции сам по себе не должен нарушать остальные правила styleguide:

```cpp
Sort(a.begin(), a.end(), [](int x, int y) -> bool {return x < y;}); // OK
```

```cpp
Sort(a.begin(), a.end(), [](int x, int y) -> bool {int z = x - y; return z < 0;}); // не OK - на одной строке не могут быть 2 statement
```

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

```cpp
auto f = [](int x, int y) -> bool { //K&R style, так же как и для for/if/while
    return x < y;
};

// так же можно auto&& f, если lambda-функция имеет "тяжелый" состав

Sort(a.begin(), a.end(), f);
```

## Переменные и классы

### Объявление переменных

Предпочтительным является вид «одно объявление — одна строка».
Допустимо в одной строке объявлять несколько переменных одного типа.
Недопустимо смешивать объявления массивов, указателей, ссылок, простых типов.
Запрещено использовать перенос строки внутри объявления.

```cpp
int    level;                  // предпочтительный вариант
int    size;

int    level, size;            // допустимо

int    level,
       size;                   // запрещено: перенос

int level, array[16], *pValue; // запрещено: смешивание
```

### Объявление классов и структур

* Структура может содержать только открытые члены. Указывать для нее `public` не нужно. Если структура содержит что-либо кроме членов, конструктора и деструктора, рекомендуется переименовать её в класс.
* Метки областей видимости пишутся с той же колонки, с которой начинается декларация класса.
  Указание областей видимости обязательно, в том числе для первой приватной области.
* В одной секции областей видимости не могут находиться одновременно члены и методы.
  Области их расположения следует разделять повторным указанием области видимости.
  Меток областей видимости должно быть минимально возможное количество, достигаемое изменением порядка следования частей декларации класса.
* В рамках одной области видимости:
  * конструкторы должны предшествовать деструктору
  * деструктор должен предшествовать переопределённым операторам
  * переопределенные операторы должны предшествовать остальным методам
* Публичная область видимости с методами должна предшествовать `protected`- и `private`-областям видимости с методами
* Члены-данные класса следует располагать в начале или в конце описания класса. Описания типов класса могут предшествовать описанию данных.

```cpp
class TClass {
public:
    TClass();
    TClass(const TClass& other);
    ~TClass();
private:
    int Member_; // comments about Member
};
```

Слово `template` следует начинать с отдельной строки.

### Оформление конструкторов

Конструкторы следует оформлять так:
```cpp
TClass::TClass()
    : FieldA(1)
    , FieldB("value")
    , FieldC(true)
{
    // some more
    // code here
}
```

### Инициализация данных класса

Допускаются следующие варианты:
```cpp
struct T {
    int X = 0;
    double Y = 1.0;
};
```
и
```cpp
struct T {
    int X;
    double Y;

    T() //реализация может находиться и в .cpp файле
        : X(0)
        , Y(1.0)
    {
    }
};
```

Причина: если смешивать 2 типа инициализации, существенно проще забыть проинициализировать какой-то член класса,
так как код инициализации получается «размазан» (возможно, по нескольким исходным файлам).

## Пространства имен

Пространства имен следует оформлять как и классы, за исключением названия. Пространства имен должны начинаться с заглавной буквы N:
```cpp
namespace NStl {
    namespace NPrivate {
        //все, уровень вложенности пространств имен ограничен двумя
    }

    class TVector {
    };
}
```

## C++17 features {#cpp17}

### Alternative function syntax

```cpp
auto f() -> decltype() {}
```
или
```cpp
auto f() {}
```

Следует использовать только там, где без него не обойтись.

### `nullptr` / `0` / `NULL`

Следует всегда использовать `nullptr`

### using/typedef

В новом коде следует использовать `using` (как более общий механизм), кроме тех случаев, когда это невозможно.
Известны случаи, когда использование `using` + шаблонов с неизвестным числом параметров + function type ведет к ошибкам компиляции в VS:
```cpp
template <class R, class Args...>
struct T {
    using TSignature = R (Args...);
};
```

В этом случае следует использовать `typedef`.

### override

В производных классах следует использовать `override` без `virtual`.
```cpp
class A {
    virtual ui32 f(ui32 k) const {
    }
};

class B: public A {
    ui32 f(ui32 k) const override {
    }
};
```

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

Комментарии предназначены для пояснения кода, внутри которого они расположены.
Не используйте комментарии, чтобы убрать ненужную функцию или блок, особенно старый вариант исправленной вами функции.
Просто стирайте ненужные части кода: всегда можно запустить VCS, чтобы извлечь удаленный участок, если вдруг выяснится его полезность.
Главный вред от комментирования предыдущих версий кода вместо их удаления — невозможность корректной работы VCS diff.

Комментарии следует писать на английском или русском языке, соблюдая правила орфографии и грамматики.
Предпочтительным является английский язык. В случае русского языка следует использовать кодировку utf-8.

Полезно напротив каждого члена в описании класса указать его предназначение. Эту строчку редактор MSVC показывает в тултипе при "умном редактировании".

Приветствуются комментарии в стиле doxygen.
Обратите внимание на [рекомендации по документированию кода](https://wiki.yandex-team.ru/TechDoc/closed-archive/Technologies/GenDoc/#dokumentirovaniekoda).

Для упрощения поиска своих TODO по коду, TODO-комментарии следует оформлять одним из двух способов:

```cpp
// Предположительно, короткоживущий комментарий, с пометками для себя:
// TODO(pg): fix me later
```

```cpp
// Комментарий с указанием тикета в ST:
// TODO(IGNIETFERRO-1245): fix me later
// TODO(IGNIETFERRO-13456)
```

## Файлы и препроцессор

### Имена файлов

В именах файлов большие буквы не допускаются. Расширения для С++ — "`cpp`", "`h`".

### Препроцессор

Сдвиги препроцессора тоже 4. Решётка сдвигается вместе с кодом.

```cpp
#ifdef Z
    #include
#elif Z
    #define
    #if
        #define
    #endif
#else
    #include
#endif
```

При условном препроцессировании в середине файла начинаем в первой позиции.

```cpp
func A() {
    int x;
#ifdef TEST_func_A // ifndef + else = schisophrenia
    x = 0;
#else
    x = 1;
#endif
}
```

Введён запрет на условное препроцессирование в коде.
Это делается в рамках кампании по замене `ifdef`'ов на другие способы кастомизации кода, например через шаблоны, интерфейсы, "кирпичики",
из которых собирается нужная функциональность.

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

### include

Взаимозависимости include-файлов быть не должно, то есть include-файл должен быть компилируемым сам по себе как отдельная единица компиляции.
Если в include-файле встречаются упоминания неописанных в нем типов, то следует
* если это стандартный тип, надо включить минимальный стандартный include файл, например `cstddef` или `cstdio`;
* если это имя класса, структуры или перечисления и оно используется по ссылке/указателю, следует написать прямо в include-файле форвард-декларацию;
* в остальных случаях включайте файл с декларацией соответствующего класса.

Внутри include-файлов запрещается использовать инструкцию `using namespace` за иключением подключения литералов **из стандартной библиотеки**.

{% note info %}

Заголовочный файл `util/generic/strbuf.h` добавляет `operator ""sv()` в глобальное пространство имён.

{% endnote %}

Порядок включения include-файлов (не важно в `cpp` или другой include) должен быть таким, что менее общий файл включается раньше более общего.
Такой порядок позволяет лишний раз проверять на самостоятельность остальные включаемые заголовочные файлы.

Например, для файла `quality/click_analysis/some_program/some_class.cpp` порядок включения следующий:
* парный h-файл. Всегда набирается в кавычках.
    ```cpp
    #include "some_class.h"
    ```
* файлы из локальной директории. Всегда набираются в кавычках.
    ```cpp
    #include "other_class.h"
    #include "other_class2.h"
    ```
* далее группы файлов из надкаталогов нашего проекта по вложенности
    ```cpp
    // quality/click_analysis
    #include <quality/click_analysys/common/mr_calc_factors.h>
    #include <quality/click_analysys/common/parse_redir_log>
    // quality
    #include <quality/mapreducelib/all.h>
    #include <quality/mr_util/io_utils.h>
    #include <quality/mr_util/statistics.h>
    #include <quality/util/datetime.h>
    #include <quality/util/prog_options.h>
    ```
* остальное, кроме `util`
    ```cpp
    #include <ysite/yandex/doppelgangers/normalize.h>
    ```
* `util`
    ```cpp
    #include <util/folder/dirut.h>
    #include <util/system/yassert.h>
    ```
* заголовочные файлы C
    ```cpp
    #include <cmath>
    #include <cstdio>
    #include <ctime>
    ```
* системные заголовочные файлы, если код непереносим
    ```cpp
    #include <Windows.h>
    ```

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

Внутри каждой группы предпочтительна сортировка по алфавиту.

Однократное включение файлов достигается так:
```cpp
#pragma once
..
..
```

## Обработка ошибок

### Ошибки времени выполнения

Ошибки следует обрабатывать с помощью исключений:
```cpp
#include <util/generic/yexception.h>

class TSomeException: public yexception {
...
};

...

if (shitHappened) {
    ythrow TSomeException(numericCode) << "shit happened (" << usefulDescription << ")";
}
```

Обработка ошибок с помощью кодов возврата
```cpp
if (Func1() == ERROR1) {
    return MY_ERROR1;
}

if (Func2() == ERROR2) {
    return MY_ERROR2;
}
```

запрещена везде, кроме особо оговоренных случаев:
* в `С`-коде
* обработка кодов возврата С-функций (например, `libc`)
* в местах, особо критичных к производительности (каждый такой случай рассматривается отдельно)

### Проверка инвариантов

Для проверки различного рода инвариантов времени компиляции (к примеру, `sizeof(int) == 4`) существует `static_assert`.

Для проверки инвариантов времени выполнения вместо `assert()` следует использовать
макрос `Y_ASSERT()`, так как он лучше интегрирован в Visual Studio.

## Кросс-платформенность

Вызов платформо-зависимых системных функций разрешен только в `/util`.
Для того, чтобы воспользоваться теми или иными системными примитивами,
следует использовать кросс-платформенные обертки из `/util`.
Если нужной обертки не существует, её можно написать (желательно с применением ООП)
и положить в `util` (не забыв пройти [code review][review]).

## Исключения из общих правил

### contrib

В папке `arcadia/contrib` лежат внешние по отношению к нам библиотеки и программы.
Очевидным образом, они подчиняются своему стилю написания кода.
Если возникает необходимость положить в `contrib` что-то, чего там нет,
нужно завести тикет в очереди `CONTRIB` для обсуждения и принятия решения.
О том, как технически правильно добавлять новые библиотеки,
можно прочитать на [этой][contrib] странице.


[util]: https://a.yandex-team.ru/arc/trunk/arcadia/util
[style_config]: https://a.yandex-team.ru/arc/trunk/arcadia/devtools/ya/handlers/style/style_config
[enum_serialization]: https://wiki.yandex-team.ru/yatool/HowToWriteYaMakeFiles/#qkakpostroitfunkciivvoda-vyvodachlenovperechislenijjopisannyxvzagolovke
[review]: https://wiki.yandex-team.ru/arcadia/review/
[contrib]: https://a.yandex-team.ru/arc/trunk/arcadia/contrib/README.md