Don't Group Exception Catchers

The following text is a partial translation of the original English article, performed by ChatGPT (gpt-3.5-turbo) and this Jekyll plugin:

Иногда мы повторно выбрасываем исключения. В Java мы делаем это чаще, чем в других языках, потому что в ней есть проверяемые исключения. Иногда нам приходится перехватывать и повторно выбрасывать несколько исключений, возникших из разных мест в методе. В Java 7 была введена группировка разных типов исключений в одном блоке catch. Но даже без группировки можно просто перехватить IOException или даже Exception и предоставить единый блок catch для всех типов и всех источников (методов, где возникают исключения). Недавно я понял, что это плохая практика. Вот почему.

Рассмотрим следующий метод на Java (я использую Apache Commons IO):

byte[] read(String uri) {
  try {
    return IOUtils.toByteArray(
      new URL(uri).openStream()
    );
  } catch (IOException ex) {
    throw new IllegalArgumentException(ex);
  }
}

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

byte[] read(String uri) {
  try {
    return IOUtils.toByteArray(
      new URL(uri).openStream()
    );
  } catch (IOException ex) {
    throw new IllegalArgumentException(
      String.format(
        "Failed to read from '%s'",
        uri
      ),
      ex
    );
  }
}

Здесь исключение может быть сгенерировано в трех местах:

Не важно, кто бросает, мы перехватываем это в том же блоке catch и повторно бросаем с тем же сообщением. Я считаю, что это плохо, потому что контекст ошибки, предоставленный повторным бросанием, менее сфокусирован на произошедшей проблеме.

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

byte[] read(String uri) {
  URL url;
  try {
    url = new URL(uri);
  } catch (MalformedURLException ex) {
    throw new IllegalArgumentException(
      String.format(
        "Failed to parse the URI '%s'",
        uri
      ),
      ex
    );
  }
  InputStream stream;
  try {
    stream = url.openStream();
  } catch (IOException ex) {
    throw new IllegalArgumentException(
      String.format(
        "Failed to open the stream for '%s'",
        uri
      ),
      ex
    );
  }
  try {
    return IOUtils.toByteArray(stream);
  } catch (IOException ex) {
    throw new IllegalArgumentException(
      String.format(
        "Failed to read the stream for '%s'",
        uri
      ),
      ex
    );
  }
}

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

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

Очевидно, я считаю, что объединение типов исключений в одном блоке catch — это плохая практика.

Translated by ChatGPT gpt-3.5-turbo/35 on 2023-09-08 at 16:28

sixnines availability badge   GitHub stars