Pull to refresh

Comments 33

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

bool mustRdraw = (frame.isChanged() || target.isChanged()) || experiment.isRunning();
bool isFullScreen = frame.getSize().equal(screen.getSize());
if (isFullScreen && mustRedraw) {
  // redraw
}

Не знаю, согласуется ли такой подход с тем что советуют профессионалы, но субъективно — так легче читаются на лету составные части условия.
Иногда проверки должны идти в строго заданном порядке и если одна будет ложной, то следующие проверять нельзя. Тогда подход с переменными становится бесполезным и приходится городить либо вложенные условия, либо сложные, а так – полезная практика.
В C# я часто для этого использовал функции
Func<bool> mustDraw = () => (frame.isChanged() || target.isChanged()) || experiment.isRunning();
Func<bool> isFullScreen = () => frame.getSize().equal(screen.getSize());
if (isFullScreen() && mustRedraw()) {
  // redraw
}

У Вас в коде ошибка ;-)
Пример того, что такие методики тоже могут дать логическую ошибку на ровном месте.
И в верхнем примере тоже ошибка, вызванная опечаткой.
мне тоже нравится такой подход, отчасти тем, что значения переменных легко посмотреть в дебаггере
bool mustRdraw = (frame.isChanged() || target.isChanged()) || experiment.isRunning();
bool isFullScreen = frame.getSize().equal(screen.getSize());
if (isFullScreen && mustRedraw) {
  // redraw
}


Допустим, что где-то вверху объявлена bool mustRedraw…
И… Бум!!!
Для себя выработал правило Yoda conditions + автоматическое форматирование + выделение сложных условий в функции или переменные. Потому что, честно говоря, эти отступы и рюшки хороши пока у кода один хозяин — да и то не долго. Каждое изменение такого условия порождает переформатирование, что бы красиво было… Это уже ASCII Art, получается, а не программирование.
Я использую для таких условий нечто вроде:

if ( true
  && condition1
  && ( false
    || condition_2_1
    || condition_2_2
  )
) {
  // do something
}

Поступаю аналогично. Очень удобно построчно переносить код а также использовать условную компиляцию. Особенно такие конструкции разрастаются в sql (при этом true заменяется на 1=1). А для сложных случаев в комментариях описывается сначала таблица истинности, по которой, собственно, и составляется условие.
Спасибо. Красиво, и почти соответствует последнему примеру.
Но, наверно, лучше, если по команде редактор покажет код условия в виде дерева, да еще и посоветует минимальную группировку выражения
А true и false просто для того, чтобы не оставлять пустые открывающие скобки? Получается же и избыточность, и увеличение сложности. Стоит ли оно того?
Предлагаю решать самостоятельно.

Для меня такой вариант более читаем и более поддерживаем. И, кстати, ничего не стоит с точки зрения производительности.

И да, speakingfish прав, — в SQL это еще более удобно (при генерации WHERE, например).
Не совсем удобно. Любой мысль в коде должна быть или завершенной полностью, к примеру вызов метода с его аргументами на одной строке, либо явно намекать на то что «to be continue», т.е. ищи продолжение. Когда вы пишите && связывая под-условия, лучше всего это делать на одной строке, а продолжение мысли, т.е. написание след. под-условия на второй. Потому что когда Вы смотрите:

if ( file.Exists() &&

то Вы сразу понимаете, что мысль не завершена! Следовательно надо читать мысль(условие) далее!

И того Ваш кусок кода, при использовании моего предложения будет выглядеть так:

if ( true &&
condition1 &&
( false ||
condition_2_1 || condition_2_2 )
) {
// do something
}

Но я бы сделал так:

bool subCondition1 = true && condition1;
bool subCondition2 = false || (condition_2_1 || condition_2_2);
if (subCondition1 && subCondition2 ) {
// more code
}

P.S.: Сорри за то что не применяю тег код, он недоступен на хабре тем у кого карма ниже нуля.
C предварительным вычислением условий есть небольшая засада.

Иногда вычисление какого-то условия является «тяжелой» операцией и часто не требуется.

Если мы расписываем условия в одном if, без предвычислений, то за счет lazy ops такие вычисления можно пропустить, что не всегда возможно при предвычислениях.
1) Преждевременная оптимизация, что?
2) Никто не отменял составлять под-условия по их наиболее вероятному состоянию.
UFO just landed and posted this here
Тогда выделять можно не в переменную, а в функцию, в которой в зависимости от наличия нулевого вектора будет либо сразу возвращаться результат, либо происходить дальнейшее вычисление результата с учетом косинуса. Вот только такими темпами можно одно условие до небольшой библиотеки раздуть.
И в вашем стиле ставить true или false перед остальными условиями бессмысленно. Вам подошла запись вида

if (
  condition1 &&
  (
    condition2_1 ||
    condition2_2 ||
    false
  ) &&
  true
) {
  // do something
}


Но в таком виде ни у кого не видел :)
Возможно, глупый вопрос, но зачем в таком варианте вообще true и false?
Для единообразной записи условий, например. В таком виде условия можно копировать, перемещать, добавлять и удалять всегда в одной и той же манере, вне зависимости от их расположения.
Не аргумент! Т.к. вызывает вопросы, товарищь gwer совсем не глупый вопрос задал!
Некоторые явления кажутся нам очевидными лишь из-за того, что они повсеместно используются.

Я задал вопрос потому, что встретил сие впервые (осмысленно). Но это действительно плюс для форматированных условий.

С другой стороны, так ли часто это дает какой-либо достаточно весомый выигрыш для того, чтобы ради этого и без того сложное условие загромождать новыми элементами?
Поверьте — условия, записанные таким образом, читаются легче. Ведь вся разница только в наличии true/false при открывающей скобке, — а в остальном все очень даже мимими.

Просто попробуйте в своем коде так переформатировать пару if.
Смотрится убого! Полезность нулевая. Посмотрел в коде Linux и FreeBSD нигде подобной записи условий не увидел. Видимо для прикладников это имеет значение, а вот что-то системщики что-то не жалуют или я плохо искал пример, тогда прошу дать ссылку на сорец из реальных боевых программ прошедших испытание временем
На мой взгляд, если изменить style guide и вместо записи
isnan (src) || isinf (src))

записть
isnan(src) || isinf(src))
,
то визуально это будет выглядет как две лексемы, а не четыре, и читать будет легче.
Согласен, пробелы очень сильно влияют на читаемость. Аналогичная ситуация, например, с приведением типов и с отрицаниями. Приведение в примерах есть, а вот отрицание в выборку интересных условий попало, но до статьи не дошло.

libgcc/libgcc2.c — 1611:
if (! (- ((DWtype) 1 << FSIZE) < u
       && u < ((DWtype) 1 << FSIZE)))
  {
    if ((UDWtype) u & (REP_BIT - 1))
      {
        u &= ~ (REP_BIT - 1);
        u |= REP_BIT;
      }
  }	

Пробелы эти, на мой взгляд, ни к чему. Кстати, это еще один вариант совокупности описанных решений. Многострочное условие вкупе с вложенным условным оператором.
>>Возможно, я не там искал, но ни разу в стандартах оформления кода не встречал упоминаний о том, как быть со сложными условиями.
Да, не там! Стив Макконнелл «Совершенный код». Также есть книга «Чистый код» от Мартина и там тоже это все описывается! Эти книги «must read», если Вы еще не читали, то настоятельно рекомендую, хоть 1% полезности из них но возьмете!
Макконнелл предлагал разбивать условие на переменные или функции. Это хороший подход. Но группируемые подусловия должны быть логично связаны, то есть вводимая функция/переменная является эдаким слоем абстракции. Но вдруг условие требуется очень сложное, и получилось что-то из следующего списка:
  • Условие состоит из большого числа слабо связанных подусловий, которые затруднительно логично и красиво разбить.
  • Мы выделили функции/переменные, но их много, и даже с ними условие воспринимается не очень хорошо.
  • Мы выделили функции/переменные, конечное условие красивое и наглядное, но в функциях/переменных теперь сложные условия.

Или еще какая-то ситуация, когда нужно написать сложное условие. Как поступим?

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

А вот у Мартина не помню упоминаний этой темы. Процитируете?
В разделе «Функции» Мартин просто в своём примере заменяет условие на функцию, никак на этом особо не заостряя внимание.

А вот Мартин Фаулер в «Рефакторинге» как раз вводит понятия «Декомпозиция условного оператора» и «Консолидация условного оператора», которые связаны с выделением условий в функции. В книге это всё описано в целом разделе «Упрощение условных выражений», в общем, есть что почитать в рамках данного вопроса
Да, вы правы, он приводил пример! Я же программист, когда читаю техническую книгу то отлично понимаю что мне надо не только представлять что происходит, но и почему! Т.е. «видеть код»!!! Поэтому для меня любой пример, почти что «абзац написанный словами». Бывают хорошие «абзацы», а бывают такие что следует взять на заметку! Да и люблю читать чужие исходные код, в конечном итоге многому учишься сам того не осознавая )
В продолжение темы — еще есть проблема вложенных многоуровневых условий.
С этим метод борьбы простой. Надо вместо
if(a) {
    if(b) {
        op1;
        if(c) op2;
        op3;
    }
} else {
  if(!c) {
    op4;
  }
}

писать
if(a && b) op1;
if(a && b && c) op2;
if(a && b) op3;
if(!a && !c) op4;

Не помню точно первоисточника, но вроде бы это было еще у Дейкстры.
У Мартина Фаулера в «Рефакторинге» это называется «Замена вложенных условных операторов граничным оператором»
Сам я тоже пришел к стилю как в примере выше в «libgcc/libgcov-driver.c — 688:», кажется самым наглядным из всех.
Sign up to leave a comment.

Articles