Как упростить сложную бизнес-логику “ЕСЛИ”?
-
05-07-2019 - |
Вопрос
Каковы хорошие способы обработки сложной бизнес-логики, которая на первый взгляд требует множества вложенных операторов 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 на switch / case, это все равно будет слишком сложно.Каковы способы сделать его читабельным, обслуживаемым, более тестируемым и простым для понимания?
Решение
Я бы написал универсальный конечный автомат, который использует списки объектов для сравнения.
Другие советы
Хороший вопрос."Условная сложность" - это запах кода. Полиморфизм это твой друг.
Условная логика невинна в своем зачаточном состоянии, когда она проста для понимания и содержится в пределах нескольких строк кода.К сожалению, он редко хорошо стареет.Вы внедряете несколько новых функций и внезапно ваша условная логика становится сложной и расширяющейся.[Джошуа Керевски:Рефакторинг к шаблонам]
Одна из самых простых вещей, которые вы можете сделать, чтобы избежать вложенных блоков if, - это научиться использовать Охранные положения.
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
Другие ценные рефакторинг методы , связанные с условными выражениями , включают Разложить Условное, Заменить Условное на Посетитель, и Обратное Условие.
Шаблон спецификации возможно, это то, что вы ищете.
Краткие сведения:
В компьютерном программировании шаблон спецификации - это особый шаблон проектирования программного обеспечения, посредством которого бизнес-логика может быть рекомбинирована путем объединения бизнес-логики в цепочку с использованием булевой логики.
Объектно ориентированный способ сделать это состоит в том, чтобы иметь несколько классов скидок, реализующих общий интерфейс:
dicsount.apply(order)
Поместите логику для определения того, подпадает ли заказ под действие скидки, в классы скидок.
Используя охранные положения могло бы кому-то помочь.
FWIW, я использовал Хэмкрест очень успешно для такого рода вещей.Я полагаю, вы могли бы сказать, что он реализует Шаблон спецификации, о котором говорил @Arnis.
Вы действительно должны увидеть
Беседы о чистом коде - Наследование, полиморфизм и тестирование
автор Мишко Хевери
Технические доклады Google 20 ноября 2008
АБСТРАКТНЫЙ
Полон ли ваш код операторов if?Переключать операторы?Есть ли у вас один и тот же оператор switch в разных местах?Когда вы вносите изменения, обнаруживаете ли вы, что вносите одно и то же изменение в одно и то же if / switch в нескольких местах?Вы когда-нибудь забывали об одном из них?
В этом докладе будут обсуждаться подходы к использованию объектно-ориентированных методов для удаления многих из этих условностей.В результате получается более чистый, упорядоченный, лучше спроектированный код, который легче тестировать, понимать и поддерживать.
Моя первая мысль заключается в том, что это не поддается проверке, что приводит меня к решению, чтобы сделать его поддающимся проверке.
if (discount.isPercentage) {
callFunctionOne(...);
} else if (discount.isValue) {
callFunctionThree(...);
} else if (discount.isXXX) {
callFunctionTwo(...);
}
Тогда вы можете сделать так, чтобы каждый вложенный оператор if был отдельным вызовом.Таким образом, вы можете протестировать их по отдельности, и когда вы тестируете большую группу, вы знаете, что каждый отдельный из них работает.
Создайте методы, которые проверяют наличие конкретного случая.
bool - это значение normalandrequirescoopon(Скидка со скидкой){...}
bool - значение normalandrequirescoupon(Скидка со скидкой){...}
и т.д.
Как только вы начнете это делать, вам станет легче увидеть, где вы можете абстрагироваться от общей логики между вариантами.Затем вы можете перейти оттуда.
Для принятия сложных решений я часто получаю класс, который обрабатывает возможные состояния.