Составная стили И / ИЛИ, если заявления

13

Как вы разрабатываете сложные составные И / ИЛИ операторы для максимальной читабельности? Как вы делаете отступ и где размещаете разрывы строк? Моя конкретная ситуация выглядит примерно так. Это определенно лучше, чем разбивать все на одну строчку, но все равно выглядит грязно.

if (
    (
        x == y
        && a != b
        && p.isGood() 
        && (
            i + u == b
            || q >= a
        )
    )
    || k.isSomething()
    || m > n
) {
    doSomething();
}
JoJo
источник
1
Является ли тупой отступ и скобки / структура скобок намеренными или частью стиля?
Эд С.
Смешной. Я задал этот же вопрос на SO неделю назад, и он был закрыт. Рад видеть этот вопрос живым где-то!
Эрик Белэйр

Ответы:

6

Сделайте логические переменные для каждого маленького шага:

bool step1 = i + u == b || q >= a;
bool step2 = a != b && p.isGood() && group1;
bool step3 = group2 || k.isSomething() || m > n;
if (step3) { doSomething(); }

Это, конечно, похоже на ответ лакримологии, но с разными именами для каждого шага.

Если вы назовете step1, step2и так step3, чтобы это имело хороший концептуальный смысл, это должно быть наиболее разборчиво. p.isGood()и k.isSomething()иногда может быть вызван в ситуациях, когда это не будет в вашем исходном коде, так что это не вариант, если эти функции дороги или если вы выполняете этот код в очень узком цикле.

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

Пример с обнаружением столкновения прямоугольника (который вы, вероятно, не будете использовать из-за вышеупомянутого снижения производительности):

if((a.x + a.width >= b.x || b.x + b.width >= a.x)
 && (a.y + a.height >= b.y || b.y + b.width >= a.y)
)
{ collision(); }

Может стать:

bool horizMatch = a.x + a.width >= b.x || b.x + b.width >= a.x;
bool vertMatch = a.y + a.height >= b.y || b.y + b.width >= a.y;
if(horizMatch && vertMatch) { collision(); }

Кроме того, если вы хотите оставить свой код как есть, я думаю, что это тоже будет нормально. Честно говоря, я думаю, что ваш код вполне читабелен. Очевидно, я не знаю, что именно a b x y i u p k m n, но что касается структуры, она выглядит хорошо для меня.

Рей Миясака
источник
8

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

JohnFx
источник
Я бы избегал рефракторинга в этой ситуации. Было бы глупо тестировать такие маленькие функции изолированно, а определение, висящее вне функции, делает код менее очевидным.
Рей Миясака
Это зависит от того, насколько хорошо вы рефакторинг. Я бы сказал, что это делает ваш код гораздо более очевидным, чтобы иметь значащие имена функций вместо строки условий, которая выглядит как учебник по алгебре, вырванный в вашем коде.
JohnFx
Тем не менее, визуально ваши функции будут висеть снаружи, и не будет сразу очевидно, что именно делает функция. Это на мгновение черный ящик, пока вы не прокрутите вверх. Если вы не говорите на языке, который допускает функции в функциях, я не думаю, что это было бы очень удобно ни для читателя, ни для писателя, независимо от того, насколько хорошо вы рефракторите. И если вы говорите на языке, который допускает функции в функциях, то скорее всего синтаксис практически не отличается от объявления привязки или переменной вместо, например, let x = a > bили let f a b = a > b.
Рей Миясака
Переменные будут работать одинаково хорошо. Я бы тоже подумал об этом рефакторинге.
JohnFx
Ах хорошо.
Рей Миясака
8

Я бы сделал что-то подобное на этом уровне сложности

bool doIt = x == y && a != b && p.isGood();
doIt &= ( i + u == b || q >= a);
doIt |= k.isSomething() || m > n;

if(doIt)
{
    doSomething();
}

это уродливо, но это читабельно, и я уверен, что компилятор будет знать, как его реорганизовать.

С другой стороны, если я когда-нибудь увижу себя в ситуации написания такого оператора IF, я переосмысливаю решение, потому что я УВЕРЕН, что есть способ сделать это проще или, по крайней мере, абстрагировать некоторые из этих условий (например, возможно, x == y && a != b && p.isGood()на самом деле просто имею в виду, this->isPolygon()и я могу сделать этот метод;

Lacrymology
источник
4

Я становлюсь менее одержимым вертикальным выравниванием с течением времени, но моя общая форма с многострочными выражениями ...

if (   (   (expr1 == expr2)
        || (expr3 == expr4)
        || (expr5 == expr6)
       )
    && (   (expr7 == expr8)
        || (expr9 == expra)
       )
   )
{
  blah;
}

Ключевые моменты ...

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

Иногда я буду форматировать +и / *или некоторые другие операторы, как это тоже. Довольно много сложных выражений принимают форму суммы продукта или продукта суммы (которая может относиться к логическим «суммам» и «продуктам»), так что, вероятно, достаточно распространено, что стоит придерживаться согласованного стиля для него.

Будьте осторожны с этим, хотя. Часто лучше выполнить рефакторинг (переместить части выражения в функцию или вычислить и сохранить промежуточные части в переменной), а не использовать отступы, чтобы сделать более сложное выражение более читабельным.

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

if (   (   (expr1 == expr2)
        || (expr3 == expr4)
        || (expr5 == expr6))

    && (   (expr7 == expr8)
        || (expr9 == expra)))
{
  blah;
}
Steve314
источник
+1 Мне нравится твой стиль. Он ответил на мой вопрос напрямую, но я думаю, что Рей Миясака пригвоздил корень проблемы. Если мне когда-нибудь будет лень использовать метод Рей, я буду использовать ваш стиль.
JoJo
Вау, это действительно мило.
Рей Миясака
1

http://www.codinghorror.com/blog/2006/01/flattening-arrow-code.html

Я согласен с ответом JohnFx, а также ответом по Lacrymology. Я хотел бы создать набор функций (предпочтительно статических), которые выполняют небольшие задачи, а затем разумно их использовать.

Итак, как насчет этого? Обратите внимание, что это не идеальное решение, но оно работает. Есть способы убрать это дальше, но нужна более конкретная информация. Примечание: этот код должен выполняться так же быстро, потому что компилятор умен.

// Currently based on members or global vars
// (which is often a bad idea too)
function doSomethingCondirionally()
{
  if (k.isSomething() || m > n)
  {
    doSomething();
    return;
  }

  // Else ... 
  if (x != y) return;
  if (a == b) return;
  if (!p.isGood()) return;

  // Final, positive check
  if (i + u == b || q >= a)
  {
    doSomething();
  }
}
работа
источник
Если вы цените только одну точку выхода из функции (например, если вы работаете на функциональном языке), это может быть не лучшим вариантом или даже доступным. Тем не менее, да, это то, что я часто делаю.
Рей Миясака
Что если это интерпретируемый язык, такой как Javascript?
JoJo
@ Рей Миясака, насколько я ценю, зависит от языка. Хотя мне нравится семейство языков LISP, мне не приходилось использовать его на работе. Если бы мне пришлось пересмотреть чью-то функцию, но не трогать другой код (часто реальность), то я бы сделал что-то вроде выше. Если я смогу написать / переписать эту логику с нуля, тогда мой подход будет другим, но я не могу написать такой код, не имея конкретного примера того, что автор пытается сделать здесь.
Работа
1
@Rei Miyasaka, Этот человек может быть гением или полон дерьма. Я не знаю всего, но мне было бы интересно узнать, как этот человек защищает точку единого выхода. Здесь и в SO обсуждается некоторая дискуссия, и у меня сложилось впечатление, что этот подход был популярен среди ученых в 80-х годах, может быть, но он больше не имеет значения и может фактически препятствовать удобочитаемости. Конечно, если вы делаете все в функциональном стиле LINQ, то эта проблема даже не возникнет.
Работа
2
@Job @Steve Я думаю, что это более важное руководство для языков, которые требуют явного освобождения. Очевидно, что не для каждой функции, но, вероятно, это привычка, которую начинающим программистам предлагалось придерживаться, чтобы не забыть освободить ресурсы.
Рей Миясака
1

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

Позвольте мне подчеркнуть, что вы сделали эту часть правильно: && a != b НИКОГДА не ставьте логический разъем в конце строки, это слишком легко пропустить визуально. Еще одно место, где вы НИКОГДА не должны ставить оператор в конце строки, - это конкатенация строк на языках с таким оператором.

Сделай это:

String a = b
   + "something"
   + c
   ;

Не делай этого:

String a = b +
   "something" +
   c;
Брюс Эдигер
источник
Есть ли у вас логика или исследования, подтверждающие ваше утверждение о логических коннекторах, или это просто ваше предпочтение, изложенное как факт? Я много слышал об этом в разных местах и ​​никогда не понимал этого (в отличие от условных выражений йоды, которые имеют вескую (если ошибочную) причину).
Калеб Хуитт - cjhuitt
@Caleb - Математика была набрана таким образом на протяжении веков. При сканировании кода мы фокусируемся на левой стороне каждой строки. Строки, начинающиеся с оператора, очевидно, являются продолжением предыдущей строки, а не новым оператором с неверным отступом.
Кевин Клайн
Мне также нравится префикс математических операторов. Я понял это в конце моей карьеры программиста :)
JoJo
0

Если условие настолько сложное, это обычно указывает на то, что оно должно быть разбито на части. Возможно, одному предложению можно присвоить промежуточную переменную. Возможно, одно предложение можно превратить в вспомогательный метод. Я вообще предпочитаю не иметь так много ands и ors в одной строке.

Чжэхао Мао
источник
0

Вы можете разбить код на несколько операторов, что облегчает понимание. Но настоящий ниндзя сделал бы что-то подобное. :-)

if
(
    (
        x == y
    &&
        a != b
    &&
        p.isGood()
    &&
        (
            i + u == b
        ||
            q >= a
        )
    )
||
    k.isSomething()
||
    m > n
)
{
    doSomething();
}
Даниэль Любаров
источник
5
Я фанат пустого пространства, но это чрезмерно дополнено почти пустыми строками на мой вкус.
Steve314