Pull to refresh

Comments 46

Не совсем понятно почему упор сделан именно на ошибки с C/C++. Это достаточно распространенный синтаксис и встречается во многих языках.
Ага, сам постоянно туплю, когда одно из условий с отрицанием (когда оба, тогда проще — надо составить условие без отрицаний, а потом || на && и наоборот заменить). Правда == && == у себя в ошибках не встречал.
Я когда делаю рефакторинг с вынесением отрицаний наружу или внесением внутрь, не задумываюсь о смысле выражения, а просто механически применяю формулы. Ошибок не возникает.
Видимо потому что PVS-studio чаще всего C\C++ проекты проверяет открытые. А так да, проблема с операторами думаю для всех языков актуальна.
По поводу тернарного оператора — уже давно вошло в привычку брать его в скобки. И условие в свои отдельные скобки. ИМХО — это должно войти в привычку у многих. Иначе от граблей из-за различных приоритетов долго будут страдать все…
И еще — когда идут сложные условия ветвления (больше 1), рекомендуется хоть как-то составить таблицу истинности — никто не застрахован от ошибок, а так за счет пары лишних минут можно прилично уменьшить вероятность создания бага.
UFO just landed and posted this here
if( (pSymDef->GetType() != SbxEMPTY) ||
    (pSymDef->GetType() != SbxNULL) )

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

А вот о правильных вариантах почти невозможно рассуждать, потому что у каждого будет своя логика в программе.

Например, в условии ниже есть ошибка — оно всегда истинно:
if ( err != code1 || err != code2)
{
  ....
}

Сделав замену оператора || на && мы получим такое условие:
if ( err != code1 && err != code2)
{
  ....
}

которое будет истинно для любых значений err, не равных code1 и code2. В этом синтетическом примере отсутствует исходная логическая ошибка, но мы не можем утверждать, что это будет правильной правкой в каждом конкретном проекте. А условия, которые не зависят от части логического подвыражения, непонятно даже как исправить на синтетическом примере, не то, что в реальном коде.

Я несколько о другом говорил. Не знаю, как у всех, но части людей нельзя говорить про то, как делать нельзя, но не говорить про то, как делать можно (хотя оно логически вытекает из запрета). Простой пример: мать посылает сына в магазин за хлебом и говорит: «Купи любой, только батон нарезной не покупай». И пока он собирается и одевается, постоянно напоминает — «не покупай нарезной». Приходит сын в магазин и вспоминает — «Какой хлеб купить надо было? А! Мама постоянно про батон нарезной говорила, куплю его.»

Я не рассуждаю о том, что два оставшихся варианта всегда правильны. Но статья нам доказывает, что перечисленные четыре — всегда избыточны в количестве условий. Поэтому, если наше условное выражение не подходит под != && != или == || ==, то оно заведомо содержит в себе лишние условия.
Кому как, но мне проще запомнить два верных в большинстве случаев выражения, чем запоминать четыре, которые содержат лишнее условие.
Существуют и такие, и такие методологии обучения, у всего есть плюсы/минусы, свои требования к аудитории. Я вот увидел статью в таком ключе.
UFO just landed and posted this here
UFO just landed and posted this here
Не понял пассажа про ошибки и ВУЗ, поясните, пожалуйста.
UFO just landed and posted this here
Эмм, а что это за поток сознания? Где кому какие ошибки в вузе внушают\развивают?
UFO just landed and posted this here
Это прям какой-то конкурс на звание «мистер-неопределенность».
Можно больше конкретики?
UFO just landed and posted this here
Как в вашу теорию вписываются зарубежные разработчики, участвующие и делающие ошибки в открытых проектах? В западных вузах совсем иной подход к обучению программированию.
UFO just landed and posted this here
Осталось узнать, как оно работает при таком коде.
Defensive programming. То, что не отловится одной проверкой заметит другая… скорее всего.

С одной стороны даёт возможность создавать современных монстров на сотни миллионов строк кода, а с другой — гарантирует что дыры в безопасности будут «лататься» вечно.

Чтобы дыр не было нужно писать fail-fast/crash-only software и/или использовать GIGO (тогда проверок в системе остаётся мало и каждую из них можно хорошо обдумать), но это замедляет разработку кода, потому так никто не делает…
Надо бы ещё добавить проверку флагов. Тоже большой источник ошибок.
«Подстраховать себя от таких ошибок можно путём проверки своего кода с помощью построения таблиц истинности.»

Есть еще один метод — выучить, наконец, законы де Моргана.
Ага, с их помощью не очень очевидное
err != code1 || err != code2

становится более понятным
!(err == code1 && err == code2)
#include <iostream>

enum ERROR{
		code1 = 0,
		code2 = 1,
		code3 = 2
	};

int main(int argc, char const *argv[])
{


	ERROR err  = code3;

	if ( err == code1 || err == code2)
	{
  		std::cout<< "err equal code1 or code2: "<<err<<std::endl;

	} else {

		std::cout<< "err is: "<<err<<std::endl;
	}

	return 0;
}


g++ log.cpp
./a.out
err is: 2


Логическое условие выполняется, как и задумано. Т.е. выполняется ветвь кода, когда ERROR err = code3
Это вы к чему написали? В вашем случае ошибки нет.
См. https://habrahabr.ru/company/pvs-studio/blog/281316/#comment_8848274
Т.е. в таких ситуациях писать switch безопаснее. Но длиннее…
Ещё можно было напомнить, что, когда компилятор не вычисляет правую часть выражения, при определённых условиях в || и &&
В случае, если заменить условие

if ( err != code1 || err != code2)

Компилятор вычислив левую часть выражения и получив true — правую (после оператора || )вычислять уже не будет, и вывод программы будет ошибочным

#include <iostream>

enum ERROR{
		code1 = 0,
		code2 = 1,
		code3 = 2
	};

int main(int argc, char const *argv[])
{


	ERROR err  = code3;

	if ( err != code1 || err != code2)
	{
  		std::cout<< "err equal code1 or code2: "<<err<<std::endl;

	} else {

		std::cout<< "err is: "<<err<<std::endl;
	}

	return 0;
}


g++ log.cpp
./a.out
err equal code1 or code2: 2

В предыдущем комментарии забыл поправить иходник.
Тут видя false в первой половине, компилятор вычисляет вторую и она оказывается true — при || итог всего условия true, хотя err == code1

#include <iostream>

enum ERROR{
		code1 = 0,
		code2 = 1,
		code3 = 2
	};

int main(int argc, char const *argv[])
{


	ERROR err  = code1;

	if ( err != code1 || err != code2)
	{
  		std::cout<< "err isn't equal code1 or code2: "<<err<<std::endl;

	} else {

		std::cout<< "err is: "<<err<<std::endl;
	}

	return 0;
}




g++ log.cpp
./a.out
err isn't equal code1 or code2: 0

Это всё понятно. Только мало кто во время работы пишет экспериментальные программки.
На каждом шагу. Ну, для проверки двух условий в ифе — нет, конечно, но перед тем, как притянуть в проект какой-то паттерн или либу — почти каждый раз.
А еще наверное частая ошибка когда без скобочек используют маски и сравнения вроде if ( a & 3 == b & 3), нет?
Я повидал много частых ошибок) Возможно, про флаги и маски можно будет свою историю рассказать, если поизучать всю нашу базу ошибок.
А эта проверка только для простых типов проводится, или для всех? А то с C++-ной возможностью перегрузить операторы можно какое угодно поведение получить, и описанные случаи окажутся вполне корректными. Например, если делается интерпретатор/интероп для некоего слаботипизированного языка программирования, где "" == 0 && "" == false – true. Или eDSL с компактным синтаксисом, вроде boost::spirit, где старые операторы наделяют другой семантикой из-за невозможности добавлять новые.
Анализатор ищет неправильные конструкции, какие были приведены в примерах. Но как и во всех диагностиках, для правил V547 и V590 реализован ряд своих исключений, которые позволяют избавить диагностику от ложных срабатываний. Я сейчас не вспомню все исключения, тем более для двух правил. Скорее всего, благодаря им я не встречал ложные срабатывания в описанных вами ситуациях, зато встречал много ошибок на простом коде.

Что также может быть интересно — для пользовательских операторов не работает short-circuit-поведение.

Если речь идет не о элементарных типах, а о классах, оператор сравнения для которых может быть перегружен, то конструкция
if (en_RenderType==RT_BRUSH &&
en_RenderType==RT_FIELDBRUSH)

Может быть не ложной.
Есть много способов выстрелить себе в ногу. Есть целые языки, устроенные так, чтобы люди, их использующие страдали до тех пор, пока не наберутся опыта и не перейдут на что-то менее ужасное. Но… Зачем? Равенство должно быть отношением эквиватентности, незачем из него делать невесть что.
Лучше сразу пристрелите автора, чтоб не мучился и не мучил других.
UFO just landed and posted this here
понятно, что в
if (err == ERR_OK || err != ERR_PENDING)…
можно спокойно убрать кусок с ERR_OK, но КМК иногда это может быть «документацией», что обрабатываем, например, «хороший» и «плохие» сценарии, а «злой», в смысле асинхронный, будет, например, где-то ещё.
Что в общем не должно мешать оторвать руки человеку, придумавшему такой API. 8-)
3 и 4 это ошибки. 1 и 2 нет.
!= || !=
== || !=
Оба случая — может достаточно наличия/отсутствия одного из двух признаков. Не согласен совершенно с автором статьи.
Ну прям совершенно не согласны?) Это же реальные баги и реально ничего не делающий код.
Оба случая — может достаточно наличия/отсутствия одного из двух признаков.

Это как раз причина того, почему такие ошибки долго не замечают.
Sign up to leave a comment.