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

Форматирование кода таблицей \("табличное" оформление кода\) помогает сделать текст программы простым для чтения и восприятия\. Это, в свою очередь, упрощает проведение [обзоров кода](https://pvs-studio.ru/ru/blog/terms/0073/) и выявление ошибок и опечаток\.

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

Рассмотрим фрагмент кода из проекта ReactOS, в котором мы обнаружили ошибку благодаря предупреждению PVS\-Studio: [V560](https://pvs-studio.ru/ru/docs/warnings/v560/) A part of conditional expression is always true: 10035L\.

```cpp
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`\. Корректный код:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

## Ложка дёгтя

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

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

Мы [встретили](https://pvs-studio.ru/ru/blog/examples/v536/) этот код в проекте eLynx SDK\. Программист хотел выровнять код, поэтому перед `713` дописал `0`\. К сожалению, он не учёл, что `0` в начале числа означает, что число будет представлено в восьмеричном формате\.

## Массив строк

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

Фрагмент взят из проекта Asterisk\. Ошибка выявляется диагностикой PVS\-Studio: [V653](https://pvs-studio.ru/ru/docs/warnings/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"\.

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

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

```cpp
  ....
  "KW_INCLUDESKW_JUMP",
  ....
```

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

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

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

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

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

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

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

1. Андрей Карпов\. [Распространённые паттерны опечаток при программировании](https://pvs-studio.ru/ru/blog/posts/cpp/1064/)\.
1. Андрей Карпов\. [Главный вопрос программирования, рефакторинга и всего такого](https://pvs-studio.ru/ru/blog/posts/cpp/0391/)\.