Первоначально опубликовано на www.gunnargissel.com
Продолжая с того места, на котором мы остановились в части II , давайте подготовим почву для использования валидатора. Мы оставили нашего примерного слона в крутом кабриолете, но давайте улучшим его езду до ракетного корабля. Мы начнем, как все хорошие ученые-ракетчики, с раунда очистки!
После мизансцены мы будем готовы внедрить Валидатор и Результат , который создаст единый стандартный расширяемый формат для бизнес-правил и api для использования результатов запросов бизнес-правил. Больше не нужно гадать, как выглядит вызов, соответствующий правилам! Больше никаких нанизываний хрустальных башен вложенной булевой логики!
Уборка
Есть пара мест, где мы использовали . и () для объединения предикатов в блоке if . Замените их независимым предикатом, так что все блоки if относятся только к одному предикату
static final PredicatesuffientAmount = trans -> trans.getTransferer().getAccount(trans.getFromAccount()).getBalance().compareTo(trans.getAmount()) < 0; static final Predicate isPartner = trans -> trans.getTransferTypeCode().equals("200"); static final Predicate isFriendsAndFamily = trans -> trans.getTransferTypeCode().equals("710"); static final Predicate isFriendAndFamilyDiscountLegal = trans -> trans.getAreaCode().matches("574|213|363|510"); static final Predicate isPartneringArea = trans -> trans.getAreaCode().matches("907|412|213"); static final Predicate isDirigibleForbiddenArea = trans -> trans.getAreaCode().matches("213"); static final Predicate isDirigibleCategory = trans -> trans.getTransferer().getCategory().equals("D"); static final Predicate isInternal = trans -> trans.getTypeCode().equals("I"); static final Predicate isBlockSize = trans -> isBlockSize(trans); static final Predicate isTotalOverCap = trans -> isTotalOverCap(trans); static final Predicate parterTransferReqs = trans -> isPartner.and(isPartneringArea.negate()).test(trans); static final Predicate dirigibleTransferReqs = trans -> isPartner.and(isDirigibleForbiddenArea.negate()).and(isDirigibleCategory).test(trans); static final Predicate friendsAndFamilyReqs = trans -> isFriendsAndFamily.and(isFriendAndFamilyDiscountLegal.negate()).test(trans); static final Predicate internalBlockReqs = trans -> isInternal.and(isBlockSize).test(trans); static final Predicate internalTotalCapReqs = trans -> isInternal.and(isTotalOverCap).test(trans); public static final String checkWidgetTransfer(WidgetTransfer transfer) { String businessRuleErrors = ""; if (suffientAmount.test(transfer)) { businessRuleErrors += "Insufficient balance to transfer ; "; } if (parterTransferReqs.test(transfer)) { businessRuleErrors += "This area is not a transfer eligible area. ; "; } if (dirigibleTransferReqs.test(transfer)) { businessRuleErrors += "D Category Transferer can only be transferred in transfer area 213. ; "; } if (friendsAndFamilyReqs.test(transfer)) { businessRuleErrors += "This area is not a transfer eligible area. ; "; } if (internalBlockReqs.test(transfer)) { businessRuleErrors += "Amount is too small for I type transfer. ; "; } if (internalTotalCapReqs.test(transfer)) { businessRuleErrors += "This transfer is too large. ; "; } return businessRuleErrors; }
Приведенный выше код содержит много шаблонов, но это шаблон, который невидим для обычного разработчика Java. Все эти если блоки являются котельной плитой. Вы должны вводить их снова и снова. Что, если я скажу вам, что вы могли бы использовать валидатор и очистить все свои бизнес-правила? Подумайте о множестве условных выражений в вашем бизнес-коде и представьте, что каждое из них сведено к одному составному вызову функции.
Удаление шаблона
Другая проблема заключается в том, что checkWidget Transfer возвращает строку. Это возлагает ответственность за определение того, произошла ли ошибка, на вызывающий метод. Всем checkWidget Transfer вызывающим абонентам нужен раздел, который выглядит следующим образом:
String result = checkWidgetTransfer(transfer);
if(null == result || 0 == result.size()) {
//continue
}else{
handleError(result);
}
Умножьте это на каждый причудливый процесс, который использует Боб в бухгалтерии, Кэрол в продажах и Дуэйн в штаб-квартире. Это может получиться…. большой.
Мы можем сэкономить на наборе текста, совместно использовать условные обозначения и бизнес-логику, используя Валидатор и Результат техника. Вот как это выглядит с точки зрения звонящего:
checkWidgetTransfer(transfer).onError(err -> handleError(err)); //continue
Это предоставляет api для вызывающего абонента, который указывает, что такое состояние ошибки. Вызывающим абонентам больше не нужно догадываться, что пустая строка является пропуском, а непустая строка – ошибкой. API предоставляет место для обработки ошибок, которое может использовать существующую функцию или иметь лямбду “на лету”. Мило!
Вам все еще нужно написать любимый рабочий процесс Боба, Кэрол и Дуэйна. но все компактно, и вам не нужно спускаться по множеству извилистых ветвей, каждая из которых больше похожа на предыдущую.
Теперь с помощью валидатора
Что, если бы вам не нужно было писать никаких утверждений “если”/”тогда”/”еще”? Что, если бы вам нужно было только написать логику, и что-то другое справилось бы с объединением этой логики? Валидатор может сделать это возможным.
Вот что такое реализация передачи checkWidget похоже, с помощью валидатора:
public static final ResultcheckWidgetTransfer(WidgetTransfer transfer) { Validator widgetTransferValidator = new Validator(); widgetTransferValidator.addRule(suffientAmount, "Insufficient balance to transfer"); widgetTransferValidator.addRule(parterTransferReqs, "This area is not a transfer eligible area"); widgetTransferValidator.addRule(dirigibleTransferReqs, "D Category Transferer can only be transferred in transfer area 213"); widgetTransferValidator.addRule(friendsAndFamilyReqs, "This area is not an eligible area"); widgetTransferValidator.addRule(internalBlockReqs, "Amount is too small for I type transfer"); widgetTransferValidator.addRule(internalTotalCapReqs, "This transfer is too large"); return widgetTransferValidator.validate(transfer); }
Валидатор может принимать столько правил, сколько необходимо, и каждое правило получает имя и соответствующее сообщение. Средство проверки гарантирует, что к передаче применяется каждое правило, и возвращается Результат , содержащий либо сообщения об ошибках, либо передачу.
Детали реализации
Я реализовал Валидатор как хэш-карту функций для строк ошибок. Метод validate проверяет каждую функцию и, если тест верен, собирает соответствующее сообщение в Результате .
Я реализовал Результат в качестве Либо . Вызывающие абоненты имеют возможность получить логическое значение hasErrors или передать Потребитель для onError . Важная вещь в Результате заключается в том, что он никогда не равен нулю и указывает разработчику правильный метод обработки ошибки.
Заворачивать
Всего лишь крошечным толчком мы помогли нашему слону выйти на орбиту. Мы перешли от стандартной бизнес-логики спагетти-болота к чистым функциям, воплощающим составную бизнес-логику. Спасибо, что пришли прокатиться со мной. Я приветствую комментарии или отзывы по адресу gunnar@gunnargissel.com , или @monknomo в Твиттере
Подпишитесь на мой список рассылки, чтобы получать уведомления об обновлениях в продолжающихся сериях, а также ежемесячный сборник интересных статей о программировании и техническом лидерстве.
Кредиты
Спасибо Оливер Додд для слон
Спасибо НАСА, ЕКА, Н. Смит (U. Калифорния, Беркли) и др. и команда Наследия Хаббла (STScI/AURA) для туманности Карина
Фотография шаттла “Индевор” около Мистер Литтлхэнд
Плита котла из Леса Чатфилда
Бластофф около Мэтью Ланкастер
Оригинал: “https://dev.to/monknomo/turn-your-spaghetti-code-into-functions—part-3-3dif”