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

StackOverflow https://stackoverflow.com/questions/6126

  •  08-06-2019
  •  | 
  •  

Вопрос

Это то, что не дает мне покоя на каждом языке, который я использовал, у меня есть оператор 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)
     {
Это было полезно?

Решение

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

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)
     {
         // ...
     }
}

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

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

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

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

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

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

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.

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

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

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

bool orderValid = orderDate < DateTime.Now && orderStatus != Status.Canceled;
bool custValid = customerBalance == 0 && customerName != "Mike";
if (orderValid && custValid)
{
...

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

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

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

Проверьте Схемы реализации автор: Кент Бек.Есть определенный шаблон, о котором я думаю, который может помочь в этой ситуации...это называется "Стражи порядка".Вместо того чтобы иметь массу условий, вы можете разбить их на защитные, что дает понять, какие неблагоприятные условия в методе.

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

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
}

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

Кстати, я ОЧЕНЬ рекомендую эту книгу.

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

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

Обычно я помещаю close paren в ту же строку, что и последнее условие:

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

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

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

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

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 );
  }
};

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

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

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

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

логическое значение ok = cond1;
ok &= cond2;
ok &= cond3;
ok &= cond4;
ок &= кондиция5;
ok &= cond6;

Что является тем же самым, что и: (это не одно и то же, см. Примечание выше!)

ok = (условие 1 && условие 2 && условие 3 && условие 4 && условие 5 && условие 6);

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

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

if ( cond1 && cond2 ) {}

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

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

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

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

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)){

Если вам посчастливилось программировать на 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?).Если это так, то, скорее всего, это самый чистый подход.

Макдауэлл,

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

@tweakt

Это не лучше, но то, что я делал в прошлом:

логическое значение ok = cond1;ok &= cond2;ok &= cond3;ok &= cond4;ок &= кондиция5;ok &= cond6;

Что является тем же самым, что и:

ok = (условие 1 && условие 2 && условие 3 && условие 4 && условие 5 && условие 6);

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

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

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

значение while (false) какое-то дрянное.Я хотел бы, чтобы в языках был оператор определения области действия под названием "once" или что-то в этом роде, от чего вы могли бы легко отказаться.

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

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

  ... # Place Code Here
}

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

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

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
}
    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)
       )
    {
    }

Нет ничего плохого в горизонтальных пробелах (переводах строк), выравнивании по вертикали или явных скобках, направляющих вычисление выражения, - все это ПОВЫШАЕТ удобочитаемость и ясность

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top