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

时间:2011-08-25 11:23:34

标签: java parsing exception

相当愚蠢的问题。鉴于代码:

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. 你的想法?
  5. 更新

    想象一下,它不是一个开源框架,你应该出于某种原因使用它。你看看方法的签名并思考 - “好吧,它永远不会抛出”。然后,有一天,你有一个例外。这是正常的吗?

    更新2

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

    问题定义如下:您有一个数据源,其中数字存储为String s。这个源可能是XML文件,网页,带有2个编辑框的桌面窗口,等等。

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

    无论您使用什么方法设计/实现这一点,您都拥有这两点内在功能

    1. 您将String转换为int
    2. 的地方
    3. 您添加2 int s
    4. 的地方

      我原帖的主要问题是:

      Integer.parseInt()期望传递正确的字符串。每当你传递一个坏字符串时,就意味着你的程序是不正确的(不是“你的用户是一个白痴”)。你需要实现一段代码,一方面你有Integer.parseInt()和 MUST语义,另一方面你需要在输入不正确的情况下正常 - 应该是语义

      简而言之:如果我只有 MUST库,我该如何实现 SHOULD语义

18 个答案:

答案 0 :(得分:49)

在我看来,最好尽可能地处理异常逻辑。因此我更喜欢签名

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

使用您的方法签名,我会更改任何内容。你是

  • 以编程方式使用不正确的值,您可以在其中验证生产者算法
  • 或从例如用户输入发送值,在这种情况下该模块应执行验证

因此,在这种情况下,异常处理成为文档问题。

答案 1 :(得分:35)

这是一个很好的问题。我希望更多的人会考虑这些事情。

恕我直言,如果你已经通过了垃圾参数,那么抛出未经检查的异常是可以接受的。

一般来说,您不应该抛出BadDataException,因为您不应该使用Exceptions来控制程序流。例外是特殊情况。你的方法的调用者可以在之前知道,如果他们的字符串是数字,他们会调用它,所以传递垃圾是可以避免的,因此可以被认为是编程错误,这意味着它是好的,可以抛出未经检查的异常。

关于声明throws NumberFormatException - 这没有用,因为很少会注意到由于未选中NumberFormatException。但是,IDE可以使用它并提供正确包装try/catch。一个好的选择是使用javadoc,例如:

/**
 * 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
评论者提出了有效的观点。您需要考虑如何使用它以及您的应用程序的整体设计。

如果该方法将在所有地方使用,并且所有调用者处理问题都很重要,则将该方法声明为抛出已检查的异常(强制调用者处理问题),但是使用{{1}使代码混乱。块。

另一方面,如果我们将这个方法与我们信任的数据一起使用,那么就像上面那样声明它,因为它不会爆炸并且你避免了基本上不必要的try/catch块的代码混乱。

答案 2 :(得分:5)

Number 4.如上所述,此方法不应将字符串作为应采用整数的参数。在这种情况下(因为java包装而不是溢出),不存在异常的可能性。

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

比什么意思清楚得多      x = sum(a, b)

您希望异常尽可能靠近源(输入)发生。

对于选项1-3,您没有定义异常,因为您希望您的调用者假设您的代码不会失败,您定义一个异常来定义在已知失败条件下发生的事情,这对您来说是独一无二的方法。即如果你有一个方法是另一个对象的包装器,它会抛出一个异常,然后传递它。只有当异常对你的方法是唯一的时候才应该抛出一个自定义异常(在你的例子中,如果sum应该只返回正结果,那么检查它并抛出异常将是合适的,如果另一方面java抛出溢出异常而不是包装,然后你会传递它,不在你的签名中定义,重命名或吃掉它。)

更新以回应问题的更新:

  

简而言之:如果我只有MUST库,我该如何实现SHOULD语义。

解决方法是包装MUST库,并返回SHOULD值。在这种情况下,返回一个Integer的函数。编写一个接受字符串并返回Integer对象的函数 - 它可以工作,或者返回null(如guava的Ints.tryParse)。您的验证是否与您的操作分开,您的操作应该采取整合。当您输入无效时,是否使用默认值调用操作,或者您执行其他操作时,将取决于您的规范 - 我可以说最多的是,是否真的不可能做出该决定的地方在操作中方法

答案 3 :(得分:3)

  

1。我应该将NumberFormatException指定为方法签名的一部分。

我是这么认为的。这是一个很好的文档。

  

2。我应该定义自己的已检查异常(BadDataException),在方法内部处理NumberFormatException并将其重新抛出为BadDataException。

有时是的。在某些情况下,被检查的异常被认为是更好的,但与它们合作是一个非常PITA。这就是为什么许多框架(例如,Hibernate)仅使用运行时异常的原因。

  

3。我应该定义自己检查的异常(BadDataException),像正则表达式一样验证两个字符串,如果它不匹配则抛出BadDataException。

从不。更多的工作,更少的速度(除非你期望将异常作为一个规则),并且根本没有收获。

  

4。你的想法?

完全没有。

答案 4 :(得分:2)

Nr 4。

我想我根本不会改变这个方法。 我会尝试捕获调用方法或更高的堆栈跟踪,我在上下文中,我可以从异常中优雅地恢复业务逻辑。

我不确定#3因为我认为它有点过分。

答案 5 :(得分:2)

假设您正在编写的内容将被其他人使用(例如作为API),那么您应该使用1,NumberFormatException专门用于传达此类异常并且应该使用。

答案 6 :(得分:2)

  1. 首先,您需要问问自己,我的方法的用户是否需要担心输入错误的数据,或者是否希望他输入正确的数据(在本例中为String)。 这种期望也被contract称为设计。

  2. 和3.是的,您可能应该定义BadDataException,甚至更好地使用一些像NumberFormatException这样的切除,而是让标准消息显示出来。在方法中捕获NumberFormatException并使用您的消息重新抛出它,不要忘记包含原始堆栈跟踪。

  3. 这取决于我可能会重新抛出NumberFormatException以及其他一些信息。并且必须有一个javadoc解释String a, String b

    的预期值

答案 7 :(得分:1)

回答您的最新问题。

是的,获得“惊喜”例外是完全正常的。 想想新编程时遇到的所有运行时错误。

e.g ArrayIndexOutofBound

也是每个循环的常见惊喜例外。

ConcurrentModificationException or something like that

答案 8 :(得分:1)

我认为你的第一句“相当愚蠢的问题”是非常相关的。你为什么要首先用这个签名写一个方法?将两个字符串相加甚至是否有意义?如果调用方法想要对两个字符串求和,那么调用方法的责任是确保它们是有效的int并在调用方法之前转换它们。

在这个例子中,如果调用方法不能将两个字符串转换为int,它可以做几件事。这实际上取决于此求和发生在哪一层。我假设字符串转换将非常接近前端代码(如果它已正确完成),这样情况1.最有可能:

  1. 设置错误消息并停止处理或重定向到错误页面
  2. 返回false(即,它会将总和放入其他对象中,并且不需要返回它)
  3. 按照你的建议抛出一些BadDataException,但是除非这两个数字的总和非常重要,否则这是过度的,如上所述,这可能是糟糕的设计,因为它暗示转换是在错误的地方完成的

答案 9 :(得分:1)

Google's 'Guava' libraryApache's 'Validator' librarycomparison)实施的输入验证模式怎么样?

根据我的经验,在函数开头验证函数的参数并在适当的时候抛出异常被认为是一种好习惯。

另外,我认为这个问题主要与语言无关。这里的“良好做法”适用于所有具有可能带有或可能无效的参数的函数的语言。

答案 10 :(得分:1)

这个问题有很多有趣的答案。但我仍然想补充一点:

对于字符串解析,我总是喜欢使用“正则表达式”。 java.util.regex包可以帮助我们。所以我最终会得到类似的东西,从不抛出任何异常。如果我想捕获一些错误,我可以返回一个特殊值:

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;
}

正如我们所看到的,代码只是稍长一点,但我们可以处理我们想要的东西(并设置x和y的默认值,控制else子句发生的事情等等) 我们甚至可以编写一个更通用的转换例程,我们可以传递字符串,defaut返回值,编译REGEX代码,抛出错误消息,......

希望它很有用。

警告:我无法测试此代码,因此请原谅最终的语法问题。

答案 11 :(得分:1)

我认为这取决于你的目的,但我会将其记录在最低限度:

/**
 * @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;
}

或者,从Java源代码中获取java.lang.Integer类的页面:

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

答案 12 :(得分:1)

当你在谈论良好的java练习时,在我看来它总是更好

  • 要处理未经检查的异常,请通过自定义未经检查的异常对其进行分析。

  • 此外,在抛出自定义未经检查的异常时,您可以添加客户端可以理解的异常消息,并打印原始异常的堆栈跟踪

  • 无需将自定义异常声明为“throws”,因为它未被选中。

  • 通过这种方式,您不会违反使用未经检查的异常,同时代码客户端可以轻松理解异常的原因和解决方案。

  • 在java-doc中正确记录也是一种很好的做法,并且有很多帮助。

答案 13 :(得分:1)

任何例外行为都应在文档中说明。它应该声明此方法返回一个特殊值,以防失败(如null,通过将返回类型更改为Integer)或案例1。如果通过其他方式确保正确的字符串,则在方法的签名中明确允许用户忽略它,但显然该方法本身不能处理这种类型的故障。

答案 14 :(得分:1)

虽然我同意应该允许运行时异常被渗透的答案,但从设计和可用性的角度来看,将它包装成IllegalArgumentException而不是将其作为NumberFormatException抛出是一个好主意。然后,这会使您的方法的合同更加清晰,因为它声明了一个非法的参数被传递给它,因为它引发了异常。

关于问题的更新“Imagine,它不是一个开源框架,你应该出于某种原因使用它。你看看方法的签名并思考 - ”好吧,它永远不会抛出“。然后,有一天,你有例外。这是正常的吗?“你的方法的javadoc应该总是溢出你的方法的行为(前后约束)。考虑一下收集接口的界限,如果不允许使用null,javadoc会抛出空指针异常,尽管它从不是方法签名的一部分。

答案 15 :(得分:1)

  1. 您可以这样做,以明确不正确的输入可能会发生这种情况。它可能会帮助使用您的代码的人记住处理这种情况。更具体地说,您明确表示自己没有在代码中处理它,或者返回一些特定值。当然,JavaDoc也应该明确这一点。
  2. 仅当您要强制调用者处理已检查的异常时。
  3. 这看起来有点矫枉过正。依靠解析来检测错误的输入。
  4. 总之,未选中NumberFormaException,因为预期会提供正确的可解析输入。输入验证是您应该处理的。但是,实际解析输入是最简单的方法。你可以简单地保留你的方法,并在文档中警告正确的输入是预期的,任何调用你的函数的人都应该在使用之前验证这两个输入。

答案 16 :(得分:1)

取决于您所处的情景。

案例1.您总是调试代码,没有其他人和例外不会导致糟糕的用户体验

抛出默认的NumberFormatException

案例2:代码应该非常易于维护和理解

定义您自己的异常并添加更多数据以便在抛出时进行调试。

你不需要正则表达式检查,无论如何它会在错误的输入上发生异常。

如果它是生产级代码,我的想法是定义多个自定义异常,例如

  1. 数字格式例外
  2. 溢出异常
  3. 空例外等......
  4. 单独处理所有这些

答案 17 :(得分:0)

您遇到此问题是因为您将用户错误传播到应用程序的核心太深,部分原因还在于您滥用Java数据类型。

您应该在用户输入验证和业务逻辑之间有一个更清晰的分离,使用正确的数据类型,这个问题将自行消失。

事实是Integer.parseInt()的语义是已知的 - 它的主要目的是解析有效的整数。您缺少明确的用户输入验证/解析步骤。