Как вы справляетесь с огромными if-условиями?

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

Есть ли какие-либо другие методы, которые вы нашли, которые могут быть полезны мне и кому-либо еще, столкнувшемуся с той же проблемой?

Пример, все в одной строке:

if (var1 = true && var2 = true && var2 = true && var3 = true && var4 = true && var5 = true && var6 = true)
{

Пример, многострочный:

if (var1 = true && var2 = true && var2 = true
 && var3 = true && var4 = true && var5 = true
 && var6 = true)
{

Вложенный пример:

if (var1 = true && var2 = true && var2 = true && var3 = true)
{
     if (var4 = true && var5 = true && var6 = true)
     {
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
29
0
4 786
21
Перейти к ответу Данный вопрос помечен как решенный

Ответы 21

Во-первых, я бы удалил все части == true, что сделало бы его на 50% короче;)

Когда у меня большое состояние, я ищу причины. Иногда я вижу, что нужно использовать полиморфизм, иногда мне нужно добавить какой-то объект состояния. По сути, это означает, что нужен рефакторинг (запах кода).

Иногда я использую Законы Де-Моргана, чтобы немного упростить логические выражения.

Я прибегаю к отдельным логическим значениям:

Bool cond1 == (var1 && var2);
Bool cond2 == (var3 && var4);

if ( cond1 && cond2 ) {}

Я видел много людей и редакторов, которые либо делали отступ для каждого условия в вашем операторе if одной табуляцией, либо сопоставляли его с открытым пареном:

if (var1 == true
    && var2 == true
    && var3 == true
   ) {
    /* do something.. */
}

Я обычно помещаю закрывающую скобку в ту же строку, что и последнее условие:

if (var1 == true
    && var2 == true
    && var3 == true) {
    /* do something.. */
}

Но я не думаю, что это так чисто.

Я часто разделяю их на логические переменные компонентов:

bool orderValid = orderDate < DateTime.Now && orderStatus != Status.Canceled;
bool custValid = customerBalance == 0 && customerName != "Mike";
if (orderValid && custValid)
{
...
Ответ принят как подходящий

Разделите условие на несколько логических значений, а затем используйте главное логическое значение в качестве условия.

bool isOpaque = object.Alpha == 1.0f;
bool isDrawable = object.CanDraw && object.Layer == currentLayer;
bool isHidden = hideList.Find(object);

bool isVisible = isOpaque && isDrawable && ! isHidden;

if (isVisible)
{
    // ...
}

Еще лучше:

public bool IsVisible {
    get
    {
        bool isOpaque = object.Alpha == 1.0f;
        bool isDrawable = object.CanDraw && object.Layer == currentLayer;
        bool isHidden = hideList.Find(object);

        return isOpaque && isDrawable && ! isHidden;
    }
}

void Draw()
{
     if (IsVisible)
     {
         // ...
     }
}

Убедитесь, что вы дали своим переменным имя, которое на самом деле указывает на намерение, а не на функцию. Это очень поможет разработчику поддерживать ваш код ... это можете быть ВЫ!

Просто, легко и эффективно.

Graham Clark 13.05.2010 15:12

Ну, во-первых, почему бы и нет:

if (var1 && var2 && var2 && var3 && var4 && var5 && var6) {
...

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

Не лучше, но то, что я делал в прошлом: (Следующий метод предотвращает короткое замыкание логического тестирования, все тесты запускаются, даже если первый ложный. Не рекомендуется, если вы не знаете, что вам нужно всегда выполнять весь код перед возвратом - спасибо ptomato за обнаружение моей ошибки!)

boolean ok = cond1;
ok &= cond2;
ok &= cond3;
ok &= cond4;
ok &= cond5;
ok &= cond6;

Это то же самое, что: (не то же самое, см. Примечание выше!)

ok = (cond1 && cond2 && cond3 && cond4 && cond5 && cond6);

Это не то же самое, если оператор && закорачивает.

ptomato 10.05.2010 19:57

Вау. Я должен был это знать. Мой первый фейспалм по одному из моих собственных ответов ;-)

Mark Renouf 13.05.2010 14:43

Мне нравится разбивать их по уровням, поэтому я бы отформатировал ваш пример следующим образом:

if (var1 = true
 && var2 = true
 && var2 = true
 && var3 = true
 && var4 = true
 && var5 = true
 && var6 = true){

Это удобно, когда у вас больше вложений, как это (очевидно, что реальные условия были бы более интересными, чем "= true" для всего):

if ((var1 = true && var2 = true)
 && ((var2 = true && var3 = true)
  && (var4 = true && var5 = true))
 && (var6 = true)){

Посмотрите Шаблоны реализации Кента Бека. Я думаю, что есть особый паттерн, который может помочь в этой ситуации ... он называется «Стражи». Вместо того, чтобы иметь множество условий, вы можете превратить их в охрану, которая проясняет, какие неблагоприятные условия в методе.

Так, например, если у вас есть метод, который что-то делает, но есть определенные условия, при которых он не должен что-то делать, а не:

public void doSomething() {
    if (condition1 && condition2 && condition3 && condition4) {
        // do something
    }
}

Вы можете изменить его на:

public void doSomething() {
    if (!condition1) {
        return;
    }

    if (!condition2) {
        return;
    }

    if (!condition3) {
        return;
    }

    if (!condition4) {
        return;
    }

    // do something
}

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

Кстати, я НАСТОЯТЕЛЬНО рекомендую эту книгу.

"Быстрый возврат" убивает и антипаттерн "стрелка" :)

Arnis Lapsa 13.05.2010 22:29

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

Randy Stegbauer 21.12.2017 01:10

Я удивлен, что этого еще никто не получил. Специально для этого типа проблем есть рефакторинг:

http://www.refactoring.com/catalog/decomposeConditional.html

Мне не нравится Decompose Conditional, потому что он загрязняет структуру кода одноразовыми функциями, которые нельзя использовать повторно. Я бы предпочел большой оператор IF с комментариями для каждой «группы» связанных проверок.

Milan Babuškov 09.05.2010 16:03

Здесь необходимо решить две проблемы: читаемость и понятность.

Решение «читабельности» - это проблема стиля, и как таковая она открыта для интерпретации. Я предпочитаю следующее:

if (var1 == true && // Explanation of the check
    var2 == true && // Explanation of the check
    var3 == true && // Explanation of the check
    var4 == true && // Explanation of the check
    var5 == true && // Explanation of the check
    var6 == true)   // Explanation of the check
    { }

или это:

if (var1 && // Explanation of the check
    var2 && // Explanation of the check
    var3 && // Explanation of the check
    var4 && // Explanation of the check
    var5 && // Explanation of the check
    var6)   // Explanation of the check
    { }

Тем не менее, такую ​​сложную проверку может быть довольно сложно мысленно проанализировать при сканировании кода (особенно если вы не являетесь первоначальным автором). Рассмотрите возможность создания вспомогательного метода, чтобы отвлечься от некоторых сложностей:

/// <Summary>
/// Tests whether all the conditions are appropriately met
/// </Summary>
private bool AreAllConditionsMet (
    bool var1,
    bool var2,
    bool var3,
    bool var4,
    bool var5,
    bool var6)
{
    return (
        var1 && // Explanation of the check
        var2 && // Explanation of the check
        var3 && // Explanation of the check
        var4 && // Explanation of the check
        var5 && // Explanation of the check
        var6);  // Explanation of the check
}

private void SomeMethod()
{
    // Do some stuff (including declare the required variables)
    if (AreAllConditionsMet (var1, var2, var3, var4, var5, var6))
    {
        // Do something
    }
}

Теперь при визуальном сканировании метода SomeMethod фактическая сложность тестовой логики скрыта, но семантическое значение сохраняется для понимания людьми на высоком уровне. Если разработчику действительно необходимо разобраться в деталях, можно изучить метод AreAllConditionsMet.

Я думаю, это формально известно как шаблон рефакторинга «Разобрать условный». Такие инструменты, как Resharper или Refactor Pro! может облегчить выполнение такого рода рефакторинга!

Во всех случаях ключом к читаемому и понятному коду является использование реалистичных имен переменных. Хотя я понимаю, что это надуманный пример, «var1», «var2» и т. д. Являются допустимыми именами переменных нет. У них должно быть имя, которое отражает основную природу данных, которые они представляют.

Если вы занимаетесь программированием на Python, это удобно со встроенной функцией all(), применяемой к списку ваших переменных (здесь я просто буду использовать логические литералы):

>>> L = [True, True, True, False, True]
>>> all(L) # True, only if all elements of L are True.
False
>>> any(L) # True, if any elements of L are True.
True

Есть ли на вашем языке соответствующая функция (C#? Java?). Если да, то это, вероятно, самый чистый подход.

Если вы сделаете это:

if (var1 == true) {
    if (var2 == true) {
        if (var3 == true) {
            ...
        }
    }
}

Затем вы также можете реагировать на случаи, когда что-то не так. Например, если вы проверяете ввод, вы можете дать пользователю совет, как его правильно отформатировать, или что-то еще.

Это, пожалуй, худшее решение этого вопроса.

Brad Gilbert 15.10.2008 00:59

Макдауэлл,

Вы правы, когда используете единственный оператор '&', который оценивают обе стороны выражения. Однако при использовании оператора '&&' (по крайней мере, в C#) первое выражение, которое возвращает false, является последним вычисленным выражением. Это делает вывод вычисления перед оператором FOR так же хорошо, как и любой другой способ сделать это.

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

@tweakt

It's no better, but what I've done in the past:

boolean ok = cond1; ok &= cond2; ok &= cond3; ok &= cond4; ok &= cond5; ok &= cond6;

Which is the same as:

ok = (cond1 && cond2 && cond3 && cond4 && cond5 && cond6);

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

Для удобочитаемости я лично предпочитаю предложение Майка Стоуна, приведенное выше. Легко дать подробные комментарии и сохранить все вычислительные преимущества возможности раннего выхода. Вы также можете использовать ту же технику в функции, если это запутает организацию вашего кода, так как условное вычисление переместится подальше от другой вашей функции. Это немного банально, но вы всегда можете сделать что-нибудь вроде:

do {
    if (!cond1)
       break;
    if (!cond2)
       break;
    if (!cond3)
       break;
    ...
    DoSomething();
} while (false);

в то время как (ложь) немного глупо. Я бы хотел, чтобы у языков был оператор области видимости под названием «once» или что-то такое, от чего можно было бы легко избавиться.

Попробуйте взглянуть на Функторы и Предикаты. В проекте Apache Commons есть отличный набор объектов, позволяющий инкапсулировать условную логику в объекты. Пример их использования доступен на Oreilly здесь. Отрывок из примера кода:

import org.apache.commons.collections.ClosureUtils;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.functors.NOPClosure;

Map predicateMap = new HashMap();

predicateMap.put( isHonorRoll, addToHonorRoll );
predicateMap.put( isProblem, flagForAttention );
predicateMap.put( null, ClosureUtils.nopClosure() );

Closure processStudents = 
    ClosureUtils.switchClosure( predicateMap );

CollectionUtils.forAllDo( allStudents, processStudents );

Теперь подробности всех этих предикатов isHonorRoll и замыканий, используемых для их оценки:

import org.apache.commons.collections.Closure;
import org.apache.commons.collections.Predicate;

// Anonymous Predicate that decides if a student 
// has made the honor roll.
Predicate isHonorRoll = new Predicate() {
  public boolean evaluate(Object object) {
    Student s = (Student) object;

    return( ( s.getGrade().equals( "A" ) ) ||
            ( s.getGrade().equals( "B" ) && 
              s.getAttendance() == PERFECT ) );
  }
};

// Anonymous Predicate that decides if a student
// has a problem.
Predicate isProblem = new Predicate() {
  public boolean evaluate(Object object) {
    Student s = (Student) object;

    return ( ( s.getGrade().equals( "D" ) || 
               s.getGrade().equals( "F" ) ) ||
             s.getStatus() == SUSPENDED );
  }
};

// Anonymous Closure that adds a student to the 
// honor roll
Closure addToHonorRoll = new Closure() {
  public void execute(Object object) {
    Student s = (Student) object;

    // Add an award to student record
    s.addAward( "honor roll", 2005 );
    Database.saveStudent( s );
  }
};

// Anonymous Closure flags a student for attention
Closure flagForAttention = new Closure() {
  public void execute(Object object) {
    Student s = (Student) object;

    // Flag student for special attention
    s.addNote( "talk to student", 2005 );
    s.addNote( "meeting with parents", 2005 );
    Database.saveStudent( s );
  }
};

Отлично! Один, который нужно скачать и поиграть с methinks.

toolkit 09.10.2008 19:07

Мне нравится разбивать каждое условие на описательные переменные.

bool isVar1Valid, isVar2Valid, isVar3Valid, isVar4Valid;
isVar1Valid = ( var1 == 1 )
isVar2Valid = ( var2.Count >= 2 )
isVar3Valid = ( var3 != null )
isVar4Valid = ( var4 != null && var4.IsEmpty() == false )
if ( isVar1Valid && isVar2Valid && isVar3Valid && isVar4Valid ) {
     //do code
}

Какой смысл создавать отдельные буллы. Кроме того, вы сравниваете каждый varX с true и назначаете его логическому элементу isVarXValid, который по сути является просто от руки для isVar1Valid = var, который является избыточным. У вас уже есть bools для начала, так почему бы не просто if (var1 && var2 && var3 && var4)

dreamlax 13.05.2010 14:57

Совет Стива Макконелла от Код завершен: Используйте многомерный стол. Каждая переменная служит индексом для таблицы, а оператор if превращается в поиск по таблице. Например, если (размер == 3 && вес> 70) переводится в решение о входе в таблицу [размер] [группа_веса]

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

{
  last unless $var1;
  last unless $var2;
  last unless $var3;
  last unless $var4;
  last unless $var5;
  last unless $var6;

  ... # Place Code Here
}

Если вы планируете использовать это вместо подпрограммы, замените каждый экземпляр last на return;

В рефлексивных языках, таких как PHP, вы можете использовать переменные-переменные:

$vars = array('var1', 'var2', ... etc.);
foreach ($vars as $v)
    if ($$v == true) {
        // do something
        break;
    }

    if (   (condition_A)
        && (condition_B)
        && (condition_C)
        && (condition_D)
        && (condition_E)
        && (condition_F)
       )
    {
       ...
    }

в отличие от

    if (condition_A) {
       if (condition_B) {
          if (condition_C) {
             if (condition_D) {
                if (condition_E) {
                   if (condition_F) {
                      ...
                   }
                }
             }
          }
       }
    }

и

    if (   (   (condition_A)
            && (condition_B)
           )
        || (   (condition_C)
            && (condition_D)
           )
        || (   (condition_E)
            && (condition_F)
           )
       )
    {
       do_this_same_thing();
    }

в отличие от

    if (condition_A && condition_B) {
       do_this_same_thing();
    }
    if (condition_C && (condition_D) {
       do_this_same_thing();
    }
    if (condition_E && condition_F) {
       do_this_same_thing();
    }

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

Вертикальное выравнивание на одном уровне отступов открытых / закрывающих фигурных скобок {}, открывающих закрывающих круглых скобок (), условных выражений с круглыми скобками и операторов слева - очень полезная практика, которая значительно УЛУЧШАЕТ читаемость и ясность кода, а не заглушает все которые могут быть втиснуты в одну строку, без вертикального выравнивания, пробелов или скобок

Правила приоритета операторов сложны, например && имеет более высокий приоритет, чем ||, но | имеет приоритет, чем &&

Так, ...

    if (expr_A & expr_B || expr_C | expr_D & expr_E || expr_E && expr_F & expr_G || expr_H {
    }

- это действительно простое множественное условное выражение, которое простые люди могут неправильно прочитать и оценить.

    if (   (  (expr_A)
            & (expr_B)
           )
        || (  (expr_C)
            | (  (expr_D)
               & (expr_E)
              )
           )
        || (   (expr_E)
            && (  (expr_F)
                & (expr_G)
               )
           )
        || (expr_H)
       )
    {
    }

Нет ничего плохого в горизонтальном пространстве (перевод строки), вертикальном выравнивании или явной оценке выражений, определяющих скобки, - все это УЛУЧШАЕТ удобочитаемость и ясность.

Другие вопросы по теме