style.md 48.9 KB
Newer Older
1 2
---
toc_priority: 68
3
toc_title: "Как писать код на C++"
4 5 6
---


7
# Как писать код на C++ {#kak-pisat-kod-na-c}
B
BayoNet 已提交
8

9
## Общее {#obshchee}
B
BayoNet 已提交
10

11 12 13 14 15 16 17
**1.** Этот текст носит рекомендательный характер.

**2.** Если вы редактируете код, то имеет смысл писать так, как уже написано.

**3.** Стиль нужен для единообразия. Единообразие нужно, чтобы было проще (удобнее) читать код. А также, чтобы было легче осуществлять поиск по коду.

**4.** Многие правила продиктованы не какими либо разумными соображениями, а сложившейся практикой.
B
BayoNet 已提交
18

19
## Форматирование {#formatirovanie}
B
BayoNet 已提交
20

21
**1.** Большую часть форматирования сделает автоматически `clang-format`.
B
BayoNet 已提交
22

23
**2.** Отступы — 4 пробела. Настройте среду разработки так, чтобы таб добавлял четыре пробела.
B
BayoNet 已提交
24

25
**3.** Открывающая и закрывающие фигурные скобки на отдельной строке.
B
BayoNet 已提交
26

27
``` cpp
28 29 30 31 32 33 34
inline void readBoolText(bool & x, ReadBuffer & buf)
{
    char tmp = '0';
    readChar(tmp, buf);
    x = tmp != '0';
}
```
B
BayoNet 已提交
35

36
**4.** Если всё тело функции — один `statement`, то его можно разместить на одной строке. При этом, вокруг фигурных скобок ставятся пробелы (кроме пробела на конце строки).
B
BayoNet 已提交
37

38
``` cpp
39 40 41
inline size_t mask() const                { return buf_size() - 1; }
inline size_t place(HashValue x) const    { return x & mask(); }
```
B
BayoNet 已提交
42

43
**5.** Для функций. Пробелы вокруг скобок не ставятся.
B
BayoNet 已提交
44

45
``` cpp
46 47
void reinsert(const Value & x)
```
B
BayoNet 已提交
48

49
``` cpp
50 51
memcpy(&buf[place_value], &x, sizeof(x));
```
B
BayoNet 已提交
52

53
**6.** В выражениях `if`, `for`, `while` и т.д. перед открывающей скобкой ставится пробел (в отличие от вызовов функций).
54

55
``` cpp
56 57
for (size_t i = 0; i < rows; i += storage.index_granularity)
```
58

59
**7.** Вокруг бинарных операторов (`+`, `-`, `*`, `/`, `%`, …), а также тернарного оператора `?:` ставятся пробелы.
60

61
``` cpp
62 63 64 65
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');
```
66

67
**8.** Если ставится перенос строки, то оператор пишется на новой строке, и перед ним увеличивается отступ.
68

69
``` cpp
70 71 72 73 74
if (elapsed_ns)
    message << " ("
        << rows_read_on_server * 1000000000 / elapsed_ns << " rows/s., "
        << bytes_read_on_server * 1000.0 / elapsed_ns << " MB/s.) ";
```
75

76
**9.** Внутри строки можно, выполнять выравнивание с помощью пробелов.
77

78
``` cpp
79 80 81 82
dst.ClickLogID         = click.LogID;
dst.ClickEventID       = click.EventID;
dst.ClickGoodEvent     = click.GoodEvent;
```
B
BayoNet 已提交
83

84
**10.** Вокруг операторов `.`, `->` не ставятся пробелы.
A
Alexey Milovidov 已提交
85

86
При необходимости, оператор может быть перенесён на новую строку. В этом случае, перед ним увеличивается отступ.
B
BayoNet 已提交
87

88
**11.** Унарные операторы `--`, `++`, `*`, `&`, … не отделяются от аргумента пробелом.
89

90
**12.** После запятой ставится пробел, а перед — нет. Аналогично для точки с запятой внутри выражения `for`.
91

92
**13.** Оператор `[]` не отделяется пробелами.
93

94
**14.** В выражении `template <...>`, между `template` и `<` ставится пробел, а после `<` и до `>` не ставится.
A
Alexey Milovidov 已提交
95

96
``` cpp
97 98 99 100
template <typename TKey, typename TValue>
struct AggregatedStatElement
{}
```
101

102 103
**15.** В классах и структурах, `public`, `private`, `protected` пишется на том же уровне, что и `class/struct`, а остальной код с отступом.

104
``` cpp
105 106 107 108 109 110 111 112 113 114 115 116
template <typename T>
class MultiVersion
{
public:
    /// Version of object for usage. shared_ptr manage lifetime of version.
    using Version = std::shared_ptr<const T>;
    ...
}
```

**16.** Если на весь файл один `namespace` и кроме него ничего существенного нет, то отступ внутри `namespace` не нужен.

117
**17.** Если блок для выражения `if`, `for`, `while`, … состоит из одного `statement`, то фигурные скобки не обязательны. Вместо этого поместите `statement` на отдельную строку. Это правило справедливо и для вложенных `if`, `for`, `while`, …
118 119 120

Если внутренний `statement` содержит фигурные скобки или `else`, то внешний блок следует писать в фигурных скобках.

121
``` cpp
122 123 124 125 126 127 128 129 130 131 132
/// Finish write.
for (auto & stream : streams)
    stream.second->finalize();
```

**18.** Не должно быть пробелов на концах строк.

**19.** Исходники в кодировке UTF-8.

**20.** В строковых литералах можно использовать не-ASCII.

133
``` cpp
134 135 136 137 138 139
<< ", " << (timer.elapsed() / chunks_stats.hits) << " μsec/hit.";
```

**21.** Не пишите несколько выражений в одной строке.

**22.** Внутри функций группируйте блоки кода, отделяя их не более, чем одной пустой строкой.
B
BayoNet 已提交
140

141 142 143 144
**23.** Функции, классы, и т. п. отделяются друг от друга одной или двумя пустыми строками.

**24.** `const` (относящийся к значению) пишется до имени типа.

145
``` cpp
146 147 148 149 150 151 152 153 154
//correct
const char * pos
const std::string & s
//incorrect
char const * pos
```

**25.** При объявлении указателя или ссылки, символы `*` и `&` отделяются пробелами с обеих сторон.

155
``` cpp
156 157 158 159 160 161 162 163 164 165 166 167 168
//correct
const char * pos
//incorrect
const char* pos
const char *pos
```

**26.** При использовании шаблонных типов, пишите `using` (кроме, возможно, простейших случаев).

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

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

169
``` cpp
170 171 172 173 174 175 176 177 178
//correct
using FileStreams = std::map<std::string, std::shared_ptr<Stream>>;
FileStreams streams;
//incorrect
std::map<std::string, std::shared_ptr<Stream>> streams;
```

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

179
``` cpp
180 181 182 183 184 185
//incorrect
int x, *y;
```

**28.** C-style cast не используется.

186
``` cpp
187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204
//incorrect
std::cerr << (int)c <<; std::endl;
//correct
std::cerr << static_cast<int>(c) << std::endl;
```

**29.** В классах и структурах, группируйте отдельно методы и отдельно члены, внутри каждой области видимости.

**30.** Для не очень большого класса/структуры, можно не отделять объявления методов от реализации.

Аналогично для маленьких методов в любых классах/структурах.

Для шаблонных классов/структур, лучше не отделять объявления методов от реализации (так как иначе они всё равно должны быть определены в той же единице трансляции).

**31.** Не обязательно умещать код по ширине в 80 символов. Можно в 140.

**32.** Всегда используйте префиксный инкремент/декремент, если постфиксный не нужен.

205
``` cpp
206 207
for (Names::const_iterator it = column_names.begin(); it != column_names.end(); ++it)
```
B
BayoNet 已提交
208

209
## Комментарии {#kommentarii}
B
BayoNet 已提交
210

211
**1.** Необходимо обязательно писать комментарии во всех нетривиальных местах.
B
BayoNet 已提交
212

213
Это очень важно. При написании комментария, можно успеть понять, что код не нужен вообще, или что всё сделано неверно.
B
BayoNet 已提交
214

215
``` cpp
216 217 218 219 220 221
/** 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).
  */
```
B
BayoNet 已提交
222

223
**2.** Комментарии могут быть сколь угодно подробными.
B
BayoNet 已提交
224

225
**3.** Комментарии пишутся до соответствующего кода. В редких случаях после, на той же строке.
B
BayoNet 已提交
226

227
``` cpp
228 229 230 231 232 233 234 235 236 237
/** 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
    )
```
B
BayoNet 已提交
238

239
**4.** Комментарии следует писать только на английском языке.
B
BayoNet 已提交
240

241
**5.** При написании библиотеки, разместите подробный комментарий о том, что это такое, в самом главном заголовочном файле.
B
BayoNet 已提交
242

243
**6.** Нельзя писать комментарии, которые не дают дополнительной информации. В частности, нельзя писать пустые комментарии вроде этого:
B
BayoNet 已提交
244

245
``` cpp
246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264
/*
* 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:
*/
```
B
BayoNet 已提交
265

266
Пример взят с ресурса http://home.tamk.fi/~jaalto/course/coding-style/doc/unmaintainable-code/.
267

268
**7.** Нельзя писать мусорные комментарии (автор, дата создания…) в начале каждого файла.
269 270 271

**8.** Однострочные комментарии начинаются с трёх слешей: `///` , многострочные с `/**`. Такие комментарии считаются «документирующими».

S
Sergei Bocharov 已提交
272
Замечание: такие комментарии могут использоваться для генерации документации с помощью Doxygen. Но, фактически, Doxygen не используется, так как для навигации по коду гораздо удобнее использовать возможности IDE.
273 274 275

**9.** В начале и конце многострочного комментария, не должно быть пустых строк (кроме строки, на которой закрывается многострочный комментарий).

276
**10.** Для закомментированных кусков кода, используются обычные, не «документирующие» комментарии.
277 278 279 280 281 282 283

**11.** Удаляйте закомментированные куски кода перед коммитом.

**12.** Не нужно писать нецензурную брань в комментариях или коде.

**13.** Не пишите прописными буквами. Не используйте излишнее количество знаков препинания.

284
``` cpp
285 286 287 288 289
/// WHAT THE FAIL???
```

**14.** Не составляйте из комментариев строки-разделители.

290
``` cpp
291 292 293 294 295
///******************************************************
```

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

296
``` cpp
297 298 299 300 301
/// Why did you do this stuff?
```

**16.** Не нужно писать комментарий в конце блока о том, что представлял собой этот блок.

302
``` cpp
303 304
/// for
```
B
BayoNet 已提交
305

306
## Имена {#imena}
B
BayoNet 已提交
307

308
**1.** В именах переменных и членов класса используйте маленькие буквами с подчёркиванием.
B
BayoNet 已提交
309

310
``` cpp
311 312 313
size_t max_block_size;
```

314
**2.** Имена функций (методов) camelCase с маленькой буквы.
B
BayoNet 已提交
315

316
``` cpp
317 318 319
std::string getName() const override { return "Memory"; }
```

320
**3.** Имена классов (структур) - CamelCase с большой буквы. Префиксы кроме I для интерфейсов - не используются.
B
BayoNet 已提交
321

322
``` cpp
323 324
class StorageMemory : public IStorage
```
B
BayoNet 已提交
325

326
**4.** `using` называются также, как классы, либо с `_t` на конце.
B
BayoNet 已提交
327

328
**5.** Имена типов — параметров шаблонов: в простых случаях - `T`; `T`, `U`; `T1`, `T2`.
B
BayoNet 已提交
329

330
В более сложных случаях - либо также, как имена классов, либо можно добавить в начало букву `T`.
B
BayoNet 已提交
331

332
``` cpp
333 334 335
template <typename TKey, typename TValue>
struct AggregatedStatElement
```
B
BayoNet 已提交
336

337
**6.** Имена констант — параметров шаблонов: либо также, как имена переменных, либо `N` в простом случае.
B
BayoNet 已提交
338

339
``` cpp
340 341 342
template <bool without_www>
struct ExtractDomain
```
B
BayoNet 已提交
343

344
**7.** Для абстрактных классов (интерфейсов) можно добавить в начало имени букву `I`.
B
BayoNet 已提交
345

346
``` cpp
347 348
class IBlockInputStream
```
349

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

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

354
``` cpp
355 356
bool info_successfully_loaded = false;
```
357

A
Alexey Milovidov 已提交
358
**9.** В именах `define` и глобальных констант используется ALL_CAPS с подчёркиванием.
B
BayoNet 已提交
359

360
``` cpp
361 362
#define MAX_SRC_TABLE_NAMES_TO_STORE 1000
```
B
BayoNet 已提交
363

364
**10.** Имена файлов с кодом называйте по стилю соответственно тому, что в них находится.
B
BayoNet 已提交
365

366
Если в файле находится один класс, назовите файл, как класс (CamelCase).
367

368
Если в файле находится одна функция, назовите файл, как функцию (camelCase).
A
Alexey Milovidov 已提交
369

370 371
**11.** Если имя содержит сокращение, то:

372 373
-   для имён переменных, всё сокращение пишется маленькими буквами `mysql_connection` (не `mySQL_connection`).
-   для имён классов и функций, сохраняются большие буквы в сокращении `MySQLConnection` (не `MySqlConnection`).
374 375 376

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

377
``` cpp
378 379 380 381 382 383 384 385 386 387 388 389 390 391 392
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.** Именование локальных переменных и членов класса никак не отличается (никакие префиксы не нужны).
B
BayoNet 已提交
393

394
``` cpp
395 396
timer (not m_timer)
```
B
BayoNet 已提交
397

A
Alexey Milovidov 已提交
398
**14.** Константы в `enum` — CamelCase с большой буквы. Также допустим ALL_CAPS. Если `enum` не локален, то используйте `enum class`.
A
Alexey Milovidov 已提交
399

400
``` cpp
401 402 403 404 405 406 407
enum class CompressionMethod
{
    QuickLZ = 0,
    LZ4     = 1,
};
```

S
Sergei Bocharov 已提交
408
**15.** Все имена - по-английски. Транслит с русского использовать нельзя.
409

410
``` text
411 412 413 414 415
не Stroka
```

**16.** Сокращения (из нескольких букв разных слов) в именах можно использовать только если они являются общепринятыми (если для сокращения можно найти расшифровку в английской википедии или сделав поисковый запрос).

416
``` text
417 418 419 420 421 422 423 424 425 426
`AST`, `SQL`.

Не `NVDH` (что-то неведомое)
```

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

Впрочем, сокращения также можно использовать, если расшифровка находится рядом в комментарии.

**17.** Имена файлов с исходниками на C++ должны иметь расширение только `.cpp`. Заголовочные файлы - только `.h`.
A
Alexey Milovidov 已提交
427

428
## Как писать код {#kak-pisat-kod}
B
BayoNet 已提交
429

430
**1.** Управление памятью.
B
BayoNet 已提交
431

432
Ручное освобождение памяти (`delete`) можно использовать только в библиотечном коде.
B
BayoNet 已提交
433

434
В свою очередь, в библиотечном коде, оператор `delete` можно использовать только в деструкторах.
B
BayoNet 已提交
435

436
В прикладном коде следует делать так, что память освобождается каким-либо объектом, который владеет ей.
B
BayoNet 已提交
437

438
Примеры:
A
Alexey Milovidov 已提交
439

I
Ivan Blinkov 已提交
440 441 442
-   проще всего разместить объект на стеке, или сделать его членом другого класса.
-   для большого количества маленьких объектов используйте контейнеры.
-   для автоматического освобождения маленького количества объектов, выделенных на куче, используйте `shared_ptr/unique_ptr`.
B
BayoNet 已提交
443

444
**2.** Управление ресурсами.
B
BayoNet 已提交
445

446
Используйте `RAII` и см. пункт выше.
B
BayoNet 已提交
447

448
**3.** Обработка ошибок.
A
Alexey Milovidov 已提交
449

450
Используйте исключения. В большинстве случаев, нужно только кидать исключения, а ловить - не нужно (потому что `RAII`).
A
Alexey Milovidov 已提交
451

452
В программах офлайн обработки данных, зачастую, можно не ловить исключения.
A
Alexey Milovidov 已提交
453

454
В серверах, обрабатывающих пользовательские запросы, как правило, достаточно ловить исключения на самом верху обработчика соединения.
B
BayoNet 已提交
455

456
В функциях потока, следует ловить и запоминать все исключения, чтобы выкинуть их в основном потоке после `join`.
B
BayoNet 已提交
457

458
``` cpp
459 460 461 462 463 464 465 466
/// Если вычислений ещё не было - вычислим первый блок синхронно
if (!started)
{
    calculate();
    started = true;
}
else    /// Если вычисления уже идут - подождём результата
    pool.wait();
A
Alexey Milovidov 已提交
467

468 469 470
if (exception)
    exception->rethrow();
```
A
Alexey Milovidov 已提交
471

472
Ни в коем случае не «проглатывайте» исключения без разбора. Ни в коем случае, не превращайте все исключения без разбора в сообщения в логе.
B
BayoNet 已提交
473

474
``` cpp
475 476 477
//Not correct
catch (...) {}
```
B
BayoNet 已提交
478

479
Если вам нужно проигнорировать какие-то исключения, то игнорируйте только конкретные, а остальные кидайте обратно.
B
BayoNet 已提交
480

481
``` cpp
482 483 484 485 486 487 488 489
catch (const DB::Exception & e)
{
    if (e.code() == ErrorCodes::UNKNOWN_AGGREGATE_FUNCTION)
        return nullptr;
    else
        throw;
}
```
B
BayoNet 已提交
490

491
При использовании функций, использующих коды возврата или `errno`, проверяйте результат и кидайте исключение.
B
BayoNet 已提交
492

493
``` cpp
494 495 496
if (0 != close(fd))
    throwFromErrno("Cannot close file " + file_name, ErrorCodes::CANNOT_CLOSE_FILE);
```
B
BayoNet 已提交
497

498
`assert` не используются.
B
BayoNet 已提交
499

500
**4.** Типы исключений.
A
Alexey Milovidov 已提交
501

502
В прикладном коде не требуется использовать сложную иерархию исключений. Желательно, чтобы текст исключения был понятен системному администратору.
A
Alexey Milovidov 已提交
503

504
**5.** Исключения, вылетающие из деструкторов.
B
BayoNet 已提交
505

506
Использовать не рекомендуется, но допустимо.
B
BayoNet 已提交
507

508
Используйте следующие варианты:
B
BayoNet 已提交
509

510 511 512 513
-   Сделайте функцию (`done()` или `finalize()`), которая позволяет заранее выполнить всю работу, в процессе которой может возникнуть исключение. Если эта функция была вызвана, то затем в деструкторе не должно возникать исключений.
-   Слишком сложную работу (например, отправку данных по сети) можно вообще не делать в деструкторе, рассчитывая, что пользователь заранее позовёт метод для завершения работы.
-   Если в деструкторе возникло исключение, желательно не «проглатывать» его, а вывести информацию в лог (если в этом месте доступен логгер).
-   В простых программах, если соответствующие исключения не ловятся, и приводят к завершению работы с записью информации в лог, можно не беспокоиться об исключениях, вылетающих из деструкторов, так как вызов `std::terminate` (в случае `noexcept` по умолчанию в C++11), является приемлемым способом обработки исключения.
B
BayoNet 已提交
514

515
**6.** Отдельные блоки кода.
B
BayoNet 已提交
516

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

519
``` cpp
520
Block block = data.in->read();
A
Alexey Milovidov 已提交
521

522 523 524 525 526
{
    std::lock_guard<std::mutex> lock(mutex);
    data.ready = true;
    data.block = block;
}
A
Alexey Milovidov 已提交
527

528 529
ready_any.set();
```
B
BayoNet 已提交
530

531
**7.** Многопоточность.
A
Alexey Milovidov 已提交
532

533
В программах офлайн обработки данных:
A
Alexey Milovidov 已提交
534

535
-   cначала добейтесь более-менее максимальной производительности на одном процессорном ядре, потом можно распараллеливать код, но только если есть необходимость.
A
Alexey Milovidov 已提交
536

537
В программах - серверах:
A
Alexey Milovidov 已提交
538

539
-   используйте пул потоков для обработки запросов. На данный момент, у нас не было задач, в которых была бы необходимость использовать userspace context switching.
B
BayoNet 已提交
540

541
Fork для распараллеливания не используется.
B
BayoNet 已提交
542

543
**8.** Синхронизация потоков.
A
Alexey Milovidov 已提交
544

545
Часто можно сделать так, чтобы отдельные потоки писали данные в разные ячейки памяти (лучше в разные кэш-линии), и не использовать синхронизацию потоков (кроме `joinAll`).
A
Alexey Milovidov 已提交
546

547
Если синхронизация нужна, то в большинстве случаев, достаточно использовать mutex под `lock_guard`.
B
BayoNet 已提交
548

549
В остальных случаях, используйте системные примитивы синхронизации. Не используйте busy wait.
B
BayoNet 已提交
550

551
Атомарные операции можно использовать только в простейших случаях.
A
Alexey Milovidov 已提交
552

553
Не нужно писать самостоятельно lock-free структуры данных, если вы не являетесь экспертом.
B
BayoNet 已提交
554

555
**9.** Ссылки и указатели.
A
Alexey Milovidov 已提交
556

557
В большинстве случаев, предпочитайте ссылки.
B
BayoNet 已提交
558

559
**10.** const.
A
Alexey Milovidov 已提交
560

561
Используйте константные ссылки, указатели на константу, `const_iterator`, константные методы.
A
Alexey Milovidov 已提交
562

563
Считайте, что `const` — вариант написания «по умолчанию», а отсутствие `const` только при необходимости.
B
BayoNet 已提交
564

565
Для переменных, передающихся по значению, использовать `const` обычно не имеет смысла.
B
BayoNet 已提交
566

567
**11.** unsigned.
A
Alexey Milovidov 已提交
568

569
Используйте `unsigned`, если нужно.
A
Alexey Milovidov 已提交
570

571
**12.** Числовые типы.
A
Alexey Milovidov 已提交
572

573
Используйте типы `UInt8`, `UInt16`, `UInt32`, `UInt64`, `Int8`, `Int16`, `Int32`, `Int64`, а также `size_t`, `ssize_t`, `ptrdiff_t`.
A
Alexey Milovidov 已提交
574

575
Не используйте для чисел типы `signed/unsigned long`, `long long`, `short`, `signed/unsigned char`, `char`.
A
Alexey Milovidov 已提交
576

577
**13.** Передача аргументов.
B
BayoNet 已提交
578

579
Сложные значения передавайте по ссылке (включая `std::string`).
B
BayoNet 已提交
580

581
Если функция захватывает владение объектом, созданным на куче, то сделайте типом аргумента `shared_ptr` или `unique_ptr`.
A
Alexey Milovidov 已提交
582

583
**14.** Возврат значений.
B
BayoNet 已提交
584

O
ocadaruma 已提交
585
В большинстве случаев, просто возвращайте значение с помощью `return`. Не пишите `return std::move(res)`.
B
BayoNet 已提交
586

587
Если внутри функции создаётся объект на куче и отдаётся наружу, то возвращайте `shared_ptr` или `unique_ptr`.
B
BayoNet 已提交
588

589
В некоторых редких случаях, может потребоваться возвращать значение через аргумент функции. В этом случае, аргументом будет ссылка.
B
BayoNet 已提交
590

591
``` cpp
592
using AggregateFunctionPtr = std::shared_ptr<IAggregateFunction>;
B
BayoNet 已提交
593

594 595 596 597 598 599 600 601
/** Позволяет создать агрегатную функцию по её имени.
  */
class AggregateFunctionFactory
{
public:
    AggregateFunctionFactory();
    AggregateFunctionPtr get(const String & name, const DataTypes & argument_types) const;
```
A
Alexey Milovidov 已提交
602

603
**15.** namespace.
A
Alexey Milovidov 已提交
604

605
Для прикладного кода отдельный `namespace` использовать не нужно.
A
Alexey Milovidov 已提交
606

607
Для маленьких библиотек - не требуется.
A
Alexey Milovidov 已提交
608

609
Для не совсем маленьких библиотек - поместите всё в `namespace`.
A
Alexey Milovidov 已提交
610

611
Внутри библиотеки в `.h` файле можно использовать `namespace detail` для деталей реализации, не нужных прикладному коду.
A
Alexey Milovidov 已提交
612

613
В `.cpp` файле можно использовать `static` или анонимный namespace для скрытия символов.
A
Alexey Milovidov 已提交
614

615
Также, `namespace` можно использовать для `enum`, чтобы соответствующие имена не попали во внешний `namespace` (но лучше использовать `enum class`).
A
Alexey Milovidov 已提交
616

617
**16.** Отложенная инициализация.
B
BayoNet 已提交
618

619
Обычно, если для инициализации требуются аргументы, то не пишите конструктор по умолчанию.
A
Alexey Milovidov 已提交
620

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

623
``` cpp
624
Loader(DB::Connection * connection_, const std::string & query, size_t max_block_size_);
B
BayoNet 已提交
625

626 627 628
/// Для отложенной инициализации
Loader() {}
```
B
BayoNet 已提交
629

630
**17.** Виртуальные функции.
B
BayoNet 已提交
631

632
Если класс не предназначен для полиморфного использования, то не нужно делать функции виртуальными зря. Это относится и к деструктору.
B
BayoNet 已提交
633

634
**18.** Кодировки.
B
BayoNet 已提交
635

636
Везде используется UTF-8. Используется `std::string`, `char *`. Не используется `std::wstring`, `wchar_t`.
A
Alexey Milovidov 已提交
637

S
Sergei Bocharov 已提交
638
**19.** Логирование.
B
BayoNet 已提交
639

640
См. примеры везде в коде.
B
BayoNet 已提交
641

S
Sergei Bocharov 已提交
642
Перед коммитом, удалите всё бессмысленное и отладочное логирование, и другие виды отладочного вывода.
B
BayoNet 已提交
643

S
Sergei Bocharov 已提交
644
Не должно быть логирования на каждую итерацию внутреннего цикла, даже уровня Trace.
B
BayoNet 已提交
645

S
Sergei Bocharov 已提交
646
При любом уровне логирования, логи должно быть возможно читать.
A
Alexey Milovidov 已提交
647

S
Sergei Bocharov 已提交
648
Логирование следует использовать, в основном, только в прикладном коде.
649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677

Сообщения в логе должны быть написаны на английском языке.

Желательно, чтобы лог был понятен системному администратору.

Не нужно писать ругательства в лог.

В логе используется кодировка 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` для функций, если в этом нет необходимости.

678
``` cpp
O
ocadaruma 已提交
679
auto f() -> void
680 681 682 683
```

**25.** Объявление и инициализация переменных.

684
``` cpp
685 686 687 688 689 690 691 692 693
//right way
std::string s = "Hello";
std::string s{"Hello"};

//wrong way
auto s = std::string{"Hello"};
```

**26.** Для виртуальных функций, пишите `virtual` в базовом классе, а в классах-наследниках, пишите `override` и не пишите `virtual`.
A
Alexey Milovidov 已提交
694

695
## Неиспользуемые возможности языка C++ {#neispolzuemye-vozmozhnosti-iazyka-c}
696

697 698
**2.** Спецификаторы исключений из C++03 не используются.

699 700 701
## Сообщения об ошибках {#error-messages}

Сообщения об ошибках -- это часть пользовательского интерфейса программы, предназначенная для того, чтобы позволить пользователю:
I
Ivan Blinkov 已提交
702

703 704 705 706 707 708 709
* замечать ошибочные ситуации,
* понимать их смысл и причины,
* устранять эти ситуации.

Форма и содержание сообщений об ошибках должны способствовать достижению этих целей.

Есть два основных вида ошибок:
I
Ivan Blinkov 已提交
710

711 712 713 714 715 716
* пользовательская или системная ошибка,
* внутренняя программная ошибка.

### Пользовательская ошибка {#error-messages-user-error}

Такая ошибка вызвана действиями пользователя (неверный синтаксис запроса) или конфигурацией внешних систем (кончилось место на диске). Предполагается, что пользователь может устранить её самостоятельно. Для этого в сообщении об ошибке должна содержаться следующая информация:
A
Alexander Kuzmenkov 已提交
717

718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733
* что произошло. Это должно объясняться в пользовательских терминах (`Function pow() is not supported for data type UInt128`), а не загадочными конструкциями из кода (`runtime overload resolution failed in DB::BinaryOperationBuilder<FunctionAdaptor<pow>::Impl, UInt128, Int8>::kaboongleFastPath()`).
* почему/где/когда -- любой контекст, который помогает отладить проблему. Представьте, как бы её отлаживали вы (программировать и пользоваться отладчиком нельзя).
* что можно предпринять для устранения ошибки. Здесь можно перечислить типичные причины проблемы, настройки, влияющие на это поведение, и так далее.

Пример нормального сообщения:
```
No alias for subquery or table function in JOIN (set joined_subquery_requires_alias=0 to disable restriction).
While processing '(SELECT 2 AS a)'.
```
Сказано что не хватает алиаса, показано, для какой части запроса, и предложена настройка, позволяющая ослабить это требование.

Пример катастрофически плохого сообщения:
```
The dictionary is configured incorrectly.
```
Из него не понятно:
I
Ivan Blinkov 已提交
734

735 736 737 738 739 740 741 742 743 744 745 746 747
- какой словарь?
- в чём ошибка конфигурации?

Что может сделать пользователь в такой ситуации: применять внешние отладочные инструменты, спрашивать совета на форумах, гадать на кофейной гуще, и, конечно же, ненавидеть софт, который над ним так издевается. Не нужно издеваться над пользователями, это плохой UX.


### Внутренняя программная ошибка {#error-messages-internal-error}

Такая ошибка вызвана нарушением внутренних инвариантов программы: например, внутренняя функция вызвана с неверными параметрами, не совпадают размеры колонок в блоке, произошло разыменование нулевого указателя, и так далее. Сигналы типа `SIGSEGV` относятся к этой же категории.

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

Есть два основных варианта проверки на такие ошибки:
I
Ivan Blinkov 已提交
748

749 750 751 752 753
* Исключение с кодом `LOGICAL_ERROR`. Его можно использовать для важных проверок, которые делаются в том числе в релизной сборке.
* `assert`. Такие условия не проверяются в релизной сборке, можно использовать для тяжёлых и опциональных проверок.	

Пример сообщения, у которого должен быть код `LOGICAL_ERROR`:
`Block header is inconsistent with Chunk in ICompicatedProcessor::munge(). It is a bug!`
I
Ivan Blinkov 已提交
754 755
По каким признакам можно заметить, что здесь говорится о внутренней программной ошибке?

756 757 758 759 760 761 762 763 764 765 766
* в сообщении упоминаются внутренние сущности из кода,
* в сообщении написано it's a bug,
* непосредственные действия пользователя не могут исправить эту ошибку. Мы ожидаем, что пользователь зарепортит её как баг, и будем исправлять в коде.

### Как выбрать код ошибки? {#error-messages-choose}

Код ошибки предназначен для автоматической обработки некоторых видов ошибок, подобно кодам HTTP. SQL стандартизирует некоторые коды, но на деле ClickHouse не всегда соответствует этим стандартам. Лучше всего выбрать существующий код из `ErrorCodes.cpp`, который больше всего подходит по смыслу. Можно использовать общие коды типа `BAD_ARGUMENTS` или `TYPE_MISMATCH`. Заводить новый код нужно, только если вы чётко понимаете, что вам нужна специальная автоматическая обработка конкретно этой ошибки на клиенте. Для внутренних программных ошибок используется код `LOGICAL_ERROR`.

### Как добавить новое сообщение об ошибке? {#error-messages-add}

Когда добавляете сообщение об ошибке:
I
Ivan Blinkov 已提交
767

768 769 770 771 772 773 774
1. Опишите, что произошло, в пользовательских терминах, а не кусками кода.
2. Добавьте максимум контекста (с чем произошло, когда, почему, и т.д.).
3. Добавьте типичные причины.
4. Добавьте варианты исправления (настройки, ссылки на документацию).
5. Вообразите дальнейшие действия пользователя. Ваше сообщение должно помочь ему решить проблему без использования отладочных инструментов и без чужой помощи.
6. Если сообщение об ошибке не формулируется в пользовательских терминах, и действия пользователя не могут исправить проблему -- это внутренняя программная ошибка, используйте код LOGICAL_ERROR или assert.

775
## Платформа {#platforma}
776

A
alexey-milovidov 已提交
777
**1.** Мы пишем код под конкретные платформы.
778

779
Хотя, при прочих равных условиях, предпочитается более-менее кроссплатформенный или легко портируемый код.
780

781
**2.** Язык - C++20 (см. список доступных [C++20 фич](https://en.cppreference.com/w/cpp/compiler_support#C.2B.2B20_features)).
782

783
**3.** Компилятор - `gcc`. На данный момент (август 2020), код собирается версией 9.3. (Также код может быть собран `clang` версий 10 и 9)
784

A
alexey-milovidov 已提交
785
Используется стандартная библиотека (реализация `libc++`).
786

A
alexey-milovidov 已提交
787
**4.** ОС - Linux, Mac OS X или FreeBSD.
788

A
Alexey Milovidov 已提交
789
**5.** Код пишется под процессоры с архитектурой x86_64, AArch64 и ppc64le.
790

A
alexey-milovidov 已提交
791
**6.** Используются флаги компиляции `-Wall -Wextra -Werror` и `-Weverything` с некоторыми исключениями.
792

A
alexey-milovidov 已提交
793
**7.** Используется статическая линковка со всеми библиотеками кроме libc.
794

795
## Инструментарий {#instrumentarii}
796

797 798 799 800 801 802 803 804 805
**1.** Хорошая среда разработки - KDevelop.

**2.** Для отладки используется `gdb`, `valgrind` (`memcheck`), `strace`, `-fsanitize=...`, `tcmalloc_minimal_debug`.

**3.** Для профилирования используется `Linux Perf`, `valgrind` (`callgrind`), `strace -cf`.

**4.** Исходники в Git.

**5.** Сборка с помощью `CMake`.
806

807
**6.** Программы выкладываются с помощью `deb` пакетов.
808

809 810 811 812 813 814 815 816 817 818 819 820 821
**7.** Коммиты в master не должны ломать сборку проекта.

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

**8.** Коммитьте как можно чаще, в том числе и нерабочий код.

Для этого следует использовать бранчи.

Если ваш код в ветке `master` ещё не собирается, исключите его из сборки перед `push`, также вы будете должны его доработать или удалить в течение нескольких дней.

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

**10.** Ненужный код удаляется из исходников.
A
Alexey Milovidov 已提交
822

823
## Библиотеки {#biblioteki}
824

825
**1.** Используются стандартная библиотека C++20 (допустимо использовать экспериментальные расширения) а также фреймворки `boost`, `Poco`.
826

827
**2.** При необходимости, можно использовать любые известные библиотеки, доступные в ОС из пакетов.
828

829
Если есть хорошее готовое решение, то оно используется, даже если для этого придётся установить ещё одну библиотеку.
830

831 832 833 834 835 836 837
(Но будьте готовы к тому, что иногда вам придётся выкидывать плохие библиотеки из кода.)

**3.** Если в пакетах нет нужной библиотеки, или её версия достаточно старая, или если она собрана не так, как нужно, то можно использовать библиотеку, устанавливаемую не из пакетов.

**4.** Если библиотека достаточно маленькая и у неё нет своей системы сборки, то следует включить её файлы в проект, в директорию `contrib`.

**5.** Предпочтение всегда отдаётся уже использующимся библиотекам.
838

839
## Общее {#obshchee-1}
840

841 842 843 844 845 846 847 848 849 850 851
**1.** Пишите как можно меньше кода.

**2.** Пробуйте самое простое решение.

**3.** Не нужно писать код, если вы ещё не знаете, что будет делать ваша программа, и как будет работать её внутренний цикл.

**4.** В простейших случаях, используйте `using` вместо классов/структур.

**5.** Если есть возможность - не пишите конструкторы копирования, операторы присваивания, деструктор (кроме виртуального, если класс содержит хотя бы одну виртуальную функцию), move-конструкторы и move-присваивания. То есть, чтобы соответствущие функции, генерируемые компилятором, работали правильно. Можно использовать `default`.

**6.** Приветствуется упрощение и уменьшение объёма кода.
852

853
## Дополнительно {#dopolnitelno}
854

855
**1.** Явное указание `std::` для типов из `stddef.h`.
856

857
Рекомендуется не указывать. То есть, рекомендуется писать `size_t` вместо `std::size_t`, это короче.
A
Alexey Milovidov 已提交
858

859
При желании, можно дописать `std::`, этот вариант допустим.
860

861
**2.** Явное указание `std::` для функций из стандартной библиотеки C.
862

863
Не рекомендуется. То есть, пишите `memcpy` вместо `std::memcpy`.
A
Alexey Milovidov 已提交
864

865
Причина - существуют похожие нестандартные функции, например, `memmem`. Мы можем использовать и изредка используем эти функции. Эти функции отсутствуют в `namespace std`.
A
Alexey Milovidov 已提交
866

867
Если вы везде напишете `std::memcpy` вместо `memcpy`, то будет неудобно смотреться `memmem` без `std::`.
A
Alexey Milovidov 已提交
868

869
Тем не менее, указывать `std::` тоже допустимо, если так больше нравится.
870

871
**3.** Использование функций из C при наличии аналогов в стандартной библиотеке C++.
872

873
Допустимо, если это использование эффективнее.
874

875
Для примера, для копирования длинных кусков памяти, используйте `memcpy` вместо `std::copy`.
876

877
**4.** Перенос длинных аргументов функций.
878

879
Допустимо использовать любой стиль переноса, похожий на приведённые ниже:
880

881
``` cpp
882 883 884 885
function(
  T1 x1,
  T2 x2)
```
886

887
``` cpp
888 889 890 891 892
function(
  size_t left, size_t right,
  const & RangesInDataParts ranges,
  size_t limit)
```
893

894
``` cpp
895 896 897 898
function(size_t left, size_t right,
  const & RangesInDataParts ranges,
  size_t limit)
```
899

900
``` cpp
901 902 903 904
function(size_t left, size_t right,
      const & RangesInDataParts ranges,
      size_t limit)
```
A
Alexey Milovidov 已提交
905

906
``` cpp
907 908 909 910 911 912
function(
      size_t left,
      size_t right,
      const & RangesInDataParts ranges,
      size_t limit)
```
I
Ivan Blinkov 已提交
913