mirror of
https://github.com/ClickHouse/ClickHouse.git
synced 2024-11-17 21:24:28 +00:00
67c2e50331
* update presentations * CLICKHOUSE-2936: redirect from clickhouse.yandex.ru and clickhouse.yandex.com * update submodule * lost files * CLICKHOUSE-2981: prefer sphinx docs over original reference * CLICKHOUSE-2981: docs styles more similar to main website + add flags to switch language links * update presentations * Less confusing directory structure (docs -> doc/reference/) * Minify sphinx docs too * Website release script: fail fast + pass docker hash on deploy * Do not underline links in docs * shorter * cleanup docker images * tune nginx config * CLICKHOUSE-3043: get rid of habrastorage links * Lost translation * CLICKHOUSE-2936: temporary client-side redirect * behaves weird in test * put redirect back * CLICKHOUSE-3047: copy docs txts to public too * move to proper file * remove old pages to avoid confusion * Remove reference redirect warning for now * Refresh README.md * Yellow buttons in docs * Use svg flags instead of unicode ones in docs * fix test website instance * Put flags to separate files * wrong flag * Copy Yandex.Metrica introduction from main page to docs * Yet another home page structure change, couple new blocks (CLICKHOUSE-3045) * Update Contacts section * CLICKHOUSE-2849: more detailed legal information * CLICKHOUSE-2978 preparation - split by files * More changes in Contacts block * Tune texts on index page * update presentations * One more benchmark * Add usage sections to index page, adapted from slides * Get the roadmap started, based on slides from last ClickHouse Meetup * CLICKHOUSE-2977: some rendering tuning * Get rid of excessive section in the end of getting started * Make headers linkable * CLICKHOUSE-2981: links to editing reference - https://github.com/yandex/ClickHouse/issues/849 * CLICKHOUSE-2981: fix mobile styles in docs * Ban crawling of duplicating docs * Open some external links in new tab * Ban old docs too * Lots of trivial fixes in english docs * Lots of trivial fixes in russian docs * Remove getting started copies in markdown * Add Yandex.Webmaster * Fix some sphinx warnings * More warnings fixed in english docs * More sphinx warnings fixed * Add code-block:: text * More code-block:: text * These headers look not that well * Better switch between documentation languages * merge use_case.rst into ya_metrika_task.rst * Edit the agg_functions.rst texts * Add lost empty lines * Lost blank lines * Add new logo sizes * update presentations * Next step in migrating to new documentation * Fix all warnings in en reference * Fix all warnings in ru reference * Re-arrange existing reference * Move operation tips to main reference * Fix typos noticed by milovidov@ * Get rid of zookeeper.md * Looks like duplicate of tutorial.html * Fix some mess with html tags in tutorial * No idea why nobody noticed this before, but it was completely not clear whet to get the data * Match code block styling between main and tutorial pages (in favor of the latter) * Get rid of some copypaste in tutorial * Normalize header styles * Move example_datasets to sphinx * Move presentations submodule to website * Move and update README.md * No point in duplicating articles from habrahabr here * Move development-related docs as is for now * doc/reference/ -> docs/ (to match the URL on website) * Adapt links to match the previous commit * Adapt development docs to rst (still lacks translation and strikethrough support) * clean on release * blacklist presentations in gulp * strikethrough support in sphinx * just copy development folder for now * fix weird introduction in style article * Style guide translation (WIP) * Finish style guide translation to English * gulp clean separately * Update year in LICENSE * Initial CONTRIBUTING.md * Fix remaining links to old docs in tutorial * Some tutorial fixes * Typo * Another typo * Update list of authors from yandex-team accoding to git log
747 lines
44 KiB
ReStructuredText
747 lines
44 KiB
ReStructuredText
.. role:: strike
|
||
:class: strike
|
||
|
||
Как писать код на C++
|
||
=====================
|
||
|
||
Общее
|
||
-----
|
||
|
||
#. Этот текст носит рекомендательный характер.
|
||
#. Если вы редактируете код, то имеет смысл писать так, как уже написано.
|
||
#. Стиль нужен для единообразия. Единообразие нужно, чтобы было проще (удобнее) читать код. А также, чтобы было легче осуществлять поиск по коду.
|
||
#. Многие правила продиктованы не какими либо разумными соображениями, а сложившейся практикой.
|
||
|
||
Форматирование
|
||
--------------
|
||
|
||
#. Большую часть форматирования сделает автоматически ``clang-format``.
|
||
#. Отступы - 4 пробела. Настройте среду разработки так, чтобы таб добавлял четыре пробела.
|
||
#. Открывающая фигурная скобка на новой, отдельной строке. (Закрывающая - тоже.)
|
||
|
||
.. code-block:: cpp
|
||
|
||
inline void readBoolText(bool & x, ReadBuffer & buf)
|
||
{
|
||
char tmp = '0';
|
||
readChar(tmp, buf);
|
||
x = tmp != '0';
|
||
}
|
||
|
||
|
||
#. Но если всё тело функции достаточно короткое (один statement) - при желании, его можно целиком разместить на одной строке. При этом, вокруг фигурных скобок ставятся пробелы (кроме пробела на конце строки).
|
||
|
||
.. code-block:: cpp
|
||
|
||
inline size_t mask() const { return buf_size() - 1; }
|
||
inline size_t place(HashValue x) const { return x & mask(); }
|
||
|
||
|
||
#. Для функций, пробелы вокруг скобок не ставятся.
|
||
|
||
.. code-block:: cpp
|
||
|
||
void reinsert(const Value & x)
|
||
|
||
.. code-block:: cpp
|
||
|
||
memcpy(&buf[place_value], &x, sizeof(x));
|
||
|
||
|
||
#. При использовании выражений if, for, while, ... (в отличие от вызовов функций) перед открывающей скобкой ставится пробел.
|
||
|
||
.. code-block:: cpp
|
||
|
||
for (size_t i = 0; i < rows; i += storage.index_granularity)
|
||
|
||
#. Вокруг бинарных операторов (+, -, \*, /, %, ...), а также тернарного оператора ?: ставятся пробелы.
|
||
|
||
.. code-block:: 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');
|
||
|
||
|
||
#. Если ставится перенос строки, то оператор пишется на новой строке, и перед ним увеличивается отступ.
|
||
|
||
.. code-block:: cpp
|
||
|
||
if (elapsed_ns)
|
||
message << " ("
|
||
<< rows_read_on_server * 1000000000 / elapsed_ns << " rows/s., "
|
||
<< bytes_read_on_server * 1000.0 / elapsed_ns << " MB/s.) ";
|
||
|
||
#. Внутри строки можно, при желании, выполнять выравнивание с помощью пробелов.
|
||
|
||
.. code-block:: cpp
|
||
|
||
dst.ClickLogID = click.LogID;
|
||
dst.ClickEventID = click.EventID;
|
||
dst.ClickGoodEvent = click.GoodEvent;
|
||
|
||
|
||
#. Вокруг операторов ``.``, ``->`` не ставятся пробелы.
|
||
При необходимости, оператор может быть перенесён на новую строку. В этом случае, перед ним увеличивается отступ.
|
||
|
||
#. Унарные операторы (``--, ++, *, &``, ...) не отделяются от аргумента пробелом.
|
||
|
||
#. После запятой ставится пробел, а перед - нет. Аналогично для точки с запятой внутри выражения for.
|
||
|
||
#. Оператор ``[]`` не отделяется пробелами.
|
||
|
||
#. В выражении ``template <...>``, между ``template`` и ``<`` ставится пробел; после ``<`` и до ``>`` - не ставится.
|
||
|
||
.. code-block:: cpp
|
||
|
||
template <typename TKey, typename TValue>
|
||
struct AggregatedStatElement
|
||
|
||
|
||
#. В классах и структурах, public, private, protected пишется на том же уровне, что и class/struct, а все остальные внутренности - глубже.
|
||
|
||
.. code-block:: cpp
|
||
|
||
template <typename T, typename Ptr = std::shared_ptr<T>>
|
||
class MultiVersion
|
||
{
|
||
public:
|
||
/// Конкретная версия объекта для использования. shared_ptr определяет время жизни версии.
|
||
using Version = Ptr;
|
||
|
||
|
||
#. Если на весь файл один namespace и кроме него ничего существенного нет - то отступ внутри namespace не нужен.
|
||
|
||
#. Если блок для выражения if, for, while... состоит из одного statement-а, то фигурные скобки писать не обязательно. Вместо этого поместите statement на отдельную строку. Этим statement-ом также может быть вложенный if, for, while... Но если внутренний statement содержит фигурные скобки или else, то у внешнего блок следует писать в фигурных скобках.
|
||
|
||
.. code-block:: 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));
|
||
|
||
#. Не должно быть пробелов на концах строк.
|
||
|
||
#. Исходники в кодировке UTF-8.
|
||
|
||
#. В строковых литералах можно использовать не-ASCII.
|
||
|
||
.. code-block:: cpp
|
||
|
||
<< ", " << (timer.elapsed() / chunks_stats.hits) << " μsec/hit.";
|
||
|
||
|
||
#. Не пишите несколько выражений в одной строке.
|
||
|
||
#. Внутри функций, группируйте куски кода, отделяя их не более, чем одной пустой строкой.
|
||
|
||
#. Функции, классы, и т. п. отделяются друг от друга минимум одной, максимум двумя пустыми строками.
|
||
|
||
#. const (относящийся к значению) пишется до имени типа.
|
||
|
||
.. code-block:: cpp
|
||
|
||
const char * pos
|
||
|
||
.. code-block:: cpp
|
||
|
||
const std::string & s
|
||
|
||
:strike:`char const * pos`
|
||
|
||
#. При объявлении указателя или ссылки, символы \* и & отделяются пробелами с обеих сторон.
|
||
|
||
.. code-block:: cpp
|
||
|
||
const char * pos
|
||
|
||
:strike:`const char\* pos`
|
||
:strike:`const char \*pos`
|
||
|
||
#. При использовании шаблонных типов, пишите using (кроме, возможно, простейших случаев).
|
||
То есть, параметры шаблона указываются только в using-е и затем не повторяются в коде.
|
||
using может быть объявлен локально, например, внутри функции.
|
||
|
||
.. code-block:: cpp
|
||
|
||
using FileStreams = std::map<std::string, std::shared_ptr<Stream>>;
|
||
FileStreams streams;
|
||
|
||
:strike:`std::map<std::string, std::shared_ptr<Stream>> streams;`
|
||
|
||
#. Нельзя объявлять несколько переменных разных типов в одном объявлении.
|
||
|
||
:strike:`int x, *y;`
|
||
|
||
#. C-style cast не используется.
|
||
|
||
:strike:`std::cerr << (int)c << std::endl;`
|
||
|
||
.. code-block:: cpp
|
||
|
||
std::cerr << static_cast<int>(c) << std::endl;
|
||
|
||
|
||
#. В классах и структурах, группируйте отдельно методы и отдельно члены, внутри каждой области видимости.
|
||
|
||
#. Для не очень большого класса/структуры, можно не отделять объявления методов от реализации.
|
||
Аналогично для маленьких методов в любых классах/структурах.
|
||
Для шаблонных классов/структур, лучше не отделять объявления методов от реализации (так как иначе они всё равно должны быть определены в той же единице трансляции).
|
||
|
||
#. Не обязательно умещать код по ширине в 80 символов. Можно в 140.
|
||
|
||
#. Всегда используйте префиксный инкремент/декремент, если постфиксный не нужен.
|
||
|
||
.. code-block:: cpp
|
||
|
||
for (Names::const_iterator it = column_names.begin(); it != column_names.end(); ++it)
|
||
|
||
|
||
Комментарии
|
||
-----------
|
||
|
||
#. Необходимо обязательно писать комментарии во всех нетривиальных местах.
|
||
Это очень важно. При написании комментария, можно успеть понять, что код не нужен вообще, или что всё сделано неверно.
|
||
|
||
.. code-block:: 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).
|
||
*/
|
||
|
||
|
||
#. Комментарии могут быть сколь угодно подробными.
|
||
|
||
#. Комментарии пишутся до соответствующего кода. В редких случаях - после, на той же строке.
|
||
|
||
.. code-block:: text
|
||
|
||
/** 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
|
||
|
||
#. Комментарии следует писать только на английском языке.
|
||
|
||
#. При написании библиотеки, разместите подробный комментарий о том, что это такое, в самом главном заголовочном файле.
|
||
|
||
#. Нельзя писать комментарии, которые не дают дополнительной информации. В частности, *НЕЛЬЗЯ* писать пустые комментарии вроде этого:
|
||
|
||
.. code-block:: 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/)
|
||
|
||
#. Нельзя писать мусорные комментарии (автор, дата создания...) в начале каждого файла.
|
||
|
||
#. Однострочные комментарии начинаются с трёх слешей: ``///``, многострочные - с ``/**``. Такие комментарии считаются «документрующими».
|
||
Замечание: такие комментарии могут использоваться для генерации документации с помощью Doxygen. Но, фактически, Doxygen не используется, так как для навигации по коду гораздо удобне использовать возможности IDE.
|
||
|
||
#. В начале и конце многострочного комментария, не должно быть пустых строк (кроме строки, на которой закрывается многострочный комментарий).
|
||
|
||
#. Для закомментированных кусков кода, используются обычные, не «документирующие» комментарии.
|
||
Удаляйте закомментированные куски кода перед коммитом.
|
||
|
||
#. Не нужно писать нецензурную брань в комментариях или коде.
|
||
|
||
#. Не нужно писать в комментариях слишком много восклицательных знаков или знаков вопроса, или выделять слишком много слов большими буквами.
|
||
:strike:`/// WHAT THE FAIL???`
|
||
|
||
#. Не нужно составлять из комментариев строки-разделители.
|
||
:strike:`/*******************************************************/`
|
||
|
||
#. Не нужно писать в комментарии диалог (лучше сказать устно).
|
||
:strike:`/// Зачем ты сделал эту фигню?`
|
||
|
||
#. Не нужно писать комментарий в конце блока о том, что представлял собой этот блок.
|
||
:strike:`} /// for`
|
||
|
||
|
||
Имена
|
||
-----
|
||
|
||
#. Имена переменных и членов класса - маленькими буквами с подчёркиванием.
|
||
|
||
.. code-block:: cpp
|
||
|
||
size_t max_block_size;
|
||
|
||
#. Имена функций (методов) - camelCase с маленькой буквы.
|
||
|
||
.. code-block:: cpp
|
||
|
||
std::string getName() const override { return "Memory"; }
|
||
|
||
#. Имена классов (структур) - CamelCase с большой буквы. Префиксы кроме I для интерфейсов - не используются.
|
||
|
||
.. code-block:: cpp
|
||
|
||
class StorageMemory : public IStorage
|
||
|
||
|
||
#. Имена using-ов - также, как классов, либо можно добавить _t на конце.
|
||
|
||
#. Имена типов - параметров шаблонов: в простых случаях - T; T, U; T1, T2.
|
||
В более сложных случаях - либо также, как имена классов, либо можно добавить в начало букву T.
|
||
|
||
.. code-block:: cpp
|
||
|
||
template <typename TKey, typename TValue>
|
||
struct AggregatedStatElement
|
||
|
||
#. Имена констант - параметров шаблонов: либо также, как имена переменных, либо N - в простом случае.
|
||
|
||
.. code-block:: cpp
|
||
|
||
template <bool without_www>
|
||
struct ExtractDomain
|
||
|
||
#. Для абстрактных классов (интерфейсов) можно добавить в начало имени букву I.
|
||
|
||
.. code-block:: cpp
|
||
|
||
class IBlockInputStream
|
||
|
||
#. Если переменная используется достаточно локально, то можно использовать короткое имя.
|
||
В остальных случаях - используйте достаточно подробное имя, описывающее смысл.
|
||
|
||
.. code-block:: cpp
|
||
|
||
bool info_successfully_loaded = false;
|
||
|
||
|
||
#. define-ы - ALL_CAPS с подчёркиванием. Глобальные константы - тоже.
|
||
|
||
.. code-block:: cpp
|
||
|
||
#define MAX_SRC_TABLE_NAMES_TO_STORE 1000
|
||
|
||
#. Имена файлов с кодом называйте по стилю соответственно тому, что в них находится.
|
||
Если в файле находится один класс - назовите файл, как класс - в CamelCase.
|
||
Если в файле находится одна функция - назовите файл, как функцию - в camelCase.
|
||
|
||
#. Если имя содержит сокращение, то:
|
||
* для имён переменных, всё сокращение пишется маленькими буквами;
|
||
``mysql_connection``
|
||
:strike:`mySQL_connection`
|
||
|
||
* для имён классов и функций, сохраняются большие буквы в сокращении.
|
||
``MySQLConnection``
|
||
:strike:`MySqlConnection`
|
||
|
||
#. Параметры конструктора, использующиеся сразу же для инициализации соответствующих членов класса, следует назвать также, как и члены класса, добавив подчёркивание в конец.
|
||
|
||
.. code-block:: 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"))
|
||
{
|
||
}
|
||
|
||
Также можно называть параметры конструктора так же, как и члены класса (не добавлять подчёркивание), но только если этот параметр не используется в теле конструктора.
|
||
|
||
#. Именование локальных переменных и членов класса никак не отличается (никакие префиксы не нужны).
|
||
``timer``
|
||
:strike:`m_timer`
|
||
|
||
#. Константы в enum-е - CamelCase с большой буквы. Также допустимо ALL_CAPS. Если enum не локален, то используйте enum class.
|
||
|
||
.. code-block:: cpp
|
||
|
||
enum class CompressionMethod
|
||
{
|
||
QuickLZ = 0,
|
||
LZ4 = 1,
|
||
};
|
||
|
||
#. Все имена - по английски. Транслит с русского использовать нельзя.
|
||
:strike:`Stroka`
|
||
|
||
#. Сокращения (из нескольких букв разных слов) в именах можно использовать только если они являются общепринятыми (если для сокращения можно найти расшифровку в английской википедии или сделав поисковый запрос).
|
||
``AST`` ``SQL``
|
||
:strike:`NVDH (что-то неведомое)`
|
||
Сокращения в виде обрезанного слова можно использовать, только если такое сокращение является широко используемым.
|
||
Впрочем, сокращения также можно использовать, если расшифровка находится рядом в комментарии.
|
||
|
||
#. Имена файлов с исходниками на C++ должны иметь расширение только .cpp. Заголовочные файлы - только .h.
|
||
:strike:`.hpp` :strike:`.cc` :strike:`.C` :strike:`.inl`
|
||
Можно ``.inl.h``, но не :strike:`.h.inl:strike:`
|
||
|
||
|
||
Как писать код
|
||
--------------
|
||
|
||
#. Управление памятью.
|
||
Ручное освобождение памяти (delete) можно использовать только в библиотечном коде.
|
||
В свою очередь, в библиотечном коде, оператор delete можно использовать только в деструкторах.
|
||
В прикладном коде следует делать так, что память освобождается каким-либо объектом, который владеет ей.
|
||
Примеры:
|
||
* проще всего разместить объект на стеке, или сделать его членом другого класса.
|
||
* для большого количества маленьких объектов используйте контейнеры.
|
||
* для автоматического освобождения маленького количества объектов, выделенных на куче, используйте shared_ptr/unique_ptr.
|
||
|
||
#. Управление ресурсами.
|
||
Используйте RAII и см. пункт выше.
|
||
|
||
#. Обработка ошибок.
|
||
Используйте исключения. В большинстве случаев, нужно только кидать исключения, а ловить - не нужно (потому что RAII).
|
||
В программах offline обработки данных, зачастую, можно не ловить исключения.
|
||
В серверах, обрабатывающих пользовательские запросы, как правило, достаточно ловить исключения на самом верху обработчика соединения.
|
||
В функциях потока, следует ловить и запоминать все исключения, чтобы выкинуть их в основном потоке после join.
|
||
|
||
.. code-block:: cpp
|
||
|
||
/// Если вычислений ещё не было - вычислим первый блок синхронно
|
||
if (!started)
|
||
{
|
||
calculate();
|
||
started = true;
|
||
}
|
||
else /// Если вычисления уже идут - подождём результата
|
||
pool.wait();
|
||
|
||
if (exception)
|
||
exception->rethrow();
|
||
|
||
Ни в коем случае не «проглатывайте» исключения без разбора. Ни в коем случае, не превращайте все исключения без разбора в сообщения в логе.
|
||
:strike:`catch (...) {}`
|
||
Если вам нужно проигнорировать какие-то исключения, то игнорируйте только конкретные, а остальные - кидайте обратно.
|
||
|
||
.. code-block:: cpp
|
||
|
||
catch (const DB::Exception & e)
|
||
{
|
||
if (e.code() == ErrorCodes::UNKNOWN_AGGREGATE_FUNCTION)
|
||
return nullptr;
|
||
else
|
||
throw;
|
||
}
|
||
|
||
При использовании функций, использующих коды возврата или errno - проверяйте результат и кидайте исключение.
|
||
|
||
.. code-block:: cpp
|
||
|
||
if (0 != close(fd))
|
||
throwFromErrno("Cannot close file " + file_name, ErrorCodes::CANNOT_CLOSE_FILE);
|
||
|
||
assert-ы не используются.
|
||
|
||
#. Типы исключений.
|
||
В прикладном коде не требуется использовать сложную иерархию исключений. Желательно, чтобы текст исключения был понятен системному администратору.
|
||
|
||
#. Исключения, вылетающие из деструкторов.
|
||
Использовать не рекомендуется, но допустимо.
|
||
Используйте следующие варианты:
|
||
* Сделайте функцию (done() или finalize()), которая позволяет заранее выполнить всю работу, в процессе которой может возникнуть исключение. Если эта функция была вызвана, то затем в деструкторе не должно возникать исключений.
|
||
* Слишком сложную работу (например, отправку данных по сети) можно вообще не делать в деструкторе, рассчитывая, что пользователь заранее позовёт метод для завершения работы.
|
||
* Если в деструкторе возникло исключение, желательно не "проглатывать" его, а вывести информацию в лог (если в этом месте доступен логгер).
|
||
* В простых программах, если соответствующие исключения не ловятся, и приводят к завершению работы с записью информации в лог, можно не беспокоиться об исключениях, вылетающих из деструкторов, так как вызов std::terminate (в случае noexcept по умолчанию в C++11), является приемлимым способом обработки исключения.
|
||
|
||
#. Отдельные блоки кода.
|
||
Внутри одной функции, можно создать отдельный блок кода, для того, чтобы сделать некоторые переменные локальными в нём, и для того, чтобы соответствующие деструкторы были вызваны при выходе из блока.
|
||
|
||
.. code-block:: cpp
|
||
|
||
Block block = data.in->read();
|
||
|
||
{
|
||
std::lock_guard<std::mutex> lock(mutex);
|
||
data.ready = true;
|
||
data.block = block;
|
||
}
|
||
|
||
ready_any.set();
|
||
|
||
#. Многопоточность.
|
||
В программах offline обработки данных:
|
||
* cначала добейтесь более-менее максимальной производительности на одном процессорном ядре;
|
||
* потом можно распараллеливать код, но только если есть необходимость.
|
||
В программах - серверах:
|
||
* используйте пул потоков для обработки запросов;
|
||
* на данный момент, у нас не было задач, в которых была бы необходимость использовать userspace context switching.
|
||
Fork для распараллеливания не используется.
|
||
|
||
#. Синхронизация потоков.
|
||
Часто можно сделать так, чтобы отдельные потоки писали данные в разные ячейки памяти (лучше - в разные кэш-линии), и не использовать синхронизацию потоков (кроме joinAll).
|
||
Если синхронизация нужна, то в большинстве случаев, достаточно использовать mutex под lock_guard-ом.
|
||
В остальных случаях, используйте системные примитивы синхронизации. Не используйте busy wait.
|
||
Атомарные операции можно использовать только в простейших случаях.
|
||
Не нужно писать самостоятельно lock-free структуры данных, если вы не являетесь экспертом.
|
||
|
||
#. Ссылки и указатели.
|
||
В большинстве случаев, предпочитайте ссылки.
|
||
|
||
#. const.
|
||
Используйте константные ссылки, указатели на константу, const_iterator, константные методы.
|
||
Считайте, что const - вариант написания «по умолчанию», а отсутствие const - только при необходимости.
|
||
Для переменных, передающихся по значению, использовать const обычно не имеет смысла.
|
||
|
||
#. unsigned.
|
||
Используйте unsigned, если нужно.
|
||
|
||
#. Числовые типы.
|
||
Используйте типы 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.
|
||
|
||
#. Передача аргументов.
|
||
Сложные значения передавайте по ссылке (включая std::string).
|
||
Если функция захватывает владение объектом, созданным на куче, то сделайте типом аргумента shared_ptr или unique_ptr.
|
||
|
||
#. Возврат значений.
|
||
В большинстве случаев, просто возвращайте значение с помощью return. Не пишите :strike:`return std::move(res)`.
|
||
Если внутри функции создаётся объект на куче и отдаётся наружу, то возвращайте shared_ptr или unique_ptr.
|
||
В некоторых редких случаях, может потребоваться возвращать значение через аргумент функции. В этом случае, аргументом будет ссылка.
|
||
|
||
.. code-block:: cpp
|
||
|
||
using AggregateFunctionPtr = std::shared_ptr<IAggregateFunction>;
|
||
|
||
/** Позволяет создать агрегатную функцию по её имени.
|
||
*/
|
||
class AggregateFunctionFactory
|
||
{
|
||
public:
|
||
AggregateFunctionFactory();
|
||
AggregateFunctionPtr get(const String & name, const DataTypes & argument_types) const;
|
||
|
||
#. namespace.
|
||
Для прикладного кода отдельный namespace использовать не нужно.
|
||
Для маленьких библиотек - не требуется.
|
||
Для не совсем маленьких библиотек - поместите всё в namespace.
|
||
Внутри библиотеки в .h файле можно использовать namespace detail для деталей реализации, не нужных прикладному коду.
|
||
В .cpp файле можно использовать static или анонимный namespace для скрытия символов.
|
||
Также, namespace можно использовать для enum, чтобы соответствующие имена не попали во внешний namespace (но лучше использовать enum class).
|
||
|
||
#. Отложенная инициализация.
|
||
Обычно, если для инициализации требуются аргументы, то не пишите конструктор по умопчанию.
|
||
Если потом вам потребовалась отложенная инициализация, то вы можете дописать конструктор по умолчанию (который создаст объект с некорректным состоянием). Или, для небольшого количества объектов, можно использовать shared_ptr/unique_ptr.
|
||
|
||
.. code-block:: cpp
|
||
|
||
Loader(DB::Connection * connection_, const std::string & query, size_t max_block_size_);
|
||
|
||
/// Для отложенной инициализации
|
||
Loader() {}
|
||
|
||
#. Виртуальные функции.
|
||
Если класс не предназначен для полиморфного использования, то не нужно делать функции виртуальными зря. Это относится и к деструктору.
|
||
|
||
#. Кодировки.
|
||
Везде используется UTF-8. Используется ``std::string``, ``char *``. Не используется ``std::wstring``, ``wchar_t``.
|
||
|
||
#. Логгирование.
|
||
См. примеры везде в коде.
|
||
Перед коммитом, удалите всё бессмысленное и отладочное логгирование, и другие виды отладочного вывода.
|
||
Не должно быть логгирования на каждую итерацию внутреннего цикла, даже уровня Trace.
|
||
При любом уровне логгирования, логи должно быть возможно читать.
|
||
Логгирование следует использовать, в основном, только в прикладном коде.
|
||
Сообщения в логе должны быть написаны на английском языке.
|
||
Желательно, чтобы лог был понятен системному администратору.
|
||
Не нужно писать ругательства в лог.
|
||
В логе используется кодировка UTF-8. Изредка можно использовать в логе не-ASCII символы.
|
||
|
||
#. Ввод-вывод.
|
||
Во внутренних циклах (в критичных по производительности участках программы) нельзя использовать iostreams (в том числе, ни в коем случае не используйте stringstream).
|
||
Вместо этого используйте библиотеку DB/IO.
|
||
|
||
#. Дата и время.
|
||
См. библиотеку DateLUT.
|
||
|
||
#. include.
|
||
В заголовочном файле используется только ``#pragma once``, а include guard-ы писать не нужно.
|
||
|
||
#. using.
|
||
using namespace не используется.
|
||
using что-то конкретное - можно. Лучше локально - внутри класса или функции.
|
||
|
||
#. Не нужно использовать trailing return type для функций, если в этом нет необходимости.
|
||
:strike:`auto f() -> void;`
|
||
|
||
#. Не нужно объявлять и инициализировать переменные так:
|
||
:strike:`auto s = std::string{"Hello"};`
|
||
Надо так:
|
||
``std::string s = "Hello";``
|
||
``std::string s{"Hello"};``
|
||
|
||
#. Для виртуальных функций, пишите virtual в базовом классе, а в классах-наследниках, пишите override и не пишите virtual.
|
||
|
||
|
||
Неиспользуемые возможности языка C++
|
||
------------------------------------
|
||
|
||
#. Виртуальное наследование не используется.
|
||
|
||
#. Спецификаторы исключений из C++03 не используются.
|
||
|
||
#. Function try block не используется, за исключением функции main в тестах.
|
||
|
||
|
||
Платформа
|
||
---------
|
||
|
||
#. Мы пишем некроссплатформенный код (под конкретную платформу).
|
||
Хотя, при прочих равных условиях, предпочитается более-менее кроссплатформенный или легко портируемый код.
|
||
|
||
#. Язык - C++17. Возможно использование расширений GNU при необходимости.
|
||
|
||
#. Компилятор - gcc. На данный момент (апрель 2017), код собирается версией 6.3. (Также код может быть собран clang 4)
|
||
Используется стандартная библиотека от gcc.
|
||
|
||
#. ОС - Linux Ubuntu, не более старая, чем Precise.
|
||
|
||
#. Код пишется под процессор с архитектурой x86_64.
|
||
Набор инструкций - минимальный поддерживаемый среди наших серверов. Сейчас это - SSE4.2.
|
||
|
||
#. Используются флаги компиляции ``-Wall -Werror``.
|
||
|
||
#. Используется статическая линковка со всеми библиотеками кроме тех, которые трудно подключить статически (см. вывод команды ldd).
|
||
|
||
#. Код разрабатывается и отлаживается с релизными параметрами сборки.
|
||
|
||
|
||
Инструментарий
|
||
--------------
|
||
|
||
#. Хорошая среда разработки - KDevelop.
|
||
|
||
#. Для отладки используется gdb, valgrind (memcheck), strace, -fsanitize=..., tcmalloc_minimal_debug.
|
||
|
||
#. Для профилирования используется Linux Perf, valgrind (callgrind), strace -cf.
|
||
|
||
#. Исходники в Git.
|
||
|
||
#. Сборка с помощью CMake.
|
||
|
||
#. Программы выкладываются с помощью deb пакетов.
|
||
|
||
#. Коммиты в master не должны ломать сборку проекта.
|
||
А работоспособность собранных программ гарантируется только для отдельных ревизий.
|
||
|
||
#. Коммитьте как можно чаще, в том числе и не рабочий код.
|
||
Для этого следует использовать бранчи.
|
||
Если ваш код в master-е ещё не собирается, перед push-ем - исключите его из сборки;
|
||
также вы будете должны его доработать или удалить в течение нескольких дней.
|
||
|
||
#. Для нетривиальных изменений, используются бранчи. Следует загружать бранчи на сервер.
|
||
|
||
#. Ненужный код удаляется из исходников.
|
||
|
||
|
||
Библиотеки
|
||
----------
|
||
|
||
#. Используются стандартная библиотека C++14 (допустимо использовать experimental расширения) а также фреймворки boost, Poco.
|
||
|
||
#. При необходимости, можно использовать любые известные библиотеки, доступные в ОС из пакетов.
|
||
Если есть хорошее готовое решение, то оно используется, даже если для этого придётся установить ещё одну библиотеку.
|
||
(Но будьте готовы к тому, что иногда вам придётся выкидывать плохие библиотеки из кода.)
|
||
|
||
#. Если в пакетах нет нужной библиотеки, или её версия достаточно старая, или если она собрана не так, как нужно, то можно использовать библиотеку, устанавливаемую не из пакетов.
|
||
|
||
#. Если библиотека достаточно маленькая и у неё нет своей системы сборки, то следует включить её файлы в проект, в директорию contrib.
|
||
|
||
#. Предпочтение всегда отдаётся уже использующимся библиотекам.
|
||
|
||
|
||
Общее
|
||
-----
|
||
|
||
#. Пишите как можно меньше кода.
|
||
|
||
#. Пробуйте самое простое решение.
|
||
|
||
#. Не нужно писать код, если вы ещё не знаете, что будет делать ваша программа, и как будет работать её внутренний цикл.
|
||
|
||
#. В простейших случаях, используйте using вместо классов/структур.
|
||
|
||
#. Если есть возможность - не пишите конструкторы копирования, операторы присваивания, деструктор (кроме виртуального, если класс содержит хотя бы одну виртуальную функцию), move-конструкторы и move-присваивания. То есть, чтобы соответствущие функции, генерируемые компилятором, работали правильно. Можно использовать default.
|
||
|
||
#. Приветствуется упрощение и уменьшение объёма кода.
|
||
|
||
|
||
Дополнительно
|
||
-------------
|
||
|
||
#. Явное указание std:: для типов из stddef.h.
|
||
Рекомендуется не указывать. То есть, рекомендуется писать size_t вместо std::size_t - потому что это короче.
|
||
Но при желании, вы можете всё-таки приписать std:: - такой вариант тоже допустим.
|
||
|
||
#. Явное указание std:: для функций из стандартной библиотеки C.
|
||
Не рекомендуется. То есть, пишите memcpy вместо std::memcpy.
|
||
Причина - существуют похожие нестандартные функции, например, memmem. Мы можем использовать и изредка используем эти функции. Эти функции отсутствуют в namespace std.
|
||
Если вы везде напишете std::memcpy вместо memcpy, то будет неудобно смотреться memmem без std::.
|
||
Тем не менее, указывать std:: тоже допустимо, если так больше нравится.
|
||
|
||
#. Использование функций из C при наличии аналогов в стандартной библиотеке C++.
|
||
Допустимо, если это использование эффективнее.
|
||
Для примера, для копирования длинных кусков памяти, используйте memcpy вместо std::copy.
|
||
|
||
#. Перенос длинных аргументов функций.
|
||
Допустимо использовать любой стиль переноса, похожий на приведённые ниже:
|
||
|
||
.. code-block:: cpp
|
||
|
||
function(
|
||
T1 x1,
|
||
T2 x2)
|
||
|
||
.. code-block:: cpp
|
||
|
||
function(
|
||
size_t left, size_t right,
|
||
const & RangesInDataParts ranges,
|
||
size_t limit)
|
||
|
||
.. code-block:: cpp
|
||
|
||
function(size_t left, size_t right,
|
||
const & RangesInDataParts ranges,
|
||
size_t limit)
|
||
|
||
.. code-block:: cpp
|
||
|
||
function(size_t left, size_t right,
|
||
const & RangesInDataParts ranges,
|
||
size_t limit)
|
||
|
||
.. code-block:: cpp
|
||
|
||
function(
|
||
size_t left,
|
||
size_t right,
|
||
const & RangesInDataParts ranges,
|
||
size_t limit)
|