Мы используем куки, чтобы пользоваться сайтом было удобно.
Хорошо
to the top

Вебинар: C++ и неопределённое поведение - 27.02

>
>
>
Форматирование кода таблицей

Форматирование кода таблицей

25 Фев 2025

Форматирование кода таблицей ("табличное" оформление кода) помогает сделать текст программы простым для чтения и восприятия. Это, в свою очередь, упрощает проведение обзоров кода и выявление ошибок и опечаток.

Принцип использования

Рассмотрим фрагмент кода из проекта ReactOS, в котором мы обнаружили ошибку благодаря предупреждению PVS-Studio: V560 A part of conditional expression is always true: 10035L.

void adns__querysend_tcp(adns_query qu, struct timeval now) {
  ....
  if (!(errno == EAGAIN || EWOULDBLOCK || 
        errno == EINTR || errno == ENOSPC ||
        errno == ENOBUFS || errno == ENOMEM)) {
  ....
}

Здесь приведён маленький фрагмент кода. Найти в нём ошибку несложно, но в реальном коде заметить её весьма проблематично. Взгляд просто пропускает блок однотипных сравнений и идёт дальше.

Почему разработчики пропускают такие ошибки? Условия плохо отформатированы, поэтому не хочется внимательно их читать, прикладывая усилия. Человек надеется, что раз проверки однотипные, то всё хорошо, и автор кода не допустил ошибок в условии.

Одним из способов борьбы с таки опечатками как раз и является "табличное" оформление кода.

Ошибка здесь в том, что в одном месте пропущено errno ==. В результате условие всегда истинно, так как константа EWOULDBLOCK равна 10035. Корректный код:

if (!(errno == EAGAIN || errno == EWOULDBLOCK || 
      errno == EINTR || errno == ENOSPC ||
      errno == ENOBUFS || errno == ENOMEM)) {

Теперь рассмотрим, как лучше провести рефакторинг этого фрагмента. Для начала рассмотрим код, оформленный самым простым "табличным" способом.

if (!(errno == EAGAIN  || EWOULDBLOCK     || 
      errno == EINTR   || errno == ENOSPC ||
      errno == ENOBUFS || errno == ENOMEM)) {

Стало лучше, но ненамного. Такой стиль оформления неудовлетворителен по двум причинам:

  • Ошибка по-прежнему не очень заметна;
  • Приходится вставлять большое количество пробелов для выравнивания.

Поэтому надо сделать два усовершенствования в оформлении кода. Первое: не больше одного сравнения на строку, тогда ошибку легко заметить. Смотрите, в таком коде опечатка сразу бросится в глаза:

a == 1 &&
b == 2 &&
c      &&
d == 3 &&

Второе: рационально писать операторы &&, || и т. д. не справа, а слева.

Обратите внимание, как много работы для написания пробелов:

x == a          &&
y == bbbbb      &&
z == cccccccccc &&

Так работы намного меньше:

   x == a
&& y == bbbbb
&& z == cccccccccc

Выглядит код немного необычно, но к этому быстро привыкаешь. Поэтому мы рекомендуем именно такой подход к форматированию кода.

Объединим это всё вместе и напишем в новом стиле код, приведённый в начале:

if (!(   errno == EAGAIN
      || EWOULDBLOCK
      || errno == EINTR
      || errno == ENOSPC
      || errno == ENOBUFS
      || errno == ENOMEM)) {

Код стал занимать больше строк, зато ошибка стала заметнее. Увеличившееся количество строк вообще не является в чем-то плохим в этом случае. Лёгкость чтения и отсутствие опечаток куда важнее количества строк кода.

Можно продолжить рефакторинг:

const bool error =    errno == EAGAIN
                   || errno == EWOULDBLOCK
                   || errno == EINTR
                   || errno == ENOSPC
                   || errno == ENOBUFS
                   || errno == ENOMEM;
if (!error) {

Ещё один возможный шаг по улучшению — вынести код проверки в функцию:

static bool IsInterestingError(int errno)
{
  return    errno == EAGAIN
         || errno == EWOULDBLOCK
         || errno == EINTR
         || errno == ENOSPC
         || errno == ENOBUFS
         || errno == ENOMEM;
}
....
if (!IsInterestingError(errno)) {

Ложка дёгтя

В редких случаях форматирование "таблицей" может пойти во вред. Вот один из примеров:

inline 
void elxLuminocity(const PixelRGBi& iPixel,
                   LuminanceCell< PixelRGBi >& oCell)
{
  oCell._luminance = 2220*iPixel._red +
                     7067*iPixel._blue +
                     0713*iPixel._green;
  oCell._pixel = iPixel;
}

Мы встретили этот код в проекте eLynx SDK. Программист хотел выровнять код, поэтому перед 713 дописал 0. К сожалению, он не учёл, что 0 в начале числа означает, что число будет представлено в восьмеричном формате.

Массив строк

Форматирование таблицей можно применять не только к условиям, но и к совершенно разным конструкциям языка.

Фрагмент взят из проекта Asterisk. Ошибка выявляется диагностикой PVS-Studio: V653 A suspicious string consisting of two parts is used for array initialization. It is possible that a comma is missing. Consider inspecting this literal: "KW_INCLUDES" "KW_JUMP".

static char *token_equivs1[] =
{
  ....
  "KW_IF",
  "KW_IGNOREPAT",
  "KW_INCLUDES"
  "KW_JUMP",
  "KW_MACRO",
  "KW_PATTERN",
  ....
};

Опечатка — забыта запятая. В результате две различные по смыслу строки соединяются в одну, т.е. c точки зрения компилятора здесь написано:

  ....
  "KW_INCLUDESKW_JUMP",
  ....

Ошибки можно было бы избежать, выравнивая код таблицей. Тогда, если запятая будет пропущена, это будет легко заметить.

static char *token_equivs1[] =
{
  ....
  "KW_IF"        ,
  "KW_IGNOREPAT" ,
  "KW_INCLUDES"  ,
  "KW_JUMP"      ,
  "KW_MACRO"     ,
  "KW_PATTERN"   ,
  ....
};

Как и в прошлый раз, обращаю внимание, что если мы ставим разделитель справа (в данном случае это запятая), то приходится добавлять массу пробелов, что неудобно. Особенно неудобно, если появляется новая длинная строка/выражение — придётся переформатировать всю таблицу.

Поэтому вновь актуальна рекомендация оформлять код так:

static char *token_equivs1[] =
{
  ....
  , "KW_IF"
  , "KW_IGNOREPAT"
  , "KW_INCLUDES"
  , "KW_JUMP"
  , "KW_MACRO"
  , "KW_PATTERN"
  ....
};

Теперь очень легко заметить недостающую запятую и нет необходимости писать много пробелов — код красивый и понятный. Оформление поначалу кажется странным, но к нему быстро привыкаешь, попробуйте.

Дополнительные ссылки

Популярные статьи по теме


Комментарии (0)

Следующие комментарии next comments
close comment form
close form

Заполните форму в два простых шага ниже:

Ваши контактные данные:

Шаг 1
Поздравляем! У вас есть промокод!

Тип желаемой лицензии:

Шаг 2
Team license
Enterprise license
** Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности
close form
Запросите информацию о ценах
Новая лицензия
Продление лицензии
--Выберите валюту--
USD
EUR
RUB
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

close form
Бесплатная лицензия PVS‑Studio для специалистов Microsoft MVP
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

close form
Для получения лицензии для вашего открытого
проекта заполните, пожалуйста, эту форму
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

close form
Мне интересно попробовать плагин на:
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

close form
check circle
Ваше сообщение отправлено.

Мы ответим вам на


Если вы так и не получили ответ, пожалуйста, проверьте, отфильтровано ли письмо в одну из следующих стандартных папок:

  • Промоакции
  • Оповещения
  • Спам