Стиль С/С++. Оформление
Прежде всего, принимаю поздравления – этому блогу уже полгода! За это время мы с вами успели накопить довольно большой запас знаний —
Спасибо за вашу поддержку!
Прошлая статья про стиль оказалась на удивление удачной – как по количеству комментариев, так и по количеству полезной информации. Эту информацию нужно будет переработать, а пока предлагаю вам продолжение – оформление кода. К сожалению, точное форматирование сложно пропустить через все фильтры вордпресса, поэтому в некоторых местах оно съезжает. Не судите строго.
4. Оформление
Каждый привык оформлять код по-своему. Это плохо. Это провоцирует читающего больше обращать внимание на оформление, чем на содержание. Давайте следовать общим правилам! Тогда, наш код можно будет безболезненно использовать повторно. Конечно, привыкание займет некоторое время, но оно того стоит.
4.1 Отступы
Для выделения отступов в коде используйте только пробелы. Настройте ваш редактор так, чтобы он транслировал табуляцию в нужное количество пробелов. Табуляция по-разному отображается разными редакторами.
Используйте 4 пробела для отступа.
Плюсы:
Редакторы воспринимают табуляцию по-разному. Получается, что если открыть ваш код с отступами табуляцией другим редактором, форматирование расплывется.
4.2 Длинна строки
Строки должны быть короче 80 символов.
Плюсы:
Это сделает возможным редактирование двух и более файлов одновременно и гарантирует правильную распечатку исходных текстов.
Минусы:
Современные мониторы легко могут показать строки с длинной значительно превосходящей 80 символов. Ресурс монитора используется не полностью.
4.3 Стиль скобок
Для оформления кода используйте стиль Олмана.
uint32 FunctionName( bool input_parameter_1, bool input_parameter2 )
{
for ( uint i = 5; i < 10; i++ )
{
…
}
}
А не K&R
uint32 FunctionName( bool input_parameter_1, bool input_parameter2 ) {
for ( uint i = 5; i < 10; i++ ) {
…
}
}
Этот стиль хорош тем, что открывающаяся скобка не теряется в отличии от стиля K&R.
Экономия места на экране сегодня не очень актуальна. Если ваша функция не вмещается в один экран 19’’ монитора, то стоит серьезно задуматься о разбиении ее на несколько более простых.
4.4 Объявление и обозначение функций
Функции должны выглядеть следующим образом:
ReturnType FunctionName( Type1 par_name1, Type2 par_name2 )
{
DoSomething();
...
}
или, если функция является членом класса,
ReturnType ClassName::FunctionName( Type par_name1, Type par_name2 )
{
DoSomething();
...
}
Если параметры функции не помещаются на одну строку, стоит перенести их на другую и выровнять по открывающейся скобке:
ReturnType ReallyLongFunctionName( Type param1, Type param2, Type param3 ) { DoSomething(); ... }
В функциях должны сначала следовать входные переменные, затем выходные. Все указатели, которые не изменяют значения в функции должны иметь спецификатор const
Возвращаемое значение должно находится на одной строке с названием функции.
Параметры должны находится на одной строке с названием функции, если они помещаются в строке.
4.5 Разделение кода на блоки
Разделяйте блоки кода пустыми строками. К блокам кода относятся операторы if, while, switch или их группы, группы объявляемых переменных. Старайтесь, чтобы в одном блоке было не более пяти сущностей – это сделает чтение кода легче. Старайтесь прокомментировать что делает блок. Это лучше, чем комментарии на каждой строке. Следующий пример демонстрирует такой подход:
volatile static bool stage2_enabled;
volatile static bool stage3_enabled;
// блок переменных stageX_enabled
volatile static bool stage2_trigger_once;
volatile static bool stage3_trigger_once;
// блок переменных stageX_counter
volatile static uint16 stage2_counter;
volatile static uint16 stage3_counter;
…
if ( (SpiRegRx( 0x40 ) >> 8 ) != 0xc0 ) while(1) {};
// блок for
// Write registers 0~28
for ( i = 0; i < sizeof( rf_regi_table ) / sizeof( *rf_regi_table ); i++)
{
…
}
// блок for
// Are all registers are written correctly ?
uint16 val;
for ( i = 0; i < sizeof( rf_fram_table ) / sizeof( *rf_fram_table ); i++ )
{
…
}
4.6 Выравнивание
Все, что возможно выровнять в столбец, должно быть выровнено. Это сильно облегчает чтение. Держите название переменных в одном столбце, а квалификаторы и тип в другом.
uint8 channel; static uint8 adress; static volatile EmOnPacket on_packet;
uint8 Channel;
static uint8 Adress;
static volatile em_OnPacket onPacket;
Пример выравнивания выражений:
DDRD = 0xff — EM_PKT_PD — EM_FIFO_PD;
PORTB = 0;
PORTC = 0;
PINC ^= 0x1F;
4.7 Пробелы
Унарные операторы должны записываться без пробелов:
!is_ok
~PORTC
i++
(uint32)x
*ptr
&x
sizeof( x )
Бинарные и тринарные операторы записываются с пробелом с каждой стороны от оператора
c1 = c2;
x + y
i += 2;
n > 0 ? n : –n
a < b
c >= 2
Как минимум один пробел ставится после точки с запятой:
for ( i = 0; i < 10; i++ )
После ключевых слов if, else, while, for, switch и return ставится один пробел:
if ( a > b )
while ( x > 0 )
for ( i = 0; i < 10; i++ )
switch ( x )
return y
А после названий функций – не ставится:
uint32 HartGetAddress( AdressClass *hart_adress )
Выражение со скобками пишется без пробела после открывающейся скобкой и и перед закрывающейся
x = (a + b) * c;
Но вызовы и объявления функций, а также, скобки, относящиеся к ключевым словам должны иметь отступы от аргументов.
for ( i = 0; i < loop_end; i++ )
uint32 FunctionName( bool input_parameter_1, bool input_parameter2 )
FunctionToBeCalled( bool param1, bool param2 );
4.8. Оформление ключевых слов
Для ключевых слов for и while любое выражение, даже пустое должно быть заключено в фигурные скобки (рекомендация MIRSA 59)
Для if любое выражение кроме состоящего из одной операции, написанной на одной строке с if, должно заключаться в скобки.
Пример:
while (1) {}
while (1) { PORTC ^= 0xff; }
if ( set_port == true ) PORTC = portc_settings;
if ( set_port == true )
{
PORTC = portc_settings;
}
Смысл тут в том, что если написать
if ( set_port == true )
PORTC = portc_settings;
то можно случайно добавить
if ( set_port == true )
PORTC = portc_settings;
PORTD = portd_settings;
Не заметив того, что вторая стройка исполнится вне зависимости от условий. Я сам часто натыкался на такие ошибки.
Если проверяется несколько нетривиальных (сложнее, чем a == 1, b == 2) условий, то они должны быть разделены на несколько строк и выровнены
if ( ((uint8)( val >> 8) != rf_fram_table[i][1] ) ||
((uint8)( val & 0x00FF) != rf_fram_table[i][2] ) ) while(1) {};
if ( ((uint8)( val>>8)!=rf_fram_table[i][1] ) || ((uint8)( val&0x00FF)!=rf_fram_table[i][2])) while(1) {};
Switch нужно форматировать так:
switch ( cmd )
{
case CMD_START:
StartEngine();
break;
case CMD_STOP:
case CMD_EMERGENCY:
StopEngine();
break;
default:
ControlEngineParams();
break;
}
Любой непустой case должен заканчиваться break или return (MISRA 61).
goto можно использовать только для выхода из множества вложенных циклов:
for ( i = 0; i < loop_end; i++ )
{
for ( j = 0; j < loop_end; j++ )
{
for ( k = 0; k < loop_end; k++ )
{
for ( l = 0; l < loop_end; l++ )
{
if ( some_condition ) goto loop_escape;
}
}
}
}
loop_escape:
или, в редких случаях, для обработки ошибок
if ( !some_condition ) goto error_label;
if ( !some_condition2 ) goto error_label;
if ( !some_condition3 ) goto error_label;
if ( !some_condition4 ) goto error_label;
error_label:
но не в коем случае не для основного хода программной логики
4.9 Оформление операций
Каждая строка должна содержать только одну операцию.
*++some_ptr = (PTR_TYPE *)&var;
Стоит заменить на
some_ptr++;
*some_ptr = (PTR_TYPE *)&var;
4.10 Оформление макросов
Все аргументы макрофункции при использовании должны быть заключены в скобки
#define MIN(n,m) (((n) < (m)) ? (n) : (m))
Если макрофункция содержит один и более операторов, их необходимо заключить в фигурные скобки
#define CRITICAL_SECTION() { uint8 cs_t_ = __save_interrupt(); __disable_interrupt(); }
Фууух, на сегодня все )) Ваши комментарии были очень полезны в прошлой статье!
uint16 val;
for ( i = 0; i < sizeof( rf_fram_table ) / sizeof( *rf_fram_table ); i++ )
{
…
}
— а объявление переменных не нужно выделять? ))
В функциях должны сначала следовать входные переменные, затем выходные.
в том случае если не важна производительность. Для увеличения производительности параметры функций, так же как и прочие переменные могут группироваться с учетом выравнивания.
Ага, но за выравниванием еще нужно сделить, да и не совсем это в духе высокоуровневых языков.
Раньше использовал стиль Оллмана, но начало оператора (функции) ясно видно благодаря отступам. Думаю большинство после написания оператора сразу творят скобки рефлекторным движением по ХЪ.
Но это уже вопрос религии. )))
Согласен почти со всем, а то бывает такую гадость приходится читать.
Трехпробельный отступ тоже рулит. Подсмотрел в Gentee.
А так нужны железные 4 пробела, а не символ табуляции.
>Редакторы воспринимают табуляцию по-разному. Получается, что если открыть ваш код с отступами табуляцией другим редактором, форматирование расплывется.
Это не баг, а фича!
Каждый делает ширину отступа такой, какая ему нравится. Кому 2, кому 4, а кому 10. Поэтому пробелами всем не угодишь. А вот ширина табуляции (в нормальных редакторах) может произвольно менятся. Поэтому открыв в своём любимом редакторе исходник с табуляцией вместо пробелов вы увидите такие отступы какие вам нравятся, а не те которые по вкусу какому-то левому кодеру. Да и просто напросто кнопок меньше нажимать.
Фактически, я пользуюсь кнопкой табуляции, а редактор настраиваю так, чтобы он вставлял 4 пробела.
Отчасти, goshik прав — редакторы, как правило, настраивают под себя, поэтому увидят желаемые отступы. Но с другой стороны, чтобы для всех было читаемо, есть смысл всем прийти к общим рекомендациям.
while (1) {} тут не рекомендуется ли запись while (1) {;} ?
while (1) {;} правильней. Хотя я еше не видел ниодного компилятора, который убирает этот цикл.
Я пишу непрофессионально и такого рода текст, как
if ( ((uint8)( val >> 8) != rf_fram_table[i][1] ) ||
((uint8)( val & 0x00FF) != rf_fram_table[i][2] ) ) while(1) {};
писал, думая что так было бы доходчивее для чужого взгляда, так:
if ( ((uint8)( val >> 8) != rf_fram_table[i][1] )
|| ((uint8)( val & 0x00FF) != rf_fram_table[i][2] ) ) while(1) {};
или все же лучше так, как Вы рекомендуете?
Первый вариант лучше. Сразу видно, что дальше идет продолжение ифа. а на новой строке || можно и не заметить.
Спасибо! А я в этом случае после if( ставил больше пробелов перед первым условием, соответственно выравнивая и явственней выделяя || во второй строке (соответсвенно и в последующих, если они были).
Последую Вашему совету.
В текущем стандарте Google скобки пишут как:
if (state) {
} else {
}
а в статье используется стиль Олмана:
if (state)
{
}
else
{
}
это изменился стандарт Google или это изменения автора?