﻿# Как уменьшить вероятность ошибки на этапе написания кода\. Заметка N2

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

## Введение

![a0072_expressions_ru/image1.png](https://import.viva64.com/docx/blog/a0072_expressions_ru/image1.png)

С предыдущей заметкой можно познакомиться [здесь](https://pvs-studio.ru/ru/blog/posts/cpp/a0070/)\. В этот раз примеры ошибок будут взяты из различных известных проектов, чтобы подчеркнуть их распространенность\. Ошибки, которые будут продемонстрированы, были найдены мной с помощью анализатора [PVS\-Studio](https://pvs-studio.ru/ru/pvs-studio/) за относительно большой промежуток времени\. Практически про все я писал разработчикам и поэтому можно надеяться, что они поправлены в новых редакциях кода\. Это я пишу во введении, потому что после статей мне всегда приходят письма с просьбой "напишите про найденные вами ошибки разработчикам проекта"\.

## 1\. Не используйте тернарную операцию '?:' в составных выражениях

[Тернарная условная операция](https://ru.wikipedia.org/wiki/%D0%A2%D0%B5%D1%80%D0%BD%D0%B0%D1%80%D0%BD%D0%B0%D1%8F_%D1%83%D1%81%D0%BB%D0%BE%D0%B2%D0%BD%D0%B0%D1%8F_%D0%BE%D0%BF%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D1%8F) записывается в языке Си/Си\+\+ с помощью оператора '?:'\. Операция, возвращающая свой второй или третий операнд в зависимости от значения логического выражения, заданного первым операндом\. Пример:

```cpp
int minValue = A < B ? A : B;
```

Тернарная операция имеет очень низкий приоритет \(см\. таблицу\)\. Про это часто забывают и именно из\-за этого тернарная операция крайне опасна\.

![a0072_expressions_ru/image2.png](https://import.viva64.com/docx/blog/a0072_expressions_ru/image2.png)

Рисунок 1 \- Операции языка Си/Си\+\+, в порядке снижения приоритета

Обратите внимание, что операция '?:' имеет более низкий приоритет, чем сложение, умножение, оператора побитового ИЛИ и так далее\. Рассмотрим код:

int Z \= X \+ \(A \=\= B\) ? 1 : 2;

Этот код работает не так, как может показаться на первый взгляд\. Программист, скорее всего, хотел сложить значение X с числом 1 или с числом 2, в зависимости от условия \(A \=\= B\)\. Но на самом деле условием является выражение "X \+ \(A \=\= B\)"\. Фактически, здесь написано:

int Z \= \(X \+ \(A \=\= B\)\) ? 1 : 2;

А хотелось:

int Z \= X \+ \(A \=\= B ? 1 : 2\);

Первая мысль – так ведь надо знать приоритет операций\. А программисты их и знают, но уж очень коварна  эта тернарная операция\! Ошибки с ней допускают не только новички, но и матёрые программисты\. Её нередко можно встретить даже в самом качественном коде\. Вот пара примеров\.

![a0072_expressions_ru/image3.png](https://import.viva64.com/docx/blog/a0072_expressions_ru/image3.png)

[V502](https://pvs-studio.ru/ru/docs/warnings/v502/)  Perhaps the '?:' operator works in a different way than it was expected\. The '?:' operator has a lower priority than the '\*' operator\.  physics  dgminkowskiconv\.cpp  1061

```cpp
dgInt32 CalculateConvexShapeIntersection (...)
{
  ...
  den = dgFloat32 (1.0e-24f) *
        (den > dgFloat32 (0.0f)) ? 
           dgFloat32 (1.0f) : dgFloat32 (-1.0f);
  ...
}
```

![a0072_expressions_ru/image4.png](https://import.viva64.com/docx/blog/a0072_expressions_ru/image4.png)

V502 Perhaps the '?:' operator works in a different way than it was expected\. The '?:' operator has a lower priority than the '\-' operator\.  views custom\_frame\_view\.cc 400

```cpp
static const int kClientEdgeThickness;
int height() const;
bool ShouldShowClientEdge() const;
void CustomFrameView::PaintMaximizedFrameBorder(gfx::Canvas* canvas) {
  ...
  int edge_height = titlebar_bottom->height() -
                    ShouldShowClientEdge() ? kClientEdgeThickness : 0;
  ...
}
```

![a0072_expressions_ru/image5.png](https://import.viva64.com/docx/blog/a0072_expressions_ru/image5.png)

V502 Perhaps the '?:' operator works in a different way than it was expected\. The '?:' operator has a lower priority than the '\|' operator\. vm vm\_file\_win\.c 393

```cpp
#define FILE_ATTRIBUTE_NORMAL    0x00000080
#define FILE_FLAG_NO_BUFFERING    0x20000000
vm_file* vm_file_fopen(...)
{
  ...
  mds[3] = FILE_ATTRIBUTE_NORMAL |
           (islog == 0) ? 0 : FILE_FLAG_NO_BUFFERING;
  ...
}
```

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

Избежать подобных ошибок можно, если не стремиться поместить несколько операций в одну строку кода\. А если и помещать, то не жадничать поставить дополнительные скобки\. Про скобки мы поговорим ниже\. Сейчас попробуем просто предотвратить потенциальные ошибки при использовании '?:'\.

Конечно оператор '?:' является синтаксическим сахаром и его практически всегда можно заменить на if\. К редким исключениям относится такие задачи, как инициализация ссылки:

```cpp
MyObject &ref = X ? A : B;
```

Конечно, и с этим проблем нет, но создание ссылки на A или B без оператора '?:' будет гораздо многословней:

```cpp
MyObject *tmpPtr;
If (X)
  tmpPtr = &A;
else
  tmpPtr = &B;
MyObject &ref = *tmpPtr;
```

Итак, отказываться от оператора '?:' не рационально\. Но и ошибиться с ним просто\. Поэтому лично я выработал для себя следующее правило\. Результат '?:' должен сразу куда\-то помещаться и не сочетаться с другими действиями\.  То есть слева от условия оператора '?:' должна стоять операция присваивания\. Вернемся к первоначальному примеру:

```cpp
int Z = X + (A == B) ? 1 : 2;
```

Я предлагаю писать так:

```cpp
int Z = X;
Z += A == B ? 1 : 2;
```

В случае примера, относящегося к IPP Samples, я бы написал так:

```cpp
mds[3] = FILE_ATTRIBUTE_NORMAL;
mds[3] |= (islog == 0) ? 0 : FILE_FLAG_NO_BUFFERING;
```

С рекомендацией можно не согласиться\. Не буду её отстаивать\. Например, мне не очень нравится, что вместо одной строки мы получаем две или более\. Неплохой альтернативой может быть обязательное взятие оператора  '?:' в скобки\. Я ставлю основной задачей показать паттерны ошибок\. Выбор паттерна защиты от ошибок зависит от предпочтений программиста\.

## 2\. Не стесняйтесь использовать скобки

Почему\-то так повелось, что использование лишних скобок при программировании на Си/Си\+\+ считается неким постыдным действием\. Возможно, это идёт от того, что вопрос про приоритет операций очень любят задавать на собеседовании\. И у человека подсознательно откладывается стремление всегда максимально использовать механизм приоритетов\. Ведь если он поставит лишние скобки, то вдруг кто\-то подумает что он новичок, а не истинный джедай\.

Я даже встречал на просторах интернета обсуждение, где человек категорично утверждал, что использование лишних скобок \- это плохой тон\. А если человек не уверен как будет вычислено выражение, то ему надо учиться, а не программы писать\. К сожалению, не смог найти эту дискуссию, но я не разделяю подобные точки зрения\. Знать приоритеты, конечно, надо, но если в выражении используются разнородные операции, то лучше подстраховывать себя скобками\. Это не только защитит от потенциальных ошибок, но и сделает код более легко читаемым для других разработчиков\. 

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

![a0072_expressions_ru/image6.png](https://import.viva64.com/docx/blog/a0072_expressions_ru/image6.png)

[V564](https://pvs-studio.ru/ru/docs/warnings/v564/) The '&' operator is applied to bool type value\. You've probably forgotten to include parentheses or intended to use the '&&' operator\.  game g\_client\.c 1534

```cpp
#define SVF_CASTAI 0x00000010
char *ClientConnect(...) {
  ...
  if ( !ent->r.svFlags & SVF_CASTAI ) {
  ...
}
```

![a0072_expressions_ru/image7.png](https://import.viva64.com/docx/blog/a0072_expressions_ru/image7.png)

V564 The '&' operator is applied to bool type value\. You've probably forgotten to include parentheses or intended to use the '&&' operator\.  dosbox sdlmain\.cpp 519

```cpp
static SDL_Surface * GFX_SetupSurfaceScaled(Bit32u sdl_flags, 
                                            Bit32u bpp) {
  ...
  if (!sdl.blit.surface || (!sdl.blit.surface->flags&SDL_HWSURFACE)) {
  ...
}
```



Ну и еще один пример из **Chromium**:

V564 The '&' operator is applied to bool type value\. You've probably forgotten to include parentheses or intended to use the '&&' operator\. base platform\_file\_win\.cc 216

```cpp
#define FILE_ATTRIBUTE_DIRECTORY 0x00000010
bool GetPlatformFileInfo(PlatformFile file, PlatformFileInfo* info) {
  ...
  info->is_directory =
    file_info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY != 0;
  ...
}
```

Выражения просты\. Разработчики молодцы\. А ошибки всё равно есть\. Так что использование скобок в скользких местах лишним не будет\.

Наверное, стоит поступать так\. Если операции просты и привычны, то дополнительные скобки не нужны\. Примеры:

```cpp
if (A == B && X != Y)
if (A - B < Foo() * 2)
```

А вот если используются более редкие операторы \(\~, ^, &, \|, <<, \>\>, ?:\), то явные скобки не повредят\. Это и упростит чтение кода и защит от потенциальной ошибки\. Примеры:

```cpp
If ( ! (A & B))
x = A | B | (z < 1 ? 2 : 3);
```

Не стесняться использовать скобки при использовании редких операций поможет и для рассмотренного ранее оператора "?:"\. Как именно лучше поступить с "?:"  \- дело вкуса\. Мне более нравится вариант с упрощением\. 

## Заключение

Пишите простой и понятный код\. Разбивая длинные и сложные выражения на несколько строк, вы получаете более длинный код\. Но такой код бывает намного проще понять и прочитать\. В нем меньше вероятность допустить ошибку\. Не бойтесь завести лишнюю переменную\. Компилятор отлично всё оптимизирует\.

**Не жадничайте на скобках в выражениях, где используются редкие операторы или где смешиваются битовые и логические операции\.**

Программист, который в будущем будет читать ваш код со скобками, только скажет спасибо за это\.