В недавней статье под названием «Брюс Экель неправ» Эллиот Гарольд решил написать контраргумент критике проверенных исключений Брюса Экеля.
Я думаю, что разработчики, пытающиеся утверждать, что проверенные исключения являются «неудачным экспериментом» и что мы должны использовать только исключения времени выполнения, упускают фундаментальный момент в теории обработки ошибок.
как мы сюда попали?
Я думаю, что реализация исключений Java — это хорошо, но изначально она использовалась разработчиками, которые не совсем понимали, как она должна работать, и из-за этого много неправильного кода в итоге было передано JDK, который у нас теперь есть. жить с. Многие API в JDK используют проверенные исключения вместо исполняемых, и по этой причине у многих программистов возникло негативное отношение к этой концепции, что привело к нескольким абсурдным крайним утверждениям о том, что проверенные исключения никогда не следует использовать, Когда-либо.
На самом деле, идея иметь два разных типа исключений имеет большой смысл, как я покажу ниже.
Категоризация неудач
Когда в программе возникает ошибка, я хочу знать о ней две вещи: 1) ожидается ли она? и 2) это восстановимо?
Давайте внимательнее посмотрим:
Извлекаемые | Не подлежит восстановлению | |
ожидаемый | (А) | (С) |
непредвиденный | Икс | (Б) |
Давайте обсудим каждый из них по очереди:
- X Я перешел этот вариант, потому что просто не могу придумать сценарий, в котором мы можем восстановиться после неожиданного исключения.
- (а) Извлекаемые и ожидаемые. Это довольно распространенный сценарий, охватываемый проверенными исключениями Java. Типичным примером является FileNotFoundException: очень часто вы либо хотите, чтобы пользователь знал, что вы не можете найти файл, либо, если это не та информация, которая интересует пользователя, вы всегда можете зарегистрировать его или проигнорировать.
- (б) Неожиданно и не подлежит восстановлению. Этот сценарий покрывается исключениями времени выполнения Java. Типичным примером является NullPointerException. Конечно, нулевые указатели довольно распространены в коде Java, но вы знаете, что такая ситуация не ожидается, потому что если бы это было так, разработчик защитил бы ее («if (p! = Null)»). Поскольку это неожиданно, ваша система сейчас находится в нестабильном состоянии, и вполне вероятно, что ваше приложение будет либо аварийно завершать работу, либо работать некорректно, поэтому эту ситуацию невозможно исправить.
- (c) Ожидаемый, но не подлежащий восстановлению. Интересно, что я нашел очень мало документации этого сценария. В конце концов, если ожидается исключение, разве мы не сможем восстановиться после него? Ну не всегда. Читать дальше.
Понимание сценария (c) требует осознания того, что одно и то же исключение иногда можно восстановить, а иногда нет, в зависимости от того, когда это произошло.
Например, вы можете представить себе ситуацию, когда приложению необходимо открыть один и тот же файл в двух разных местах. В первый раз, когда он не находит этот файл, значит, попросить пользователя предоставить другой файл. Но как только этот файл был предоставлен, система может по праву убедиться, что он там есть, поэтому во второй раз, когда ему нужно открыть этот файл, она будет ожидать его нахождения. Но что, если что-то случилось, что заставило этот файл исчезнуть между этими двумя разами? (например, сбой жесткого диска). Внезапно наше исключение стало невосстановимым.
Похоже, это ведет нас по новому пути: нам кажется, что нам нужны два разных исключения для FileNotFound: одно проверено, а другое нет.
На пути к решению
Если бы у меня была возможность переделать механизмы исключений в Java, я бы практически ничего не изменил. Прежде всего, я считаю, что мы должны сохранить отмеченное / непроверенное различие, которое очень хорошо отображается в реальном мире, как я только что продемонстрировал.
Тем не менее, я бы выбрал разные имена, чтобы сделать разницу между этими двумя понятиями более приемлемой. JDK использует класс Error для этого, но разница между Error и Exception довольно тонкая и часто неправильно понимается. Что еще хуже, JDK использует слово «Exception» для вызова как проверенных, так и исключений времени выполнения. Не удивительно, что люди смущены.
Как насчет паники, или, возможно, ошибка вместо ошибки? Я знаю, что это звучит немного по-детски, но имеет смысл быть очень ясным, и когда кто-то, не знакомый с этими концепциями, пишет новый код Java, решить, должны ли они генерировать исключение или ошибку, должно быть проще.
Во-вторых, я бы предоставил две версии наиболее распространенных классов исключений: одну — исключение, а другую — ошибку. Например, у нас будет и FileNotFoundException, и FileNotFoundBug:
- Вы просите пользователя ввести число, и они вводят буквы? Ваше приложение должно генерировать NumberFormatException и перехватывать его, чтобы пользователь мог исправить свою ошибку.
- Вы пытаетесь прочитать файл настроек, который вы сохранили ранее, и число не может быть правильно проанализировано? Вероятно, файл поврежден, и вы, вероятно, хотите выбросить NumberFormatBug
Сказав это, я не фанат идеи, что вся иерархия исключений теперь будет дублироваться, поэтому, возможно, мы могли бы продвинуть эту идею немного дальше и использовать Generics, чтобы сократить количество классов:
throw new FileNotFound<Bug>();
или же
throw new FileNotFound<Exception>();
Конечно, компилятор должен будет применять разные правила в зависимости от параметра Generic, что, я не думаю, выполнимо сегодня, но вы поняли идею.
Движение вперед
Жаль, что дебаты об исключениях стали настолько поляризованными, потому что я действительно считаю, что если двойной подход Java к обработке исключений не является лучшим способом поддержки сбоев в программах, это, безусловно, шаг в правильном направлении.
В будущем я хотел бы, чтобы дискуссии на эту тему были сосредоточены на том, где и когда использовать проверенные исключения и исключения времени выполнения вместо чтения статей, говорящих вам, что вы всегда должны делать «это» и никогда не делать «это».