Рубрики
Без рубрики

Превратите Свой код Спагетти в функции – Часть 3

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

Первоначально опубликовано на www.gunnargissel.com

Продолжая с того места, на котором мы остановились в части II , давайте подготовим почву для использования валидатора. Мы оставили нашего примерного слона в крутом кабриолете, но давайте улучшим его езду до ракетного корабля. Мы начнем, как все хорошие ученые-ракетчики, с раунда очистки!

После мизансцены мы будем готовы внедрить Валидатор и Результат , который создаст единый стандартный расширяемый формат для бизнес-правил и api для использования результатов запросов бизнес-правил. Больше не нужно гадать, как выглядит вызов, соответствующий правилам! Больше никаких нанизываний хрустальных башен вложенной булевой логики!

Уборка

Есть пара мест, где мы использовали . и () для объединения предикатов в блоке if . Замените их независимым предикатом, так что все блоки if относятся только к одному предикату

static final Predicate suffientAmount = 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 Result checkWidgetTransfer(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”