Pull to refresh

Comments 113

Стандарт С++ не гарантирует, что float(0) и даже int(0) реализуются как «все биты 0»
Тем не менее стандарт гарантирует, что при приведении float(0) или int(0) к bool мы получим true.

Так что этот совет про
// ПЛОХО
if (float_num) {}
if (int_num) {}
я бы исключил из статьи.
при приведении float(0) или int(0) к bool мы получим true
false конечно же, опечатался.
Согласен только частично :-) Моё объяснение, согласен, не точное… даже неверное… позор мне, в общем.

Но вот сам совет хороший. Он даёт не только указанное мной преимущество.
Во-первых, он сразу проверяет тип, чем помогает не совершать ошибки.
Во-вторых, использовать не bool просто не концептуально. Это пережиток C, которому не место в C++. Согласитесь?
Во-первых, он сразу проверяет тип, чем помогает не совершать ошибки.
Если вы проверяете на равенство/неравенство нулю, то тип вам не важен, я думаю.

Во-вторых, использовать не bool просто не концептуально.
Условие в if и while приводится к bool, если оно не bool. Писать if (x) вместо if (0 != x) это уже идиома. Чтобы поддержать эту идиому в классах определяется operator bool и operator!

Сравните

boost::shared_ptr<T> p;
if (p) { ... }
if (NULL != p.get()) { ... }

Первый вариант намного лаконичнее.
Многие конструкции уже укоренились, например проверка указателя. Тут вопрос в том как это применять. Если я проверяю указатель — это одно, но если я использую if(x) для проверки знаменателя на равенство нулю — это уже совсем другое. Последний вариант, как и многие ему подобные, использовать само собой не стоит.
Числа с плавающей точкой лучше проверять не на равенство .0, а на попадание в окрестность нуля. Потому что у вас всегда будет погрешность, кроме совсем тривиальных случаев.
Соглашаюсь обеими руками! Не вношу в статью только потому, что она немного не об этом и без того уже слишком длинная. Пусть это замечание останется украшением обсуждения :-)
Кстати проблема легко решается наличием всего пары строк:

static inline bool fuzzyCompare1(float p1, float p2)
{
    return abs(p1 - p2) <= 1.0E-4;
}


или

static inline bool fuzzyCompare2(float p1, float p2)
{
    return abs(p1 - p2) <= 0.00001f * min(abs(p1), abs(p2));
}


Первый вариант сравнивает с постоянным epsilon = 1.0E-4, второй с процентом от меньшего числа.
Рекомендую хорошую статью о сравнении чисел с плавающей точкой.
В частности, там подробно разбираются некоторые тонкости при реализации относительного сравнения.
Про то, что for(;;) это плохо — тоже глупость. Это легальный способ написать бесконечный цикл не получив предупреждения «conditional expression is constant».
Согласен. В плане легальности, все три способа, которые я привожу, эквивалентны. Но тут речь не о легальности, а об… эстетике.
зачем лишние «затычки»?
Конечно, в forever смысла особо нету, но есть и полезнейший foreach, например.
for(;;) — самый корректный способ бесконечного цикла. уберите из плохих практик, а то плохому научите )
Ну это спорный вопрос :-) вот тут совет 60
geosoft.no/development/cppstyle.html#Types
И я с ними согласен. В С++ (а речь именно про него) типы заслуживают особого уважения.
Чтобы компилятор сразу же дёрнул вас за руку, некоторые люди вырабатывают привычку писать сравнения наоборот (признаюсь, я не смог её выработать в себе :-))
Прийдется выработать привычку ставить максимальный уровень предупреждений и трактовать их как ошибки ;)
Ох. Эта тема уже обсуждалась. Я принадлежу к людям, которые считают, что совершенство недостижимо :-)
>Описывайте сперва нормальный ход событий

Довольно часто встречаются такие ситуации:

if(isOk)
{
//~100 строк кода
}
else
{
setError();
return -1;
}

Такие блоки гораздо легче читать, если сначала описать более короткую последовательность действий.
Та ну, здесь абсолютно правильно автор пишет. Вы что чаще будете читать — эти 100 строк кода, или 2 строки с обработкой ошибки?
Та ну, я лучше сразу увижу две строчки обработки ошибки и перейду к остальной части кода. Две строчки мне не повредят никак. А вот в случае с автором, за сотней строк можно и не заметить обработку ошибки, что плачевно.

Всё, что для вас «абсолютно правильно» не означает, что это автоматически правильно для других. Автор описал достаточно спорные моменты и попытался обобщить, напялить на всех, а это вредно.
Мне кажется, эти 100 строчек хорошо бы вынести в отдельную функцию и дать ей понятное имя. Так всем будет проще.
Да, вынесение 100 строчек кода в отдельную функцию, во многих случаях оправданное решение. Читаемость кода повысится.

Но дальше возникают довольно-таки холиварные моменты. Одни готовы терпеть 100 строчек кода в одной из веток if'а, другие закроют глаза на узкоспециализированную функцию, использующуюся один раз и имеющую в параметрах разнородные данные (контексты, внутренние структуры разных уровней, буферы с данными, текущее значение счетчика (если снаружи цикл)). Это уже дело вкуса и, как правило, разработчики между собой договариваются, как в подобных случаях поступать.

Скажем, в команде, в которой сейчас я работаю, принято в функцию выносить фрагменты кода, которые используются два раза или более. А те 100 строк кода изрядно разбавляем комментариями с описанием что и зачем делается. Вполне возможно, что кому-то из здесь присутствующих неудобно следовать этому правилу. Главное, что всегда можно выбрать фломастер, вкус которого вам понравится.
Чем больше строк в функции, тем сложнее её тестировать. А если вы раза 3-4 не вынесете 100 строк кода в отдельную фукнцию, у вас получится простыня на 300-400 строк, в которой разобраться будет намного сложнее.
самое приятное это когда мы разобрались с особыми случаями и забыли о них.

if (error_occured) {
return -1;
}
// no errors further
кроме того такой стиль кодирования уменьшает вложенность и код выглядит проще
сравните

void f()
{
if(ok1)
{
do1();
if(ok2)
{
// а вот тут собственно код вашей фукнции - уже третий уровень вложенности
}
else
return -2;
}
else
return -1;
}

void f()
{
// проверим все условия
if(!ok1)
return -1;
do1();
if(!ok2)
return -2;
// теперь все гарантированно хорошо, можно сделать то что мы намеревались
// и никаких лишних {} и отступов
}

а если такую обработку еще и отформатировать в виде:

void f()
{
   // проверим все условия
   if(!ok1) return -1;
   do1();
   if(!ok2) return -2;
   // теперь все гарантированно хорошо, можно сделать то что мы намеревались
   // и никаких лишних {} и отступов
}


то будет еще красивее.
Правда это противоречит одному из правил в статье, но на мой взгляд, такие ситуации, как раз являются исключением из этого правила.
Как по мне, одним из основополагающих принципов является «функция должна иметь одну точку возврата». Множественные return-ы только запутывают.
Это холиварное утверждение. Многоуровневая вложенность ифов запутывает не меньше.

Выбрасывание исключения между прочим — тоже может быть точкой выхода из функции, так что теперь, исключения не использовать?
описанный мной пример, а также, скажем, исключения подтверждают, что это правило — полный отстой :)
И за что заминусовали человека? Высказывание абсолютно верное, выведено такими личностями, как Кнут, Дейкстра и Вирт.
Ага, наверное поэтому у Кнута в каждой второй функции несколько ретурнов. Кстати, когда он писал на Паскале его и гоуту в конец функции не смущал, как заменитель ретурна, в те времена Exit'а в Паскале не было.
Чтобы не быть голословным:

node *sum(p,q) /* compute the sum of two binary trees */
  node *p,*q;
{
  register node *pl,*ql;
  register int s;
  easy=1;
  if (!p) return q;
  if (!q) return p;
  pl=left(p); ql=left(q);
  s=compare(pl,ql);
  if (s==0) {
    @<Add |right(p)| to |right(q)| and append this to |succ(pl)|@>@;
    easy=0;@+return p;
  } else {
    if (s<0) @<Swap |p| and |q| so that |p>q|@>;
    q=sum(right(p),q);
    if (easy) goto no_sweat;
    else {
      ql=left(q);
      s=compare(pl,ql); /* does a carry need to be propagated? */
      if (s==0) { /* yup */
        change(&p->r,right(q));
        recycle(ql);@+free_node(q);
        if (pl) succ(pl);
        else change(&p->l,get_avail());
        return p;
      }
      else easy=1; /* nope */
    }
  no_sweat: change(&p->r,q);
    return p;
  }
}


5 ретурнов, 1 гоуту
А теперь быстренько объясните, что делает этот код. Мне не понятно. :) (вы выдернули его из контекста, а у мои телепатические способности не позволяют его восстановить.)

ЗЫ ладно, вычеркните Кнута из списка.
Дык видать народ не читал классики, чего уж там ;) И, кроме как, кучей ретурнов уже не умеют красиво организовать потом исполнения.
Я читал не только классиков 60-х 70-х, но и современных классиков. Так вот Фаулер в «Рефакторинге» утверждает, что правило единственного выхода из функции нужно забыть как ошибку прошлого.

Мир не стоит на месте, поэтому, читайте современные книги.
Один советует одно, другой рекомендует другое. Для вас одно — для меня другое. Захламляйте код кучей точек выхода и сопровождающие попомнят вас не злым тихим словом.
Мне кажется, что код, с несколькими точками выхода, который имеет 2 уровня вложенности читается и сапровождается намного проще, чем код с одной точкой выхода и четырьмя уровнями вложенности.

Не надо слепо верить словам Дейкстры (это правило придумал именно он), хотя, на вкус, как известно, все фломастеры… ну вы знаете.
Дык правило «один вход — один выход» возникло не просто так.

Ключевое слово тут «структурность». Больше структуры — легче рассуждать о поведении кода.

На этом же основываются и design patterns. Например, увидев в коде синглтон, программист сразу же начинает представлять себе, лол, что можно с ним делать, а что — нельзя.
если в else return, то логичнее

if(!isOk)
{
setError();
return -1;
}

//~100 строк кода
Да, я, например, люблю писать небольшие функции, и при возникновении ошибок завершать функцию с return или throw (в зависимости от характера ошибки).
Такие ситуации не должны встречаться. Если у Вас получилась простыня на 100 строк, то стоит разбить их на функции. Читаемость кода повысится сильнее.
Статья как статья, а вот некоторые комментарии перед кусками кода улыбнули =)…
// ПОХУЖЕ вообще убило =). Извиняюсь за оффтоп.
В добавок к уже сказанному.
Например, мне проще выносить негатив в if, а обработку в else. К примеру, кусок кода:
if (some_error)
  throw Error();
else
{
  // make job
}
как по мне выглядит логичнее.
Ну и создание лишних переменных тоже зло. Опять же, как по мне, так легче в одном условии всё описать, чем плодить гору лишних переменных ради одного условия. Ясное дело, если они нужны далее, тогда вопросов нет. Но в противном случае, зачем их плодить?
Опять же, выражения:
if (readDdata(getFileNameFromArray(getEnvAsArray()))) {}
if (lid = getLabel() == curentLabel())
вполне читабельны и юзабельны. Не вижу проблемы.

Вам бы стоило сделать акцент на контингент. Для новичок читать исходники какого-нибудь embedded конечно сложно, но зачем адекватным людям усложнять себе же жизнь лишним мусором? :)
Зачем после throw нужен else?
просто пример выброса ошибки, замените на ProcessError(), без разницы. Смысл в «малом кол-ве кода»
Я к тому, что в случае с throw не нужен else c фигурными скобками, увеличивающий вложенность.
Я к тому, что просто хотел показать в первом блоке «малое кол-во кода» и первое, что пришло в голову — это throw. Я согласен, что пример говняный, но суть была не в самом throw, а показать, что, имхо, сразу нужна обработка ошибок, а потом уже всё остальное. Да, ошибся, но в прошлом коменте вроде пояснил, что к чему.
Так, надеюсь, понятно? ;)
Ага :)

Невозможность править комментарии — отстой :(
Например, мне проще выносить негатив в if, а обработку в else.

Наверное это как наполовину полный / пустой стакан. Интересно психологи думали об этом?
int a = 0;
if( a ) что-то-там

Насколько я знаю, с expression внутри if происхдит неприятность — он evaluates в true или false. И, опять же, насколько я помню, char, short, int и __int64 если ==0 то evaluates в false, в противном случае в true.

float a = 0.0
if( a ) что-то-там

Так делать нельзя не потому что «все биты 0», а потому что float по стандарту не гарантирует что то, что мы туда положили будет 100% соответствовать тому, что изъяли. Из-за внутреннего представления: 1.0 / 2.0 может получиться 0.49999999999 :). Это тоже написано в стандарте.

В соатльном согласен, спасибо за статью.
BTW, еще говорят что:

if( что-то-длинное
    && что-то длинное
    && что-то длинное )


читатеся лучше, чем

if( что-то-длинное &&
    что-то-длинное &&
    что-то-длинное )


Но лично я не уверен.
Самый первый комментарий, процитирую:
«Тем не менее стандарт гарантирует, что при приведении float(0) или int(0) к bool мы получим false. „
Да, я читал. Здесь уточнил, в каком именно месте и как он это гарантирует :). BTW, float( 0.0 ) только в том случае если мы только что положили 0.0 и не делали резких движений. Если 0 там подрозумевается как результат математической операции — то может не срастись :).
!errorFlag наверное имелось ввиду errorFlag
Нет, тут всё правильно. Имелось ввиду «если ошибки не(!) произошло, то делаем что-то, в противном случае — бросаем работу».
переименуйте в haveError — будет яснее
еще бы стоило посоветовать почитать Code Complete глава 31 (а лучше всю книгу)
// СОВСЕМ ПЛОХО
if (cond()) act();
else err();


я обычно в таких случаях пишу
if (cond()) act();
       else err();
Разницы с приведенным кодом почти нет. Плохость кода в примере не только в том, что он плохо читается как есть, а в основном в том, что его неудобно расширять. Добавляя код прийдется не забыть правильно расставить фигурные скобки и перенести на новую строку act(); или err(); — больше работы и диффы сложнее смотреть.
особенно это удобно отлаживать, ага.
Задача проследить путь выполнения программы в данном месте не представляет никакой трудности ни в одном из известных мне дебагеров. К тому же такая запись обычна применима в том случае, когда условие действительно простое, и если надо прервать выполнение если это условие ложно то можно использовать conditional breakpoints. Ах да, вариант переписать это в if else когда это действительно надо для отладки никто не отменял.
Да, я предпочту переписать через месяц пару таких простых кусков кода с добавлением подробного логирования, чем получить кашу из if else с кучей уровней вложенности.
Я считаю, что фигурные скобки надо ставить всегда.

А запись
else
bazzz();
ещё хуже, чем
else bazzz();

Как-то долго втыкал в кусок кода примерно следующего вида (линия между строк важна):
if(somethin)
a();
b();

c();

Долго не мог понять, почему выполняется b(), а a() при этом не отрабатывает.
По моему
if (expr)
{
//do something
}
else
{
//do another work
}
логичнее и смотриться лучше. Зачем скобки ставить на уровень if-else, это книжная запись чтобы экономить место. У нас же места навалом.
То же самое хотел написать =) Только еще отступов добавить внутри блоков.
Когда много вложенных фигурных скобок их очень легко цеплять взглядом, если открывающая и закрывающая на одной вертикали.
Скобки ставятся на уровне if и else, чтобы подчеркнуть, что последующий код неразрывно связан с данным if или else (java-style). Если ставить скобки на новой строке, то if и код после него выглядят как независимые блоки(C#-style). То же касается объявления методов/функций.
«Независимые» вы можете отодвинуть пустой строкой, или парой таких строк. Да и блог все-таки про c++.
«Неразрывно связанный» код для меня читается хуже.
каким же образом язык c#, появившийся много позже, чем c++ у нас уже заимел свою нотацию устаканненую? всегда пишу скобки на следущей строке, при чем тут c#, если я его открывал для ознакомления только?
Терпеть не могу такое написание. И не понимаю почему оно логичнее и не могу сказать, что оно смотрится лучше. И экран у меня ограничен по высоте, а хочется видеть побольше кода на экране:(
Ну, привычки и code convention у людей разные. Главное чтобы стиль был одинаковый по всему проекту. Насчёт побольше кода, imho функции, которые занимают более 1-1.5 экранов являются первыми кандидатами на простенький рефакторинг extract method.
Небольшая идея по улучшению кода, не относящаяся к статье.
При определении, находится ли число в некотором интервале, начало интервала, число и конец интервала лучше записывать в порядке их расположения на числовой оси.
Было:
bool isInside = figureX > leftLimit && figureX < rightLimit;
Стало:
bool isInside = leftLimit < figureX && figureX < rightLimit;
Жалко, что в Си нельзя написать как в Питоне:
isInside = leftLimit < figureX < rightLimit
Написать то можно. Только в результате получится не то, что вы имели в виду :)
Это сбережёт процессорное время, память… в общем всё, что так дорого каждому человеку, исповедующему гуманистические идеалы :-)

Это вас кто-то обманул. Локальные переменные живут в стеке. Завести такой объект — одна инструкция: добавить размер объекта к esp.
Ну, если создание объекта занимает много времени, то сбережет в тех случаях, когда мы не попадаем в блок. Ну и вообще это хорошая практика — объявлять переменные непосредственно перед их использованием.
Ну и вообще это хорошая практика — объявлять переменные непосредственно перед их использованием.

Это тонкий философский вопрос. :)
Но широко распространенные компиляторы, типа gcc или msvc выделяют кадр стека один раз при входе в функцию и не меняют esp/ebp ходя по блокам внутри неё.

Кроме того, вызов конструктора и деструктора будет весить наверно больше, чем изменение размера кадра стека.
Нет ли здесь противоречия:
// ПЛОХО
SomeClass p; // нужен только в блоке
if (x == y) {
  p.moveTo(x, y);
  p.logResultsOfMovement();
}

и
// ОЧЕНЬ МИЛО
bool isInside = figureX > leftLimit && figureX < fightLimit;
bool isGreen = figureColor == GREEN;
if (isInside && isGreen) {
}

в том смысле, что переменные isInside && isGreen нужны только для проверки условия…
Хотя согласен, при отладке breakpoint + evaluate на if
if(someCheck())
{

}

выполнит someCheck, а на
bool isGreen = someCheck();
if(isGreen)
{
}
нет, что может быть важно, если someCheck возвращает другой результат при повторном вызове.

А скажите, вот так форматировать код:
if (a == b)
  {
  foo();
  }
else
  {
  bar();
  }
неприлично? Никак не могу привыкнуть писать «по стандарту», т.е. скобку сразу после if…
Вы попробуйте «по стандарту» делать — когда будете разбираться в программе — это упростит понимание. Главное — в одну кашу не пишите.

Всё дело в том, что
if (a == b){
foo();
}else{
bar();
}

1) выглядит компактнее
2) конец if-else ловится хорошо
а вот как раз на мой вкус именно в моём варианте скобочки хорошо ловятся (на одном уровне, отдельная строка, блок отчетливо виден). А компактность должна заботить только заказчика, если он оплачивает код построчно ;-)
Тем более почти все приведенные примеры «ХОРОШО» не добавляют компактности…
>>// ПЛОХО?
>>if (x == 1)
>>а
>>// ХОРОШО?
>>if (1 == x)

Нет, не хорошо. Есть ведь ещё перегрузка операторов и неявное преобразование из типа. Писать надо так как наиболее понятно. Думать о том, что может быть ошибка и из-за этого делать уродский код не стоит. В противном случае весь код окажется уродским и непонятным.
В чем проблема с оператором совсем непонятно. Неужели сложно сделать
bool operator ==(const A& a, const B& b)
{
	// Implementation
}

bool operator==(const B& b, const A& a)
{
	return a == b;
}
Эти примеры не о том. Они созданы для использования операторов вне классов. К тому же представим, что типов преобразования не два A и B, а больше. С каждым вот таким использованием количество методов будет расти в геометрической прогрессии. Между прочим твои примеры как раз и показывают как делать не надо, тема называется «конструкторы и преобразования». Вот условно назову некий оператор #

1.operator#(x); // ошибка

И моё высказывание относилось вовсе не к тому, как её преодолеть. А именно к тому, что не нужно писать уродский код полагаясь на особенности некой реализации. Реализация в конце концов может оказаться совсем другой.
Если вы сравниваете экземпляр класса A с экземпляром класса B, в большинстве случаев у вас будет определен либо конструктор A::A(const B&) либо A::operator B(). В первом случае вам нужен bool operator==(const A&, const A&), во втором bool operator==(const B&, const B&), которые и так будут реализованы.
На мой взгляд не важно как именно расставлены скобки и {}. Важно чтобы стиль был один и тот же во всем проекте. И все разработчики придерживались этого стиля.
Почитайте Макконела «Совершенный код». Там все это рассматривается лучше, подробнее, понятнее и интереснее.
Извините, придираюсь :)
bool dataReaded = readDdata(fileName);

не readed, а read…
глагол неправильный…

зы: сам недавно узнал что не payed, a paid :D

Статья каррошая, я очень трогательно отношусь к хорошему визуальному восприятию кода
// ПЛОХО
SomeClass p; // нужен только в блоке
if (x == y) {
p.moveTo(x, y);
p.logResultsOfMovement();
}

// ХОРОШО
if (x == y) {
SomeClass p; // нужен только в блоке
p.moveTo(x, y);
p.logResultsOfMovement();
}

Это сбережёт процессорное время, память… в общем всё, что так дорого каждому человеку, исповедующему гуманистические идеалы :-)

Ну не правда. Возьмите этот код. Отключите оптимизацию, откомпелируйте… потом возьмем дизасемблер… и посмотрим что получилось. Так вот. Даже без оптимизации, компилятор создает одинаковый код. :)
(я беру компилятор от MS)
Если же включить функцию оптимизации, то код красиво так сложится в разы… и там вы уже даже с трудом обнаружите иницилизацию класса.

Остальные примеры исследовать с помощью написанием тестовых прог и их исследованием не стал. :)

P.S.
ДРугое дело, что код получается более правильный, но вот с точки зрения компилятора, без разницы.
Я уже писал выше. Если конструктор SomeClass тяжёлый, а ситуация, когда x == y случается редко, в производительности вы выиграете заметно.
Код тут

Paul@paulcomp /cygdrive/d/src/cpp/benchmarks
$ g++-4 --version
g++-4 (GCC) 4.3.2 20080827 (alpha-testing) 1
Copyright © 2008 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.


Paul@paulcomp /cygdrive/d/src/cpp/benchmarks
$ g++-4 -O2 -o test test.cpp

Paul@paulcomp /cygdrive/d/src/cpp/benchmarks
$ time ./test.exe 1

real    0m6.816s
user    0m6.625s
sys     0m0.015s

Paul@paulcomp /cygdrive/d/src/cpp/benchmarks
$ time ./test.exe

real    0m0.733s
user    0m0.656s
sys     0m0.015s
В примере условие 0 == i % 10 эмулирует ситуацию, когда x == y случается в 9 раз реже, чем x != y.
ясно про что вы… мы говорим о мягком с теплым.
сделайте конструктор пустым… тогда у вас и ситуация в корне изменится.
Здравствуйте. Я же писал про тяжёлый конструктор, а вы говорите «сделайте пустым».
Всё зависит от контекста, иногда «плохие» приёмы очень выразительны. Ничего нет хуже для понимания, чем 20 строк «правильно» оформленного пятистрочного алгоритма. Тут главное без фанатизма.
В общем, очень много спорных практик.

for(;;), проверки на ошибки и т.п.

Вообще говоря, я склонен все проверки делать как раз наоборот (precondition, assertion, postcondition — все проверяют на ошибки, а не на нормальный ход, равно как и CodeContract в C#).
Вот если бы вы, уважаемый автор, хотя бы изредка читали хорошие книжки по программированию…
Так я готов! Посоветуйте! (Кстати, тогда от вашего комментария будет польза и мне и людям.)
> Так я готов! Посоветуйте!

«Космонавтом быть хочу, пусть меня научат». Так получается?

> Кстати, тогда от вашего комментария будет польза и мне и людям.

Почему эти самые люди сами не могут себе помочь? Никто же не придет их спасать. Первый шаг сделать очень просто. :)

Вопрос о расставлении фигурных скобок очень важен, лол.

> Создавайте переменные только при необходимости

Это верно.

> Используйте преимущества С++. Если переменная нужна только в блоке, то и создавайте её только в блоке. Это сбережёт процессорное время, память… в общем всё, что так дорого каждому человеку, исповедующему гуманистические идеалы :-)

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

> В качестве условия следует использовать только логические величины. Это же, кстати относится и к циклам

Это следует из того, что if представляет собой именно что условие, и из соображений типобезопасности (type safety) неявного преобразования и прочей хреноты нужно избегать. Впрочем, к реальным программам на Си/Си++ это не относится, потому что языки такие. :)

> Если логическое выражение состоит из нескольких частей, то самой плохой практикой является запись их всех в кучу. Можно ли быстро понять, что тут написано?

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

Проще говоря,

> if (figureX > leftLimit && figureX < fightLimit && figureColor == GREEN)

То же самое, что и

> if ((figureX > leftLimit) && (figureX < fightLimit) && (figureColor == GREEN))

Потому что у операций сравнения приоритет выше, чем у логического «и». К тому же логическое «и» ассоциативно и коммутативно, поэтому некоторые скобки можно опустить (что мы и сделали), а сами подвыражения можно писать в любом порядке.

> Выражения лучше выносить из условий

Если выражение сложное (отвлекает от условия), тогда его однозначно следует вынести куда подальше.

> Такую программу на много проще читать, и кроме того, переменные isInside и isGreen могут пригодиться в дальнейшем.

Вот когда пригодятся, тогда и вынесем. :) YAGNI.

> По уже изложенным причинам, в логических выражениях лучше не выполнять присвоения:

Это были не причины, а сотрясение воздуха. Настоящей причиной являются побочные эффекты, из-за которых выражение может получить новые интересные трактовки. :)

> Ну и, конечно, «каждому выражению своя строка!».

Налицо явная путаница в терминах «выражение» (expression) и «утверждение» (statement).

> то вы можете не бояться опечатать и написать "=" вместо "==".

Ни разу не попадался на эту ошибку. Счастливчик. :)

Почитайте Вирта (Алгоритмы и Структуры данных), SICP, HTDP, etc. И не пишите таких дезинформирующих статей.
> «Космонавтом быть хочу, пусть меня научат». Так получается?

И снова тоже самое — если вы видите в этом что-то постыдное (я не вижу), то предложите свой вариант (а то как-то неконструктивно). Или вы сторонник точки зрения "уже хаскель знаю и никем быть не хочу и учиться тоже фтопку"? Я же не прошу вас меня учить, я спрашиваю про книги.

> Почему эти самые люди сами не могут себе помочь?
> Никто же не придет их спасать. Первый шаг сделать очень просто. :)

Откуда вы это взяли? «не могут», «не придет», «Первый шаг»? Вы прям экстрасенс какой-то :-) И шаги уже посчитали, и возможности всех оценили… не много на себя берёте? (извините, просто интересуюсь :-))

Ну а всё остальное конструктивно (почти везде). Спасибо. Спорить не буду, на что-то я уже тут отвечал, на что-то отвечали другие, что-то бесспорно…

Вирта читал. Не очень понимаю, как эта книга относится к теме статьи. Тут народ советовал «Совершенный код» С. Макконнелла — вот это по теме было. Статья-то не про алгоритмы и не про структуры.
> И снова тоже самое — если вы видите в этом что-то постыдное (я не вижу)

Книжки искать долго что ли? Чай гуглом все умеют пользоваться.

> Или вы сторонник точки зрения «уже хаскель знаю и никем быть не хочу и учиться тоже фтопку»? Я же не прошу вас меня учить, я спрашиваю про книги.

Нет, я сторонник высказывания «век живи, век учись, — помрешь все равно дураком». ;)

> Ну а всё остальное конструктивно (почти везде). Спасибо. Спорить не буду, на что-то я уже тут отвечал, на что-то отвечали другие, что-то бесспорно…

«Почти везде» — где именно неконструктивно? Там нету ни одного наезда на Си/Си++.

> Вирта читал. Не очень понимаю, как эта книга относится к теме статьи. Тут народ советовал «Совершенный код» С. Макконнелла — вот это по теме было. Статья-то не про алгоритмы и не про структуры.

У Вирта хорошо написано о структурном программировании. В статье говорится о том, как улучшить структуру кода (должно говориться, вообще-то).

«Совершенный код» не читал, но исправлю при первой же возможности. Главное, чтобы не оказалось как с Г.Бучем и его ООП. :)
При поиске книжек гугл человека никак не может заменить. Хороших книжек очень мало (по сравнению с вообще книжками :-)). Тут как раз интересно мнение опытного человека.

Почему вы читаете «неконструктивно» == «наезд»? ,-) Я отношу к неконструктивному всё, что не относится к статье. По разным причинам. Хотя бы из-за субъективности («Ни разу не попадался на эту ошибку.»; статья-то не про вас; и даже не про меня :-))

Ну и статья не о структурном программировании, как таковом. Это просто мысли про if. Я хотел бы сам про это почитать, но никто не написал и я запустил эту дискуссию.

А совершенный код, я вам (именно вам!) очень советую! И ваше мнение мне было бы очень интересно! С одной стороны, книжка очень толковая. (Очень!) С другой стороны, в ней есть наезды на рекурсию, которые даже меня на столько покоробили, что я написал про это заметку :-) Вы, как любитель рекурсии (и я с вами солидарен полностью) наверняка испытаете сильные эмоции, читая этот труд :-) По крайней мере, Макконнелл пишет прямым текстом, что вас бы он уволил не задумываясь :-)
> Почему вы читаете «неконструктивно» == «наезд»? ,-)

Стереотипы. :)

> Я отношу к неконструктивному всё, что не относится к статье. По разным причинам. Хотя бы из-за субъективности («Ни разу не попадался на эту ошибку.»; статья-то не про вас; и даже не про меня :-))

Да, субъективно очень. Не удержался.

> А совершенный код, я вам (именно вам!) очень советую! И ваше мнение мне было бы очень интересно! С одной стороны, книжка очень толковая. (Очень!) С другой стороны, в ней есть наезды на рекурсию, которые даже меня на столько покоробили, что я написал про это заметку :-) Вы, как любитель рекурсии (и я с вами солидарен полностью) наверняка испытаете сильные эмоции, читая этот труд :-) По крайней мере, Макконнелл пишет прямым текстом, что вас бы он уволил не задумываясь :-)

Ну точно надо почитать. :)
Спасибо, сейчас активно изучаю Java и статья пришлась впору :)
Прошу не минусуйте, почти все моменты с конструкторами схожи в этих языках.

P.S. раньше я кодил на одном из не очень известных языках скриптинга, и там постоянно были проверки такого типа — (и избежать их было нельзя, в силу ограниченности языка)

Obj = <src.act>
if (<Serv.Uid.<Obj.Tag0.linkToAnotherObject>.tag0.someInteger> == <Obj.SomeAnotherInteger>)

и даже больше и страшнее. Слава богу, что появилась возможность и желание изучать нормальные логичные языки программирования :)
>Можно ли быстро понять, что тут написано?
>
>// ПЛОХО
>if (figureX > leftLimit && figureX < fightLimit && figureColor == GREEN)
>
>Подсветка, конечно, помогла бы, но и без подсветки следующее выражение читается на много проще:
>
>// ХОРОШО
>if (figureX > leftLimit &&
> figureX < fightLimit &&
> figureColor == GREEN)

Мне кажется, единственный надежный способ быстро понять, что здесь написано — вынести выражение в отдельную функцию и дать ей говорящее имя. Всё равно остается непонятно, что имелось в виду и не ошибся ли автор логического выражения или тот кто это выражение недавно правил.

if (isFigureTarget(figureX, figureColor))
Вынесение в отдельные функции — это всегда хорошо. Но тут есть два риска.
1. Во многих случаях функции могут неоправданно расплодиться.
2. А внутри функции что будет? Такое же непонятное выражение? Название, конечно прояснить его смысл, но вот чтение (поиск ошибок) не облегчится.
1. Да, могут. тут нужно чувство меры. как правило, если в выражении больше 3 бинарных логических операторов, то уже пора.
2. Да, будет непонятное выражение. Вот тут уже можно его и на стадии разбить. Ну и повторно использовать, если вдруг.
Забавно, ссылка на ГеоСофт — когда-то и сам активно пользовался тем документом, а нашёл его в то время, когда каталоги ссылок были популярнее поисковиков.

Кто-нибудь знает, кто его составил? Что это за ГеоСофт такой?
Sign up to leave a comment.

Articles