style.md 42.8 KB
Newer Older
B
BayoNet 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
# Как писать код на C++

## Общее

1. Этот текст носит рекомендательный характер.
2. Если вы редактируете код, то имеет смысл писать так, как уже написано.
3. Стиль нужен для единообразия. Единообразие нужно, чтобы было проще (удобнее) читать код. А также, чтобы было легче осуществлять поиск по коду.
4. Многие правила продиктованы не какими либо разумными соображениями, а сложившейся практикой.

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

1. Большую часть форматирования сделает автоматически `clang-format`.
1. Отступы - 4 пробела. Настройте среду разработки так, чтобы таб добавлял четыре пробела.
1. Открывающая фигурная скобка на новой, отдельной строке. (Закрывающая - тоже.)

    ```cpp
    inline void readBoolText(bool & x, ReadBuffer & buf)
    {
        char tmp = '0';
        readChar(tmp, buf);
        x = tmp != '0';
    }
    ```
1. Но если всё тело функции достаточно короткое (один statement) - при желании, его можно целиком разместить на одной строке. При этом, вокруг фигурных скобок ставятся пробелы (кроме пробела на конце строки).

    ```cpp
    inline size_t mask() const                { return buf_size() - 1; }
    inline size_t place(HashValue x) const    { return x & mask(); }
    ```
1. Для функций, пробелы вокруг скобок не ставятся.

    ```cpp
    void reinsert(const Value & x)
    ```

    ```cpp
    memcpy(&buf[place_value], &x, sizeof(x));
    ```
1.  При использовании выражений if, for, while, ... (в отличие от вызовов функций) перед открывающей скобкой ставится пробел.

    ```cpp
    for (size_t i = 0; i < rows; i += storage.index_granularity)
    ```
1.  Вокруг бинарных операторов (+, -, \*, /, %, ...), а также тернарного оператора ?: ставятся пробелы.

    ```cpp
    UInt16 year = (s[0] - '0') * 1000 + (s[1] - '0') * 100 + (s[2] - '0') * 10 + (s[3] - '0');
    UInt8 month = (s[5] - '0') * 10 + (s[6] - '0');
    UInt8 day = (s[8] - '0') * 10 + (s[9] - '0');
    ```
1.  Если ставится перенос строки, то оператор пишется на новой строке, и перед ним увеличивается отступ.

    ```cpp
    if (elapsed_ns)
        message << " ("
             << rows_read_on_server * 1000000000 / elapsed_ns << " rows/s., "
            << bytes_read_on_server * 1000.0 / elapsed_ns << " MB/s.) ";
    ```
1.  Внутри строки можно, при желании, выполнять выравнивание с помощью пробелов.

    ```cpp
    dst.ClickLogID         = click.LogID;
    dst.ClickEventID       = click.EventID;
    dst.ClickGoodEvent     = click.GoodEvent;
    ```
9. Вокруг операторов `.`, `->` не ставятся пробелы.

   При необходимости, оператор может быть перенесён на новую строку. В этом случае, перед ним увеличивается отступ.
69 70 71 72 73
10. Унарные операторы (`--, ++, *, &`, ...) не отделяются от аргумента пробелом.
11. После запятой ставится пробел, а перед - нет. Аналогично для точки с запятой внутри выражения for.
12. Оператор `[]` не отделяется пробелами.
13. В выражении `template <...>`, между `template` и `<` ставится пробел; после `<` и до `>` - не ставится.

B
BayoNet 已提交
74 75 76 77 78
    ```cpp
    template <typename TKey, typename TValue>
    struct AggregatedStatElement
    {}
    ```
79 80
14. В классах и структурах, public, private, protected пишется на том же уровне, что и class/struct, а все остальные внутренности - глубже.

B
BayoNet 已提交
81 82 83 84 85 86 87 88 89 90
    ```cpp
    template <typename T, typename Ptr = std::shared_ptr<T>>
    class MultiVersion
    {
    public:
        /// Конкретная версия объекта для использования. shared_ptr определяет время жизни версии.
        using Version = Ptr;
        ...
    }
    ```
91 92 93
15. Если на весь файл один namespace и кроме него ничего существенного нет - то отступ внутри namespace не нужен.
16. Если блок для выражения if, for, while... состоит из одного statement-а, то фигурные скобки писать не обязательно. Вместо этого поместите statement на отдельную строку. Этим statement-ом также может быть вложенный if, for, while... Но если внутренний statement содержит фигурные скобки или else, то у внешнего блок следует писать в фигурных скобках.

B
BayoNet 已提交
94 95 96 97 98 99 100 101
    ```cpp
    /// Если файлы не открыты, то открываем их.
    if (streams.empty())
        for (const auto & name : column_names)
            streams.emplace(name, std::make_unique<Stream>(
                 storage.files[name].data_file.path(),
                storage.files[name].marks[mark_number].offset));
    ```
102 103 104 105 106

17. Не должно быть пробелов на концах строк.
18. Исходники в кодировке UTF-8.
19. В строковых литералах можно использовать не-ASCII.

B
BayoNet 已提交
107 108 109
    ```cpp
    << ", " << (timer.elapsed() / chunks_stats.hits) << " μsec/hit.";
    ```
110 111 112 113 114
20. Не пишите несколько выражений в одной строке.
21. Внутри функций, группируйте куски кода, отделяя их не более, чем одной пустой строкой.
22. Функции, классы, и т. п. отделяются друг от друга минимум одной, максимум двумя пустыми строками.
23. const (относящийся к значению) пишется до имени типа.

B
BayoNet 已提交
115 116 117 118 119 120 121
    ```
    //correct
    const char * pos
    const std::string & s
    //incorrect
    char const * pos
    ```
122 123
24. При объявлении указателя или ссылки, символы \* и & отделяются пробелами с обеих сторон.

B
BayoNet 已提交
124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143
    ```
    //correct
    const char * pos
    //incorrect
    const char* pos
    const char *pos
    ```
25. При использовании шаблонных типов, пишите `using` (кроме, возможно, простейших случаев).

    То есть, параметры шаблона указываются только в `using` и затем не повторяются в коде.
    
    `using` может быть объявлен локально, например, внутри функции.

    ```
    //correct
    using FileStreams = std::map<std::string, std::shared_ptr<Stream>>;
    FileStreams streams;
    //incorrect
    std::map<std::string, std::shared_ptr<Stream>> streams;
    ```
144 145
26. Нельзя объявлять несколько переменных разных типов в одном объявлении.

B
BayoNet 已提交
146 147 148 149
    ```
    //incorrect
    int x, *y;
    ```
150 151
27. C-style cast не используется.

B
BayoNet 已提交
152 153 154 155 156 157
    ```cpp
    //incorrect
    std::cerr << (int)c <<; std::endl;
    //correct
    std::cerr << static_cast<int>(c) << std::endl;
    ```
158
28. В классах и структурах, группируйте отдельно методы и отдельно члены, внутри каждой области видимости.
B
BayoNet 已提交
159
29. Для не очень большого класса/структуры, можно не отделять объявления методов от реализации.
160

B
BayoNet 已提交
161 162 163
    Аналогично для маленьких методов в любых классах/структурах.
    
    Для шаблонных классов/структур, лучше не отделять объявления методов от реализации (так как иначе они всё равно должны быть определены в той же единице трансляции).
164 165 166
30. Не обязательно умещать код по ширине в 80 символов. Можно в 140.
31. Всегда используйте префиксный инкремент/декремент, если постфиксный не нужен.

B
BayoNet 已提交
167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299
    ```cpp
    for (Names::const_iterator it = column_names.begin(); it != column_names.end(); ++it)
    ```


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

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

    Это очень важно. При написании комментария, можно успеть понять, что код не нужен вообще, или что всё сделано неверно.

    ```cpp
    /** Part of piece of memory, that can be used.
      * For example, if internal_buffer is 1MB, and there was only 10 bytes loaded to buffer from file for reading,
      * then working_buffer will have size of only 10 bytes
      * (working_buffer.end() will point to position right after those 10 bytes available for read).
      */
    ```
2. Комментарии могут быть сколь угодно подробными.
3. Комментарии пишутся до соответствующего кода. В редких случаях - после, на той же строке.

    ```cpp
    /** Parses and executes the query.
    */
    void executeQuery(
        ReadBuffer & istr, /// Where to read the query from (and data for INSERT, if applicable)
        WriteBuffer & ostr, /// Where to write the result
        Context & context, /// DB, tables, data types, engines, functions, aggregate functions...
        BlockInputStreamPtr & query_plan, /// Here could be written the description on how query was executed
        QueryProcessingStage::Enum stage = QueryProcessingStage::Complete /// Up to which stage process the SELECT query
        )
    ```
4. Комментарии следует писать только на английском языке.
5. При написании библиотеки, разместите подробный комментарий о том, что это такое, в самом главном заголовочном файле.
6. Нельзя писать комментарии, которые не дают дополнительной информации. В частности, нельзя писать пустые комментарии вроде этого:

    ```cpp
    /*
    * Procedure Name:
    * Original procedure name:
    * Author:
    * Date of creation:
    * Dates of modification:
    * Modification authors:
    * Original file name:
    * Purpose:
    * Intent:
    * Designation:
    * Classes used:
    * Constants:
    * Local variables:
    * Parameters:
    * Date of creation:
    * Purpose:
    */
    ```

    (пример взят с ресурса [http://home.tamk.fi/~jaalto/course/coding-style/doc/unmaintainable-code/](http://home.tamk.fi/~jaalto/course/coding-style/doc/unmaintainable-code/)
7. Нельзя писать мусорные комментарии (автор, дата создания...) в начале каждого файла.
8. Однострочные комментарии начинаются с трёх слешей: `///` , многострочные с `/**`. Такие комментарии считаются «документирующими».

    Замечание: такие комментарии могут использоваться для генерации документации с помощью Doxygen. Но, фактически, Doxygen не используется, так как для навигации по коду гораздо удобне использовать возможности IDE.
9. В начале и конце многострочного комментария, не должно быть пустых строк (кроме строки, на которой закрывается многострочный комментарий).
10. Для закомментированных кусков кода, используются обычные, не "документирующие" комментарии.
1. Удаляйте закомментированные куски кода перед коммитом.
11. Не нужно писать нецензурную брань в комментариях или коде.
12. Не пишите прописными буквами. Не используйте излишнее количество знаков препинания.

    ```cpp
    /// WHAT THE FAIL???
    ```
13. Не составляйте из комментариев строки-разделители.

    ```
    ///******************************************************
    ```
14. Не нужно писать в комментарии диалог (лучше сказать устно).

    ```
    /// Зачем ты сделал эту фигню?
    ```
15. Не нужно писать комментарий в конце блока о том, что представлял собой этот блок.

    ```
    /// for
    ```

## Имена

1. Имена переменных и членов класса - маленькими буквами с подчёркиванием.

    ```cpp
    size_t max_block_size;
    ```
2. Имена функций (методов) - camelCase с маленькой буквы.

    ```cpp
    std::string getName() const override { return "Memory"; }
    ```
3. Имена классов (структур) - CamelCase с большой буквы. Префиксы кроме I для интерфейсов - не используются.

    ```cpp
    class StorageMemory : public IStorage
    ```
4. Имена using-ов - также, как классов, либо можно добавить _t на конце.
5. Имена типов - параметров шаблонов: в простых случаях - T; T, U; T1, T2.

    В более сложных случаях - либо также, как имена классов, либо можно добавить в начало букву T.

    ```cpp
    template <typename TKey, typename TValue>
    struct AggregatedStatElement
    ```
6. Имена констант - параметров шаблонов: либо также, как имена переменных, либо N - в простом случае.

    ```cpp
    template <bool without_www>
    struct ExtractDomain
    ```

7. Для абстрактных классов (интерфейсов) можно добавить в начало имени букву I.

    ```cpp
    class IBlockInputStream
    ```

8. Если переменная используется достаточно локально, то можно использовать короткое имя.

    В остальных случаях - используйте достаточно подробное имя, описывающее смысл.

    ```cpp
    bool info_successfully_loaded = false;
    ```
300 301
9.  define-ы - ALL_CAPS с подчёркиванием. Глобальные константы - тоже.

B
BayoNet 已提交
302 303 304 305
    ```cpp
    #define MAX_SRC_TABLE_NAMES_TO_STORE 1000
    ```
10. Имена файлов с кодом называйте по стилю соответственно тому, что в них находится.
306

B
BayoNet 已提交
307
    Если в файле находится один класс - назовите файл, как класс - в CamelCase.
308

B
BayoNet 已提交
309 310 311 312
    Если в файле находится одна функция - назовите файл, как функцию - в camelCase.
11. Если имя содержит сокращение, то:
    - для имён переменных, всё сокращение пишется маленькими буквами `mysql_connection` (не `mySQL_connection`).
    - для имён классов и функций, сохраняются большие буквы в сокращении `MySQLConnection` (не `MySqlConnection`).
313 314
12. Параметры конструктора, использующиеся сразу же для инициализации соответствующих членов класса, следует назвать также, как и члены класса, добавив подчёркивание в конец.

B
BayoNet 已提交
315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334
    ```cpp
    FileQueueProcessor(
        const std::string & path_,
        const std::string & prefix_,
        std::shared_ptr<FileHandler> handler_)
        : path(path_),
        prefix(prefix_),
        handler(handler_),
        log(&Logger::get("FileQueueProcessor"))
    {
    }
    ```

    Также можно называть параметры конструктора так же, как и члены класса (не добавлять подчёркивание), но только если этот параметр не используется в теле конструктора.

13. Именование локальных переменных и членов класса никак не отличается (никакие префиксы не нужны).

    ```
    timer (не m_timer)
    ```
335 336
14. Константы в enum-е - CamelCase с большой буквы. Также допустимо ALL_CAPS. Если enum не локален, то используйте enum class.

B
BayoNet 已提交
337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605
    ```cpp
    enum class CompressionMethod
    {
        QuickLZ = 0,
        LZ4     = 1,
    };
    ```
15. Все имена - по английски. Транслит с русского использовать нельзя.
    
    ```
    не Stroka
    ```
16. Сокращения (из нескольких букв разных слов) в именах можно использовать только если они являются общепринятыми (если для сокращения можно найти расшифровку в английской википедии или сделав поисковый запрос).

    `AST`, `SQL`.

    Не `NVDH` (что-то неведомое)
        
    Сокращения в виде обрезанного слова можно использовать, только если такое сокращение является широко используемым.
        
    Впрочем, сокращения также можно использовать, если расшифровка находится рядом в комментарии.
17. Имена файлов с исходниками на C++ должны иметь расширение только .cpp. Заголовочные файлы - только .h.

## Как писать код

1. Управление памятью.

    Ручное освобождение памяти (delete) можно использовать только в библиотечном коде.

    В свою очередь, в библиотечном коде, оператор delete можно использовать только в деструкторах.

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

    Примеры:
    
    -   проще всего разместить объект на стеке, или сделать его членом другого класса.
    -   для большого количества маленьких объектов используйте контейнеры.
    -   для автоматического освобождения маленького количества объектов, выделенных на куче, используйте shared_ptr/unique_ptr.
2. Управление ресурсами.

    Используйте RAII и см. пункт выше.

3. Обработка ошибок.

    Используйте исключения. В большинстве случаев, нужно только кидать исключения, а ловить - не нужно (потому что RAII).
    
    В программах offline обработки данных, зачастую, можно не ловить исключения.
    
    В серверах, обрабатывающих пользовательские запросы, как правило, достаточно ловить исключения на самом верху обработчика соединения.
    
    В функциях потока, следует ловить и запоминать все исключения, чтобы выкинуть их в основном потоке после join.

    ```cpp
    /// Если вычислений ещё не было - вычислим первый блок синхронно
    if (!started)
    {
        calculate();
        started = true;
    }
    else    /// Если вычисления уже идут - подождём результата
        pool.wait();

    if (exception)
        exception->rethrow();
    ```

    Ни в коем случае не «проглатывайте» исключения без разбора. Ни в коем случае, не превращайте все исключения без разбора в сообщения в логе.
    
    Не `catch (...) {}`.
    
    Если вам нужно проигнорировать какие-то исключения, то игнорируйте только конкретные, а остальные - кидайте обратно.

    ```cpp
    catch (const DB::Exception & e)
    {
        if (e.code() == ErrorCodes::UNKNOWN_AGGREGATE_FUNCTION)
            return nullptr;
        else
            throw;
    }
    ```

    При использовании функций, использующих коды возврата или errno - проверяйте результат и кидайте исключение.

    ```cpp
    if (0 != close(fd))
        throwFromErrno("Cannot close file " + file_name, ErrorCodes::CANNOT_CLOSE_FILE);
    ```

    assert-ы не используются.

4. Типы исключений.

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

5. Исключения, вылетающие из деструкторов.
    Использовать не рекомендуется, но допустимо.
    
    Используйте следующие варианты:
    
    -   Сделайте функцию (done() или finalize()), которая позволяет заранее выполнить всю работу, в процессе которой может возникнуть исключение. Если эта функция была вызвана, то затем в деструкторе не должно возникать исключений.
    -   Слишком сложную работу (например, отправку данных по сети) можно вообще не делать в деструкторе, рассчитывая, что пользователь заранее позовёт метод для завершения работы.
    -   Если в деструкторе возникло исключение, желательно не "проглатывать" его, а вывести информацию в лог (если в этом месте доступен логгер).
    -   В простых программах, если соответствующие исключения не ловятся, и приводят к завершению работы с записью информации в лог, можно не беспокоиться об исключениях, вылетающих из деструкторов, так как вызов std::terminate (в случае noexcept по умолчанию в C++11), является приемлимым способом обработки исключения.

6. Отдельные блоки кода.

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

    ```cpp
    Block block = data.in->read();

    {
        std::lock_guard<std::mutex> lock(mutex);
        data.ready = true;
        data.block = block;
    }

    ready_any.set();
    ```
7. Многопоточность.

    В программах offline обработки данных:
    - cначала добейтесь более-менее максимальной производительности на одном процессорном ядре, потом можно распараллеливать код, но только если есть необходимость.
        
    В программах - серверах:
    - используйте пул потоков для обработки запросов. На данный момент, у нас не было задач, в которых была бы необходимость использовать userspace context switching.
        
    Fork для распараллеливания не используется.
8. Синхронизация потоков.

    Часто можно сделать так, чтобы отдельные потоки писали данные в разные ячейки памяти (лучше - в разные кэш-линии), и не использовать синхронизацию потоков (кроме joinAll).
    
    Если синхронизация нужна, то в большинстве случаев, достаточно использовать mutex под lock_guard-ом.
    
    В остальных случаях, используйте системные примитивы синхронизации. Не используйте busy wait.
    
    Атомарные операции можно использовать только в простейших случаях.
    
    Не нужно писать самостоятельно lock-free структуры данных, если вы не являетесь экспертом.
9. Ссылки и указатели.

    В большинстве случаев, предпочитайте ссылки.
10. const.

    Используйте константные ссылки, указатели на константу, const_iterator, константные методы.
    
    Считайте, что const - вариант написания «по умолчанию», а отсутствие const - только при необходимости.
    
    Для переменных, передающихся по значению, использовать const обычно не имеет смысла.
11. unsigned.

    Используйте unsigned, если нужно.
12. Числовые типы.

    Используйте типы UInt8, UInt16, UInt32, UInt64, Int8, Int16, Int32, Int64, а также size_t, ssize_t, ptrdiff_t.
    
    Не используйте для чисел типы signed/unsigned long, long long, short; signed char, unsigned char, а также char.
13. Передача аргументов.

    Сложные значения передавайте по ссылке (включая std::string).
    
    Если функция захватывает владение объектом, созданным на куче, то сделайте типом аргумента shared_ptr или unique_ptr.
14. Возврат значений.

    В большинстве случаев, просто возвращайте значение с помощью return. Не пишите [return std::move(res)]{.strike}.
    
    Если внутри функции создаётся объект на куче и отдаётся наружу, то возвращайте shared_ptr или unique_ptr.
    
    В некоторых редких случаях, может потребоваться возвращать значение через аргумент функции. В этом случае, аргументом будет ссылка.

    ```cpp
    using AggregateFunctionPtr = std::shared_ptr<IAggregateFunction>;

    /** Позволяет создать агрегатную функцию по её имени.
      */
    class AggregateFunctionFactory
    {
    public:
        AggregateFunctionFactory();
        AggregateFunctionPtr get(const String & name, const DataTypes & argument_types) const;
    ```
15. namespace.

    Для прикладного кода отдельный namespace использовать не нужно.
    
    Для маленьких библиотек - не требуется.
    
    Для не совсем маленьких библиотек - поместите всё в namespace.
    
    Внутри библиотеки в .h файле можно использовать namespace detail для деталей реализации, не нужных прикладному коду.
    
    В .cpp файле можно использовать static или анонимный namespace для скрытия символов.
    
    Также, namespace можно использовать для enum, чтобы соответствующие имена не попали во внешний namespace (но лучше использовать enum class).

16. Отложенная инициализация.

    Обычно, если для инициализации требуются аргументы, то не пишите конструктор по умопчанию.
    
    Если потом вам потребовалась отложенная инициализация, то вы можете дописать конструктор по умолчанию (который создаст объект с некорректным состоянием). Или, для небольшого количества объектов, можно использовать shared_ptr/unique_ptr.

    ```cpp
    Loader(DB::Connection * connection_, const std::string & query, size_t max_block_size_);

    /// Для отложенной инициализации
    Loader() {}
    ```
17. Виртуальные функции.

    Если класс не предназначен для полиморфного использования, то не нужно делать функции виртуальными зря. Это относится и к деструктору.
18. Кодировки.

    Везде используется UTF-8. Используется `std::string`, `char *`. Не используется `std::wstring`, `wchar_t`.
19. Логгирование.

    См. примеры везде в коде.
    
    Перед коммитом, удалите всё бессмысленное и отладочное логгирование, и другие виды отладочного вывода.
    
    Не должно быть логгирования на каждую итерацию внутреннего цикла, даже уровня Trace.
    
    При любом уровне логгирования, логи должно быть возможно читать.
        
    Логгирование следует использовать, в основном, только в прикладном коде.
    
    Сообщения в логе должны быть написаны на английском языке.
    
    Желательно, чтобы лог был понятен системному администратору.
    
    Не нужно писать ругательства в лог.
    
    В логе используется кодировка UTF-8. Изредка можно использовать в логе не-ASCII символы.
20. Ввод-вывод.

    Во внутренних циклах (в критичных по производительности участках программы) нельзя использовать iostreams (в том числе, ни в коем случае не используйте stringstream).
    
    Вместо этого используйте библиотеку DB/IO.

21. Дата и время.

    См. библиотеку DateLUT.

22. include.

    В заголовочном файле используется только `#pragma once`, а include guard-ы писать не нужно.

23. using.

    using namespace не используется.
    
    using что-то конкретное - можно. Лучше локально - внутри класса или функции.

24. Не нужно использовать trailing return type для функций, если в этом нет необходимости.

    [auto f() -&gt; void;]{.strike}

25. Не нужно объявлять и инициализировать переменные так:

    ```cpp
    auto s = std::string{"Hello"};
    ```
    
    Надо так:
    
    ```cpp
    std::string s = "Hello";
    std::string s{"Hello"};
    ```
606 607
26. Для виртуальных функций, пишите virtual в базовом классе, а в классах-наследниках, пишите override и не пишите virtual.

B
BayoNet 已提交
608
## Неиспользуемые возможности языка C++
609

B
BayoNet 已提交
610 611 612
1. Виртуальное наследование не используется.
2. Спецификаторы исключений из C++03 не используются.
3. Function try block не используется, за исключением функции main в тестах.
613

B
BayoNet 已提交
614
## Платформа
615

B
BayoNet 已提交
616
1. Мы пишем некроссплатформенный код (под конкретную платформу).
617

B
BayoNet 已提交
618
   Хотя, при прочих равных условиях, предпочитается более-менее кроссплатформенный или легко портируемый код.
619

B
BayoNet 已提交
620
2. Язык - C++17. Возможно использование расширений GNU при необходимости.
621

B
BayoNet 已提交
622
3. Компилятор - gcc. На данный момент (апрель 2017), код собирается версией 6.3. (Также код может быть собран clang 4)
623

B
BayoNet 已提交
624
    Используется стандартная библиотека от gcc.
625

B
BayoNet 已提交
626
4. ОС - Linux Ubuntu, не более старая, чем Precise.
627

B
BayoNet 已提交
628
5. Код пишется под процессор с архитектурой x86_64.
629

B
BayoNet 已提交
630
    Набор инструкций - минимальный поддерживаемый среди наших серверов. Сейчас это - SSE4.2.
631

B
BayoNet 已提交
632
6. Используются флаги компиляции `-Wall -Werror`.
633

B
BayoNet 已提交
634
7. Используется статическая линковка со всеми библиотеками кроме тех, которые трудно подключить статически (см. вывод команды ldd).
635

B
BayoNet 已提交
636
8. Код разрабатывается и отлаживается с релизными параметрами сборки.
637

B
BayoNet 已提交
638
## Инструментарий
639

B
BayoNet 已提交
640 641 642 643 644 645 646
1. Хорошая среда разработки - KDevelop.
2. Для отладки используется gdb, valgrind (memcheck), strace, -fsanitize=..., tcmalloc_minimal_debug.
3. Для профилирования используется Linux Perf, valgrind (callgrind), strace -cf.
4. Исходники в Git.
5. Сборка с помощью CMake.
6. Программы выкладываются с помощью deb пакетов.
7. Коммиты в master не должны ломать сборку проекта.
647

B
BayoNet 已提交
648 649
   А работоспособность собранных программ гарантируется только для отдельных ревизий.
8. Коммитьте как можно чаще, в том числе и не рабочий код.
650

B
BayoNet 已提交
651 652 653 654
    Для этого следует использовать бранчи.
    
    Если ваш код в master-е ещё не собирается, перед push-ем - исключите его из сборки, также вы будете должны его доработать или удалить в течение нескольких дней.
9. Для нетривиальных изменений, используются бранчи. Следует загружать бранчи на сервер.
655 656
10. Ненужный код удаляется из исходников.

B
BayoNet 已提交
657
## Библиотеки
658

B
BayoNet 已提交
659 660
1. Используются стандартная библиотека C++14 (допустимо использовать experimental расширения) а также фреймворки boost, Poco.
2. При необходимости, можно использовать любые известные библиотеки, доступные в ОС из пакетов.
661

B
BayoNet 已提交
662
    Если есть хорошее готовое решение, то оно используется, даже если для этого придётся установить ещё одну библиотеку.
663

B
BayoNet 已提交
664
    (Но будьте готовы к тому, что иногда вам придётся выкидывать плохие библиотеки из кода.)
665

B
BayoNet 已提交
666 667 668
3. Если в пакетах нет нужной библиотеки, или её версия достаточно старая, или если она собрана не так, как нужно, то можно использовать библиотеку, устанавливаемую не из пакетов.
4. Если библиотека достаточно маленькая и у неё нет своей системы сборки, то следует включить её файлы в проект, в директорию contrib.
5. Предпочтение всегда отдаётся уже использующимся библиотекам.
669

B
BayoNet 已提交
670
## Общее
671

B
BayoNet 已提交
672 673 674 675 676 677
1. Пишите как можно меньше кода.
2. Пробуйте самое простое решение.
3. Не нужно писать код, если вы ещё не знаете, что будет делать ваша программа, и как будет работать её внутренний цикл.
4. В простейших случаях, используйте using вместо классов/структур.
5. Если есть возможность - не пишите конструкторы копирования, операторы присваивания, деструктор (кроме виртуального, если класс содержит хотя бы одну виртуальную функцию), move-конструкторы и move-присваивания. То есть, чтобы соответствущие функции, генерируемые компилятором, работали правильно. Можно использовать default.
6. Приветствуется упрощение и уменьшение объёма кода.
678

B
BayoNet 已提交
679
## Дополнительно
680

B
BayoNet 已提交
681
1. Явное указание std:: для типов из stddef.h.
682

B
BayoNet 已提交
683 684 685
    Рекомендуется не указывать. То есть, рекомендуется писать size_t вместо std::size_t - потому что это короче.
    
    Но при желании, вы можете всё-таки приписать std:: - такой вариант тоже допустим.
686

B
BayoNet 已提交
687
2. Явное указание std:: для функций из стандартной библиотеки C.
688

B
BayoNet 已提交
689 690 691 692 693 694 695 696
    Не рекомендуется. То есть, пишите memcpy вместо std::memcpy.
    
    Причина - существуют похожие нестандартные функции, например, memmem. Мы можем использовать и изредка используем эти функции. Эти функции отсутствуют в namespace std.
    
    Если вы везде напишете std::memcpy вместо memcpy, то будет неудобно смотреться memmem без std::.
    
    Тем не менее, указывать std:: тоже допустимо, если так больше нравится.
3. Использование функций из C при наличии аналогов в стандартной библиотеке C++.
697

B
BayoNet 已提交
698
    Допустимо, если это использование эффективнее.
699

B
BayoNet 已提交
700
    Для примера, для копирования длинных кусков памяти, используйте memcpy вместо std::copy.
701

B
BayoNet 已提交
702
4. Перенос длинных аргументов функций.
703

B
BayoNet 已提交
704
    Допустимо использовать любой стиль переноса, похожий на приведённые ниже:
705

B
BayoNet 已提交
706 707 708 709 710
    ```cpp
    function(
        T1 x1,
        T2 x2)
    ```
711

B
BayoNet 已提交
712 713 714 715 716 717
    ```cpp
    function(
        size_t left, size_t right,
        const & RangesInDataParts ranges,
        size_t limit)
    ```
718

B
BayoNet 已提交
719 720 721 722 723
    ```cpp
    function(size_t left, size_t right,
        const & RangesInDataParts ranges,
        size_t limit)
    ```
724

B
BayoNet 已提交
725 726
    ```cpp
    function(size_t left, size_t right,
727 728
            const & RangesInDataParts ranges,
            size_t limit)
B
BayoNet 已提交
729
    ```
730

B
BayoNet 已提交
731 732 733 734
    ```cpp
    function(
            size_t left,
            size_t right,
735 736
            const & RangesInDataParts ranges,
            size_t limit)
B
BayoNet 已提交
737 738
    ```