Java 中的 True-way 解决方案: 从2个字符串中解析2个数字,然后返回它们的和

根据密码:

public static int sum(String a, String b) /* throws? WHAT? */ {
int x = Integer.parseInt(a); // throws NumberFormatException
int y = Integer.parseInt(b); // throws NumberFormatException
return x + y;
}

你能告诉我这是不是好 Java 吗?我要说的是,NumberFormatException是一个未检查的异常。将其指定为 sum()签名的一部分。此外,据我所知,未检查异常的概念只是为了表明程序的实现是不正确的,更重要的是,捕获未检查异常是一个坏主意,因为它类似于 在运行时修复错误程序

有没有人能澄清一下:

  1. 我应该指定 NumberFormatException作为方法签名的一部分。
  2. 我应该定义我自己的检查异常(BadDataException) ,在方法中处理 NumberFormatException并将其重新抛出为 BadDataException
  3. 我应该定义自己的检查异常(BadDataException) ,像正则表达式一样验证这两个字符串,如果不匹配则抛出 BadDataException
  4. 你的主意?

更新 :

想象一下,它不是一个开源框架,出于某种原因,您应该使用它。你看着方法的签名,然后想: “好吧,它从来不扔。”。然后,有一天,你得到了一个例外。这正常吗?

更新2 :

有一些评论说我的 sum(String, String)是一个糟糕的设计。我完全同意,但是对于那些相信如果我们有好的设计原始问题就不会出现的人,这里有一个额外的问题:

问题定义是这样的: 您有一个数据源,其中数字存储为 String。这个来源可能是 XML 文件,网页,桌面窗口与2个编辑框,无论什么。

您的目标是实现采用这两个 String的逻辑,将它们转换为 int并显示消息框,其中显示“和为 xxx”。

无论您使用什么方法来设计/实现它,您都将 有这两点内部功能:

  1. String转换为 int的位置
  2. 在这个位置加入两个 int

我最初的帖子的主要问题是:

Integer.parseInt()期望传递 正确字符串。每当你传递一个 坏线,这意味着 你的程序是不正确的(而不是“ 你的用户是一个白痴”)。您需要实现这段代码,其中一方面您拥有带有 必须语义的 Integer.parseInt () ,另一方面您需要确定输入不正确的情况-应该是语义学上的

因此,简单地说: 如果我只有 图书馆必须,我如何实现 应该是语义学上的

5371 次浏览

In my opinion it would be preferable to handle exception logic as far up as possible. Hence I would prefer the signature

 public static int sum(int a, int b);

With your method signature I would not change anything. Either you are

  • Programmatically using incorrect values, where you instead could validate your producer algorithm
  • or sending values from e.g., user input, in which case that module should perform the validation

Hence, exception handling in this case becomes a documentation issue.

Nr 4.

I think I wouldn't change the method at all. I would put a try catch around the calling method or higher in the stack-trace where I'm in a context where I can gracefully recover with business logic from the exception.

I wouldn't certainty do #3 as I deem it overkill.

This is a good question. I wish more people would think about such things.

IMHO, throwing unchecked exceptions is acceptable if you've been passed rubbish parameters.

Generally speaking, you shouldn't throw BadDataException because you shouldn't use Exceptions to control program flow. Exceptions are for the exceptional. Callers to your method can know before they call it if their strings are numbers or not, so passing rubbish in is avoidable and therefore can be considered a programming error, which means it's OK to throw unchecked exceptions.

Regarding declaring throws NumberFormatException - this is not that useful, because few will notice due to NumberFormatException being unchecked. However, IDE's can make use of it and offer to wrap in try/catch correctly. A good option is to use javadoc as well, eg:

/**
* Adds two string numbers
* @param a
* @param b
* @return
* @throws NumberFormatException if either of a or b is not an integer
*/
public static int sum(String a, String b) throws NumberFormatException {
int x = Integer.parseInt(a);
int y = Integer.parseInt(b);
return x + y;
}

EDITED:
The commenters have made valid points. You need to consider how this will be used and the overall design of your app.

If the method will be used all over the place, and it's important that all callers handle problems, the declare the method as throwing a checked exception (forcing callers to deal with problems), but cluttering the code with try/catch blocks.

If on the other hand we are using this method with data we trust, then declare it as above, because it is not expected to ever explode and you avoid the code clutter of essentially unnecessary try/catch blocks.

Depends a lot on the scenario you are in.

Case 1. Its always you who debug the code and no one else and exception wont cause a bad user experience

Throw the default NumberFormatException

Case2: Code should be extremely maintainable and understandable

Define your own exception and add lot more data for debugging while throwing it.

You dont need regex checks as, its gonna go to exception on bad input anyway.

If it was a production level code, my idea would be to define more than one custom exceptions, like

  1. Number format exception
  2. Overflow exception
  3. Null exception etc...

and deal with all these seperately

  1. You may do so, to make it clear that this can happen for incorrect input. It might help someone using your code to remember handling this situation. More specifically, you're making it clear that you don't handle it in the code yourself, or return some specific value instead. Of course, the JavaDoc should make this clear too.
  2. Only if you want to force the caller to deal with a checked exception.
  3. That seems like overkill. Rely on the parsing to detect bad input.

Overal, a NumberFormaException is unchecked because it is expected that correctly parseable input is provided. Input validation is something you should handle. However, actually parsing the input is the easiest way to do this. You could simply leave your method as it is and warn in the documentation that correct input is expected and anyone calling your function should validate both inputs before using it.

1. I should specify NumberFormatException as a part of method's signature.

I think so. It's a nice documentation.

2. I should define my own checked exception (BadDataException), handle NumberFormatException inside the method and re-throw it as BadDataException.

Sometimes yes. The checked exceptions are consider to be better in some cases, but working with them is quite a PITA. That's why many frameworks (e.g., Hibernate) use runtime exceptions only.

3. I should define my own checked exception (BadDataException), validate both strings some way like regular expressions and throw my BadDataException if it doesn't match.

Never. More work, less speed (unless you expect throwing the exception to be a rule), and no gain at all.

4. Your idea?

None at all.

While I agree with the answer that the runtime exception should be allowed to be percolated, from a design and usability perspective, it would be a good idea to wrap it into a IllegalArgumentException rather than throw it as NumberFormatException. This then makes the contract of your method more clear whereby it declares an illegal argument was passed to it due to which it threw an exception.

Regarding the update to the question "Imagine, it's not an open-source framework, that you should use for some reason. You look at method's signature and think - "OK, it never throws". Then, some day, you got an exception. Is it normal?" the javadoc of your method should always spill out the behavior of your method (pre and post constraints). Think on the lines of say collection interfaces where in if a null is not allowed the javadoc says that a null pointer exception will be thrown although it is never part of the method signature.

Any exceptional behaviour should be clarified in the documentation. Either it should state that this method returns a special value in case the of failure (like null, by changing the return type to Integer) or case 1 should be used. Having it explicit in the method's signature lets the user ignore it if he ensures correct strings by other means, but it still is obvious that the method doesn't handle this kind of failure by itself.

Assuming that what you are writing is going to be consumed (like as an API) by someone else, then you should go with 1, NumberFormatException is specifically for the purpose of communicating such exceptions and should be used.

Answer to your updated question.

Yes it's perfectly normal to get "surprise" exceptions. Think about all the run time errors one got when new to programming.

e.g ArrayIndexOutofBound

Also a common surprise exception from the for each loop.

ConcurrentModificationException or something like that

As you are talking about good java practice ,in my opinion it is always better

  • To handle the unchecked exception then analyze it and through a custom unchecked exception.

  • Also while throwing custom unchecked exception you can add the Exception message that your client could understand and also print the stack trace of original exception

  • No need to declare custom exception as "throws" as it is unchecked one.

  • This way you are not violating the use of what unchecked exceptions are made for, at the same time client of the code would easily understand the reason and solution for the exception .

  • Also documenting properly in java-doc is a good practice and helps a lot.

  1. First you need to ask your self, does the user of my method needs to worry about entering wrong data, or is it expected of him to enter proper data (in this case String). This expectation is also know as design by contract.

  2. and 3. Yes you probably should define BadDataException or even better use some of the excising ones like NumberFormatException but rather the leaving the standard message to be show. Catch NumberFormatException in the method and re-throw it with your message, not forgetting to include the original stack trace.

  3. It depends on the situation bu I would probably go with re-throwing NumberFormatException with some additional info. And also there must be a javadoc explanation of what are the expected values for String a, String b

Number 4. As given, this method should not take strings as parameters it should take integers. In which case (since java wraps instead of overflowing) there's no possibility of an exception.

 x = sum(Integer.parseInt(a), Integer.parseInt(b))

is a lot clearer as to what is meant than x = sum(a, b)

You want the exception to happen as close to the source (input) as possible.

As to options 1-3, you don't define an exception because you expect your callers to assume that otherwise your code can't fail, you define an exception to define what happens under known failure conditions WHICH ARE UNIQUE TO YOUR METHOD. I.e. if you have a method that is a wrapper around another object, and it throws an exception then pass it along. Only if the exception is unique to your method should you throw a custom exception (frex, in your example, if sum was supposed to only return positive results, then checking for that and throwing an exception would be appropriate, if on the other hand java threw an overflow exception instead of wrapping, then you would pass that along, not define it in your signature, rename it, or eat it).

Update in response to update of the question:

So, briefly: how do I implement SHOULD semantics if I only have MUST libraries.

The solution to this is to to wrap the MUST library, and return a SHOULD value. In this case, a function that returns an Integer. Write a function that takes a string and returns an Integer object -- either it works, or it returns null (like guava's Ints.tryParse). Do your validation seperate from your operation, your operation should take ints. Whether your operation gets called with default values when you have invalid input, or you do something else, will depend upon your specs -- most I can say about that, is that it's really unlikely that the place to make that decision is in the operation method.

I think it depends on your purpose, but I would document it at a minimum:

/**
* @return the sum (as an int) of the two strings
* @throws NumberFormatException if either string can't be converted to an Integer
*/
public static int sum(String a, String b)
int x = Integer.parseInt(a);
int y = Integer.parseInt(b);
return x + y;
}

Or, take a page from the Java source code for the java.lang.Integer class:

public static int parseInt(java.lang.String string) throws java.lang.NumberFormatException;

There are lots of interesting answers to this question. But I still want to add this :

For string parsing, I always prefer to use "regular expressions". The java.util.regex package is there to help us. So I will end up with something like this, that never throws any exception. It's up to me to return a special value if I want to catch some error :

import java.util.regex.Pattern;
import java.util.regex.Matcher;


public static int sum(String a, String b) {
final String REGEX = "\\d"; // a single digit
Pattern pattern = Pattern.compile(REGEX);
Matcher matcher = pattern.matcher(a);
if (matcher.find()) { x = Integer.matcher.group(); }
Matcher matcher = pattern.matcher(b);
if (matcher.find()) { y = Integer.matcher.group(); }
return x + y;
}

As one can see, the code is just a bit longer, but we can handle what we want (and set default values for x and y, control what happens with else clauses, etc...) We could even write a more general transformation routine, to which we can pass strings, defaut return values, REGEX code to compile, error messages to throw, ...

Hope It was usefull.

Warning : I was not able to test this code, so please excuse eventual syntax problems.

How about the input validation pattern implemented by Google's 'Guava' library or Apache's 'Validator' library (comparison)?

In my experience, it is considered good practice to validate a function's parameters at the beginning of the function and throw Exceptions where appropriate.

Also, I would consider this question to be largely language independent. The 'good practice' here would apply to all languages that have functions which can take parameters which may or may not be valid.

I think your very first sentence of "Quite a stupid question" is very relevant. Why would you ever write a method with that signature in the first place? Does it even make sense to sum two strings? If the calling method wants to sum two strings, it is the calling method's responsibility to make sure they are valid ints and to convert them before calling the method.

In this example, if the calling method cannot convert the two Strings into an int, it could do several things. It really depends at what layer this summation occurs at. I am assuming the String conversion would be very close to front-end code (if it was done properly), such that case 1. would be the most likely:

  1. Set an error message and stop processing or redirect to an error page
  2. Return false (ie, it would put the sum into some other object and would not be required to return it)
  3. Throw some BadDataException as you are suggesting, but unless the summation of these two numbers is very important, this is overkill, and like mentioned above, this is probably bad design since it implies that the conversion is being done in the wrong place

You face this issue because you let user errors propagate too deep into the core of the application and partly also because you abuse Java data types.

You should have a clearer separation between user input validation and business logic, use proper data typing, and this problem will disappear by itself.

The fact is the semantics of Integer.parseInt() are known - it's primary purpose it to parse valid integers. You're missing an explicit user input validation/parsing step.