Рефакторинг сложного if-условия

может ли кто-нибудь предложить лучший способ избежать большинства условий if? У меня ниже кода, я хочу избежать большинства случаев, если условия, как это сделать ? любое решение-большая помощь;

if (adjustment.adjustmentAccount.isIncrease) {
    if (adjustment.increaseVATLine) {
        if (adjustment.vatItem.isSalesType) {
            entry2.setDebit(adjustment.total);
            entry2.setCredit(0d);
        } else {
            entry2.setCredit(adjustment.total);
            entry2.setDebit(0d);
        }
    } else {
        if (adjustment.vatItem.isSalesType) {
            entry2.setCredit(adjustment.total);
            entry2.setDebit(0d);
        } else {
            entry2.setDebit(adjustment.total);
            entry2.setCredit(0d);
        }
    }
} else {
    if (adjustment.increaseVATLine) {
        if (adjustment.vatItem.isSalesType) {
            entry2.setCredit(adjustment.total);
            entry2.setDebit(0d);
        } else {
            entry2.setDebit(adjustment.total);
            entry2.setCredit(0d);
        }
    } else {
        if (adjustment.vatItem.isSalesType) {
            entry2.setDebit(adjustment.total);
            entry2.setCredit(0d);
        } else {
            entry2.setCredit(adjustment.total);
            entry2.setDebit(0d);
        }
    }
}

10 ответов


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

Edit: Я хотел бы отметить из комментариев к этому сообщению, что решение XOR, предоставленное Марсело и BlueRaja, идентично по функции.

/* This is to avoid a crazy 3-way switch. Generalised.
 * Instead of using a complicated if-else branch, we can use the number of true
 * and false to entail the intended action. */
/* This is the same as a ^ b ^ c (chained XOR), 
 * which is used to count the parity of truth values. */
int a = adjustment.adjustmentAccount.isIncrease ? 1 : 0;
int b = adjustment.increaseVATLine ? 1 : 0;
int c = adjustment.vatItem.isSalesType ? 1 : 0;

if ((a + b + c) % 2 == 1)
{
    entry2.setDebit(adjustment.total);          // Odd number of trues
    entry2.setCredit(0d);
}
else
{
    entry2.setCredit(adjustment.total);         // Even number of trues
    entry2.setDebit(0d);
}

как это сделать... Давайте извлекать несколько методов, чтобы мы могли лучше видеть логику.

private void a() {
    entry2.setDebit(adjustment.total);
    entry2.setCredit(0d);
}
private void b() {
    entry2.setCredit(adjustment.total);
    entry2.setDebit(0d);
}

if (adjustment.adjustmentAccount.isIncrease) {
    if (adjustment.increaseVATLine) {
        if (adjustment.vatItem.isSalesType) {
            a();
        } else {
            b();
        }
    } else {
        if (adjustment.vatItem.isSalesType) {
            b();
        } else {
            a();
        }
    }
} else {
    if (adjustment.increaseVATLine) {
        if (adjustment.vatItem.isSalesType) {
            b();
        } else {
            a();
    }
} else {
    if (adjustment.vatItem.isSalesType) {
        a();
    } else {
        b();
    }
}

Итак, теперь, глядя на него, этот первый блок

if (adjustment.increaseVATLine) {
    if (adjustment.vatItem.isSalesType) {
        a();
    } else {
        b();
    }
} else {
    if (adjustment.vatItem.isSalesType) {
        b();
    } else {
        a();
    }
}

просто делаешь составляет a() если adjustment.increaseVATLine имеет то же значение, что и adjustment.vatItem.isSalesType, b() иначе. Таким образом, мы можем уменьшить его:

if (adjustment.adjustmentAccount.isIncrease) {
    if (adjustment.increaseVATLine == adjustment.vatItem.isSalesType) {
        a();
    } else {
        b();
    }
} else {
    if (adjustment.increaseVATLine) {
        if (adjustment.vatItem.isSalesType) {
            b();
        } else {
            a();
        }
    } else {
        if (adjustment.vatItem.isSalesType) {
            a();
        } else {
            b();
        }
    }
}

и оставшийся блок тот же самый, только обратный a() и b():

if (adjustment.adjustmentAccount.isIncrease) {
    if (adjustment.increaseVATLine == adjustment.vatItem.isSalesType) {
        a();
    } else {
        b();
    }
} else {
    if (adjustment.increaseVATLine == adjustment.vatItem.isSalesType) {
        b();
    } else {
        a();
    }
}

Итак, мы начинаем видеть логику. Если это увеличение, и increaseVATLine соответствует isSalesType, то мы дебетуем, иначе кредит, но если это уменьшение, то мы кредитуем только если они не совпадают. Как это лучше выразить? Ну, во - первых, назовите A() и b() умнее-теперь, когда мы можем видеть, что они делают

if (adjustment.adjustmentAccount.isIncrease) {
    if (adjustment.increaseVATLine == adjustment.vatItem.isSalesType) {
        debitEntry();
    } else {
        creditEntry();
    }
} else {
    if (adjustment.increaseVATLine == adjustment.vatItem.isSalesType) {
        creditEntry();
    } else {
        debitEntry();
    }
}

и теперь это еще немного яснее. Дебетовать счет, когда это счет увеличения и строка увеличения НДС, и тип продажи, или когда это уменьшение и либо строка уменьшения НДС Или это тип продаж, но не оба. Эта таблица правды помогает? Первая колонка -adjustmentAmount.isIncrease; второй -adjustment.increaseVATLine; третий -adjustment.vatItem.isSalesType. Четвертый столбец - D для дебета,C для кредита; в скобках указано количество истинных значений среди флагов.

TTT -> D (3) 
TFF -> D (1) 
TTF -> C (2)
TFT -> C (2) 
FTT -> C (2) 
FFF -> C (0)
FTF -> D (1) 
FFT -> D (1)

теперь вы можете понять, почему решение @Xavier Ho работает; нечетные итоги-все дебеты, четные-все кредиты.

это только один исследовательский путь; я надеюсь, что это полезно.


Я не полностью проверил логику, но это основная идея:

amt = adjustment.total
if (adjustment.adjustmentAccount.isIncrease
    ^ adjustment.increaseVATLine
    ^ adjustment.vatItem.isSalesType)
{
    amt = -amt;
}

entry2.setCredit(amt > 0 ? amt : 0);
entry2.setDebit(amt < 0 ? -amt : 0);

Я должен отметить, что эта логика немного отличается тем, что она правильно обрабатывает отрицательное значение adjustment.total, тогда как оригинал, похоже, предполагает (возможно, правильно), что значение всегда будет неотрицательным.


вы можете использовать таблицу истинности, как это:

debit = ((isIncrease && increaseVATLine && !isSalesType) ||
         (isIncrease && !increaseVATLine && isSalesType) ||
         (!isIncrease && increaseVATLine && isSalesType) ||
         (!isIncrease && !increaseVATLine && !isSalesType)) ? 0 : adjustment.total;
entry2.setCredit(debit);

нет никаких "если", и вы можете легко видеть, в каких случаях дебет равен 0. То же самое и с кредитом.


к комментарию Мартина Смита добавлю:

помните, что Карно может помочь вам упростить условие этого if.


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

//Set debit if exactly one or all three are true, else set credit
if(adjustment.adjustmentAccount.isIncrease ^ adjustment.increaseVATLine ^
   adjustment.vatItem.isSalesType)
{
    entry2.setDebit(adjustment.total);
    entry2.setCredit(0d);
}
else
{
    entry2.setCredit(adjustment.total);
    entry2.setDebit(0d);
}

похоже, у вас есть только 2 случая, чтобы вы могли объединить их с OR и т. д.

        if (<case1expression>) {
            entry2.setCredit(adjustment.total);
            entry2.setDebit(0d);
        } else {
            entry2.setDebit(adjustment.total);
            entry2.setCredit(0d);
        }

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


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

Если у вас, например, два класса Increase и NonIncrease это подклассы одного и того же суперкласса, у вас может быть метод doSomething это делает-хорошо-что-то в соответствии с любым классом, который у вас есть в настоящее время. Затем вам не нужно проверять "если объект do X", а просто позвонить .doSomething () и он делает все, что он должен делать.

вы можете пойти дальше и иметь больше и более подклассы для дальнейшего "совершенствования" и "не более МФС".

могут быть другие возможности (в основном в зависимости от вашей среды/требований), такие как указатели функций, делегаты, шаблон стратегии (GoF) или подобные конструкции.


самое лучшее решение-следовать шаблону дизайна. Шаблон проектирования на основе состояния определяет класс для каждого состояния.

класс state затем инкапсулирует, каков курс действий для этого конкретного состояния. Это не только предотвращает большой мусор сетки операторов if-else.