И это сэкономит больше времени в будущем

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

Все дело в соотношении сигнал / шум в вашем коде

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

Используйте другой канал для бизнес-логики и обработки ошибок

Как упоминалось в предыдущем посте:

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

Но мы еще не достигли этого, как бы мы ни старались, нам все равно нужно написать код обработки ошибок. Ошибка должна быть первоклассным гражданином в нашей кодовой базе и иметь собственный канал. Традиционно у нас будет проверка ошибок и бизнес-логика в одном блоке:

if (dto != null) {
 result1 = processA(dto);
}
 
if (result1 != null) {
 result2 = processB(result1);
}
 
if (result2 != null) {
 result3 = processC(result2); 
}
 
return result3 != null;

Более предпочтительный подход - иметь отдельные каналы для бизнес-логики и логики обработки ошибок. Неконтролируемое исключение Java может служить таким каналом обработки ошибок. В следующем фрагменте кода каждая функция выдает одно и то же исключение MyBusinessException:

try {
 result1 = processA(dto);
 result2 = processB(result1);
 result3 = processC(result2); 
} catch(ApplicationExeption e) {
 //handling exception here
}

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

В Java 8 Optional есть встроенный канал для такой обработки ошибок.

Optional.ofNullable(dto)
        .map(this::processA)
        .map(this::processB)
        .map(this::processC)
        .map(r -> r != null)
        .orElse(false)

Из трех приведенных выше примеров традиционный способ проверки нулевого указателя определенно имеет самое низкое отношение сигнал / шум. Необязательно и Исключение имеют одинаковое соотношение сигнал / шум. Но, возможно, опциональный способ более удобен для глаз обученных профессионалов. Кроме того, он предоставляет один удобный способ вернуть значение по умолчанию. За исключением исключений, это можно сделать только в блоке catch. Одним из преимуществ исключений является то, что мы можем легко генерировать исключение на нескольких уровнях, и иногда это может помочь нам получить чистый код.

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

Помните об уровне абстракции ошибки

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

public class AppExeption extends RuntimeException {
    public ApplicationExeption(final String msg) {
        super(msg);
    }
    public AppExeption(final String msg, 
                       final Throwable cause) {
        super(msg, cause);
    }
    public AppExeption(final Throwable cause) {
        super(cause);
    }
}

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

Дизайн обработки ошибок является частью дизайна API

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

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

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

Стратегии обработки ошибок

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

  • Возврат null: это неявно перекладывает ответственность за нулевую проверку на вызывающую. Где-то в другом месте должно быть указано, что возвращаемое значение может быть нулевым, и вызывающий должен его обработать.
  • Возврат Необязательно: это лучше, чем ноль. Он моделирует действительное отсутствие значения явным образом. Никакой дополнительной документации не требуется.
  • Вернуть пустой список: это предпочтительный подход, если наш бизнес согласен с ним. Никакого специального обращения не требуется.

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

Избегайте простого журнала и повторного просмотра

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

Null - это запах кода

Теперь стало общепризнанным, что null - это, по крайней мере, запах кода в программном обеспечении, если не ошибка дизайна. Это неправильный способ моделирования отсутствия значения в языке программирования. Необязательно в Java 8 сделать это отсутствие явным. Он также предоставляет такие конструкции, как map/filter, для извлечения того, что вы хотите, с помощью легко понятного оператора, без использования условных ветвей с высокой когнитивной нагрузкой.

Использовать механизм повтора для внешней зависимости

Для любой внешней зависимости у нас обычно есть фасады или слой DAL, чтобы абстрагироваться от нашей бизнес-логики. Чтобы избежать временного сбоя этих зависимостей, мы можем определить стратегию повторных попыток, используя такую ​​структуру, как spring-retry. Система более надежна, а бизнес-логика не связана с механизмом повторных попыток низкого уровня.

Резюме

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