Исправление 7 Распространенных Ошибок Обработки Исключений В Java

Привет, Хабр! Представляю вашему вниманию перевод статьи Исправление 7 распространенных ошибок обработки исключений Java Торбен Янссен.

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

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

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



Ошибка 1: объявление java.lang.Exception или java.lang.Throwable.

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

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

Вы можете использовать любой подкласс java.lang.Throwable в предложении throws. Таким образом, вместо указания двух разных исключений, которые выдает следующий фрагмент кода, вы можете просто использовать исключение java.lang.Exception в предложении throws.

  
  
  
  
  
  
  
  
  
   

public void doNotSpecifyException() throws Exception { doSomething(); } public void doSomething() throws NumberFormatException, IllegalArgumentException { // do something }

Но это не значит, что вы должны это делать.

Указание Exception или Throwable делает практически невозможным их правильную обработку при вызове метода.

Единственная информация, которую получает ваш вызывающий метод, — это то, что что-то может пойти не так.

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

Вы скрываете эту информацию за общими причинами создания исключений.

Ситуация становится еще хуже, когда ваше приложение со временем меняется.

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

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



Используйте конкретные классы

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

Это сообщает вызывающей стороне, какие события исключения следует обрабатывать.

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

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

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



public void specifySpecificExceptions() throws NumberFormatException, IllegalArgumentException { doSomething(); }



Ошибка 2: Перехват общих исключений

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

Возможно, было бы полезно перехватить исключение java.lang.Exception в основном методе вашего приложения Java SE. Но вам следует предпочесть перехватывать определенные исключения, если вы реализуете библиотеку или работаете над более глубокими уровнями вашего приложения.

Это дает несколько преимуществ.

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

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

Поэтому обязательно сначала поймайте наиболее конкретный класс.

В противном случае ваши IDE отобразят сообщение об ошибке или предупреждение о недостижимом блоке кода.



try { doSomething(); } catch (NumberFormatException e) { // handle the NumberFormatException log.error(e); } catch (IllegalArgumentException e) { // handle the IllegalArgumentException log.error(e); }



Ошибка 3. Регистрация и выдача исключений.

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

Но делать этого не следует по трем причинам: 1. У вас недостаточно информации о варианте использования, который хочет реализовать вызывающая сторона вашего метода.

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

В этом случае регистрировать его не нужно.

Это добавит ложное сообщение об ошибке в файл журнала, который должен быть отфильтрован вашей операционной командой.

2. Сообщение журнала не содержит никакой информации, которая не является частью самого исключения.

Его трассировка и трассировка стека должны содержать всю необходимую информацию об исключительном событии.

Сообщение описывает это, а трассировка стека содержит подробную информацию о классе, методе и строке, в которой это произошло.

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



Зарегистрируйте исключение там, где вы его обрабатываете

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

Как следующий фрагмент кода.

Метод doSomething генерирует исключение.

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

Затем оно обрабатывается методом doEvenMore, который также записывает сообщение в журнал.



public void doEvenMore() { try { doMore(); } catch (NumberFormatException e) { // handle the NumberFormatException } catch (IllegalArgumentException e) { // handle the IllegalArgumentException } } public void doMore() throws NumberFormatException, IllegalArgumentException { doSomething(); } public void doSomething() throws NumberFormatException, IllegalArgumentException { // do something }



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

Использование исключений для управления потоком вашего приложения считается антишаблоном по двум основным причинам: По сути, они работают как оператор Go To, поскольку отменяют выполнение блока кода и переходят к первому блоку catch, который обрабатывает исключение.

Это делает код очень трудным для чтения.

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



Ошибка 5. Устраните причину исключения.

Иногда вам может потребоваться обернуть одно исключение внутри другого.

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

В этом подходе нет ничего плохого, если вы не устраните причину.

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

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

Класс Exception и все его подклассы предоставляют несколько методов-конструкторов, которые принимают исходное исключение в качестве параметра и устанавливают его в качестве причины.



try { doSomething(); } catch (NumberFormatException e) { throw new MyBusinessException(e, ErrorCode.CONFIGURATION_ERROR); } catch (IllegalArgumentException e) { throw new MyBusinessException(e, ErrorCode.UNEXPECTED); }



Ошибка 6: Обобщение исключений

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

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



public void doNotGeneralizeException() throws Exception { try { doSomething(); } catch (NumberFormatException e) { throw new Exception(e); } catch (IllegalArgumentException e) { throw new Exception(e); } }

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

Вам нужно перехватить общий класс Exception, а затем проверить тип его причины.

Этот код не только громоздок в реализации, но и труден для чтения.

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



try { doNotGeneralizeException(); } catch (Exception e) { if (e.getCause() instanceof NumberFormatException) { log.error("NumberFormatException: " + e); } else if (e.getCause() instanceof IllegalArgumentException) { log.error("IllegalArgumentException: " + e); } else { log.error("Unexpected exception: " + e); } }

Так какой же подход лучше?

Будьте конкретны и укажите причину возникновения исключения.

Исключения, которые вы создаете, всегда должны быть как можно более конкретными.

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



try { doSomething(); } catch (NumberFormatException e) { throw new MyBusinessException(e, ErrorCode.CONFIGURATION_ERROR); } catch (IllegalArgumentException e) { throw new MyBusinessException(e, ErrorCode.UNEXPECTED); }



Ошибка 7: Добавление ненужных преобразований исключений

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

Но некоторые архитекторы переусердствуют и вводят специальный класс исключений для каждого архитектурного уровня.

Поэтому они перехватывают исключение на уровне персистентности и бросают его в MyPersistenceException. Бизнес-уровень перехватывает и помещает его в исключение MyBusinessException, и это продолжается до тех пор, пока оно не достигнет уровня API или не будет обработано.



public void persistCustomer(Customer c) throws MyPersistenceException { // persist a Customer } public void manageCustomer(Customer c) throws MyBusinessException { // manage a Customer try { persistCustomer(c); } catch (MyPersistenceException e) { throw new MyBusinessException(e, e.getCode()); } } public void createCustomer(Customer c) throws MyApiException { // create a Customer try { manageCustomer(c); } catch (MyBusinessException e) { throw new MyApiException(e, e.getCode()); } }

Легко заметить, что эти дополнительные классы исключений не приносят никакой пользы.

Они просто вводят дополнительные слои, которые оборачивают исключение.

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



Обязательно добавьте информацию

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

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

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

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

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

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

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



public void persistCustomer(Customer c) { // persist a Customer } public void manageCustomer(Customer c) throws MyBusinessException { // manage a Customer throw new MyBusinessException(e, e.getCode()); } public void createCustomer(Customer c) throws MyBusinessException { // create a Customer manageCustomer(c); }

Теги: #java core #исключение #java #программирование #java

Вместе с данным постом часто просматривают:

Автор Статьи


Зарегистрирован: 2019-12-10 15:07:06
Баллов опыта: 0
Всего постов на сайте: 0
Всего комментарий на сайте: 0
Dima Manisha

Dima Manisha

Эксперт Wmlog. Профессиональный веб-мастер, SEO-специалист, дизайнер, маркетолог и интернет-предприниматель.