Как я могу обработать IOException, которое, как я знаю, никогда не может быть выброшено безопасным и читабельным способом? - PullRequest
18 голосов
/ 25 июня 2009

"Основное различие между вещами что может пойти не так и вещь, которая не может пойти не так, что когда вещь, которая не может пойти не так идет не так, как правило, оказывается невозможно достать или починить. " -Дуглас Адамс

У меня есть класс FileItems. Конструктор FileItems принимает файл и создает исключение (FileNotFoundException), если файл не существует. Другие методы этого класса также включают файловые операции и, таким образом, имеют возможность генерировать исключение FileNotFoundException. Я хотел бы найти лучшее решение. Решение, которое не требует, чтобы другие программисты обрабатывали все эти крайне маловероятные исключения FileNotFoundExceptions.

Факты дела:

  1. Файл проверен на наличие, но существует крайне маловероятная возможность того, что по какой-либо серьезной ошибке в реальности файл может быть удален до вызова этого метода.
  2. Поскольку вероятность возникновения 1 крайне мала и непоправима, я бы предпочел определить непроверенное исключение.
  3. Файл уже найден, что заставляет других программистов писать код и перехватывать проверенное исключение FileNotFoundException, кажется утомительным и бесполезным. Программа должна просто потерпеть неудачу на этом этапе. Например, всегда существует вероятность того, что компьютер может загореться, но никто не настолько безумен, чтобы заставить других программистов рассматривать это как проверенное исключение .
  4. Время от времени я сталкиваюсь с подобной проблемой исключений, и определение пользовательских непроверенных исключений каждый раз, когда я сталкиваюсь с этой проблемой (мое старое решение), утомительно и добавляет к раздуванию кода.

Код в настоящее время выглядит следующим образом

 public Iterator getFileItemsIterator() {
    try{
        Scanner sc = new Scanner(this.fileWhichIsKnowToExist);
        return new specialFileItemsIterator(sc);        
       } catch (FileNotFoundException e){ //can never happen} 

    return null;
 }

Как я могу сделать это лучше, не определяя пользовательскую непроверенную исключение FileNotFoundException? Есть ли какой-нибудь способ преобразовать зарегистрированное исключение в исключение uncheckException?

Ответы [ 8 ]

50 голосов
/ 25 июня 2009

Обычный шаблон для решения этой проблемы - цепочка исключений . Вы просто оборачиваете FileNotFoundException в RuntimeException:

catch(FileNotFoundException e) {
    throw new RuntimeException(e);
}

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

Редактировать : Остерегайтесь этого похожего анти-паттерна, который я слишком часто видел в дикой природе:

catch(FileNotFoundException e) {
    throw new RuntimeException(e.getMessage());
}

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

Другое редактирование: Как правильно заметил в своем ответе Турбьёрн Равн Андерсен, не повредит указать, почему вы объединяете исключение, либо в комментарии, либо, что еще лучше, в качестве исключения сообщение:

catch(FileNotFoundException e) {
    throw new RuntimeException(
        "This should never happen, I know this file exists", e);
}
11 голосов
/ 25 июня 2009

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

try {
    // code...
}
catch (IOException e) {
    throw new RuntimeException(e);
}

Это хорошее решение, которое вы не должны стесняться использовать в этих ситуациях.

10 голосов
/ 25 июня 2009

Рассмотрите возможность использования формы

throw new RuntimeException("This should never happen", e);

вместо этого. Это позволяет передать смысл сопровождающему, чтобы он следовал за вами, как при чтении кода, так и СЛЕДУЕТ, чтобы исключение возникало в каком-то странном сценарии.

РЕДАКТИРОВАТЬ: Это также хороший способ передать исключения через механизм, не ожидающий этих исключений. Например. если у вас есть итератор «получить больше строк из базы данных», то интерфейс итератора не позволяет генерировать, например, FileNotFoundException, так что вы можете обернуть его следующим образом. В коде, ИСПОЛЬЗУющем итератор, вы можете перехватить исключение runtimeexception и проверить исходное исключение с помощью getCause (). ОЧЕНЬ полезно при прохождении устаревших путей кода.

8 голосов
/ 25 июня 2009

Если ваша позиция такова, что это маловероятно и должно просто завершить программу, используйте существующее исключение времени выполнения, даже само RuntimeException (если не IllegalStateException).

try {
  ....

} catch (FileNotFoundException e) {
    throw new RuntimeException(e);
}
1 голос
/ 16 апреля 2014

Не просто заправляйте все приложение с RuntimeException, когда вы столкнетесь с маловероятной ошибкой. RuntimeException должно быть зарезервировано для ошибок программиста, а IOException, скорее всего, не вызвано ошибкой программиста.

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

Например:

class SomeClass {

  public void doActions() {
    try {
      someAction();
    } catch (HigherLevelException e) {
      notifyUser();
    }

    someOtherUnrelatedAction();
  }

  public void someAction() throws HigherLevelException {  
    try {
      // user lower-level abstraction to do our bidding
    } catch(LowerLevelException e) {
      throw new HigherLevelException(e);
    }
  }

  public void someOtherUnrelatedAction() {
    // does stuff
  }
}

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

1 голос
/ 26 июня 2009

У меня возникла такая же проблема.

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

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

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

Это:

  • сохраняет целостность данных
  • позволяет гораздо проще кодировать

Типичная функция:

returntype func(blah-blah-blah, Transaction tr)
{
    // IO example
    Stream s = null;
    try
    {
        s = new FileStream(filename);
        tr.AddRollback(File.Delete(filename));
    }
    finally
    {
        if (s != null)
            s.close();
    }
}

Типичное использование:

Transaction tr = new Transaction();
try
{
    DoAction1(blah1, tr);
    DoAction2(blah2, tr);
    //...
}
catch (Exception ex)
{
    tr.ExecuteRollbacks();
    // queue the exception message to the user along with a command to repeat all the actions above
}

Это немного немного сложнее в реальном мире, потому что

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

Но я уже привык к этому подходу, теперь мои приложения более стабильны.

1 голос
/ 25 июня 2009

Я немного погуглил и нашел этот шарик кода. Это немного более гибкий подход, я думаю,

Комплименты этой статьи

class SomeOtherException extends Exception {}

public class TurnOffChecking {
  private static Test monitor = new Test();
  public static void main(String[] args) {
    WrapCheckedException wce = new WrapCheckedException();
    // You can call f() without a try block, and let
    // RuntimeExceptions go out of the method:
    wce.throwRuntimeException(3);
    // Or you can choose to catch exceptions:
    for(int i = 0; i < 4; i++)
      try {
        if(i < 3)
          wce.throwRuntimeException(i);
        else
          throw new SomeOtherException();
      } catch(SomeOtherException e) {
          System.out.println("SomeOtherException: " + e);
      } catch(RuntimeException re) {
        try {
          throw re.getCause();
        } catch(FileNotFoundException e) {
          System.out.println(
            "FileNotFoundException: " + e);
        } catch(IOException e) {
          System.out.println("IOException: " + e);
        } catch(Throwable e) {
          System.out.println("Throwable: " + e);
        }
      }
    monitor.expect(new String[] {
      "FileNotFoundException: " +
      "java.io.FileNotFoundException",
      "IOException: java.io.IOException",
      "Throwable: java.lang.RuntimeException: Where am I?",
      "SomeOtherException: SomeOtherException"
    });
  }
} ///:~
1 голос
/ 25 июня 2009

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

Файл может существовать при запуске конструктора вашего класса, но это не гарантирует, что:

  1. Он существует при вызове методов
  2. Это доступно для записи / чтения
  3. Другой поток не получает к нему доступ и каким-то образом испортит ваши потоки
  4. Ресурс не уходит в середине обработки

и т.д.

Вместо того, чтобы заново изобретать колесо, я бы сказал, просто перезапустите IOException, где бы вы ни использовали классы JDK / java.io, которые вы используете.

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

...