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

Решение проблемы плохого дизайна в реальном мире путем применения принципа “Скажи, не спрашивай”

Можете ли вы определить плохой дизайн, когда вы его видите? Можете ли вы сказать, что здесь не так? если (! ответ.получить статусный код… С тегами ооп, архитектура, java, csharp.

Можете ли вы определить плохой дизайн, когда вы его видите? Можете ли вы сказать, что здесь не так?

if (!response.getStatusCode().is2xxSuccessful()) {
    logError(response.getBody()); 
    throwException(response.getStatusCode()); 
}

Приведенный выше фрагмент является примером шаблона, который мы часто видим в дикой природе:

if(someObject.getSomeAttribute()) { 
    doSomethingWith(someObject); 
} else { 
    doSomethingElse(someObject); 
}

Почему меня это должно волновать?

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

public class PaymentProcessorClient { 
// some code removed for brevity's sake 
    public Sale processPayment(Payment payment) {
        ResponseEntity response = rest.postForEntity("https://payment.com/payment", payment, Sale.class);

        if (!response.getStatusCode().is2xxSuccessful()) {
            logError(response.getBody()); 
            throwException(response.getStatusCode()); 
        } 
        logSuccessfulPayment(payment, response.getBody()); 
        return response.getBody(); 
    } 
}

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

(Кстати, это реальный код из реального продукта, над которым сейчас работает моя команда. Я просто внес некоторые незначительные изменения, чтобы сохранить личность нашего клиента и не подвергать его никакому риску. Я также удалил некоторый код, чтобы сэкономить место. Эти изменения не должны мешать пониманию концепции, которую я пытаюсь здесь объяснить.)

Давайте подробнее рассмотрим процесс оплаты метод. В первой строке находится основная функциональность: она выполняет вызов службы и сохраняет ответ в переменной response .

ResponseEntity response = rest.postForEntity("https://payment.com/payment", payment, Sale.class);

Затем он просматривает ответ и, если создание платежа не было успешным, он регистрирует ошибку и выдает исключение:

if (!response.getStatusCode().is2xxSuccessful()) { 
    logError(response.getBody()); 
    throwException(response.getStatusCode()); 
}

Если создание платежа прошло нормально, он регистрирует результат и возвращает текст ответа.

logSuccessfulPayment(payment, response.getBody()); 
return response.getBody();

Этот код работает, но, как я уже сказал, он был бы лучше разработан. Я собираюсь показать, почему и как.

Это приводит к дублированию

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

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

Дублирование также затрудняет повторное использование кода, поскольку концепция не изолирована и выражена одним уникальным способом и в одном месте.

Это увеличивает сцепление

Когда у нас есть что-то вроде:

response.getStatusCode().is2xxSuccessful()

Класс Клиент платежной системы должен знать, как ответ и объекты Код состояния реализованы. Он должен знать, что у объекта response есть метод getStatusCode и что у объекта StatusCode есть 2 x x успешных метода. Это знание о том, как реализуются другие объекты , является формой связи .

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

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

Это загромождает код

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

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

public Sale processPayment(Payment payment) { 
    ResponseEntity response = rest.postForEntity("https://payment.com/payment", payment, Sale.class); 

    if (!response.getStatusCode().is2xxSuccessful()) { 
        logError(response.getBody()); 
        throwException(response.getStatusCode()); 
    } 

    logSuccessfulPayment(payment, response.getBody()); 
    return response.getBody(); 
}

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

Как вы можете сделать это лучше?

Решение здесь состоит в том, чтобы разделить две концепции, которые запутаны:

Концепция 1: выполнение вызова внешней службы;

Концепция 2: действуйте на основе ответа службы.

Первая концепция реализуется с помощью Клиент платежной системы обрабатывает платеж способом, нам все еще нужно найти дом для второго. Поскольку это предполагает изучение ответа и на основе этого выполнение чего–либо с ответом , реализация этой концепции объектом ответа или чем-то подобным звучит разумно. Давайте посмотрим, как это будет выглядеть.

Решение

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

public SaleDTO processPayment(Payment payment) {
    ResponseEntity response = rest.postForEntity("https://payment.com/payment", payment, SaleDTO.class); 

    if (!response.getStatusCode().is2xxSuccessful()) { 
        logError(response.getBody()); 
        throwException(response.getStatusCode()); 
    } 

    logSuccessfulPayment(payment, response.getBody()); 
    return response.getBody(); 
}

Продажи В просто содержат данные, которые мы получаем от сервиса. Нам нужен реальный объект, который реализует концепцию продажи и будет вести себя так, как мы хотим, – это было ранее сделано PaymentProcessorClient . Давайте создадим новый класс Продажа , и это будет то, что мы возвращаем из процесс оплаты способ. Продажа будет построена с объектом ResponseEntity и объектом Оплаты .

public Sale processPayment(Payment payment) { 
    ResponseEntity response = rest.postForEntity("https://payment.com/payment", payment, SaleDTO.class); 

    if (!response.getStatusCode().is2xxSuccessful()) { 
        logError(response.getBody()); 
        throwException(response.getStatusCode()); 
    } 

    logSuccessfulPayment(payment, response.getBody()); 
    return new Sale(payment, response); 
}

Затем мы переносим поведение, которое ранее было на Клиенте платежной системы , на Продажу .

public class Sale { 

    public Sale(Payment payment, ResponseEntity saleResponse) { 
        this.saleResponse = saleResponse; 

        if (isResponseUnsuccessful()) { 
            logError(saleResponseBody()); 
            throw new Exception(saleResponseStatusCode()); 
        } 

        logSuccessfulPayment(payment, saleResponseBody()); 
    } 

    private SaleDTO saleResponseBody() { 
        return this.saleResponse.getBody(); 
    } 

    private HttpStatusCode saleResponseStatusCode() { 
        return this.saleResponse.getStatusCode(); 
    } 

    private boolean isResponseUnsuccessful() { 
        return !saleResponseStatusCode().is2xxSuccessful(); 
    } 
}

Последний шаг – очистить Клиент платежной системы ‘ы обрабатывают платеж способ:

public Sale processPayment(Payment payment) { 
    ResponseEntity response = rest.postForEntity("https://payment.com/payment", payment, SaleDTO.class); 
    return new Sale(response, payment); 
}

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

Теперь у нас есть чистый Клиент платежной системы и a умный _ Продажа _ объект, который знает, что делать когда что-то идет не так. Давайте посмотрим, как этот дизайн решает описанные нами проблемы.

Стал ли дизайн лучше сейчас?

Необходимость в дублировании концепции – реагировании на плохой ответ – отпала. Он реализован классом Продажа , который создан для отслеживания реакции на все операции во внешней службе, такие как возврат и аннулирование платежей.

Связь между Клиентом Платежной системы и Ответ и Код статуса классы исчезли. Теперь они связаны с классом Продажа , что мне кажется лучше, потому что они больше связаны друг с другом.

Мы сделали эти классы немного менее связанными, изолировав зависимость от частных методов с помощью метода SelfEncapsulation . Проверить тело ответа на продажу , Код ответа на продажу и является ли Ответ Неудачным методы.

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

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

Легко обнаружить, легко исправить, большое влияние

Каждый раз, когда вы видите этот шаблон:

if(someObject.getSomeAttribute()) { 
    doSomethingWith(someObject); 
} else { 
    doSomethingElse(someObject); 
}

Есть очень хороший шанс, что есть возможность для улучшения дизайна.

Такая конструкция приводит к дублированию, ненужной связи и загромождению кода.

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

Сообщение Решение проблемы плохого дизайна в реальном мире путем применения принципа “Скажи, не спрашивай” появилось первым на Графический интерфейс Froes .

Оригинал: “https://dev.to/guifroes/solving-real-world-bad-design-by-applying-the-tell-don-t-ask-principle-607”