Перейти к основному содержанию

Общие рекомендации

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

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

1. Большая часть форматирования выполняется автоматически с помощью clang-format. 2. Отступ — 4 пробела. Настройте среду разработки так, чтобы нажатие Tab добавляло четыре пробела. 3. Открывающие и закрывающие фигурные скобки должны располагаться на отдельной строке.
4. Если всё тело функции представляет собой один оператор, его можно записать в одну строку. Ставьте пробелы вокруг фигурных скобок (кроме пробела в конце строки).
5. Для функций. Не ставьте пробелы вокруг скобок.
6. В выражениях if, for, while и других перед открывающей скобкой ставится пробел (в отличие от вызовов функций).
7. Добавляйте пробелы вокруг бинарных операторов (+, -, *, /, %, …) и тернарного оператора ?:.
8. Если введён символ перевода строки, перенесите оператор на новую строку и увеличьте отступ перед ним.
9. При желании можно использовать пробелы для выравнивания внутри строки.
10. Не используйте пробелы вокруг операторов ., ->. При необходимости оператор можно перенести на следующую строку. В этом случае отступ перед ним увеличивают. 11. Не используйте пробел для отделения унарных операторов (--, ++, *, &, …) от аргумента. 12. Ставьте пробел после запятой, но не перед ней. То же правило распространяется на точку с запятой внутри выражения for. 13. Не используйте пробелы при написании оператора []. 14. В выражении template <...> ставьте пробел между template и <; пробелы после < и перед > не ставятся.
15. В классах и структурах пишите public, private и protected на том же уровне, что и class/struct, а остальной код выравнивайте с отступом.
16. Если одно и то же namespace используется во всём файле и больше ничего значимого нет, смещение внутри namespace не требуется. 17. Если блок для if, for, while или другого выражения состоит из одного оператора, фигурные скобки необязательны. В таком случае поместите оператор на отдельную строку. Это правило также распространяется на вложенные if, for, while, … Но если внутренний оператор содержит фигурные скобки или else, внешний блок необходимо заключить в фигурные скобки.
18. В конце строк не должно быть пробелов. 19. Исходные файлы имеют кодировку UTF-8. 20. В строковых литералах допускается использование символов, не входящих в набор ASCII.
21. Не пишите несколько выражений в одной строке. 22. Группируйте части кода внутри функций и разделяйте их не более чем одной пустой строкой. 23. Разделяйте функции, классы и т. д. одной или двумя пустыми строками. 24. A const (относящийся к значению) должен записываться перед именем типа.
25. При объявлении указателя или ссылки символы * и & должны быть отделены пробелами с обеих сторон.
26. При использовании шаблонных типов задавайте для них псевдонимы с помощью ключевого слова using (кроме самых простых случаев). Иными словами, параметры шаблона указываются только в using и не дублируются в коде. using можно объявлять локально, например внутри функции.
27. Не объявляйте в одном операторе несколько переменных разных типов.
28. Не используйте приведение типов в стиле C.
29. В классах и struct группируйте члены и функции отдельно в пределах каждой области видимости. 30. В небольших классах и struct не обязательно отделять объявление метода от его реализации. То же самое верно и для небольших методов в любых классах или struct. Для шаблонных классов и struct не отделяйте объявления методов от реализации (иначе их придётся определять в той же единице трансляции). 31. Можно переносить строки по достижении 140 символов, а не 80. 32. Всегда используйте операторы префиксного инкремента/декремента, если постфиксная форма не требуется.

Комментарии

1. Обязательно добавляйте комментарии ко всем нетривиальным участкам кода. Это очень важно. Когда вы пишете комментарий, это может помочь вам понять, что код либо не нужен, либо спроектирован неправильно.
2. Комментарии могут быть настолько подробными, насколько это нужно. 3. Размещайте комментарии перед кодом, к которому они относятся. В редких случаях комментарий можно разместить после кода, в той же строке.
4. Комментарии следует писать только на английском языке. 5. Если вы пишете библиотеку, добавьте в основной файл заголовка подробные комментарии с её описанием. 6. Не добавляйте комментарии, не несущие дополнительной информации. В частности, не оставляйте пустые комментарии, подобные этому:
Этот пример взят с ресурса http://home.tamk.fi/~jaalto/course/coding-style/doc/unmaintainable-code/. 7. Не пишите бессмысленные комментарии (автор, дата создания ..) в начале каждого файла. 8. Однострочные комментарии начинаются с трёх косых черт: ///, а многострочные — с /**. Такие комментарии считаются “документацией”. Примечание: Для генерации документации из таких комментариев можно использовать Doxygen. Но обычно Doxygen не используют, потому что по коду удобнее перемещаться прямо в IDE. 9. В многострочных комментариях не должно быть пустых строк в начале и в конце (кроме строки, закрывающей многострочный комментарий). 10. Чтобы закомментировать код, используйте обычные комментарии, а не “документирующие”. 11. Перед коммитом удаляйте закомментированные фрагменты кода. 12. Не используйте нецензурную лексику в комментариях и коде. 13. Не используйте заглавные буквы. Не злоупотребляйте знаками препинания.
14. Не используйте комментарии как разделители.
15. Не разводите дискуссии в комментариях.
16. Не нужно писать в конце блока комментарий с пояснением, о чём он.

Имена

1. Используйте в именах переменных и членов класса строчные буквы и нижние подчёркивания.
2. Названия функций (методов) записывайте в camelCase, начиная со строчной буквы.
3. Для названий классов (структур) используйте CamelCase с заглавной буквы. Для интерфейсов префиксы, кроме I, не используются.
4. using именуются так же, как и классы. 5. Имена аргументов шаблонных типов: в простых случаях используйте T; T, U; T1, T2. В более сложных случаях либо следуйте правилам именования классов, либо добавляйте префикс T.
6. Имена константных аргументов шаблона: либо соответствуют правилам именования переменных, либо в простых случаях используется N.
7. Для абстрактных классов (интерфейсов) можно использовать префикс I.
8. Если переменная используется локально, можно использовать короткое имя. Во всех остальных случаях используйте имя, отражающее смысл.
9. Имена define и глобальных констант пишутся в стиле ALL_CAPS с символами подчеркивания.
10. Имена файлов должны оформляться в том же стиле, что и их содержимое. Если файл содержит только один класс, имя файла должно совпадать с именем класса (CamelCase). Если файл содержит только одну функцию, имя файла должно совпадать с именем функции (camelCase). 11. Если имя содержит аббревиатуру, то:
  • В именах переменных аббревиатура должна записываться строчными буквами: mysql_connection (не mySQL_connection).
  • В именах классов и функций сохраняйте заглавные буквы в аббревиатуре: MySQLConnection (не MySqlConnection).
12. Аргументы конструктора, которые используются только для инициализации полей класса, должны называться так же, как и поля класса, но с подчёркиванием в конце.
Суффикс в виде подчёркивания можно опустить, если аргумент не используется в теле конструктора. 13. Имена локальных переменных и членов класса не различаются (префиксы не требуются).
14. Для констант в enum используйте CamelCase с заглавной буквы. Также допустим стиль ALL_CAPS. Если enum не является локальным, используйте enum class.
15. Все имена должны быть на английском языке. Транслитерация слов с иврита не допускается. не T&#95;PAAMAYIM&#95;NEKUDOTAYIM 16. Сокращения допустимы, если они общеупотребительны (то есть их значение можно легко найти в Википедии или через поисковую систему). AST, SQL. Не NVDH (какой-то случайный набор букв) Усечённые слова допустимы, если сокращённый вариант широко используется. Также можно использовать сокращение, если рядом с ним в комментариях указано полное название. 17. Имена файлов с исходным кодом C++ должны иметь расширение .cpp. Файлы заголовков должны иметь расширение .h.

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

1. Управление памятью. Ручное освобождение памяти (delete) можно использовать только в библиотечном коде. В библиотечном коде оператор delete можно использовать только в деструкторах. В прикладном коде память должен освобождать объект, который ею владеет. Примеры:
  • Проще всего разместить объект на стеке или сделать его членом другого класса.
  • Для большого количества небольших объектов используйте контейнеры.
  • Для автоматического освобождения небольшого числа объектов, размещённых в куче, используйте shared_ptr/unique_ptr.
2. Управление ресурсами. Используйте RAII; см. выше. 3. Обработка ошибок. Используйте исключения. В большинстве случаев достаточно просто сгенерировать исключение, а перехватывать его не требуется (благодаря RAII). В приложениях для офлайн-обработки данных часто допустимо не перехватывать исключения. В серверах, обрабатывающих пользовательские запросы, обычно достаточно перехватывать исключения на верхнем уровне обработчика соединения. В функциях потоков следует перехватывать и сохранять все исключения, чтобы повторно выбросить их в главном потоке после join.
Никогда не скрывайте исключения, не обработав их. Никогда не пишите все исключения в лог без разбора.
Если вам нужно игнорировать некоторые исключения, делайте это только для конкретных случаев, а остальные исключения перебрасывайте.
При использовании функций с кодами ответа или errno всегда проверяйте результат и в случае ошибки сгенерируйте исключение.
Вы можете использовать assert для проверки инварианта в коде. 4. Типы исключений. Нет необходимости использовать сложную иерархию исключений в прикладном коде. Текст исключения должен быть понятен системному администратору. 5. Генерация исключений в деструкторах. Это не рекомендуется, но допускается. Используйте следующие варианты:
  • Создайте функцию (done() или finalize()), которая заранее выполнит всю работу, способную привести к исключению. Если эта функция была вызвана, позже в деструкторе исключений быть не должно.
  • Слишком сложные задачи (например, отправку сообщений по сети) можно вынести в отдельный метод, который пользователь класса должен будет вызвать до уничтожения объекта.
  • Если в деструкторе возникает исключение, его лучше записать в лог, чем скрывать (если доступен логгер).
  • В простых приложениях допустимо полагаться на std::terminate (для случаев с noexcept по умолчанию в C++11) для обработки исключений.
6. Анонимные блоки кода. Вы можете создать отдельный блок кода внутри одной функции, чтобы сделать некоторые переменные локальными — тогда деструкторы будут вызваны при выходе из блока.
7. Многопоточность. В программах для офлайн-обработки данных:
  • Старайтесь добиться максимально возможной производительности на одном ядре CPU. Затем при необходимости распараллельте код.
В серверных приложениях:
  • Используйте пул потоков для обработки запросов. Пока у нас не было задач, требующих переключения контекста в пространстве пользователя.
Fork не используют для распараллеливания. 8. Синхронизация потоков. Часто можно сделать так, чтобы разные потоки использовали разные ячейки памяти (а ещё лучше — разные линии кэша) и вообще не использовать синхронизацию потоков (кроме joinAll). Если синхронизация нужна, в большинстве случаев достаточно использовать mutex с lock_guard. В остальных случаях используйте системные примитивы синхронизации. Не используйте активное ожидание. Атомарные операции следует использовать только в самых простых случаях. Не пытайтесь реализовывать lock-free-структуры данных, если это не ваша основная область экспертизы. 9. Указатели и ссылки. В большинстве случаев предпочитайте ссылки. 10. const. Используйте константные ссылки, указатели на константы, const_iterator и const-методы. Считайте 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/unsigned char, char. 13. Передача аргументов. Передавайте сложные значения по значению, если затем они будут перемещены, и используйте std::move; передавайте по ссылке, если хотите обновлять значение в цикле. Если функция принимает владение объектом, созданным в куче, используйте для аргумента тип shared_ptr или unique_ptr. 14. Возвращаемые значения. В большинстве случаев просто используйте return. Не пишите return std::move(res). Если функция выделяет объект в куче и возвращает его, используйте shared_ptr или unique_ptr. В редких случаях (например, при обновлении значения в цикле) может потребоваться возвращать значение через аргумент. В этом случае аргумент должен быть ссылкой.
15. namespace. Нет необходимости использовать отдельный namespace для прикладного кода. Маленьким библиотекам это тоже не нужно. Для библиотек среднего и большого размера помещайте всё в namespace. В файле библиотеки .h можно использовать namespace detail, чтобы скрыть подробности реализации, не нужные прикладному коду. В файле .cpp можно использовать static или анонимный namespace, чтобы скрыть символы. Кроме того, namespace можно использовать для enum, чтобы соответствующие имена не попадали во внешнее namespace (но лучше использовать enum class). 16. Отложенная инициализация. Если для инициализации требуются аргументы, то обычно не следует писать конструктор по умолчанию. Если позже потребуется отложить инициализацию, можно добавить конструктор по умолчанию, который будет создавать некорректный объект. Или, если объектов немного, можно использовать shared_ptr/unique_ptr.
17. Виртуальные функции. Если класс не предназначен для полиморфного использования, делать функции виртуальными не нужно. Это относится и к деструктору. 18. Кодировки. Везде используйте UTF-8. Используйте std::string и char *. Не используйте std::wstring и wchar_t. 19. Логирование. См. примеры в коде. Перед commit удаляйте все бессмысленные и отладочные записи в журнале, а также любые другие виды отладочного вывода. Следует избегать логирования в циклах, даже на уровне Trace. Журнал должен оставаться читаемым при любом уровне логирования. Логирование по большей части следует использовать только в прикладном коде. Сообщения лога должны быть написаны на английском языке. Желательно, чтобы журнал был понятен системному администратору. Не используйте нецензурную лексику в журнале. Используйте в журнале кодировку UTF-8. В редких случаях в журнале можно использовать символы не из ASCII. 20. Ввод-вывод. Не используйте iostreams во внутренних циклах, критичных для производительности приложения (и никогда не используйте stringstream). Вместо этого используйте библиотеку DB/IO. 21. Дата и время. См. библиотеку DateLUT. 22. include. Всегда используйте #pragma once вместо include guards. 23. using. using namespace не используется. Можно использовать using для чего-то конкретного, но делайте это локально — внутри класса или функции. 24. Не используйте для функций trailing return type, если в этом нет необходимости.
25. Объявление и инициализация переменных.
26. Для виртуальных функций в базовом классе пишите virtual, а в производных классах вместо virtual используйте override.

Неиспользуемые возможности C++

1. Виртуальное наследование не используется. 2. Конструкции, для которых в современном C++ предусмотрен удобный синтаксический сахар, например:

Платформа

1. Мы пишем код для конкретной платформы. Но при прочих равных предпочтение отдаётся кроссплатформенному или переносимому коду. 2. Язык: C++20 (см. список доступных возможностей C++20). 3. Компилятор: clang. На момент написания (март 2025 года) код компилируется с помощью clang версии >= 19. Используется стандартная библиотека (libc++). 4. ОС: Ubuntu Linux, не старее Precise. 5. Код пишется для архитектуры CPU x86_64. Набор инструкций CPU — минимально поддерживаемый среди наших серверов. В настоящее время это SSE 4.2. 6. Используйте флаги компиляции -Wall -Wextra -Werror -Weverything с несколькими исключениями. 7. Используйте статическую компоновку со всеми библиотеками, кроме тех, которые трудно подключить статически (см. вывод команды ldd). 8. Код разрабатывается и отлаживается в release-конфигурации.

Инструменты

1. KDevelop — хорошая IDE. 2. Для отладки используйте gdb, valgrind (memcheck), strace, -fsanitize=... или tcmalloc_minimal_debug. 3. Для профилирования используйте Linux Perf, valgrind (callgrind) или strace -cf. 4. Исходники хранятся в Git. 5. Для сборки используется CMake. 6. Программы выпускаются в пакетах deb. 7. Коммиты в master не должны ломать сборку. При этом работоспособными считаются только отдельные ревизии. 8. Делайте коммиты как можно чаще, даже если код готов лишь частично. Используйте для этого ветки. Если ваш код в ветке master пока не собирается, исключите его из сборки перед push. Вам нужно будет завершить его или удалить в течение нескольких дней. 9. Для нетривиальных изменений используйте ветки и публикуйте их на сервере. 10. Неиспользуемый код удаляется из репозитория.

Библиотеки

1. Используются стандартная библиотека C++20 (допускаются экспериментальные расширения), а также boost и Poco. 2. Запрещено использовать библиотеки из пакетов операционной системы. Также запрещено использовать предустановленные библиотеки. Все библиотеки должны поставляться в виде исходного кода в каталоге contrib и собираться вместе с ClickHouse. Подробности см. в рекомендациях по добавлению новых сторонних библиотек. 3. Предпочтение всегда отдается библиотекам, которые уже используются.

Общие рекомендации

1. Пишите как можно меньше кода. 2. Старайтесь выбирать самое простое решение. 3. Не пишите код, пока не поймете, как он должен работать и как будет устроен внутренний цикл. 4. В самых простых случаях используйте using вместо классов или структур. 5. По возможности не пишите конструкторы копирования, операторы присваивания, деструкторы (кроме виртуального, если класс содержит хотя бы одну виртуальную функцию), конструкторы перемещения или операторы присваивания перемещением. Иными словами, функции, сгенерированные компилятором, должны работать корректно. Можно использовать default. 6. Упрощать код полезно. По возможности уменьшайте его объем.

Дополнительные рекомендации

1. Явно указывать std:: для типов из stddef.h не рекомендуется. Иными словами, рекомендуем писать size_t вместо std::size_t, потому что так короче. Добавлять std:: допустимо. 2. Явно указывать std:: для функций из стандартной библиотеки C не рекомендуется. Иными словами, пишите memcpy вместо std::memcpy. Причина в том, что существуют похожие нестандартные функции, например memmem. Мы действительно иногда используем такие функции. В namespace std их нет. Если везде писать std::memcpy вместо memcpy, то memmem без std:: будет выглядеть странно. Тем не менее, при желании вы можете использовать std::. 3. Использование функций из C, когда такие же доступны в стандартной библиотеке C++. Это допустимо, если так эффективнее. Например, используйте memcpy вместо std::copy для копирования больших фрагментов памяти. 4. Многострочные аргументы функции. Допустим любой из следующих стилей переноса:
Последнее изменение 3 июля 2026 г.