#java #api #api-design
#java #API #api-дизайн
Вопрос:
Я создаю библиотеку, которая содержит несколько методов для анализа строковых дат и времени. Мне трудно решить, какое исключение должны выдавать эти методы, когда аргумент string не поддается анализу. Я рассматриваю несколько вариантов:
1. java.lang.IllegalArgumentException
— недопустимая строка явно является недопустимым аргументом, но для меня IllegalArgumentException
обычно означает ошибку программирования, и редко возникает желание сделать явный try catch
для одного из них. Я думаю, что синтаксический анализ строк часто выполняется для внешнего ввода и является скорее частным случаем, который заслуживает особого отношения. Если, скажем, у вас был большой блок кода, который анализировал пользовательский ввод и делал с ним что-то еще, вы могли бы захотеть обернуть этот код в блок try catch, чтобы вы могли обрабатывать случай пользовательского ввода, содержащего недопустимую строку. Но перехват IllegalArgumentException
не был бы хорош для точного определения неверного пользовательского ввода, потому что, скорее всего, в вашем коде было бы несколько мест, из которых он мог быть выброшен (не только при синтаксическом анализе пользовательского ввода).
2. java.lang.NumberFormatException
— он генерируется Integer.parseInt(String)
и другими подобными методами синтаксического анализа в java.lang
. Итак, большинство разработчиков Java знакомы с тем, что это исключение, которое перехватывается при попытке проанализировать строку, которая может быть или не быть допустимой (например, пользовательский ввод). Но в его названии есть «Число», поэтому я не уверен, что это действительно подходит к таким вещам, как даты и время, которые в некотором смысле являются числовыми, но концептуально отличаются, на мой взгляд. Если бы только это называлось «FormatException»…
3. java.text.ParseException
— на самом деле это не вариант, потому что он отмечен. Я хочу снять флажок для этого.
4. Пользовательское исключение — оно устраняет недостатки IllegalArgumentException
и NumberFormatException
, и оно также может расширяться IllegalArgumentException
. Но я не думаю, что это хорошая идея добавлять исключения в библиотеку, если они действительно не нужны. Цитирую Джоша Блоха, «Если сомневаетесь, исключите это». (Также, в моем случае, для пакета, который анализирует даты и время, довольно сложно назвать такое исключение: «DateFormatException», «TimeFormatException», «CalendricalFormatException» (например, JSR 310) — ни один из них не кажется мне идеальным применительно к методам, которые анализируют даты, время, date-times и т.д. И я думаю, было бы глупо создавать несколько исключений в одном пакете, если бы все они использовались только для идентификации неразличимых строк.)
Итак, какой вариант, по вашему мнению, имеет наибольший смысл?
ПРИМЕЧАНИЕ: У меня есть веские причины не хотеть использовать java.util.Дата или время Joda, или JSR 310, поэтому нет необходимости предлагать их. Кроме того, я думаю, было бы хорошо, если бы этот вопрос был достаточно общим, поскольку это, должно быть, проблема, с которой сталкивались другие люди, разрабатывающие API. Даты и время с таким же успехом могут быть IP-адресами, или URL-адресами, или любой другой информацией, имеющей строковые форматы, требующие синтаксического анализа.
Прецеденты, установленные другими библиотеками
Всего несколько примеров, которые я нашел:
java.sql.Timestamp.valueOf(String) throws IllegalArgumentException
java.util.Date.parse(String) throws IllegalArgumentException
(устаревший метод, и исключение не объявлено, но вы можете увидеть его в исходном коде)
java.util.UUID.fromString(String) throws IllegalArgumentException
org.apache.axis.types.Time(String) throws NumberFormatException
(также класс Day в Apache Axis)
org.apache.tools.ant.util.DeweyDecimal(String) throws NumberFormatException
com.google.gdata.data.DateTime.parseDateTime(String) throws NumberFormatException
java.lang.Package.isCompatibleWith(String versionString) throws NumberFormatException
(в Java 7 для этого используется номер версии строки с точками — в некотором смысле, что-то вроде даты или времени)
Я уверен, что есть много пакетов, которые используют пользовательское исключение, поэтому я сомневаюсь, что есть смысл приводить примеры из них.
Ответ №1:
Я бы предложил IllegalFormatException
в качестве базового класса (который наследуется от IllegalArgumentException
, но, к сожалению, единственный конструктор защищен пакетом, поэтому я бы, вероятно, использовал
IllegalArgumentException
для небольшого API (несколько методов)- ваша собственная иерархия классов, которая наследуется от
IllegalArgumentException
в противном случае.
В любом случае я бы использовал IllegalArgumentException
в качестве базового класса.
В одном из моих предыдущих проектов руководящим принципом было создавать только исключения RuntimeException, которые наследуются от
IllegalStateException
IllegalArgumentException
UnsupportedOperationException
Хотя это не официальная догма, я бы назвал это хорошей практикой. Все три из них просты и не требуют описания.
Комментарии:
1. Я действительно рассматривал возможность прямого использования исключения IllegalFormatException, но оно существует только в Java 1.5 , и эта библиотека, которую я пишу, совместима с 1.4. Начало с IllegalArgumentException может сработать хорошо. Затем, по мере роста библиотеки, у меня все еще была бы возможность создавать и использовать подкласс без нарушения совместимости.
2. @MB как я уже писал: вы не можете использовать исключение IllegalFormatException, у него нет доступного конструктора
3. Ах да, извините. По какой-то причине, когда я впервые прочитал ваш комментарий, я интерпретировал этот бит как говорящий, что исключение IllegalFormatException не может быть расширено. Но, конечно, только с конструктором package-private я тоже не мог его реально использовать. Я не смог увидеть дерево для деревьев!
Ответ №2:
Я бы выбрал исключение ParseException, потому что это то, что выдает DateFormat.parse . Таким образом, у него есть преимущество, о котором вы упомянули в 2., программисты знакомы с ним. Но поскольку вы хотите снять флажок, я думаю, это не вариант. Если необходимо выбрать непроверенный, я выберу 4.
Комментарии:
1. Знакомый, безусловно, хорош. Но я думаю, что проверяемое исключение было бы довольно громоздким для такого метода синтаксического анализа. Как и при разборе целых чисел, иногда требуется проанализировать пользовательский ввод, который, вероятно, окажется недопустимым (и поэтому вы хотите явно перехватить и обработать исключение), но часто вы анализируете что-то, что, как вы ожидаете, будет допустимым, и тогда иметь дело с проверенным исключением будет проблематично.
2. @MB: Верно. Единственное, что вы могли бы сделать, это предоставить разные функции для этих двух случаев, например,
parse
для первого случая иparseOrNull
илиuncheckedParse
для последнего. К сожалению (я имею в виду ваш комментарий parseOrNull ниже), прямо сейчас я не могу придумать ни одного хорошего примера, где базовый Java API делает это… Также это на самом деле не соответствует каким-либо принципам KISS или InDoubtLeaveItOut.3. Если бы это было только для внутреннего использования, я бы, вероятно, сделал это.
parseOrNull
иparseOrThrow
(выдача непроверенного исключения в случае сбоя синтаксического анализа) на самом деле является соглашением, которое я использую довольно часто. Но я не думаю, что это было бы так хорошо для общедоступного API по причинам, которые вы упомянули. Я подозреваю, что это смутило бы людей, поскольку, скорее всего, это не было бы соглашением, которое они видели раньше, и на самом деле они просто захотят выполнить работу как можно скорее, а не разбираться в моей терминологии.
Ответ №3:
В этом вопросе это дело вкуса. На мой взгляд, я бы не создавал исключение, а обрабатывал это в ответ. Причина в том, что пользователь не может ничего сделать в обработчике исключений, если ввод был неправильным. Таким образом, ошибки такого типа можно легко обработать, проверив код возврата. Исключения являются тяжелыми и не приносят дополнительной ценности.
Комментарии:
1. Я согласен с тем, что исключения являются тяжелыми, но я категорически не согласен с включением ошибок в возвращаемое значение. Для этого требуются раздутые объекты ответа или возвращающий объект, оба из которых могут сделать простой API непригодным для использования.
2. 1 @sean-patrick-floyd: может возвращать null, тогда в коде есть навороты, что снова делает его непригодным для использования.
3. Это действительно дело вкуса. Я рассматривал возможность использования таких методов, как parseOrNull (String), но я не думаю, что это действительно соответствует способу выполнения Java, заданному основными библиотеками JDK. Кроме того, вы заставляете людей проверять наличие нулей или получать бесполезное исключение NullPointerException в случае, если они анализируют строки, которые, как они на 100% ожидают, будут действительными (в отличие от пользовательского ввода, где они ожидают практически всего).
Ответ №4:
В настоящее время я склоняюсь к использованию NumberFormatException
. Я думал по-старому IllegalArgumentException
, но мне стало ясно, что это не идеально, когда я написал небольшой код вроде:
try {
final Day day = Day.fromString(userInputString);
someMethodThatCouldAlsoThrowIllegalArgumentException(day);
} catch (IllegalArgumentException e) {
ui.showWarningMessage("Invalid date: " userInputString);
}
Предполагая, что вы анализируете допустимую Day
строку, вы захотите что-то с ней сделать. И очень вероятно, что методы, которым вы бы передали его, выдали бы IllegalArgumentException
, если бы они получили недопустимый параметр (т. Е. ошибку программирования, а не ошибку пользователя). В коде, подобном приведенному выше примеру, вы должны быть осторожны, чтобы не перепутать ошибку программирования с неверным пользовательским вводом, поэтому, на всякий случай, вам понадобится что-то беспорядочное, например, переменная null, определенная перед блоком try / catch, и проверка на null после нее.
Замените IllegalArgumentException
на NumberFormatException
или пользовательское исключение, и проблема исчезнет.
Увидев, что JDK 1.7 java.lang.Package.isCompatibleWith(String versionNo)
использует NumberFormatException
для метода, который анализирует номер версии (полуцифровую строку типа "1.6.1.32"
), я думаю, что это также может быть разумным выбором для методов, которые анализируют такие вещи, как даты и время.
NumberFormatException
не совсем идеально подходит для анализа строк, которые являются только полуцифровыми, но, возможно, это лучший вариант, когда вы не хотите загромождать API пользовательским исключением, которое на самом деле не нужно. Если они делают это в java.lang
, то, возможно, это делает это способом выполнения Java по определению…
Я надеюсь, что это сработает