Covellite++  Version: 2.3.0 Revision: 2580 Platform: x64 Build: 15:23 16.10.2020
Кроссплатформенный фреймворк для разработки приложений на С++
Оформление кода

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

Файл настроек AStyle для форматирования кода

--suffix=none
--exclude=.svn --exclude=(Documentation) --exclude=Externals --ignore-exclude-errors
--style=allman
--indent=spaces=2
--indent-switches
--indent-preproc-block
--indent-col1-comments
--min-conditional-indent=0
--max-continuation-indent=80
--break-blocks
--pad-oper
--pad-header
--unpad-paren
--align-pointer=middle
--align-reference=middle
--add-braces
--keep-one-line-blocks
--close-templates
--max-code-length=80
--break-after-logical
--lineend=windows

Правила именования идентификаторов

class ClassName
class IInterfaceName
template<class TTemplateParam>
using Name_t = ...
ClassFunctionName
GlobalFunctionName
IsFunctionReturnBool
(*fnCallBackFunction)
m_ClassMemberVariable
_FunctionParamVariable
LocalVariable
IsBoolVariable
pPointerName
itIterator(ritReverseIterator)
iIndexValue
MemberEnum
Constant::ConstantVariable
FUCKING_DEFINE_MACROS

Общие

  • Использовать RTTI исключительно в тестах или для логирования работы программы.
  • В качестве целых типов использовать типы, определенные в <cstdint> и size_t.
  • Не использовать беззнаковые типы данных, такие как uint32_t (за исключением маски), например, чтобы показать что значение переменной не может быть отрицательным. Для этой цели использовать assert’ы.
  • Использовать assert(... && "message");
  • Использовать макросы только в случае крайней необходимости, делать #undef сразу после использования.
  • Директивы препроцессора должны иметь отступ после #.
  • Для условной компиляции (debug и release версий, например) использовать не директивы компилятора, а "const bool debug = true;".
  • Вместо массивов и оператора new[] использовать std::vector.
  • Хранить в контейнерах stl не объекты, а указатели или shared_ptr'ы.
  • Использовать постинкремент (i++) только для простых типов, для итераторов и шаблонных типов - ++it.
  • Объявлять не изменяющиеся переменные как константы (даже в параметрах функций).
  • Все enum'ы оборачивать в класс (пример).
  • Интерфейсный класс должен содержать только чисто виртуальные функции и статические методы.
  • Множественное наследование допустимо только от интерфейсных классов.
  • Открытые функции (интерфейс класса) лучше делать не виртуальными, а виртуальными - закрытые или защищенные (паттерн NVI).
  • В открытых функциях классов использовать проверку параметров с бросанием исключений, в закрытых - assert’ы.
  • Чрезмерное использование встроенных функций может сделать программу медленней. На современных процессорах меньший код обычно исполняется быстрее из-за лучшего использования командного кэша.
  • Статические переменные класса всегда "заворачивать" в функции.
  • Не константные статические переменные нельзя использовать в многопоточном коде.
  • Переменные класса должны быть закрытыми и функций доступа к ним не должно быть вообще или она должна быть только одна (SetValue() или GetValue()).
  • При передаче в функцию nullptr, boolean или символьных целых значений следует их комментировать или писать самодокументирующийся код используя константы (пример).
  • Исключения не должны пересекать границы модулей/подсистем.
  • Комментирование кода производить в стиле Doxygen.

Заголовочные файлы

  • Заголовочные файлы должны иметь расширение .hpp.
  • В начале заголовочного файла ставить "#pragma once".
  • Не использовать #include, когда достаточно предварительного объявления.
  • Определение класса должно начинаться с открытых функций, затем должны идти защищенные функции и переменные, затем - закрытые. После закрытых функций должны быть объявлены конструкторы и деструкторы.
  • Объявления дружественных тестовых функций должны находиться в самом конце объявления класса.
  • В объявлениях функций без параметров всегда явным образом писать void (чтобы визуально отличать объявление функции от ее вызова).
  • В каждом заголовочном файле должна быть только одна сущность (класс, общий перечислитель, константы и т.д.).

Файлы определений

  • Файлы определений должны иметь расширение .cpp (файл определения шаблонных и inline-функций - .inl).
  • Порядок следования заголовочных файлов: библиотеки C, библиотеки C++, .hpp файлы других библиотек, .hpp файлы текущего проекта.
  • Определение вложенного класса должно находиться в отдельном .cpp файле.
  • В .cpp файле первыми должны идти конструкторы и деструктор, затем все остальные функции в порядке их объявления.
  • В определении функции, объявленной как override (virtual, static) ставить
    int ClassName::GetValue(void) /*override*/
    {
    // ...
    }
    /*static*/ void ClassName::DoProcess(void)
    {
    // ...
    }
  • В определении функции сначала ставить входные, затем выходные параметры.
  • Параметры, являющиеся результатом работы функции должны передаваться через указатели(ссылки - только константные!).
  • Параметры, являющиеся результатом работы функции, должны быть помечены комментарием out
    void GetValue(/*out*/ int * _pValue);

Правила именования переменных/функций/классов/файлов

  • Имена переменных должны быть существительными, функций - глаголами/командами.
  • Имена файлов должны совпадать с именами объявленных в них классов (включая пространство имен).
  • Правила именования идентификаторов.

Пространства имен

  • Каждый отдельный модуль программы должен иметь свое пространство имен.
  • Не использовать директиву using namespace вне функций.
  • Пространство имен должно завершаться комментарием "} // namespace <name>".
  • Код внутри пространства имен должен идти по левому краю.
  • Вместо статических констант использовать константы в не именованных пространствах имен (использовать их в заголовочных файлах только для объявления констант!).

Конструкторы/деструкторы

  • Всегда явным образом определять конструктор по умолчанию, инициирующий внутренние переменные класса.
  • Использовать ключевое слово explicit для конструкторов с одним аргументом.
  • Если класс не имеет явного конструктора копирования, наследовать его от boost::noncopyable.
  • Деструктору всегда явным образом указывать noexcept.

Примеры

Пример 1: Перечисления

// Объявление:
class Status
{
public:
enum Value : size_t
{
Unknown = 0,
Play,
Pause,
Stop,
Illegal
};
};
// Использование
Status::Value CurrentStatatus = Status::Stop;

Пример 2: Константы

namespace
{
// Объявление
template<class T>
class Constant final
{
public:
static constexpr T PI = static_cast<T>(math::PI);
};
} // unnamed namespace
// Использование
auto DoublePI = 2.0f * Constant<float>::PI;

Пример 3: Именование параметров

const bool IsSsuccess = CalculateSomething(InterestingValue,
10,
false,
NULL); // Что означают все эти аргументы??

вместо:

const bool IsSuccess = CalculateSomething(InterestingValue,
10, // Базовое значение по умолчанию
false, // Функция вызывается не в первый раз.
nullptr); // Без возвратной функции.

Или, в качестве альтернативы, используя константы или само-описывающие переменные:

const int DefaultBaseValue = 10;
const bool IsFirstTimeCalling = false;
Callback * fnNullCallback = nullptr;
const bool IsSuccess = CalculateSomething(InterestingValue,
DefaultBaseValue,
IsFirstTimeCalling,
fnNullCallback);

Под вопросом

  • Запятые в списке инициализации переменных в конструкторе ставить после переменной.
  • Вместо <iostream> в заголовочных файлах писать <iosfwd> для ускорения компиляции.
  • Для быстрого копирования потоков ввода - вывода(из одного в другой), использовать функцию потока .rdbuf() (cout << file.rdbuf()).
  • С помощью функции std::uncaught_exception можно проверить, как завершает работу функция: это нормальный возврат или было выброшено исключение.
  • Преобразовывайте тип числа с плавающей точкой в целочисленный тип исключительно через функции стандартной библиотеки(«round», «floor», «ceil» и т.п.)
  • Для инициализации переменных использовать списки инициализации (они не позволяют сужение типов: int x1 = { 7.3 }; //compilation error).
  • Профайлер не может подсказать, какие inline функции не должны быть встраиваемыми!
  • Строки хранить в отдельном файле (лучше.cpp).
  • Исключение не должно покидать деструктор (особенно это касается работы с контейнерами STL).
  • Для класса предоставлять функцию swap() (она должна быть бессбойной!), которую можно использовать для присваивания со строгими гарантиями безопасности (для примитивных типов использовать std::swap())
    T & T::operator= (T Temp) // По значению!
    {
    swap(Temp);
    return *this;
    }
  • Для отсортированного набора объектов можно использовать индексные контейнеры, содержащие итераторы на основной контейнер.
  • Для добавления элементов в контейнер предпочтительнее использовать операции с диапазонами.
  • container<T>(c).swap(c); // Удаление всей лишней (зарезервированной) емкости контейнера.
  • container<T>().swap(c); // Удаление всех элементов и очистка памяти контейнера.
  • find / find_if и count / count_if - поиск в неотсортированном диапазоне.
  • lower_bound, upper_bound, equal_range(реже binary_search) - поиск в отсортированном диапазоне.
  • Функциональные объекты должны содержать константный operator().
  • Побочные подзадачи выделять в отдельный код.