Изящно обрабатывайте исключения Java

Java

Эта статья представляет

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

слова, написанные впереди

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

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

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

Часто мы читали всевозможные книги по java, и различные механизмы и характеристики исключений очень понятны, но мы все еще не знаем, как их использовать, или даже запоминаем концепции, но не знаем, как их применять.

Разрабатываемые нами бизнес-системы или продукты часто сталкиваются со следующими проблемами:

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

Зачем нужны пользовательские исключения

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

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

Когда обрабатывать исключения вручную

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

  • Вы умеете обрабатывать исключения и знаете, как с ними справляться
  • Вы несете ответственность за обработку исключений

Пользовательское бизнес-исключение

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

/**
 * 业务受理失败异常
 */
public class ServiceException extends RuntimeException {
    //接收reason参数用来描述业务失败原因.
  public ServiceException(String reason) {  super(reason); }
}

Далее посмотрите на слой Controller.

// UserController.java 
 /**
   * 修改用户信息
   * @param userID 用户ID
   * @param user 修改用户信息表单数据
   */
  @PutMapping("{userID}")
  public JSONResult updateUser(@PathVariable("userID") Integer userID, @RequestBody UpdateUserForm userForm) {
    User user = new User(); //准备业务逻辑层使用的领域模型
    BeanUtils.copyProperties(userForm, user); //拷贝要修改的值
    user.setUserId(userID); //设置主键到用户数据中
    userService.updateUser(user); //调用更新业务逻辑
    JSONResult json = new JSONResult(); //准备要响应的数据
    json.put("user", user); //把修改后的用户数据还给页面
    return json; // --  
  }

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

  • Действительность: например, принадлежит ли позиция пользователя идентификатору позиции, записанному в базе данных, если он не существует, он недействителен.
  • Законность: например, имя пользователя может содержать до 12 символов, а пользователь отправляет 20 символов, что является незаконным.

Проверка правильности может выполняться платформой проверки Java, такой как JSR 303. Если предположить, что данные, отправленные пользователем, действительны после проверки, все еще есть некоторые случаи, когда логика модификации не может быть вызвана.

  1. Идентификатор пользователя, который нужно изменить, не существует.
  2. Пользователь заблокирован и не может изменять.
  3. Механизм оптимистичной блокировки обнаруживает, что пользователь был кем-то изменен.
  4. По какой-то причине наша программа не может сохранить в базу данных.
  5. Некоторые программисты неправильно разработали код, что привело к возникновению исключений в процессе сохранения, таких как NPE.

Для первых 3 видов мы считаем, чтопроверка действительностиСбой, четвертая категория связана с исключениями, которые мы не можем обработать, а пятая категория — это ошибки программиста.

Теперь вопрос в том, как мы уведомим пользователя в первых трех случаях?

  1. Вызовите метод checkUserExist() для userService в ccontroller.
  2. Пишите бизнес-логику прямо в контроллере.
  3. В ответе службы механизм кода состояния, например, 1 2 3 означает сообщение об ошибке, 0 означает отсутствие ошибки.

Очевидно, что первые 2 метода не рекомендуются, потому что шаблон проектирования MVC говорит нам, что контроллер используется для получения параметров страницы, вызова логической обработки и, наконец, организации ответа страницы.Мы не можем выполнять логическую обработку в контроллере, контроллер должен отвечать только за обработку пользовательского API запись и ответы (Если нет, то подумайте, что делать, если однажды код сервиса запаковают в jar и разместят на другой платформе, а контроллера нет?)

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

Так что же делать?Теперь посмотрим, как пишется сервисный код


  /**
   * 修改用户信息
   * @param user 要修改的用户数据
   */
  public void updateUser(User user) {
    User userOrig = userDao.getUserById(user.getUserID());
    if (null == userOrig) {
      throw new ServiceException("用户不存在");
    }
    if (userOrig.isLocked()) {
      throw new ServiceException("用户被锁定,不允许修改");
    }
    if (!user.getVersion().equals(userOrig.getVersion())) {
      throw new ServiceException("用户已经被别人修改过,请刷新重试");
    }
    // TODO 保存用户数据  ... 
  }

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

Далее есть 2 варианта:

  1. Используйте try-catch в контроллере для обработки.
  2. Отправьте исключение непосредственно в верхнюю структуру для унифицированной обработки.

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

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

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

Наконец, мы решили, что это исключение наследуется от RuntimeException и включает в себя конструктор, который принимает причину ошибки, так что уровню контроллера не нужно знать об исключении, пока ServiceException глобально перехватывается и обрабатывается единообразно, будь то в эпоху of struct1, 2, Даже в springMVC, даже в эпоху сервлетов, это чрезвычайно просто!

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

Таким образом, нам нужно иметь дело только с ServiceException глобально и единообразно. Что ж, Spring предоставляет нам механизм ControllerAdvice. Для ControllerAdvice вы можете обратиться к документации по использованию springMVC. Вот простой пример:

@ControllerAdvice(basePackages = { "com.xxx.xxx.bussiness.xxx" })
public class ModuleControllerAdvice {
  private static final Logger LOGGER = LoggerFactory.getLogger(ModuleControllerAdvice.class);
  private static final Logger SERVICE_LOGGER = LoggerFactory.getLogger(ServiceException.class);

  /**
   * 业务受理失败
   */
  @ResponseBody
  @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
  @ExceptionHandler(ServiceException.class)
  private JSONResult handleServiceException(ServiceException exception) {
    String message = "业务受理失败,原因:" + exception.getLocalizedMessage();
    SERVICE_LOGGER.info(message);
    JSONResult json = new JSONResult();
    json.serCode(500001); // 500000表示系统异常,500001表示业务逻辑异常
    json.setMessage(message); 
    return json;
  }
}

В это время мы легко справляемся с различными ситуациями.

Обратите внимание, что в этом классе мы определяем 2 объекта журнала, которые указывают на ServiceException.class и ModuleControllerAdvice.class соответственно, и используем вывод журнала информационного уровня при обработке ServiceException, что очень полезно.

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

Затем вы можете захотеть, чтобы клиент отвечал JSON таким образом при изменении пользователя.

{
    code: 200001,
    message: "业务受理失败,原因:用户名称不存在!"
}

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

Как классифицировать аномалии

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

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

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

Если это фоновая работа, или сложный бизнес требует прослеживаемости. Обычно это контролируется операторами суждения о процессе, и используется обработка исключений. Мы считаем, что эти суждения о процессе должны быть в атомарном процессе. И обнаруженные (не возникшие) проблемы (не исключения) должны быть Записано в удобный журнал. Этот случай принадлежитОбработка обратной связи, не называется исключением.

Подводя итог, я обычно делю его на следующие категории:

  1. Логическое исключение, этот тип исключения используется для описания того, что бизнес не может быть обработан должным образом, и это случайность, созданная пользователем.
  2. Ошибки кода, такие исключения используются для описания разработанных ошибок кода, таких как NPE, ILLARG, которые являются ошибками, сделанными программистами.
  3. Собственные исключения в основном используются в определенных бизнес-сценариях, чтобы описать, что непредвиденные ситуации в заданном задании не могут быть обработаны заранее.

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

записка, написанная сзади

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

Приведенное выше предложение взято из , но мы думаем о следующих моментах:

Проверка бизнес-логики — тоже непредвиденная ситуация

UnknownHostException означает, что такой хост не может быть найден. В чем разница между этим исключением и NoUserException? Другими словами, ни один такой хост не является исключением, и никакой такой пользователь не является исключением? Поэтому обязательно поймите, что такое использование исключений для логика управления, Что такое определенное исключение программы.

Обработка исключений очень неэффективна

Пример, показанный в книге, состоит в том, чтобы использовать try-catch много раз в цикле для проверки, но количество раз, когда пользователь инициирует запросы в бизнес-системе, сильно отличается от этого сценария.Taobao 11`11 является хорошим контрпримером. Но, пожалуйста, рассмотрите эту проблему на этом уровне системы.

  1. Параллельных систем десятки миллионов, и рассматривать эти регулярные и пошаговые методы невозможно, не забывайте, что MVC тратит много ресурсов и увеличивает объем кода.
  2. Так же много случаев обработки огромных задач в бизнес-системах.Но эти задачи атомарны.Теперь контроллер и сервис в MVC не атомарны,иначе зачем нужно различать столько слоев?
  3. Если вы заботитесь об эффективности, рассмотрите возможность переопределения метода fillStackTrace класса Throwable. Вам нужно знать, где находятся накладные расходы на исключения. fillStackTrace — это собственный метод, который заполняет текущую трассировку внутри класса исключений.

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

Сначала рассмотрим пример:

    //这是一个非常典型的反例,也是一个误区.
  /**
   * 处理业务消息
   * @param message 要处理的消息
   */
  public void processMessage(Message<String> message) {
    try{
        // 处理消息验证
        // 处理消息解析
        // 处理消息入库
    }catch(ValidateException e ){
        // 验证失败
    }catch(ParseException e ){
        // 解析失败
    }catch(PersistException e ){
        // 入库失败
    }
  }

Приведенный выше код является типичным использованием исключений для обработки бизнес-логики. Этот метод должен быть строго запрещен! Самая большая проблема с приведенным выше кодом заключается в том, как мы можем использовать исключения для автоматической обработки транзакций?

Однако это не имеет ничего общего с нашим прерванным сервисом, это не одно и то же.

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

Исправлена ​​логика

  /**
   * 处理业务消息
   * @param message 要处理的消息
   */
  public void processMessage(Message<String> message) {
    // 处理消息验证
    if(!message.isValud()){
        MessageLogService.log("消息校验失败"+message.errors())
        return ;
    }
    // 处理消息解析
    if(!message.parse()){
        MessageLogService.log("消息解析失败"+message.errors())
        return ;
    }
     // TODO ....
  }

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