Стиль С/С++. Соглашения о именах.
Предлагаю цикл статей по оформлению кода на C/C++ для встраиваемых систем. Написать о стиле целиком – довольно большая работа, поэтому я решил разбить ее на цикл статей. После того, как весь стандарт будет написан, я скомпилирую pdf с квинтэссенцией (О_о), который можно будет распечатать и пользоваться как руководство в вашей работе.
Объединим эмбеддеров Руси!
1. Цель стандарта
Создание вменяемого стандарта (стиля) написания и оформления кода – это, пожалуй, первый и самый простой способ повысить его качество.
Очень важно, чтобы программист при просмотре чужого кода быстро понимал его. Читаемость кода достигается шаблонизацией типичных выражений и соглашением о наименовании. Такой подход позволяет задействовать быстрые ассоциативные механизмы нашего мозга и не привлекать медленное сознание там, где без него можно обойтись.
Также, единый стиль позволяет учесть часто встречающиеся ошибки в кодировании и избежать их.
Я решил не изобретать велосипед. За основу взят стандарт оформления кода google:
https://google-styleguide.googlecode.com/svn/trunk/cppguide.xml
Я лишь уточняю моменты, не описанные в этом стандарте. И адаптирую стандарт для применения во встраиваемых системах.
2. Зачем?
Зачем новый стандарт? Чем от отличается от других, к примеру, от стандарта Micrium?
Во-первых, этот стандарт не является новым и является fork’ом или клоном (кому как удобнее) стандарта гугла. Я лишь уточнаяю некоторые моменты.
Во-вторых, этот стандарт больше ориентирован на скорость написания кода при сохранении качества, чем на бескомпромиссное качество при очень низкой скорости написания кода.
Во-третьих, этот стандарт является родным, русскоязычным и именно вы, да,-да, вы принимаете участие в его создании.
В-четвертых, этот стандарт (в отличии от многих других стандартов кодирования для встраиваемых систем) допускает применение С++.
В любом случае (даже, если этот стандарт не станет распространенным), как минимум я для себя формализую свой стиль написания кода, что поможет мне в будущем.
Итак, поехали!
Это первая, тестовая часть и сегодня мы опишем самое важное – имена.
3. Имена
Самый важный момент в стиле – это стиль обозначения имен переменных. В идеале, прочитав некое название, мы должны иметь возможность определить – что именно оно обозначает – тип, переменную, константу, функцию, или что-то еще.
3.1 Общие правила
Имена функций, переменных и файлов должны быть содержательными. Названия типов и переменных должны быть существительными, а названия функций – глаголами. Используйте имена такой длинны, какой нужно чтобы описать, что делает эта переменная или функция.
Никогда! Никогда! Не называйте переменные и функции транслитом!
Пример хорошо выбранных имен переменных:
int num_errors; // Хорошо.
int num_completed_connections; // Хорошо.
Пример плохо выбранных имен переменных:
int n; // Плохо — бессмысленно.
int nerr; // Плохо – двусмысленная аббревиатура.
int n_comp_conns; // Плохо – двусмысленная аббревиатура.
Имена переменных и типов должны быть существительными MidiHeader, num_of_errors
Имена функций должны быть “командами”: OpenFile(), SetUartBaudRate(). Активно используйте слова Set и Get
Стоит избегать аббревеатур, за исключением общеизвестных.
// Хорошо
int num_dns_connections; // Большинство людей знает, что такое DNS
// Плохо
int wgc_connections; // Только ваша команда знает, о чем речь.
int pc_reader; // Много разных вещей можно обозначить как “pc”
Если вам нужно использовать аббревиатуру в названии функции или типа, поступайте как и с остальными словами – первая буква большая, остальные маленькиед
// Хорошо
class SpiDriver;
// Плохо
uint8 SPIGetChar();
3.1 Названия типов
В названиях всех типов – классов, структур, enum’ов и typedef’ов следует каждое новое слово начинать с большой буквы, не разделяя слова.
class UrlTable { …
class UrlTableTester { …
struct UrlTableProperties { …
// typedefs
typedef hash_map<UrlTableProperties *, string> PropertiesMap;
// enums
enum UrlTableErrors { …
3.2 Названия переменных
Имена переменных состоят из слов с подчерками между ними. К названиям переменных – членов класса добавляется подчерк.
К примеру:
Обычные переменные:
// Хорошо
string table_name;
// Плохо
string tableName;
Переменные-члены класса:
string table_name_; // Подчерк
Переменные в структурах обозначаются как обычные переменные:
struct UrlTableProperties
{
string name;
int num_entries;
};
Глобальные переменные. К названию глобальной переменной следует добавить префикс g_
3.3 Названия функций
Название функций точно такое-же, как и название типов. Каждое новое слово – с большей буквы
AddTableEntry()
DeleteUrl()
Если – член класса используется только для получения доступа к переменной, она обозначается так-же как и переменная.
class MyClass {
public:
…
void DoCountEntries();
int num_entries() const { return num_entries_; }
void set_num_entries(int num_entries) { num_entries_ = num_entries; }
private:
int num_entries_;
};
3.4 Названия констант
Названия констант сильно отличается от названия обычных переменных. Их следует именовать смешанным регистром (как функции или типы) и префиксом k.
К примеру:
const int kDaysInAWeek = 7;
3.5 Названия перечислений
Перечисления именуются так-же, как и константы.
enum UrlTableErrors
{
kOK = 0,
kErrorOutOfMemory,
kErrorMalformedInput,
};
В некоторых случаях удобно использовать стиль макроопределений, но его стоит избегать.
enum AlternateUrlTableErrors
{
OK = 0,
OUT_OF_MEMORY = 1,
MALFORMED_INPUT = 2,
}
3.6 Названия макросов
Макросы называются следующим образом:
#define ROUND(x) …
#define PI_ROUNDED 3.0
Стоит избегать использования макросов где это возможно и заменять их константами или inline функциями!
3.7 Имена файлов
Имена фалов должны быть уникальны и отдельные слова в именах должны разделяться подчерком. Не используйте имен, которые уже используются стандартной библиотекой.
Файлы, содержащие С++ код должны иметь расширение .cpp, файлы содержащие с код — .с заголовочные файлы — .h
Если вам необходимо создать большую inline функцию, или много коротких inline функций, создайте для них файл с окончанием –inl.h
Примеры:
url_table.h // Обьявление класса или интерфейс модуля.
url_table.cpp // Реализация класса или модуля.
url_table-inl.h // Множество inline – функций
3.8 Исключения
Если в стандартной библиотеке С/C++ существует аналог того, что вы хотите назвать, используйте соглашение, принятое в библиотеках.
К примеру:
myfopen() // функция, подобная стандартной fopen()
uint // typedef от int
sparse_hash_map // STL-подобная сущность
UINT8_MAX // константа, как INT_MAX
На сегодня – все. Ваша критика, ваши дополнения и ваша благодарность приветствуются!
по-моему, если добавить к именам букву имени типа, то будет понятней.. хотя, кому как.. лично я всегда обозначаю переменные с буквой-идентификатором типа и никогда не использую нижнее подчеркивание в именах переменных.. да и вообще в каких-либо других именах.
Например, переменные обзываю так: iNumber — сразу видно, что переменная типа int,
или: sTableName — переменная типа стринг )))
и никогда не сокращаю! т.е. не какой-то там num, а полностью — number.
В именах функций и процедур так же не сокращаю слова..
и еще в именах булевых функций в начале добавляю is, например: isNullArray()
ну это так.. издержки моего стиля ))
поправочка: не isNullArray, а IsNullArray — с большой буквы. С маленькой пишу только переменные ))
ps: использовать где-то «приставку» my — идиотизм, по-моему )) это так студенты на первых курсах пту обзывают все переопределенное.. Х)
>именам букву имени типа, то будет понятней. Вот это, ИМХО, худшее что можно только придумать ) В тиоге получается некрасивости типа.
char **lppzsMyCoolStr
Где-то я видел аналог на русском языке
Я программирую на Си = местоимЯ глагПрограммирую частНа сущСи
>никогда не использую нижнее подчеркивание в именах переменных
Почему? Это-ж классный способ разделить сущности и сделать их более читаемыми!
сравни — мой вариант — я_программирую_на_си
и венгерский аналог — действЯПрограммируюНаСи
>и никогда не сокращаю!
Вот это правильно! Полностью согласен. Ингда, правда в лом писать что-то типа
SynchronizeMultipleEvents()
>в начале добавляю is
Тоже полностью согласен, хорошая практика. Вот правда предложение типа
if ( isArrayEmpty() ) немного хуже читается, чем if ( ArrayIsEmpty() )
Имхо, код должен читаться как рассказ, тоесть нужно использовать как можно больше речевых выражений.
>if ( isArrayEmpty() ) немного хуже читается, чем if ( ArrayIsEmpty() )
а если использовать хорошую ide, то ArrayIsEmpty — заколебешся искать )) если только все имена держать в памяти %))))
проще же: ввел is, вызвал хелпера и посмотрел, что из булевых функций у тебя имеется.. 🙂
по поводу буквы имени типа: если переменных дофига, и если кода дофига, придется часто возвращаться к их объявлению и смотреть их тип.. либо опять же, держать все в памяти ))
а у меня вот память девичья.
Да, насчет is — ты права. Но насчет венгерской нотации — я всетаки не согласен. Она сложно читается. Более того, когда захочешь поменять тип переменной, прийдется менять все ее включения, некоторые из которых могут и не найтись — получится каша.
А если хочешь использовать переменную, то держать обьявления в голове всеравно прийдется. Более того, прийдется держать в голове, что эта переменная обозначает, пределы ее изменения и прочую чушь.
Я предпочитаю (и не только я) отходить от базовых типов к более абстрактным, к типам связанным с целевой областью. И вот тут венгерская нотация никак уже не поможет — ведь тебе для каждого класса или структуры нужн будет придумать букву.
Вечером добавлю, кстате про единицы измерения — их всетаки стоит добавлять в названия и про is тоже добавлю.
> class SpiDriver; // Хорошо
> uint8 SPIGetChar(); // Плохо
Согласен со всем, что предложено в этой статье кроме вышеупомянутого момента. Если в названии переменной, а особенно – функции содержится аббревиатура, а не общее слово, думаю, надо писать все буквы большими, как LCDInit(); или SPIGetChar();, желательно даже подчеркиванием отделить аббревиатуру – LCD_Init(); или SPI_GetChar(); так как сразу становится понятно с каким модулем работает функция, это особенно важно в программировании встроенных систем, так как под этими же аббревиатурами некоторые модули записаны в даташитах и юзер-мануалах. Это точно так же, как аббревиатурами названы регистры и биты в них, и их всегда принято писать большими буквами.
Насчет аббервиатур — вот хороший пример что бует, если писать все большими буквами, то получится:
GetHTTPXMLURI
Прочитать не возможно, хотя все аббревиатуры общеизвестны.
Если разделять подчерком, то
Get_HTTP_XML_URI();
Смотриться ужасно, правда? И такое название будет слишком похоже на макрос.
А теперь мой вариант
GetHttpXmlUri();
Вроде даже не вызывает отвращения.
Имя модуля действительно удобно ставить в начале к примеру,
LcdPutString();
выглядит не намнго хуже
LСD_PutString();
В принципе, запись LCD_PutChar() это аналог обращения к экземпляру класса LCD -lcd.PutChar()).
Согласно остальным соглашениям оно должно быть маленькими буквами. В итоге получатся, что стоит писать
lcd_PutStr();
Получается путанница с именами перменных (ведь мозг первым делом смотрит на первую букву да еще и подчерк там есть).
Поэтому, мне кажется, что вариант LcdPutString() — наилучший. Более того, этот вариант проверен гуглом, микриумом и прочими монстрами. Они конечно тоже могут заблуждаться, но что-то мне подсказывает, что их опыту можно доверять.
Примеры названий функций от микрума
OSTaskCreateExt
OSSemPendAbort
OSTaskNameSet
OSStart
название переменных:
OS_TCB *ptcb;
INT8U i;
OS_CPU_SR cpu_sr = 0u;
от гугла:
void BrowserTitlebar::Init()
void BrowserTitlebar::Observe(NotificationType type,
const NotificationSource& source,
const NotificationDetails& details)
в исходниках гугла на яве можно нати довольно красивые выражения:
getScaledHeight(DisplayMetrics metrics)
isRecycled()
hasAlpha()
и, для сравнения — венгерская нотация от микрософта
INT GetAddressByName(
DWORD dwNameSpace, // name space to query for service address information
LPGUID lpServiceType, // the type of the service
LPTSTR lpServiceName, // the name of the service
LPINT lpiProtocols, // points to array of protocol identifiers
DWORD dwResolution, // set of bit flags that specify aspects of name resolution
LPSERVICE_ASYNC_INFO lpServiceAsyncInfo, // reserved for future use, must be NULL
LPVOID lpCsaddrBuffer, // points to buffer to receive address information
LPDWORD lpdwBufferLength, // points to variable with address buffer size information
LPTSTR lpAliasBuffer, // points to buffer to receive alias information
LPDWORD lpdwAliasBufferLength // points to variable with alias buffer size information
);
Пример оформления кода согласно тому, что я тут написал от самого гугла легко найти в тырнете, к примеру в исходниках их браузера — хрома
https://www.google.com/codesearch/p?hl=ru#h0RrPvyPu-c/chrome/browser/gtk/browser_titlebar.cc&q=google%20chrome%20lang:c++&d=3
Хорошую тему поднял, очень полезно 🙂 Только я вот одно понять не могу, почему рекомендуется не юзать макросы, ведь мне кажется очень удобно писать
#define TSTBIT1(ADDRESS,B) (ADDRESS & (BIT(B)))// проверка бита на единицу
#define BUTTON_2_ON TSTBIT1(PIND,7)
Еще конечно проблема как лучше писать например SpiInit() или InitSpi(), LcdPutString() или PutStringLcd()
Макросы не рекомендуется юзать, потому, что они небезопасны.
Альтернатива — inline функции с константными аргументами. И константные переменные.
Хотя, конечно, для встраиваемых систем можно сделать исключение, потому как тут случайная не inline’изация может дорого обойтись.
>Еще конечно проблема как лучше писать например SpiInit() или InitSpi(), >LcdPutString() или PutStringLcd()
Проблемы никакой нет. Сначала название модуля, потом действие
Правильные варианты —
SpiInit()
LcdPutString()
То есть положение глагола может быть любым в названии функции? Как его правильно выбрать? В примере приведено OpenFile(), можно ли написать FileOpen() или UartBaudRateSet(), что нелогично ?
В варианте с фалом лушче FileOpen(), так как первое слово указывает на модуль — файл.
С уартом — глагол лушче стаивть так, как это читается по английски —
UartSetBaudRate()
имхо, лучше OpenFile(), т.к. это — функция/процедура, т.е. главное — действие. Т.е. она открывает, а вот что открывает — это уже второстепенное.
>это уже второстепенное
Что первостепенно — вопрос спорный. Функция — это всегда действие.
OpenFile() может оказаться лучше, так как это элемент библиотеки VCL. Да и читается лучше. А вот большинство фаловых систем экспортируют функцию
f_open(), что вяляется аналогом FileOpen().
Я, все-таки, считаю, что модуль, к которому относитьсся функция должен быть на первом месте. Меня поддерживает и Страуструп, к примеру, плюсовая запись FileOpen():
ExcitingFileSystem::awesome_file.Open();
всегда везде все пытаются придумать свой coding style … я например программист и пишу на с++ … могу предложить общепринятый и проверенный временем coding style … имя ему KDE coding style — этого стиля придерживаются разработчики linux … и Qt … глупо придумывать свое свое когда есть уже что-то чего придерживается огромное количество людей … например table_name — выглядит ужасно .. куда приятнее читать tableName … да и вообще лучше писать с маленькой буквы начало имени переменной — так читабельнее … как по мне KDE coding style — вот чего стоит придерживаться …
да у и конечно венгерская нотация это ****** если кто-то хочет ее предложить …
А я и не придумывал свой стиль. Это — перевод coding style от google, в статье это явно указано.
да … видимо в 2 часа ночи лучше внимательнее вчитываться в текст — буду внимательнее … но мне все равно не нравиться знак нижнего подчеркивания в именах переменных … tableName — куда нагляднее
Знак подчеркивания — штука правильная, особенно, если бы он был в начале слова, к примеру _table_name — так автоподстановка сразу покажет все локальные переменные класса. К сожалению, подчеркивание в начале класса зарезервировано для внутренних нужд си-библиотек.
Думаю … это уже дело вкуса … его ставить не удобно — тянуться далеко … в KDE coding stule нижнее подчеркивание ставиться только в именах файлов … например v_robot_facade.h в этом фале лежит класс VRobotFacade …
по опыту могу сказать что нужно и приходиться придерживаться того стиля который либо если в библиотеке если вы ее расширяете … либо стиля выбранного в начале очередного программного проекта … ибо нет ничего ужаснее чем огромный проект написанный в разных стилях …
да и еще я бы добавил что все члены класса ДОЛЖНЫ быть с префиксом m_ … с ним куда удобнее читать и понимать чужой код — а это то для чего и придуман стиль … чтобы быстрее в коде разобраться …
Ну, соглашение целиком и полностью — дело вкуса. В гугльстайле вместо превикса используется постфикс. Я, кстате, согласен, что префикс — лучше.