Java API Design: NumberFormatException for Method that Parses a Semi-Numeric String?
I'm making a library that contains a few methods for parsing string dates and times. I am having difficulty deciding what exception those methods should throw when the string argument isn't parseable. I'm considering several options:
1. java.lang.IllegalArgumentException
- an invalid string is clearly an illegal argument, but, to me, IllegalArgumentException
typically means programming error, and it's rare to want to do an explicit try catch
for one. I think string parsing is often for external input and is more of a special case that deserves special treatment. If, say, you had a big block of code that parsed user input and did something else with it, you might want to wrap that code in a try catch block so you could handle the case of the user input containing an invalid string. But catching IllegalArgumentException
wouldn't be great for pinpointing invalid user input, because most likely there'd be multiple places in your code that it could be thrown from (not just the user-input parsing).
2. java.lang.NumberFormatException
- it's thrown by Integer.parseInt(String)
and other similar parsing methods in java.lang
. So most Java developers are familiar with it being the exception to catch when you're trying to parse a string that may or may not be valid (e.g. user input). But it's got "Number" in its name, so I'm not sure it really fits with things like dates and times which are numeric in a sense, but conceptually different in my mind. If only it was called "FormatException"...
3. java.text.ParseException
- not really an option because it's checked. I want unchecked for this.
4. A custom exception - this gets around the downsides of IllegalArgumentException
and NumberFormatException
, and it could extend IllegalArgumentException
too. But I don't think it's a good idea to add exceptions to a library unless they're really needed. Quoting Josh Bloch, "If in doubt, leave it out". (Also, in my case, for a package that parses dates and times it's quite hard to name such an exception: "DateFormatException", "TimeFormatException", "CalendricalFormatException" (like JSR 310) - none seem ideal to me when applied to methods that parse dates, times, date-times etc. And I think it would be silly to create multiple exceptions in a single package if they were all just used to identify unparseable strings.)
So which option do you think makes most sense?
NB There are good reasons for me to not wanting to use java.util.Date or Joda Time, or JSR 310, so no need to suggest those. Plus I think it would be good if this question was kept fairly general, since this must be an issue that other people designing APIs have struggled with. Dates and times could just as well be IP addresses, or URLs, or any other kind of information that has string formats that need parsing.
Precedents Set by Other Libraries
Just a few examples I found:
java.sql.Timestamp.valueOf(String) throws IllegalArgumentException
java.util.Date.parse(String) throws IllegalArgumentException
(deprecated method, and the exception isn't declared, but you can see it in the source)
java.util.UUID.fromString(String) throws IllegalArgumentException
org.apache.axis.types.Time(String) throws NumberFormatException
(also Day class in Apache Axis)
org.apache.tools.ant.ut开发者_运维百科il.DeweyDecimal(String) throws NumberFormatException
com.google.gdata.data.DateTime.parseDateTime(String) throws NumberFormatException
java.lang.Package.isCompatibleWith(String versionString) throws NumberFormatException
(in Java 7, this takes a string version number with dots - kind of like a date or a time in a sense)
I'm sure there are lots of packages that use a custom exception, so I doubt there's much point in giving examples of those.
I would have suggested IllegalFormatException
as base class (which inherits from IllegalArgumentException
, but unfortunately the sole Constructor is Package-protected, so I'd probably use
IllegalArgumentException
for a small API (a few methods)- your own Class Hierarchy that inherits from
IllegalArgumentException
otherwise.
In any case, I would use IllegalArgumentException
as base class.
In one of my previous projects, the guideline was to only throw RuntimeExceptions that inherit from
IllegalStateException
IllegalArgumentException
UnsupportedOperationException
Although this isn't an official dogma, I'd call it a good practice. All three of these are simple and self-descriptive.
I would opt for ParseException, because that's what DateFormat.parse throws. Thus it has the advantage you mention in 2., programmers being familiar with it. But since you want unchecked, I guess this is not an option. If forced to go for unchecked, I'll opt for 4.
For this question, it is a matter of taste. In my opinion, I would not throw exception but handle that in return. The reason is that user cannot do anything in exception handler if the input was wrong. So these type of errors can be easily handled by checking the return code. Exceptions are heavy and dont bring extra value.
I'm currently leaning in favour of using NumberFormatException
. I was thinking plain old IllegalArgumentException
but it became clear to me that that wasn't ideal when I wrote a little code like:
try {
final Day day = Day.fromString(userInputString);
someMethodThatCouldAlsoThrowIllegalArgumentException(day);
} catch (IllegalArgumentException e) {
ui.showWarningMessage("Invalid date: " + userInputString);
}
Assuming you parse a valid Day
, you'll want to do something with it. And it's very likely that the methods you'd pass it to would throw an IllegalArgumentException
if they received an invalid parameter (i.e. a programming error rather than a user error). In code like the above example you'd want to be careful not to confuse a programming error for invalid user input, so, to be on the safe side, you'd need something messy like a null variable defined before the try/catch block, and a check for null after it.
Swap IllegalArgumentException
for NumberFormatException
or a custom exception and the problem goes away.
Having seen that JDK 1.7's java.lang.Package.isCompatibleWith(String versionNo)
uses NumberFormatException
for a method that parses a version number (a semi-numeric string like "1.6.1.32"
), I'm thinking that this might also be the sensible choice for methods that parse things like dates and times.
NumberFormatException
is not exactly ideal for parsing strings that are only semi-numeric, but perhaps it's the best option when you don't want to clutter an API with a custom exception that isn't really needed. If they're doing it in java.lang
, then perhaps that makes it the Java way of doing things by definition...
I'm hoping it catches on :)
精彩评论