Исходный код на C++ с использованием Qt
В статье описываются соглашения о стиле кода высокого уровня (руководство по стилю) и низкого уровня (форматирование).
- Соглашения о стиле кода высокого уровня
- Функциональность C++
- Общие соглашения о C++-коде
- Имена методов и переменных
- Подключение заголовочных файлов
- Преобразование типов
- Предпочитаемый синтаксис соединения сигналов и слотов Qt5
- Предпочитаемый цикл for — ранжированный for версии C++11
- Названия пространств имён в регистре CamelCase
- Проблемы, связанные с компилятором / платформой
- Эстетические соглашения
- Избегаемые конструкции
- Двоичная совместимость и совместимость исходных файлов
- Операторы
- Соглашения по использованию C++11
- Соглашения о стиле кода низкого уровня
Соглашения о стиле кода высокого уровня
Функциональность C++
- Не нужно использовать исключения.
- Не нужно использовать rtti (информация о типе во время выполнения;
то есть структура
typeinfo
, операторыdynamic_cast
илиtypeid
, включая выдачу исключений). - Рекомендуется использовать шаблоны, только когда они необходимы, а не только потому, что их можно применить.
- Подсказка: можно использовать автотест компиляции, чтобы узнать, поддерживается функция C++ всеми компиляторами в тестовой среде или нет.
Общие соглашения о C++-коде
- Весь исходный код должен быть в кодировке UTF-8.
- Каждый подкласс
QObject
должен иметь макросQ_OBJECT
, даже если он не имеет сигналов или слотов, иначеqobject_cast
завершится ошибкой. - Необходимо нормализовать аргументы для сигналов и слотов
(см.
QMetaObject::normalizedSignature
) внутри операторов подключения, чтобы ускорить поиск сигналов/слотов. Можно применитьqtrepotools/util/normalize
для нормализации существующего кода.
Имена методов и переменных
Соглашения об именах для переменных и членов классов могут определяться внутри компании. В компании «Открытая мобильная платформа» принят следующий стиль.
- Приватные и защищённые поля класса рекомендуется начинать с префикса
m_
. - Приватные и защищённые слоты и методы рекомендуется начинать с нижнего подчёркивания.
- Приватные статические методы и переменные в коде рекомендуется начинать с префикса
s_
.
Подключение заголовочных файлов
- В общедоступных заголовочных файлах всегда нужно использовать
эту форму для подключения заголовков Qt:
#include <QtCore/qwhatever.h>
. Префикс библиотеки необходим для фреймворков macOS и очень удобен для проектов, не связанных с qmake. - В исходные файлы сначала включаются специализированные заголовки, а затем общие заголовки.
Заголовки текущего проекта подключаются последними.
Можно разделить категории пустыми строками.
Рекомендуется подключать заголовки по категориям в следующем порядке,
который может быть изменён из-за конфликта директив
define
.- Системные API.
- Стандартные API.
- API Qt.
- Сторонние библиотеки.
- Файлы проекта.
Пример:
#include <limits.h> // Системный API
#include <new> // API STL
#include <QString> // Классы Qt
#include <QImage>
#include <poppler-qt5.h> // Сторонняя библиотека
#include "pdfloader.h" // Файл проекта
- Если нужно подключить qplatformdefs.h, всегда следует подключать его в качестве первого заголовочного файла.
- Если нужно подключить частные заголовки, следует делать это осторожно. Рекомендуется использовать следующий синтаксис независимо от того, в каком модуле или каталоге находится файл whatever_p.h.
#include <private/whatever_p.h>
Преобразование типов
- Следует избегать приведений типов C и предпочитать приведения C++
(
static_cast, const_cast, reinterpret_cast
).- Обоснование: приведение типа
reinterpret_cast
или приведение в стиле C опасно, но, по крайней мере,reinterpret_cast
не удалит модификаторconst
.
- Обоснование: приведение типа
// Не рекомендуется
int *blockOfMemory = (int *) byteArray.data();
// Рекомендуется
int *blockOfMemory = reinterpret_cast<int *> byteArray.data();
- Не следует использовать
dynamic_cast
, нужно применятьqobject_cast
для экземпляровQObject
или реорганизовать свой дизайн, например, ввести методtype()
. - Следует использовать конструктор для приведения простых типов:
int(myFloat)
вместо(int)myFloat
.- Обоснование: при рефакторинге кода компилятор немедленно сообщит, станет приведение опасным или нет.
Предпочитаемый синтаксис соединения сигналов и слотов Qt5
// Не рекомендуется
QObject::connect(&sender, SIGNAL(signalName()), &receiver, SLOT(slotName()));
// Рекомендуется
QObject::connect(&sender, &Sender::signalName, &receiver, &Receiver::slotName);
Обоснование: в первом варианте гарантируется, что компилятор может обнаружить проблемы несоответствия параметров, и обеспечивается незначительное улучшение производительности во время выполнения.
Предпочитаемый цикл for — ранжированный for версии C++11
Вместо цикла for
с перебором индексов:
// Не рекомендуется
for (int i = 0; i < someStringList.size(); ++i) {
const QString &str(someStringList[i]);
...
}
или вместо ключевого слова foreach:
// Не рекомендуется
foreach (const QString &str, someStringList) { ... }
Рекомендуется использовать цикл for следующего вида
// Рекомендуется
for (const QString &str : someStringList) { ... }
или
// Рекомендуется
for (const auto &str : someStringList) { ... }
Обоснование: в целом предпочитаются возможности C++11 вместо предыдущих версий языка, если они поддерживаются компилятором платформы.
Названия пространств имён в регистре CamelCase
Названия пространствам имён рекомендуется давать в регистре CamelCase.
// Не рекомендуется
namespace appnamespace { /* ... */ }
// Рекомендуется
namespace AppNamespace { /* ... */ }
Обоснование: правило обеспечивает согласованность именования классов в соответствии со стилем кода Qt.
Проблемы, связанные с компилятором / платформой
- Нужно быть предельно осторожным при использовании оператора вопросительного знака. Если возвращаемые типы не идентичны, некоторые компиляторы генерируют код, который даёт сбой во время выполнения (при этом компилятор не выдаёт предупреждение).
QString s;
return condition ? s : "nothing"; // падение при выполнении — QString vs. const char *
- Нужно быть предельно осторожными при выравнивании.
- Всякий раз, когда указатель приводится так, что требуемое выравнивание цели увеличивается,
результирующий код может дать сбой во время выполнения на некоторых архитектурах.
Например, если
const char *
приведён кconst int *
, он выйдет из строя на машинах, где целые числа должны выравниваться по двух- или четырёхбайтовой границе. - Следует использовать
union
, чтобы заставить компилятор правильно выравнивать переменные.
- Всякий раз, когда указатель приводится так, что требуемое выравнивание цели увеличивается,
результирующий код может дать сбой во время выполнения на некоторых архитектурах.
Например, если
В приведённом ниже примере можно быть уверенным,
что все экземпляры AlignHelper
выравниваются по целочисленным границам.
// Рекомендуется
union AlignHelper {
char c;
int i;
};
- Всё, что имеет конструктор или требует запуска кода для инициализации,
не может использоваться как глобальный объект в коде библиотеки, поскольку не определено,
когда этот конструктор/код будет запущен
(при первом использовании, при загрузке библиотеки, перед
main()
или вообще никогда). Если время выполнения инициализатора определено для разделяемых библиотек, возникнут проблемы при перемещении этого кода в плагин или в случае, если библиотека компилируется статически:
// Глобальная область видимости
// Не рекомендуется — для инициализации x необходимо запустить конструктор по умолчанию
static const QString x;
// Не рекомендуется — необходимо запустить конструктор, который принимает const char *
static const QString y = "Hello";
QString z; // совсем неправильно
// Не рекомендуется — время вызова foo() не определено, он может не вызываться вообще
static const int i = foo();
Что можно применять:
// Глобальная область видимости
// Рекомендуется — конструктор запускать не нужно, x устанавливается во время компиляции
static const char x[] = "someText";
// Рекомендуется — y будет установлено во время компиляции
static int y = 7;
// Рекомендуется — инициализируется статически, код не запускается
static MyStruct s = {1, 2, 3};
// Указатели на объекты в порядке — не нужно запускать код для инициализации ptr
static QString *ptr = 0;
Также можно использовать Q_GLOBAL_STATIC
для создания статических глобальных объектов:
Q_GLOBAL_STATIC(QString, s)
void foo()
{
s()->append("moo");
}
Примечание. Статические объекты в области видимости не являются проблемой, конструктор будет запущен при первом входе в область. Такой код является реентерабельным, начиная с C++11.
char
может быть знаковым или беззнаковым в зависимости от архитектуры. Следует использовать знаковыйchar
или беззнаковыйchar
явно, если знак действительно имеет значение. Условие в следующем коде всегда истинно на платформах, гдеchar
по умолчанию беззнаковый.
char c; // Значение c не может быть отрицательным, если тип беззнаковый
/********/
/*******/
if (c >= 0) { … }
// Рекомендуется — условие всегда истинно на платформах, где тип по умолчанию беззнаковый
- Следует избегать 64-битных значений перечисления.
- Встроенный AAPCS ABI жёстко кодирует все значения перечисления в 32-битное целое число.
Эстетические соглашения
- Предпочитаются перечисления для определения констант вместо
static const int
илиdefine
.- Значения перечисления будут заменены во время компиляции, что приведёт к ускорению кода.
define
небезопасны относительно пространств имён, выглядят некрасиво, а также могут создать проблемы при отладке.
- Предпочитаются подробные имена аргументов в заголовочных файлах.
- Большинство IDE будут отображать имена аргументов в панели завершения.
- Они лучше будут смотреться в документации.
- Плохой стиль:
doSomething(QRegion rgn, QPoint p)
, лучше использоватьdoSomething(QRegion clientRegion, QPoint gravitySource)
.
- При повторной реализации виртуального метода не нужно помещать
ключевое слово
virtual
в заголовочный файл. В C++ следует аннотировать их ключевым словомoverride
после объявления метода, непосредственно перед ';' (или '{'). - Рекомендуется указывать комментарий со значением по умолчанию для аргумента в реализации метода:
// app.h:
void setName(int index, const char *name = "no name");
// app.cpp:
void setName(int index, const char *name /*= "no name"*/)
{
…
}
Избегаемые конструкции
- Не нужно наследоваться от классов шаблонов/инструментов.
- Деструкторы не виртуальные, что может привести к утечке памяти.
- Символы не экспортируются (и в основном встроены), что приводит к различным конфликтам символов.
Пример:
В библиотеке A есть class Q_EXPORT X: public QList<QVariant> {};
,
в библиотеке B есть class Q_EXPORT Y: public QList<QVariant> {};
.
В определенный момент символы QList <QVariant>
экспортируются из двух библиотек,
что приведёт к конфликту.
- Не нужно смешивать константные и неконстантные итераторы. Это приведёт к сбою в сломанных компиляторах.
for (Container::const_iterator it = c.begin(); it != c.end(); ++it) // Не рекомендуется
for (Container::const_iterator it = c.cbegin(); it != c.cend(); ++it) // Рекомендуется
QCoreApplication
представляет собой класс-синглтон. Одновременно может существовать только один экземпляр. Однако этот экземпляр можно уничтожить и создать новый, например, в ActiveQt или плагине браузера.
Такой код выйдет из строя:
// Не рекомендуется
static QObject *obj = 0;
if (!obj)
obj = new QObject(QCoreApplication::instance());
Если приложение QCoreApplication уничтожено, obj
будет висящим указателем.
Нужно использовать Q_GLOBAL_STATIC
для статических глобальных объектов
или qAddPostRoutine
для очистки.
- По возможности следует избегать использования анонимных пространств имён
в пользу ключевого слова
static
. Имя, локализованное для компиляции с помощьюstatic
, гарантированно имеет внутреннюю связь. Для имён, объявленных в анонимных пространствах имён, стандарт C++, к сожалению, требует внешней связи. (7.1.1/6, или можно просмотреть различные обсуждения этого вопроса в списках рассылкиgcc
). - Рекомендуется избегать выделения памяти с помощью
malloc
и аналогичных стандартных функций. Вместо них можно использоватьQByteArray
.
Избегаемые конструкции в заголовочных файлах:
- Нет приведений в стиле C (
-Wold-style-cast
).- Нужно использовать
static_cast
,const_cast
илиreinterpret_cast
. - Для базовых типов нужно использовать конструктор:
int(a)
вместо(int)a
.
- Нужно использовать
- Нет проверок на эквивалентность чисел с плавающей точкой (
-Wfloat-equal
).- Следует использовать
qFuzzyCompare
для сравнения значений с дельтой. - Следует использовать
qFuzzyIsNull
, чтобы проверить, является ли число с плавающей точкой двоичным 0, вместо сравнения с0.0
.
- Следует использовать
- Не нужно скрывать виртуальные методы в подклассах (
-Woverloaded-virtual
).- Если базовый класс A содержит
virtual int val()
, а подкласс B — перегрузку с тем же именем,int val(int x)
, методval
уA
скрыт. Нужно использовать ключевое словоusing
, чтобы снова сделать его видимым:
- Если базовый класс A содержит
class B: public A
{
using A::val;
int val(int x);
};
- Не следует скрывать переменные (
-Wshadow
).- Нужно избегать конструкций:
this->x = x;
. - Не следует давать переменной то же имя, что и методам, объявленным в классе.
- Нужно избегать конструкций:
- Всегда нужно проверять, определена ли переменная препроцессора,
прежде чем проверять её значение (
-Wundef
).
Вместо
// Не рекомендуется
#if Foo == 0
или
// Не рекомендуется
#if Foo - 0 == 0
следует использовать
// Рекомендуется
#if defined(Foo) && (Foo == 0)
Двоичная совместимость и совместимость исходных файлов
Определения:
Qt 4.0.0 — основной выпуск, Qt 4.1.0 — второстепенный выпуск, Qt 4.1.1 — выпуск патча.
- Обратная двоичная совместимость: код, связанный с более ранней версией библиотеки, продолжает работать.
- Прямая двоичная совместимость: код, связанный с более новой версией библиотеки, работает со старой библиотекой. Совместимость исходного кода: код компилируется без изменений.
Рекомендации:
- Следует сохранять обратную двоичную совместимость и обратную совместимость исходного кода во второстепенных выпусках.
- Следует сохранять обратную и прямую двоичную совместимость
и прямую и обратную совместимость исходного кода в выпусках патчей.
- Не нужно добавлять/удалять какие-либо общедоступные API (например, глобальные функции, общедоступные/защищённые/частные методы).
- Не нужно переопределять методы (даже встроенные или защищённые/частные методы).
- Нужно проверять обходные пути двоичной совместимости.
- Информация о двоичной совместимости.
- Все экспортируемые из Qt функции должны начинаться либо с «q», либо с «Q».
Можно использовать автотест
symbols
для поиска нарушений.
Операторы
Выбор между членом и не-членом класса.
Бинарный оператор, который одинаково обрабатывает оба своих аргумента, не должен быть членом класса. Потому что, помимо причин, упомянутых в ответе о переполнении стека, аргументы не равны, когда оператор является членом класса.
Пример с QLineF
, у которого, к сожалению, есть оператор ==
в качестве члена:
QLineF lineF;
QLine lineN;
if (lineF == lineN) // Рекомендуется: lineN неявно конвертируется в QLineF
if (lineN == lineF) // Не рекомендуется: QLineF не может быть неявно преобразован в QLine,
// а LHS является членом, поэтому преобразование не применяется
Если бы оператор ==
находился за пределами класса,
правила преобразования применялись бы одинаково для обеих сторон.
Соглашения по использованию C++11
Примечание. Этот раздел ещё не общепринятый. Он служит основой для дальнейших обсуждений.
Лямбда-функции
Можно использовать лямбда-функции со следующими ограничениями.
Если используются статические функции из класса, в котором находится лямбда-функция, нужно выполнить рефакторинг кода, чтобы её не использовать. Например, вместо:
// Не рекомендуется
void Foo::something()
{
...
std::generate(begin, end, []() { return Foo::someStaticFunction(); });
...
}
можно просто передать указатель на функцию:
// Рекомендуется
void Foo::something()
{
...
std::generate(begin, end, &Foo::someStaticFunction);
...
}
Причина в том, что в GCC 4.7 и более ранних версиях была ошибка, требовавшая её фиксации, но Clang 5.0 и более поздние версии выдают предупреждение, если будет сделано так:
void Foo::something()
{
...
std::generate(begin, end, [this]() { return Foo::someStaticFunction(); });
// предупреждение: лямбда-захват 'this' не используется [-Wunused-lambda-capture]
...
}
Необходимо форматировать лямбда-функции в соответствии со следующими правилами:
- Всегда использовать скобки для списка параметров, даже если функция не принимает параметры.
Вместо
// Не рекомендуется
[] { doSomething(); }
следует использовать
// Рекомендуется
[]() { doSomething(); }
- Помещать список захвата, список параметров, тип возвращаемого значения и открывающую скобку в первую строку, текст с отступом на следующих строках и закрывающую скобку на новой строке.
Вместо
// Не рекомендуется
[]() -> bool { something();
somethingElse(); }
следует использовать
// Рекомендуется
[]() -> bool {
something();
return isSomethingElse();
}
- Помещать закрывающую круглую скобку и точку с запятой при вызове окружающей функции в той же строке, что и закрывающую скобку лямбда-функции.
// Рекомендуется
foo([]() {
something();
});
- Если используется лямбда-функция в операторе
if
, нужно начинать её с новой строки, чтобы избежать путаницы между открывающей скобкой для лямбда-функции и открывающей скобкой для оператораif
.
Вместо
// Не рекомендуется
if (anyOf(fooList, [](Foo foo) {
return foo.isGreat();
})) {
return;
}
следует использовать
// Рекомендуется
if (anyOf(fooList,
[](Foo foo) {
return foo.isGreat();
})) {
return;
}
- При желании полностью можно поместить лямбда-функцию в одну строку, если она короткая.
// Рекомендуется
foo([]() { return true; });
if (foo([]() { return true; })) {
...
}
Ключевое слово auto
При желании можно использовать ключевое слово auto
в следующих случаях.
В случае сомнений, например, если использование auto
может сделать код менее читаемым,
не нужно его применять.
Следует помнить, что код читается гораздо чаще, чем пишется.
- Когда избегается повторение типа в одном и том же операторе.
auto something = new MyCustomType;
auto keyEvent = static_cast<QKeyEvent *>(event);
auto myList = QStringList() << QLatin1String("FooThing") << QLatin1String("BarThing");
- При назначении типов итераторов.
auto it = myList.const_iterator();
Соглашения о стиле кода низкого уровня
Отступы
- Для отступа используются 4 пробела.
- Пробелы, а не табуляция!
Объявления переменных
- Нужно объявлять каждую переменную в отдельной строке. Инициализировать переменную значением по умолчанию рекомендуется сразу при объявлении.
- Нужно избегать коротких или бессмысленных имён
(например,
a
,rbarr
,nughdeget
).
// Не рекомендуется
int a, b;
char *c, *d;
// Рекомендуется
int height;
int width;
char *nameOfThis;
char *nameOfThat;
- Односимвольные имена переменных подходят только для счётчиков и временных переменных, где назначение переменной очевидно.
- Нужно подождать с объявлением переменной, пока она не понадобится.
- Переменные и функции начинаются со строчной буквы. Каждое последовательное слово в имени переменной начинается с заглавной буквы.
- Следует избегать сокращений.
// Не рекомендуется
short Cntr;
char ITEM_DELIM = ' ';
// Рекомендуется
short counter;
char itemDelimiter = ' ';
- Классы всегда начинаются с заглавной буквы.
Общедоступные классы в Qt начинаются с буквы Q (
QRgb
), за которой следует заглавная буква. Общедоступные функции в Qt чаще всего начинаются с буквы q (qRgb
). - Акронимы пишутся в регистре CamelCase (например,
QXmlStreamReader
, вместоQXMLStreamReader
).
Пробелы и операторы
- Следует использовать пустые строки для группировки операторов, где это необходимо.
// Создание модели
model = new QSqlRelationalTableModel(ui.bookTable);
model->setEditStrategy(QSqlTableModel::OnManualSubmit);
model->setTable("books");
// Сохранение индексов колонок
authorIdx = model->fieldIndex("author");
genreIdx = model->fieldIndex("genre");
- Всегда следует использовать только одну пустую строку.
// Не рекомендуется
model->setTable("books");
authorIdx = model->fieldIndex("author");
// Рекомендуется
model->setTable("books");
authorIdx = model->fieldIndex("author");
- Всегда следует использовать один пробел после ключевого слова и перед фигурной скобкой:
// Не рекомендуется
if(foo){
}
// Рекомендуется
if (foo) {
}
- Для указателей или ссылок всегда следует использовать один пробел между типом и * или &, но не использовать пробел между * или & и именем переменной.
// Рекомендуется
char *x;
const QString &myString;
const char * const y = "hello";
- Следует окружить бинарные операторы пробелами.
// Не рекомендуется
sum=a+b;
// Рекомендуется
sum = a + b;
- Следует оставлять пробел после каждой запятой.
// Не рекомендуется
QImage image(3,3,QImage::Format_RGB32);
// Рекомендуется
QImage image(3, 3, QImage::Format_RGB32);
- Не следует помещать несколько операторов в одну строку.
// Не рекомендуется
sum = a + b; product = a * b;
// Рекомендуется
sum = a + b;
product = a * b;
- В качестве расширения следует использовать новую строку для тела оператора потока управления:
// Не рекомендуется
if (foo) bar();
// Рекомендуется
if (foo)
bar();
Фигурные скобки
- Следует использовать прикреплённые фигурные скобки: открывающая фигурная скобка находится в той же строке, что и начало оператора. Если за закрывающей скобкой следует другое ключевое слово, оно также помещается в ту же строку:
// Не рекомендуется
if (codec)
{
}
else
{
}
// Рекомендуется
if (codec) {
} else {
}
- Исключение: реализации функций (но не лямбда-функций) и объявления классов всегда имеют левую скобку в начале строки:
static void foo(int g)
{
qDebug("foo: %i", g);
}
class Moo
{
};
- Фигурные скобки следует использовать только в том случае, если тело условного оператора содержит более одной строки:
// Не рекомендуется
if (address.isEmpty()) {
return false;
}
for (int i = 0; i < 10; ++i) {
qDebug("%i", i);
}
// Рекомендуется
if (address.isEmpty())
return false;
for (int i = 0; i < 10; ++i)
qDebug("%i", i);
- Первое исключение: следует использовать фигурные скобки, если родительский оператор охватывает несколько строк/переносов:
// Рекомендуется
if (address.isEmpty() || !isValid()
|| !codec) {
return false;
}
- Второе исключение связано с симметрией скобок.
Фигурные скобки также используются в блоках
if-then-else
, где либо кодif
, либо кодelse
содержат несколько строк:
// Не рекомендуется
if (address.isEmpty())
qDebug("empty!");
else {
qDebug("%s", qPrintable(address));
it;
}
// Рекомендуется
if (address.isEmpty()) {
qDebug("empty!");
} else {
qDebug("%s", qPrintable(address));
it;
}
// Не рекомендуется
if (a)
…
else
if (b)
…
// Рекомендуется
if (a) {
…
} else {
if (b)
…
}
- Следует использовать фигурные скобки, когда тело условного оператора пусто:
// Не рекомендуется
while (a);
// Рекомендуется
while (a) {}
Круглые скобки
Следует использовать круглые скобки для группировки выражений:
// Не рекомендуется
if (a && b || c)
// Рекомендуется
if ((a && b) || c)
// Не рекомендуется
a + b & c
// Рекомендуется
(a + b) & c
Оператор switch
- Обозначения
case
пишутся с тем же отступом, что иswitch
. - Каждый
case
должен иметь операторbreak
(илиreturn
) в конце или использоватьQ_FALLTHROUGH()
, чтобы указать, что нет умышленного прерывания, если за ним сразу не следует другойcase
.
switch (myEnum) {
case Value1:
doSomething();
break;
case Value2:
case Value3:
doSomethingElse();
Q_FALLTHROUGH();
default:
defaultHandling();
break;
}
Операторы перехода (break
, continue
, return
и goto
)
Не следует ставить else
после операторов перехода:
// Не рекомендуется
if (thisOrThat)
return;
else
somethingElse();
// Рекомендуется
if (thisOrThat)
return;
somethingElse();
Исключение: если код по своей сути симметричен,
допускается использование else
для визуализации этой симметрии.
Пример:
if (value > 0)
return "positive";
else
return "negative or 0";
Разрывы строк
- Строки кода и комментариев/документации должны быть ограничены по длине; при необходимости они переносятся. Нужно приспосабливаться к окружению и постараться расположить текст так, чтобы избежать «неровных» абзацев.
- Лимит строки определяется внутри компании. В компании «Открытая мобильная платформа» рекомендуют длину строк ограничивать 120 символами, а строк с комментариями — 100 символами.
- Запятые ставятся в конце строк с переносом; операторы начинаются в начале новых строк. Оператор в конце строки легко пропустить, если редактор слишком узкий.
// Не рекомендуется
if (longExpression +
otherLongExpression +
otherOtherLongExpression) {
}
// Рекомендуется
if (longExpression
+ otherLongExpression
+ otherOtherLongExpression) {
}
Лишние отрицания
Следует избегать лишних отрицаний.
// Не рекомендуется
if (!a) .. else ..
// Рекомендуется
if (a) .. else ..
а также
// Не рекомендуется
#ifdef NO_SQL
..
#endif
// Рекомендуется
#ifdef USE_SQL
..
#endif
Обоснование: для расшифровки отрицаний требуется больше умственной нагрузки.
Необходимо избегать двойного отрицания, например, ! NO_SOMETHING
.
Комментирование кода
Комментирование кода позволяет другим лучше читать исходный код. Кроме того, комментарии позволяют программисту думать о своём коде; сбивающий с толку комментарий может означать, что и сам код сбивает с толку.
В C++ есть два способа комментирования кода:
- однострочные комментарии начинаются с // и заканчиваются в конце строки;
- многострочные комментарии начинаются с /* и заканчиваются */.
Общее исключение
Если строгое следование правилу ухудшает код, можно свободно его нарушать.
Инструмент стиля Artistic Style
Следующий фрагмент можно использовать в artistic style для переформатирования кода.
--style=kr
--indent-spaces=4
--align-pointer=name
--align-reference=name
--convert-tabs
--attach-namespaces
--max-code-length=100
--max-instatement-indent=120
--pad-header
--pad-oper
Параметр "unlimited" --max-instatement-indent
используется, потому что
astyle
недостаточно, чтобы обернуть первый аргумент,
если для последующих строк потребуется ограничение отступа.
Следует вручную ограничить in-statement-indent
примерно 50 столбцами:
// Рекомендуется
int foo = some_really_long_function_name(and_another_one_to_drive_the_point_home(
first_argument, second_argument, third_arugment));
Инструмент стиля clang-format
Можно использовать clang-format для переформатирования кода.
Можно установить clang-format, например, для Ubuntu или Alt Lunux:
sudo apt-get install clang-format clang-format-10
Для macOS:
brew install clang-format
Для Windows можно загрузить clang, например, из источников LLVM.
С помощью clang-format
можно проверить и отформатировать изменённый код:
git clang-format
Нужно поместить и настроить файл формата .clang в корень каждого необходимого репозитория git. Не следует использовать стиль clang-format по умолчанию. Вместо этого настоятельно рекомендуется использовать стиль Qt, описанный в данной статье. Удобно использовать файл конфигурации из репозитория Qt Creator или с code.qt.io.