- Соглашение о стиле написания кода
Здесь вы найдете рекомендации по написанию кода на языке C/C++, принятые для разработки программного обеспечения для автономных необитаемых водных/подводных аппаратов, разрабатываемых в лаборатории необитаемых подводных аппаратов и их систем Дальневосточного Федерального Университета (ДВФУ).
Для разработки ПО в рамках данного проекта необходимо придерживаться нижеописанных соглашений.
Если у вас возник вопрос, ответ на который здесь не описан, руководствуйтесь здравым смыслом, своей фантазией и настроением людей, читающих ваш код.
-
CamelCase -- имя переменной начинается с заглавной буквы. Каждое последующее слово в имени переменной также начинается с заглавной буквы. Например: MyName.
-
camelCase -- имя переменной начинается с прописной буквы. Каждое последующее слово в имени переменной начинается с заглавной буквы. Например: myName.
-
under_score -- имя переменной начинается с прописной буквы. Каждое последующее слово отделено от предыдущего символом подчеркивания («_») и начинается с прописной буквы. Например: my_name.
-
ALL_CAPITALS -- все буквы в имени переменной являются заглавными. Каждое последующее слово отделено от предыдущего символом подчеркивания («_»). Например: MY_NAME.
- Все конфигурационные файлы и файлы исходного кода именуются в стиле under_score.
- Файлы описания сообщений именуются в стиле CamelCase (так как генератор ROS в точности также будет именовать соответствующие им классы) и имеют расширение .msg. Например:
MyMessage.msg
- Файлы с исходным кодом имеют расширение .cpp. Например:
my_source_file.cpp
- Заголовочные файлы имеют расширение .h. Например:
my_header_file.h
Давайте файлам информативные имена. Не стоит создавать файлы с названиями source.cpp, new_file.h или awesome_util.h. Исключение составляет файл с функцией main, который допускается именовать либо main.cpp, либо module_name.cpp.
Переменные именуются в стиле under_score.
Давайте переменным имена, отображающие сущность того, что в себе хранит эта переменная, исключением могут быть итераторы и счетчики в циклах.
int message_id;
int object_count;
int contour_num;
- Константные указатели и ссылки именуем, как переменные.
- Параметры, считанные из конфигурационного файла именуем, как переменные.
const MyClass* object_ptr;
const MyClass& instance = other_instance;
int window_height = cfg.read("window_height");
Константы именуем в стиле ALL_CAPITALS. Допускается именовать константы, как переменные в случае, если их значение не известно на этапе компиляции.
const int FRAME_OFFSET = 100;
const double size_ratio = cfg.read("size_ratio");
Паблик поля классов именуем как обычные переменные и константы. Прайвет или протектед поля именуем в стиле under_score_ -- с завершающим нижним подчерком.
class TableInfo
{
public:
int selected_row;
/*...*/
private:
string table_name_;
string tablename_;
static Pool<TableInfo>* pool_;
};
Глобальные переменные именуем также, как обычные.
Все объявленные программистом типы данных (class, struct, typedef, enum и параметры шаблонов) именуются в стиле CamelCase. Аббревиатуру предпочтительно считать одним словом и именовать маленьким буквами, кроме первой. Пример из Google Style Guide:
// classes and structs
class UrlTable { /*...*/ };
class UrlTableTester { /*...*/ };
struct UrlTableProperties { /*...*/ };
// typedefs
typedef hash_map<UrlTableProperties *, string> PropertiesMap;
// enums
enum UrlTableErrors { /*...*/ };
Давайте классам имена, отражающие сущность, которую они представляют. Имя класса -- имя существительное.
Функции именуем в стиле under_score. Предпочтительно использовать глаголы в качестве имен функций. Аргументы именуем как обычные переменные.
void get_name(int id) { /*...*/ }
class Runner
{
void run();
};
Пространства имен именуются в стиле under_score.
using namespace my_namespace;
Перечисления именуются в стиле CamelCase.
Поля перечисления именуются в стиле ALL_CAPITALS во избежания коллизий имен (констант в нашем коде не так много). Допускается использование перечислимых типов в качестве констант.
Например:
enum Color
{
GREEN,
RED,
YELLOW,
};
enum VisionConstants {
FRAMES = 100,
POOL_SIZE = 2,
};
Потихоньку переходим на использование enum class
в качестве перечислымых типов, это максимально уменьшает вероятность коллизий имен. Также отпадает необходимость именования в стиле ALL_CAPITALS. Поэтому при использовании enum class
и его полей в качестве перечислимого типа (не в качестве констант)допускается писать его поля в стиле CamelCase.
enum class Color
{
Red,
Green,
Blue,
};
Color line_color = Color::Red;
Макросы именуются в стиле ALL_CAPITALS.
Используйте макросы только в том случае, если действительно не можете без них обойтись.
Например:
#ifdef SURFACE_VEHICLE
/* лучше так не делать */
#endif
- Длина строки составляет не более 120 символов. (Проверять никто не будет, сдать вас может только человек с самым маленьким экраном ноутбука в команде.)
- Вместо табуляций для отступа следует использовать пробелы. Отступ составляет 4 пробела.
- Все файлы в кодировке utf-8.
- Никогда не ставим отступ или пробел после открывающейся круглой скобки и перед закрывающейся
- Допускается писать "однострочные" функции, если они содержат одну инструкцию и укладываются в оговоренный размер строки.
- После списка аргументов функции идет перенос строки (а не фигурная скобка)
int short_func() { return something; }
ReturnType ClassName::function_name(Type par_name1, Type par_name2)
{
do_something();
/*...*/
}
- Если список аргументов не влезает в оговоренный размер строки, допускается выполнять перенос необходимое количество раз, начиная с любого аргумента.
- Перенесенный список аргументов отделяется как минимум двумя отступами, чтобы отделить его визуально от кода функции.
- Аналогичные правила форматирования для вызова функции с несколькими аргументами.
ReturnType ClassName::function_name(Type par_name1, Type par_name2,
Type par_name3, Type par_name4)
{
do_something();
/*...*/
}
ReturnType ClassName::function_name(
Type par_name1,
Type par_name2,
Type par_name3,
Type par_name4)
{
do_something();
/*...*/
}
do_something(
arg1, arg2, arg3);
do_something(arg1,
arg2, arg3);
do_something(arg1,
arg2,
arg3);
Прочие правила:
- Никогда не ставим отступ между названием функции и открывающейся круглой скобкой
- Никогда не переносим открывающуюся круглую скобку после названия функции
- В операторах ветвлений и циклов открывающуюся фигурную скобку оставляем на одной строке с оператором.
- Фигурные скобки ставим в любом случае, даже если внутри находится только одна инструкция.
- Допускается писать конструкции else if на одной строке без фигурных скобок после else.
if (condition) {
...
} else if (...) {
...
} else {
...
}
for (int i = 0; i < 10000; ++i) {
printf("I love you\n");
}
- case внутри оператора switch не отделяются отступом
- Допускается не ставить скобки внутри case, если это необходимо
- Форматирование подряд идущих кейзов без брейков -- произвольным образом
- default обязателен
switch(var) {
case 0: case 1: case 2: {
do();
break;
}
case 4: {
do_something_else();
break;
}
default: {
/*...*/
}
}
- Ставим пробелы вокруг бинарных операторов, кроме
.
и->
. В случае оператора,
пробел ставится только после него. - Ставим пробелы в тернарном операторе
? :
. - не ставим пробелов между унарным оператором и его операндом.
x = *p;
p = &x;
x = r.y;
x = r->y
y = x + z--;
y = x == 0 ? 1 : 0;
- Открывающаяся фигурная скобка находится в одной строке с именем неймспейса
- Отступ внутри неймспейса не делается
- Рекомендуется добавлять комментарий с именем неймспейса, который закрываем в строке с закрывающейся фигурной скобкой
- Неймспейсы не отделяются друг от друга пустой строкой, зато отделяются ей от всего остального.
namespace foo {
namespace bar {
namespace {
void foo() {
...
}
} // namespace
/*...code...*/
} // namespace bar
} // namespace foo
- Перенос строки перед открывающейся фигурной скобкой обязателен
- Необходимо начинать описание класса с паблик полей
- Для спецификаторов области видимости полей класса отступ не делается.
class MyClass
{
public:
MyClass(): var_(0)
{
...
}
void foo() { ... }
protected:
void protected_method()
{
...
}
private:
int var_;
}
- Пишем слово
const
везде, где его только можно написать. (Речь об указателях и ссылках)
Вместо инклуд гардов в хедерах следует всегда использовать директиву #pragma once.
Механизм исключений предпочтителен над использованием кодов возврата. Соответственно исключение может быть брошено везде, кроме кода низкоуровневых драйверов устройств.
Поэтому необходимо писать безопасный относительно исключений код. А именно:
- Никаких "голых" указателей. Используем shared_ptr, unique_ptr или weak_ptr
- Любой объявленный деструктор должен быть виртуальным
- Код, который необходимо выполнить в конце функций (в духе
finalize_smth()
) нобходимо заворачивать либо в SCOPE_EXIT, либо в деструктор любого объекта, созданного на стеке. - Не следует бросать исключений в конструкторах и деструкторах
Используем структуры в том и только том случае, если все их поля будут паблик и все функции будут "однострочными". Иначе используем класс.
- Хедер файл начинается с
#pragma once
- Далее идут инклуды модулей внешних библиотек или других рос пакетов, обернутые в треугольные скобки.
- Далее идут инклуды локальных модулей, обернутые в двойные кавычки
- Далее идут неймспейсы и классы
Запрещено использовать ключевое слово using
в хедерах!, а также заводить в них алиасы для неймспейсов.
// myclass.h
#pragma once
#include <iostream>
#include <vector>
#include <opencv/core.h>
#include <opencv/highgui.h>
#include <config_reader/yaml_reader.h>
#include "my_other_class.h"
#include "my_local_utils.h"
/*...class declaration... */
- В .cpp файле разрешается использовать как
using
, так и алиасы неймспейсов.
// myclass.cpp
#include <vector>
#include "my_class.h"
using std::vector;
using namespace cv;
namespace msg = ros::geom_msgs;
- Вместо статических переменных и функций в модуле рекомендуется использовать анонимные неймспейсы. Таким образом вместо этого:
static void foo() { ...}
static int global_var;
Пишем это:
namespace {
void foo() { ...}
int global_var;
} // namespace
- Реализацию функций класса выносим из хедера. Оставляем только inline функции.
- inline функции реализуем сразу внутри класса.
- Если метод объекта не изменяет его полей, используем
const
в его определении - Не используем слово
mutable
- Используем
explicit
перед всеми конструкторами классов, имеющими ровно 1 аргумент без значения по умолчанию.
// myclass.h
...
class MyClass
{
public:
MyClass();
explicit MyClass(int a, int b = 0);
explicit MyClass(string name);
const Data& get_data() const;
inline void func()
{
/*...*/
}
};