Использование лямбда выражений для чистоты кода

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!

Эта статья демонстрирует реальный пример рефакторинга Java, направленного на достижение более чистого кода и лучшего разделения задач. Идея возникла из моего опыта программирования в профессиональной среде.

Однажды в производственном коде

Когда я работал над кодом, сохраняющим некоторые данные домена, у меня получилось следующее:

public void processMessage(InsuranceProduct product) throws Exception {
    for (int retry = 0; retry <= MAX_RETRIES; retry++) {
        try {
            upsert(product);
            return;
        } catch (SQLException ex) {
            if (retry >= MAX_RETRIES) {
                throw ex;
            }
            LOG.warn("Fail to execute database update. Retrying...", ex);
            reestablishConnection();
        }
    }
}

private void upsert(InsuranceProduct product) throws SQLException {
    //содержание не актуально
}

Метод processMessage является частью контракта фреймворка и вызывается для сохранения каждого обработанного сообщения. Код выполняет идемпотентное обновление базы данных (метод upsert) и обрабатывает логику повторных попыток в случае ошибок. 

Основная ошибка, беспокоившая меня заключалась в том, что  истек таймаут JDBC соединения, которое необходимо восстановить.

Меня не удовлетворила первоначальная версия processMessage с точки зрения чистоты кода. Я рассчитывал на что-то, моментально показывающее его замысел без необходимости погружаться в код. 

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

Я решил переписать его, чтобы решить указанные проблемы.

Менее процедурный, более декларативный

Первый шаг — перенести вызов updateDatabase() в переменную с лямбда выражением. Пусть IDE поможет нам в этом, используя рефакторинг Introduce Functional Variable. К сожалению, мы получаем сообщение об ошибке:

No applicable functional interfaces found

Причиной этого является отсутствие функционального интерфейса, обеспечивающего SAM-интерфейс, совместимый с методом upsert. 

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

Вот интерфейс, который нам нужно предоставить:

@FunctionalInterface
interface SqlRunnable {
    void run() throws SQLException;
}

Создав функциональный интерфейс, давайте повторим рефакторинг. На этот раз все прошло успешно. Кроме того, давайте перенесем присвоение переменной перед циклом for:

public void processMessage(InsuranceProduct product) throws Exception {
    final SqlRunnable handle = () -> upsert(product);
    for (int retry = 0; retry <= MAX_RETRIES; retry++) {
        try {
            handle.run();
            return;
        } catch (SQLException ex) {
            if (retry >= MAX_RETRIES) {
                throw ex;
            }
            LOG.warn("Fail to execute database update. Retrying...", ex);
            reestablishConnection();
        }
    }
}

Используйте рефакторинг  Extract Method для перемещения цикла for и его содержимое в новый метод с именем retryOnSqlException:

public void processMessage(InsuranceProduct product) throws Exception {
    final SqlRunnable handle = () -> upsert(product);
    retryOnSqlException(handle);
}

private void retryOnSqlException(SqlRunnable handle) throws SQLException {
    //skipped for clarity
}

Последний шаг заключается в использовании рефакторинга Inline Variable для встраивания переменной handle.

Окончательный результат приведен ниже.

public void processMessage(InsuranceProduct product) throws Exception {
    retryOnSqlException(() -> upsert(product));
}

Теперь метод ввода фреймворка четко указывает, что он делает. Он занимает всего одну строку, что исключает когнитивную нагрузку.

Представленный код содержит подробную информацию о том, как он выполняет свои функции и обеспечивает возможность повторного использования:

private void retryOnSqlException(SqlRunnable handle) throws SQLException {
    for (int retry = 0; retry <= MAX_RETRIES; retry++) {
        try {
            handle.run();
            return;
        } catch (SQLException ex) {
            if (retry >= MAX_RETRIES) {
                throw ex;
            }
            LOG.warn("Fail to execute database update. Retrying...", ex);
            reestablishConnection();
        }
    }
}

@FunctionalInterface
interface SqlRunnable {
    void run() throws SQLException;
}

Заключение

Стоило ли это усилий? Безусловно. Давайте подытожим преимущества.

Метод processMessage теперь четко выражает свое намерение, используя декларативный подход с высокоуровневым кодом. Логика повторных попыток отделена от работы с базой данных и помещена в собственный метод, который благодаря хорошему именованию точно раскрывает свое назначение. Кроме того, синтаксис Lambda позволяет легко повторно использовать функцию повторной попытки в других операциях с базой данных.

Источник: https://habr.com/ru/post/724968/


Интересные статьи

Интересные статьи

Если регулярно использовать статический анализатор кода, то можно сократить время на гадание, почему новый код работает как-то не так, как задумывалось. Рассмотрим очередную интересную ошибку, когда...
При использовании Active Directory для управления учетными записями на предприятии, администратору требуется настроить вход пользователей во все его информационные системы с использованием аутент...
points of view by sanja Микросервисная архитектура широко распространена в разработке программного обеспечения. Но организации, которые ее используют, помимо сложностей в реализаци...
Серверный код в Instagram пишут исключительно на Python. Ну, в основном это именно так. Мы используем немного Cython, а в состав зависимостей входит немало C++-кода, с которым можно работать из P...
Компании растут и меняются. Если для небольшого бизнеса легко прогнозировать последствия любых изменений, то у крупного для такого предвидения — необходимо изучение деталей.