Как я могу обработать IOException, которое, как я знаю, никогда не может быть выброшено безопасным и читабельным способом?
"Основное различие между тем, что может пойти не так, и тем, что не может пойти не так, состоит в том, что, когда что-то, что не может пойти не так, идет не так, как обычно, оказывается невозможным добраться или починить". -Дуглас Адамс
У меня есть класс FileItems. Конструктор FileItems принимает файл и создает исключение (FileNotFoundException), если файл не существует. Другие методы этого класса также включают файловые операции и, таким образом, имеют возможность генерировать исключение FileNotFoundException. Я хотел бы найти лучшее решение. Решение, которое не требует, чтобы другие программисты обрабатывали все эти крайне маловероятные исключения FileNotFoundException.
Факты дела:
- Файл был проверен на существование, но существует крайне маловероятная возможность того, что по какой-то серьезной ошибке реальности файл может быть удален до вызова этого метода.
- Поскольку вероятность возникновения 1 крайне мала и непоправима, я бы предпочел определить непроверенное исключение.
- Файл уже найден, что заставляет других программистов писать код и перехватывать проверенное исключение FileNotFoundException кажется утомительным и бесполезным. Программа должна просто потерпеть неудачу на этом этапе. Например, всегда есть вероятность того, что компьютер может загореться, но никто не настолько безумен, чтобы заставить других программистов рассматривать это как проверенное исключение.
- Время от времени я сталкиваюсь с подобной проблемой исключений, и определение пользовательских непроверенных исключений каждый раз, когда я сталкиваюсь с этой проблемой (мое старое решение), утомительно и добавляет к раздуванию кода.
Код в настоящее время выглядит так
public Iterator getFileItemsIterator() {
try{
Scanner sc = new Scanner(this.fileWhichIsKnowToExist);
return new specialFileItemsIterator(sc);
} catch (FileNotFoundException e){ //can never happen}
return null;
}
Как я могу сделать это лучше, не определяя пользовательскую непроверенную исключение FileNotFoundException? Есть ли какой-нибудь способ привести проверенное исключение к исключению исключить?
8 ответов
Обычный шаблон для решения этой проблемы - цепочка исключений. Вы просто оборачиваете 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);
}
Вы можете преобразовать проверенное исключение в непроверенное, разместив его внутри RuntimException. Если исключение обнаруживается выше в стеке и выводится с использованием printStackTrace(), также будет отображен стек для исходного исключения.
try {
// code...
}
catch (IOException e) {
throw new RuntimeException(e);
}
Это хорошее решение, которое вы не должны стесняться использовать в таких ситуациях.
Рассмотрите возможность использования формы
throw new RuntimeException("This should never happen", e);
вместо. Это позволяет передать смысл сопровождающему, чтобы он следовал за вами, как при чтении кода, так и СЛЕДУЕТ, чтобы исключение возникало в каком-то странном сценарии.
РЕДАКТИРОВАТЬ: Это также хороший способ передать исключения через механизм, не ожидая этих исключений. Например, если у вас есть итератор "получить больше строк из базы данных", интерфейс Iterator не позволяет выдавать, например, FileNotFoundException, чтобы вы могли обернуть его следующим образом. В коде, использующем итератор, вы можете перехватить исключение runtimeexception и проверить исходное исключение с помощью getCause(). ОЧЕНЬ полезно при прохождении устаревших путей кода.
Если ваша позиция такова, что это маловероятно и должно просто завершить программу, используйте существующее исключение времени выполнения, даже само RuntimeException (если не IllegalStateException).
try {
....
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
Я немного погуглил и нашел этот шарик кода. Это немного более гибкий подход, я думаю,
Комплименты этой статьи
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"
});
}
} ///:~
Я испытал ту же проблему.
В простейшем случае вы сообщаете пользователю об ошибке и предлагаете либо повторить, либо отменить операцию.
В общем случае рабочий процесс - это последовательность действий (включая ввод-вывод), где каждое действие "предполагает", что предыдущее было выполнено успешно.
Подход, который я выбрал, заключается в создании списка "откатных" действий. Если рабочий процесс завершается успешно, они игнорируются. Если возникает исключение, я выполняю откат и представляю исключение пользователю.
Это:
- сохраняет целостность данных
- позволяет гораздо проще кодировать
Типичная функция похожа на:
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
}
Это немного сложнее в реальном мире, потому что
- иногда необходимо получить кучу блокировок перед выполнением действий
- Код отката должен быть сам по себе без исключения (например)
Но я уже привык к такому подходу, теперь мои приложения более стабильны.
Вы, вероятно, лучше бросить суперкласс и более общее исключение IOException
в любой точке вашего кода, которая включает чтение или запись из файла.
Файл может существовать при запуске конструктора вашего класса, но это не гарантирует, что:
- Существует при вызове методов
- Это доступно для чтения / чтения
- Другой поток не получает к нему доступ и как-то портит ваши потоки
- Ресурс не уходит в середине обработки
и т.п.
Вместо того, чтобы изобретать велосипед, я бы сказал, просто перебросьте IOException везде, где JDK/java.io
классы, которые вы используете, заставляют вас делать это.
Также я, например, ненавижу классы, которые выдают исключения из их конструктора - я бы избавился от них на вашем месте.
Не просто заправить все ваше приложение с 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
выяснить, что делать, когда проблема возникает во время этой задачи. Например, вы пытались сохранить файл? Не сбой, вместо этого уведомите пользователя, что была проблема.