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

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

Как предикаты могут помочь очистить ваш код. С тегом java, программирование, практика, функционал.

Прочитайте Часть 1, сначала.

В части 1 мы начали с примера общего кода бизнес-логики, и аналогии, основанной на запихивании слона в Умную машину. Мы провели некоторый рефакторинг, чтобы распутать вложенные блоки if/else, но ушли после того, как закончили запихивать слона в Умную машину.

Во многих отношениях это кажется “достаточно хорошим”, но что, если я скажу вам, что мы можем сделать это лучше? Java 8 предоставляет нам новый инструмент для хранения и использования логики внутри оператора if – предиката. Что касается слонов, управляющих автомобилями, то мы можем получить его за рулем стильного кабриолета.

Таким образом, у вас много условной логики, и вы обнаруживаете, что копируете и вставляете условия из одного логического блока в другой. Это легко, это соблазнительно, но это неправильно. Копирование и вставка подвержены ошибкам и требуют дополнительной работы! Если вы похожи на меня, вы стараетесь работать как можно меньше – это работа компьютера. Java 8 предоставляет новый инструмент для предотвращения копирования-вставки и сохранения вашего кода СУХИМ.

Использование предикатов позволяет нам инкапсулировать логику в виде переменной. Это создает две особенно мощные функции

  1. Имена переменных сообщают о цели логики
  2. Логика является многоразовой и поддается индивидуальному тестированию

И Теперь, С предикатами

До Java 8 я не был настроен на мир функционального программирования. Последний раз, насколько я помню, я слышал о предикатах в школе, когда игнорировал урок грамматики. Оказывается, что при программировании предикаты действительно могут улучшить ваш код.

Здесь мы сделаем блоки if/else более удобочитаемыми, создав предикаты из логики, которую они представляют. Предикаты могут быть названы, что позволяет разработчикам называть правила таким образом, чтобы обеспечить четкое обсуждение даже с нетехническими пользователями.

static final Predicate suffientAmount = trans -> trans.getTransferer().getAccount(trans.getFromAccount()).getBalance().compareTo(trans.getAmount()) > 0;
static final Predicate isPartner = ttc -> ttc.equals("200");
static final Predicate isFriendsAndFamily = ttc -> ttc.equals("710");
static final Predicate isFriendAndFamilyDiscountLegal = ac -> ac.matches("574|213|363|510");
static final Predicate isPartneringArea = ac -> ac.matches("907|412|213");
static final Predicate isDirigibleArea = ac -> ac.matches("213");
static final Predicate isDirigibleCategory = cat -> cat.equals("D");
static final Predicate isInternal = tc -> tc.equals("I");

public static final String checkWidgetTransfer(WidgetTransfer transfer) {
String businessRuleErrors = "";

String transferTypeCode = transfer.getTransferTypeCode();
String areaCode = transfer.getAreaCode();
String category = transfer.getTransferer().getCategory();
String typeCode = transfer.getTypeCode();

if (suffientAmount.test(transfer)) {
    businessRuleErrors += "Insufficient balance to transfer ; ";
}

if (isPartner.test(transferTypeCode)
        && isPartneringArea.negate().test(areaCode)) {
    businessRuleErrors += "This area is not a transfer eligible area. ; ";
}

if (isPartner.test(transferTypeCode)
        && isDirigibleArea.test(areaCode)
        && isDirigibleCategory.test(category)) {
    businessRuleErrors += "D Category Transferer can only be transferred in transfer area 213. ; ";
}

if (isFriendsAndFamily.test(transferTypeCode)
        && isFriendAndFamilyDiscountLegal.negate().test(areaCode)) {
    businessRuleErrors += "This area is not an eligible area. ; ";

}

if (isInternal.negate().test(typeCode)
        && !isBlockSize(transfer)) {
    businessRuleErrors += "Amount is too small for I type transfer. ; ";
}

if (isInternal.negate().test(typeCode)
        && isTotalOverCap(transfer)) {
    businessRuleErrors += "This transfer is too large. ; ";
}

return businessRuleErrors;
Enter fullscreen mode Exit fullscreen mode

}

Хорошее

  • Каждый блок if читается на чем-то приближенном к “деловому английскому”.
  • Правила определяются как предикаты
    • Правила переносимы и могут быть использованы повторно.
    • Правила также можно тестировать индивидуально, без необходимости тестировать каждую ветвь сразу

Плохое

  • Этот метод по-прежнему использует & & , который не является идиоматичным для функций в Java

    • Мы вынуждены использовать && , потому что предикаты принимают разные типы объектов, поэтому мы не можем связать их вместе
  • В то время как предикаты, составляющие правила, являются переносимыми, сами правила состоят из нескольких предикатов и не являются переносимыми
  • Не было сделано ничего такого, чего нельзя было бы сделать обычными методами
    • Старое доброе общедоступное логическое значение - достаточное количество (количество строк) было бы достаточно
  • Нам все еще нужно создать все эти переменные свойств, чтобы получить соответствующие значения для наших предикатов

Цепочка предикатов

Давайте исправим некоторые вещи из списка “плохих” из предыдущего примера.

Мы можем избавиться от & & , используя чуть больше функционального интерфейса и рефакторинг предикатов, чтобы все они принимали один и тот же тип объекта, в данном случае объект WidgetTransfer . Цель состоит в том, чтобы сделать наши предикаты похожими на lego – взаимосвязанными и взаимозаменяемыми.

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 isDirigibleArea = 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);

public static final String checkWidgetTransfer(WidgetTransfer transfer) {
String businessRuleErrors = "";

if (suffientAmount.test(transfer)) {
    businessRuleErrors += "Insufficient balance to transfer ; ";
}

if (isPartner.and(isPartneringArea.negate()).test(transfer)) {
    businessRuleErrors += "This area is not a transfer eligible area. ; ";
}

if (isPartner.and(isDirigibleArea).and(isDirigibleCategory).test(transfer)) {
    businessRuleErrors += "D Category Transferer can only be transferred in transfer area 213. ; ";
}

if (isFriendsAndFamily.and(isFriendAndFamilyDiscountLegal.negate()).test(transfer)) {
    businessRuleErrors += "This area is not an eligible area. ; ";
}

if (isInternal.negate().and(isBlockSize.negate()).test(transfer)) {
    businessRuleErrors += "Amount is too small for I type transfer. ; ";
}

if (isInternal.negate().and(isTotalOverCap.negate()).test(transfer)) {
    businessRuleErrors += "This transfer is too large. ; ";
}

return businessRuleErrors;
Enter fullscreen mode Exit fullscreen mode

}

Хорошее

  • Мы избавляемся от дополнительных переменных, содержащих строковые значения, из Widget Transfer объект
  • Мы уплотняем наши if -блоки, сохраняя при этом удобочитаемость
  • Мы оцениваем только один тип объекта

Плохое

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

следующие шаги

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

Первое улучшение заключается в объединении небольших правил в более крупные правила – мы делаем это в условных операторах, но так же легко сделать это в предикате. Мы также можем создать объект Validator для создания набора бизнес-правил и сообщений об ошибках. Validator избавляет от необходимости создавать сложную вложенную логическую структуру if/else и представляет собой конкретную единицу бизнес-логики, которую можно совместно использовать, повторно использовать и тестировать.

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

Мы перейдем к использованию Валидаторов в подлежащем написанию Превратите Свой Спагетти-код в функции, Часть III

Кредиты

Спасибо робот для умный автомобиль, обращенный влево

Спасибо Оливер Додд для слон

Спасибо Филипп Пессар для преобразуемый

Спасибо JPL и NASA/Научному институту Космического телескопа за изображение галактики на краю

Спасибо Филип Шелдрейк для средняя школа

Оригинал: “https://dev.to/monknomo/turn-your-spaghetti-code-into-functions-part-2”