Я думаю, что прочитал то же интервью с Брюсом Экелом, что и вы, и оно всегда меня раздражало. Фактически, аргумент был сделан интервьюируемым (если это действительно тот пост, о котором вы говорите) Андерсом Хейлсбергом, гением MS, стоящим за .NET и C #.
http://www.artima.com/intv/handcuffs.html
Несмотря на то, что я из Хейлсберга и его работ, этот аргумент всегда казался мне фальшивым. Это в основном сводится к:
«Проверенные исключения являются плохими, потому что программисты просто злоупотребляют ими, всегда ловя их и игнорируя их, что приводит к скрытым и игнорируемым проблемам, которые иначе были бы представлены пользователю».
Под «иным способом представляемым пользователю» я подразумеваю, что если вы используете исключение времени выполнения, ленивый программист просто проигнорирует его (вместо того, чтобы перехватывать его пустым блоком catch), и пользователь увидит его.
Краткое изложение этого аргумента сводится к тому, что «Программисты не будут использовать их должным образом, и неправильное их использование хуже, чем отсутствие их» .
В этом аргументе есть доля правды, и на самом деле, я подозреваю, что мотивация Гослинга не помещать переопределения операторов в Java исходит из аналогичного аргумента - они вводят программиста в заблуждение, потому что им часто злоупотребляют.
Но, в конце концов, я нахожу это фиктивным аргументом Хейлсберга и, возможно, постфактумным аргументом, созданным для объяснения недостатка, а не продуманным решением.
Я бы сказал, что хотя чрезмерное использование проверенных исключений является плохой вещью и ведет к небрежной обработке пользователями, но правильное их использование позволяет программисту API дать большую пользу клиентскому программисту API.
Теперь программист API должен быть осторожен, чтобы не создавать проверенные исключения повсеместно, иначе они просто будут раздражать программиста клиента. (Exception) {}
Хейлсберг предупреждает, что очень ленивый клиент-программист прибегнет к ловле, и вся польза будет потеряна, и наступит ад. Но в некоторых обстоятельствах просто нет замены хорошему проверенному исключению.
Для меня классическим примером является API открытия файлов. Каждый язык программирования в истории языков (по крайней мере, в файловых системах) имеет где-то API, который позволяет открывать файл. И каждый клиентский программист, использующий этот API, знает, что им приходится иметь дело с тем случаем, что файл, который они пытаются открыть, не существует. Позвольте мне перефразировать это: каждый клиентский программист, использующий этот API, должен знать, что он должен иметь дело с этим делом. И тут есть одна загвоздка: могут ли программисты API помочь им узнать, что им следует справиться с этим, оставив только комментарии, или они действительно настаивают, чтобы клиент справился с этим.
В Си идиома идет что-то вроде
if (f = fopen("goodluckfindingthisfile")) { ... }
else { // file not found ...
где fopen
указывает на ошибку, возвращая 0, а C (по глупости) позволяет вам рассматривать 0 как логическое значение и ... В основном, вы изучаете эту идиому и все в порядке. Но что, если ты новичок и ты не выучил идиому. Тогда, конечно, вы начинаете с
f = fopen("goodluckfindingthisfile");
f.read(); // BANG!
и учиться трудному пути.
Обратите внимание, что здесь речь идет только о строго типизированных языках: есть четкое представление о том, что такое API в строго типизированном языке: это шведский стол функциональности (методов), который вы можете использовать с четко определенным протоколом для каждого из них.
Этот четко определенный протокол обычно определяется сигнатурой метода. Здесь fopen требует, чтобы вы передали ему строку (или символ * в случае C). Если вы дадите ему что-то еще, вы получите ошибку во время компиляции. Вы не следовали протоколу - вы не используете API должным образом.
В некоторых (малоизвестных) языках тип возвращаемого значения также является частью протокола. Если вы попытаетесь вызвать эквивалентfopen()
в некоторых языках без присвоения его переменной, вы также получите ошибку во время компиляции (вы можете сделать это только с помощью функций void).
Я пытаюсь подчеркнуть следующее: на языке со статической типизацией программист API рекомендует клиенту правильно использовать API, предотвращая компиляцию клиентского кода, если он допускает какие-либо очевидные ошибки.
(В динамически типизированном языке, таком как Ruby, вы можете передать в качестве имени файла что угодно, скажем, float, и он скомпилируется. Зачем беспокоить пользователя проверенными исключениями, если вы даже не собираетесь контролировать аргументы метода. приведенные здесь аргументы применимы только к языкам со статической типизацией.)
Итак, что насчет проверенных исключений?
Вот один из API-интерфейсов Java, который вы можете использовать для открытия файла.
try {
f = new FileInputStream("goodluckfindingthisfile");
}
catch (FileNotFoundException e) {
// deal with it. No really, deal with it!
... // this is me dealing with it
}
Видишь этот улов? Вот подпись для этого метода API:
public FileInputStream(String name)
throws FileNotFoundException
Обратите внимание, что FileNotFoundException
это проверенное исключение.
Программист API говорит вам следующее: «Вы можете использовать этот конструктор для создания нового FileInputStream, но вы
а) должен передать имя файла в виде строки
б) должен принять возможность того, что файл может быть не найден во время выполнения "
И это все, что касается меня.
Ключевым моментом является то, что этот вопрос гласит как «Вещи, которые находятся вне контроля программиста». Моей первой мыслью было, что он / она имеет в виду вещи, которые находятся вне контроля программистов API . Но на самом деле, проверенные исключения при правильном использовании должны действительно относиться к вещам, которые находятся вне контроля как программиста клиента, так и программиста API. Я думаю, что это ключ к тому, чтобы не злоупотреблять проверенными исключениями.
Я думаю, что открытие файла прекрасно иллюстрирует суть. Программист API знает, что вы можете дать им имя файла, которое оказывается несуществующим во время вызова API, и что они не смогут вернуть вам то, что вы хотели, но им придется выбросить исключение. Они также знают, что это будет происходить довольно регулярно и клиентский программист может ожидать, что имя файла будет правильным во время написания вызова, но это может быть неправильно во время выполнения по независящим от них причинам.
Итак, API делает это явным: будут случаи, когда этот файл не будет существовать в то время, когда вы мне звоните, и вам, черт побери, лучше с этим справиться.
Это было бы яснее с контр-кейсом. Представьте, что я пишу таблицу API. У меня есть модель таблицы где-то с API, включая этот метод:
public RowData getRowData(int row)
Теперь, как программист API, я знаю, что будут случаи, когда некоторый клиент передает отрицательное значение для строки или значение строки вне таблицы. Поэтому у меня может возникнуть соблазн сгенерировать проверенное исключение и заставить клиента с ним справиться:
public RowData getRowData(int row) throws CheckedInvalidRowNumberException
(Конечно, я бы не назвал это «Проверено».)
Это плохое использование проверенных исключений. Код клиента будет полон вызовов для извлечения данных строки, каждый из которых должен будет использовать try / catch, и для чего? Собираются ли они сообщить пользователю, что был найден неправильный ряд? Вероятно, нет - потому что, каким бы ни был пользовательский интерфейс, окружающий мое табличное представление, он не должен позволять пользователю переходить в состояние, когда запрашивается недопустимая строка. Так что это ошибка со стороны клиентского программиста.
Программист API все еще может предсказать, что клиент будет кодировать такие ошибки и должен обрабатывать их с исключением времени выполнения, например IllegalArgumentException
.
С проверенным исключением getRowData
, это явно тот случай, который приведет к тому, что ленивый программист Хейлсберга просто добавит пустые уловы. Когда это происходит, недопустимые значения строк не будут очевидны даже для отладчика тестировщика или разработчика клиента, скорее, они приведут к ошибкам, которые трудно определить источник. Ракеты Arianne взорвутся после запуска.
Итак, вот в чем проблема: я говорю, что проверенное исключение FileNotFoundException
- это не просто хорошая вещь, а необходимый инструмент в наборе инструментов для программистов API, который позволяет определить API наиболее полезным способом для программиста клиента. Но CheckedInvalidRowNumberException
это большое неудобство, приводящее к плохому программированию, и его следует избегать. Но как сказать разницу.
Я полагаю, что это не точная наука, и я полагаю, что она лежит в основе и, возможно, в определенной степени оправдывает аргументацию Хейлсберга. Но я не рад выбрасывать ребенка с водой, поэтому позвольте мне извлечь некоторые правила, чтобы отличить проверенные исключения от плохих:
Вне контроля клиента или Закрыто против Открытого:
Проверенные исключения следует использовать только в том случае, если ошибка не контролируется как API, так и клиентским программистом. Это связано с тем, насколько открыта или закрыта система. В ограниченном пользовательском интерфейсе, где клиентский программист имеет контроль, скажем, над всеми кнопками, командами клавиатуры и т. Д., Которые добавляют и удаляют строки из табличного представления (закрытая система), это ошибка программирования клиента, если он пытается извлечь данные из несуществующий ряд В файловой операционной системе, где любое количество пользователей / приложений может добавлять и удалять файлы (открытая система), вполне возможно, что файл, запрашиваемый клиентом, был удален без их ведома, поэтому следует ожидать, что он будет иметь с ним дело. ,
Ubiquity:
Проверенные исключения не должны использоваться в вызове API, который часто выполняется клиентом. Под часто я имею в виду много мест в клиентском коде - не часто во времени. Поэтому клиентский код не склонен многократно открывать один и тот же файл, но мое табличное представление встречается RowData
везде разными способами. В частности, я собираюсь написать много кода, как
if (model.getRowData().getCell(0).isEmpty())
и будет больно каждый раз оборачиваться в try / catch.
Информирование пользователя:
Проверенные исключения следует использовать в тех случаях, когда вы можете представить полезное сообщение об ошибке, которое будет представлено конечному пользователю. Это "а что ты будешь делать, когда это произойдет?" вопрос, который я поднял выше. Это также относится к пункту 1. Поскольку вы можете предсказать, что что-то за пределами вашей системы клиент-API может привести к отсутствию файла, вы можете разумно сообщить об этом пользователю:
"Error: could not find the file 'goodluckfindingthisfile'"
Так как ваш неправильный номер строки был вызван внутренней ошибкой и не по вине пользователя, на самом деле нет никакой полезной информации, которую вы можете им дать. Если ваше приложение не позволяет исключениям среды выполнения попадать на консоль, оно, вероятно, в конечном итоге выдаст им какое-то уродливое сообщение вроде:
"Internal error occured: IllegalArgumentException in ...."
Короче говоря, если вы не думаете, что ваш клиентский программист может объяснить ваше исключение так, чтобы это помогло пользователю, то вам, вероятно, не следует использовать проверенное исключение.
Таковы мои правила. Несколько придумано, и, несомненно, будут исключения (пожалуйста, помогите мне уточнить их, если хотите). Но мой главный аргумент в том, что есть такие случаи, какFileNotFoundException
когда проверяемое исключение является такой же важной и полезной частью контракта API, как и типы параметров. Поэтому мы не должны отказываться от него только потому, что он используется не по назначению.
Извините, я не хотел делать это так долго и вафельно. Позвольте мне закончить с двумя предложениями:
A: Программисты API: экономно используйте проверенные исключения, чтобы сохранить их полезность. В случае сомнений используйте непроверенное исключение.
B: Клиентские программисты: привыкайте создавать упакованное исключение (Google google) на ранней стадии разработки. JDK 1.4 и более поздние версии предоставляют конструктор RuntimeException
для этого, но вы также можете легко создать и свой собственный. Вот конструктор:
public RuntimeException(Throwable cause)
Затем приобретите привычку всякий раз, когда вам нужно обработать проверенное исключение, и вы чувствуете себя ленивым (или вы думаете, что программист API слишком усердно использовал проверенное исключение в первую очередь), не просто проглотите исключение, оберните его и отбросить его.
try {
overzealousAPI(thisArgumentWontWork);
}
catch (OverzealousCheckedException exception) {
throw new RuntimeException(exception);
}
Поместите это в один из маленьких шаблонов кода вашей IDE и используйте его, когда вам лень. Таким образом, если вам действительно нужно обработать проверенное исключение, вы будете вынуждены вернуться и разобраться с ним, увидев проблему во время выполнения. Потому что, поверьте мне (и Андерсу Хейлсбергу), вы никогда не вернетесь к этому TODO в своем
catch (Exception e) { /* TODO deal with this at some point (yeah right) */}