Как упростить сложную бизнес-логику "ЕСЛИ"?

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

Пример:

Скидка. может быть:

1a) Скидка стоимости
1b) Процентная скидка

2a) Нормальная скидка
2b) Прогрессивная скидка

3a) Требуется купон доступа
3b) Не требуется купон доступа

4a) Применяется только к клиенту, который уже купил ранее 4b) Применяется к любому клиенту

5a) Применяется только для клиентов из стран (X, Y,...)

Это требует еще более сложного кода:

if (discount.isPercentage) {
    if (discount.isNormal) {
        if (discount.requiresAccessCoupon) {
        } else {
        }
    } else if (discount.isProgressive) {
        if (discount.requiresAccessCoupon) {
        } else {
        }
    }
} else if (discount.isValue) {
    if (discount.isNormal) {
        if (discount.requiresAccessCoupon) {
        } else {
        }
    } else if (discount.isProgressive) {
        if (discount.requiresAccessCoupon) {
        } else {
        }
    }
} else if (discount.isXXX) {
    if (discount.isNormal) {
    } else if (discount.isProgressive) {
    }
}

Даже если вы замените IFs на переключатель /case, это все еще слишком сложно. Каковы способы сделать его читабельным, ремонтопригодным, более проверяемым и понятным?

Ответы

Ответ 1

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

Ответ 2

Хороший вопрос. "Условная сложность" - это запах кода. Polymorphism является вашим другом.

Условная логика невинна в зачаточном состоянии, когда ее просто понять и несколько строк кода. К сожалению, он редко стареет. Вы реализуете несколько новых функций и внезапно ваша условная логика становится сложной и экспансивной. [Джошуа Керевский: Рефакторинг для образцов]

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

double getPayAmount() {
if (_isDead) return deadAmount();
if (_isSeparated) return separatedAmount();
if (_isRetired) return retiredAmount();
return normalPayAmount();
};  

Другая вещь, которую я нашел, упрощает все, что делает ваш код самодокументированным, Консолидация условностей.

double disabilityAmount() {
    if (isNotEligableForDisability()) return 0;
    // compute the disability amount

Другие ценные методы refactoring, связанные с условными выражениями, включают Decompose Conditional, Заменить условный с посетителем и Обратный условный.

Ответ 3

Спецификация шаблона может быть тем, что вы ищете.

Сводка:

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

Ответ 4

Объектно-ориентированный способ сделать это состоит в том, чтобы иметь несколько классов скидок, реализующих общий интерфейс:

dicsount.apply(order)

Поместите логику для определения того, соответствует ли заказ для скидки в классах скидок.

Ответ 6

Вы действительно должны увидеть

Чистые кодовые разговоры - наследование, полиморфизм и тестирование
by Miško Hevery

Google Tech Talks 20 ноября 2008 г.

РЕЗЮМЕ

Является ли ваш код полным для операторов if? Выключить инструкции? У вас есть одно и то же предложение switch в разных местах? Когда вы вносите изменения, вы обнаружите, что делаете одно и то же изменение с тем же, если /switch в нескольких местах? Вы когда-нибудь забыли его?

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

Ответ 7

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

if (discount.isPercentage) {
  callFunctionOne(...);
} else if (discount.isValue) {
  callFunctionThree(...);
} else if (discount.isXXX) {
  callFunctionTwo(...);
}

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

Ответ 8

Сделайте методы, которые проверяют конкретный случай.

bool IsValueNormalAndRequiresCoopon (скидка скидка) {...}

bool IsValueNormalAndRequiresCoupon (скидка скидка) {...}

и т.д.

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

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

Ответ 9

FWIW, я использовал Hamcrest очень успешно для такого рода вещей. Я думаю, вы могли бы сказать, что он реализует шаблон спецификации, о котором говорил @Arnis.