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


84

相当愚蠢的问题。给定代码:

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)的设计不好。我绝对同意,但是对于那些相信,如果我们拥有良好的设计,就不会出现原始问题,这是一个额外的问题:

问题定义是这样的:您有一个数据源,数字存储为 Strings。该源可以是XML文件,网页,带有2个编辑框的桌面窗口,无论如何。

您的目标是实现采用这2 Strings的逻辑,并将其转换为ints并显示消息框,说“和为xxx”。

无论您使用什么方法来设计/实现此功能,您都将拥有以下两点内部功能

  1. 在那里你转换的地方String,以int
  2. 加2int秒的地方

我的原始帖子的主要问题是:

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

因此,简要地讲:如果我只有MUST库,如何实现应该的语义


13
这绝不是一个愚蠢的问题。其实这是一个相当不错的!我不确定,我会选择哪个选项。
马丁

1
要更新:是的,按照快速失败原则,这也不一定是一件坏事。
JohanSjöberg

1
我同意马丁的观点。这是Java编程的方面之一,人们对其了解最少,需要一些最佳实践。这可以通过缺少有关该主题的页面来看出,除了奥赖利说“这是应该做的”之外,页面很少。
James P.

2
实际上,这是一个很好的问题。
克里斯·库德莫

1
好问题!还提出了一些很好的答案:D
凯尔达(Kheldar)2011年

Answers:


35

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

恕我直言,如果您已传递垃圾参数,则可以抛出未经检查的异常。

一般而言,您不应该抛出BadDataException异常,因为您不应该使用异常来控制程序流。例外是例外。方法的调用者可以调用之前知道它们的字符串是否为数字,因此可以避免传入垃圾,因此可以将其视为编程错误,这意味着可以抛出未经检查的异常。

关于声明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;
}

编辑
评论者提出了有效的观点。您需要考虑如何使用它以及应用程序的整体设计。

如果该方法将在各处使用,并且所有调用者都应处理问题很重要,则将该方法声明为抛出一个已检查的异常(强制调用者处理问题),但使代码混乱并带有try/catch块。

另一方面,如果我们将此方法与我们信任的数据一起使用,请按上面的说明进行声明,因为它永远不会爆炸,并且您避免了本质上不必要的try/catch块的代码混乱。


6
问题在于,未经检查的异常通常会被忽略,并可能渗透到您的调用堆栈中,从而对应用程序的某些部分造成严重破坏,这些部分根本不知道其背后是什么。问题是,哪一部分代码应该检查输入是否有效。
2011年

1
@G_H有一点。Java教程说:“如果可以合理地期望客户端从异常中恢复,则将其设置为已检查的异常。如果客户端无法采取任何措施从异常中恢复,则将其设置为未检查的异常”。问题是应该在哪里RuntimeException处理?应该是调用方还是应该让Exception浮动起来?如果是这样,应该如何处理?
James P.

1
为了部分回答这个问题,我见过两种方法:第一种是Exceptions从较低层拦截并将它们包装在更高级别的Exception中,这对于最终用户而言更有意义;第二种是使用未捕获的异常处理程序,可以在其中初始化应用程序启动器。
James P.

2
恕我直言,其在Javadoc的NumberFormatException的是多少更重要。将其放在throws子句中是很好的补充,但不是必需的。
Dorus

3
对于“方法的调用者可以在调用之前知道它们的字符串是否为数字,因此可以避免传入垃圾”:这不是真的。验证字符串是否解析为int的唯一简单方法是尝试一下。尽管您可能需要进行一些前期检查,但是精确检查是相当不错的PITA。
maaartinus 2011年

49

我认为最好尽可能处理异常逻辑。因此,我希望签名

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

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

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

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


4
我喜欢这个。它迫使用户遵守合同,并使该方法仅做一件事。
克里斯·库德莫

我不明白这种方法的用法与用户使用a + b的方法有何不同。为什么我们需要这种方法?
斯瓦蒂

4
@Swati:我认为这是一个例子,这是一种简单展示事物的好技术。总和方法也可以是myVeryComplicatedMethodWhichComputesPhoneNumberOfMyIdealMatchGirl(int myID,int myLifeExpectancy)...
Kheldar 2011年

3
完全同意这里。一个sum期望有两个数字的函数应该获得两个数字。获得这些内容的方式与sum功能无关。正确分离您的逻辑。因此,我将此与设计问题相关。
c00kiemon5ter 2011年

5

数字4。如给定的那样,此方法不应将字符串作为参数,而应将整数作为参数。在这种情况下(因为Java包装而不是溢出),不可能发生异常。

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

关于什么比这更清楚 x = sum(a, b)

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

对于选项1-3,您没有定义异常,因为您希望调用者假设您的代码不会失败,而是定义异常以定义在您的方法独特的已知失败条件下会发生的情况。也就是说,如果您有一个方法是另一个对象的包装器,并且它抛出异常,则将其传递。仅当异常对于您的方法而言是唯一的时,您才应引发自定义异常(在示例中为frex,如果sum应该仅返回正结果,则检查该异常并引发异常将是适当的,如果使用java抛出溢出异常而不是包装,那么您将传递该异常,而不是在签名中定义它,重命名它或吃掉它)。

更新以响应问题的更新:

因此,简要地说:如果我只有MUST库,如何实现应该的语义。

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


您在这里有什么想法?即使有sum(int, int)和我也会遇到同样的问题parseIntFromString(String)。为了获得相同的功能,无论哪种情况,我都必须编写一段代码,其中有2个调用parseIntFromString()和1个调用sum()。考虑这种情况,如果这对您有意义-从原始问题的角度来看,我认为没有区别。
2011年

@ loki2302:关键是调用者然后可以确定不是数字的情况下合适的行为。另外,如果他们不进行错误检查,则故障会在分配值的位置更近一步-出于调试目的,距离分配越近,调试越容易。如果您正在执行TDD,则很可能会错过为调用者编写适当的单元测试的机会。基本上,您不想在方法x中提供一个应该是数字的字符串,然后5个类和20个方法调用在尝试将其视为数字时会抛出异常。
jmoreno,2011年

我不明白 您是否要说int sum(String, String)实际上不可能提供接口?
Andrey Agibalov 2011年

1
@ loki2302:给出显示的代码,这是可能的,但不是一个好主意。如果字符串中的数字可能是一个单词,其中“ 2”,“两个”,“ dos”,“ deux”,“ zwo”都应被视为相同,则该签名将是适当的。但实际上,该方法有两个字符串,并将它们视为整数。你为什么要这么做?这是一个坏主意。
jmoreno 2011年

2
@ loki2302:您接受了答案,说通过垃圾可以引发未经检查的异常,但是强类型语言的目的是防止垃圾通过。拥有一个期望字符串始终为整数值的方法只会带来麻烦。甚至Integer.parseInt也不能很好地处理(应该预期的输入异常是不好的,.Net的integer.TryParse方法要好得多,尽管Java缺少out参数使其有些不切实际)。
jmoreno 2011年

3

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

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

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

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

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

决不。更多的工作,更低的速度(除非您期望抛出异常是规则),并且根本没有收获。

4.你的想法?

一个都没有。


2

Nr 4。

我想我根本不会改变方法。我会尝试在上下文中可以从异常中正常恢复业务的上下文中的调用方法或更高版本的堆栈跟踪中使用。

我不确定是否做得过分,所以无法确定其排名第三。



2
  1. 首先,您需要问问自己,我的方法的用户是否需要担心输入错误的数据,还是希望他输入正确的数据(在这种情况下为String)。这种期望也被称为按合同设计。

  2. 和3.是的,您可能应该定义BadDataException或什至最好使用一些诸如NumberFormatException之类的有趣代码,而不必显示标准消息。在方法中捕获NumberFormatException,然后将其与您的消息一起重新抛出,不要忘记包括原始堆栈跟踪。

  3. 这取决于情况,我可能会重新抛出NumberFormatException并附带一些其他信息。并且还必须有一个javadoc解释什么是期望值String a, String b


1

在很大程度上取决于您所处的场景。

情况1.调试代码的总是您,没有其他人和异常不会导致不良的用户体验

抛出默认的NumberFormatException

案例2:代码应具有极大的可维护性和可理解性

定义自己的异常,并在引发异常时添加更多数据以进行调试。

您不需要正则表达式检查,因为它无论如何都会因输入错误而异常。

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

  1. 数字格式异常
  2. 溢出异常
  3. 空异常等...

并分别处理所有这些


1
  1. 您可以这样做,以明确输入错误可能会发生这种情况。它可能会帮助使用您的代码的人记住这种情况。更具体地说,您要清楚地表明您自己没有在代码中处理它,或者返回一些特定的值。当然,JavaDoc也应该明确这一点。
  2. 仅当您要强制调用方处理已检查的异常时。
  3. 这似乎有点过分了。依靠解析来检测错误的输入。

总的来说,未选中NumberFormaException,因为期望提供正确的可解析输入。输入验证是您应该处理的事情。但是,实际上解析输入是最简单的方法。您可以简单地保留您的方法,并在文档中警告期望输入正确,并且任何调用您的函数的人都应在使用前验证两个输入。


1

任何异常行为都应在文档中阐明。要么应该声明该方法在失败的情况下(例如null,通过将返回类型更改为Integer)返回一个特殊值,或者应该使用情况1。如果用户通过其他方法确保正确的字符串,则在方法签名中明确显示它可以让用户忽略它,但是仍然很明显,该方法本身无法处理此类失败。


1

回答您更新的问题。

是的,出现“意外”异常是完全正常的。想一想编程时刚遇到的所有运行时错误。

e.g ArrayIndexOutofBound

对于每个循环,这也是一个常见的意外例外。

ConcurrentModificationException or something like that

1

尽管我同意应允许渗透运行时异常的答案,但从设计和可用性的角度来看,将其包装到IllegalArgumentException中而不是将其作为NumberFormatException抛出将是一个好主意。然后,这使您的方法的契约更加清晰,从而声明由于传递了一个异常参数而导致了一个异常。

关于问题“想像,它不是一个开放源代码框架,由于某种原因而应使用。查看方法的签名并认为-“好,它永远不会抛出”。然后,有一天,您遇到了一个例外是正常的吗?” 方法的javadoc应该始终说明方法的行为(前后约束)。想想说说集合接口的地方,如果不允许使用null,则javadoc会说尽管它永远不是方法签名的一部分,但将抛出null指针异常。


2
NumberFormatException是IllegalArgumentException的子类,因此此包装不添加任何信息。
唐·罗比

这意味着可以按原样渗透异常(在这种情况下,nfe已经是一个非法参数异常),并且可以使用通用处理来处理调用层次结构中某个地方的非法参数。因此,如果这是一个参数由用户传递的示例,则可能存在通用代码,该代码将包装对非法参数的所有处理并通知用户有关该参数的信息。
Scorpion

1

在您谈论良好的Java实践时,我认为它总是更好

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

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

  • 无需将自定义异常声明为“引发”,因为它未经检查。

  • 这样,您就不会违反对未经检查的异常的处理方式,与此同时,代码客户端将很容易理解异常的原因和解决方案。

  • 在Java-doc中正确记录文档也是一个好习惯,对您有很大帮助。


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;

1

Google的“ Guava”库Apache的“ Validator”库比较)实现的输入验证模式怎么样?

以我的经验,在函数的开头验证函数的参数并在适当的地方抛出Exception被认为是一种好习惯。

另外,我认为这个问题在很大程度上与语言无关。此处的“良好做法”适用于所有具有可以接受可能有效或无效参数的函数的语言。


1

我认为您的第一句“相当愚蠢的问题”非常重要。您为什么首先要编写带有该签名的方法?对两个字符串求和甚至有意义吗?如果调用方法要对两个字符串求和,则调用方法有责任确保它们是有效的int并在调用该方法之前进行转换。

在此示例中,如果调用方法无法将两个String转换为int,则可以执行几项操作。这实际上取决于此求和发生在哪一层。我假设String转换将非常接近前端代码(如果正确完成的话),那么情况1最有可能:

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

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代码,要抛出的错误消息...

希望是有用的。

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


1
我们在谈论Java吗?什么Integer.matcherprivate内部变量的方法?缺少()了IFS,丢失大量的;xy不申报,matcher申报两次,...
user85421

确实,卡洛斯(Carlos)急着做到了,正如我所说,无法立即对其进行测试。抱歉。我想展示正则表达式的工作方式。
路易斯

好的,但这不能解决NumberFormatException的问题Integer.matcher.group()Integer.parseInt(matcher.group())
-IMO

0

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

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

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.