在期待异常时避免使用空捕获块

时间:2016-07-19 20:41:09

标签: java exception try-catch date-format control-flow

我正在尝试使用SimpleDateFormat解析日期。由于我的服务采用多种日期格式,因此我采用了this方法:

String[] formats = {
        "yyyy-MM-dd'T'HH:mm:ss.SSSZ",
        "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'",
        "yyyy-MM-dd'T'HH:mm:ss.SSS-HH:mm",
        "EEE MMM dd HH:mm:ss Z yyyy"};

for (String format : formats)
{
    try
    {
        return new SimpleDateFormat(format).parse(dateString);
    }
    catch (ParseException e) {}
}
return null;

try-catch背后的基本原理是,如果当前日期格式无法解析dateString,则会抛出Exception并且代码将继续循环直到合适的日期格式找到,或返回null

catch块就在那里,所以try块会跟随它(如果try之后没有任何内容,则可能发生编译错误)。

我可以保留代码,但空catch块是不好的做法。将来维持其他人也会感到困惑。它简直不优雅。

我可以在catch块中添加以下内容:

catch (Exception e)
{
    if (!(e instanceof ParseException))
    {
        throw e;
    }
}

但同样,内部代码没有任何意义,因为没有Exception其他ParseException可以被try块抛出(我先前检查NullPointerException码)。使用final块而不是catch块也没用。

有没有办法避免空的或无用的catch阻止?可以完全避免try-catch吗?

类似的问题(但不完全):

Avoiding an empty catch clause

Empty catch blocks

Is it ever ok to have an empty catch statement?

7 个答案:

答案 0 :(得分:2)

你的代码很好。在这种情况下,SimpleDateFormat抛出ParseException时不采取任何行动是有意和合理的。我唯一要做的就是为这种效果插入纪录片评论:

for (String format : formats)
{
    try
    {
        return new SimpleDateFormat(format).parse(dateString);
    }
    catch (ParseException e) {
        // The string does not conform to the trial format.
        // Just try the next format, if any.
    }
}

使用空的catch块来避免处理应该处理的异常是一种不好的形式。这不是你正在做的事情 - 你的正确处理是不寻常的情况。

答案 1 :(得分:2)

到目前为止给出的所有答案都说你必须忍受异常捕获,但有办法避免异常。我演示了两种方法,一种是内置SimpleDateFormat - API,另一种是使用我的库Time4J

的SimpleDateFormat

private static final List<SimpleDateFormat> SDF_FORMATS;

static {
    String[] formats =
        {
               "yyyy-MM-dd'T'HH:mm:ss.SSSX", 
               "yyyy-MM-dd'T'HH:mm:ss.SSS-HH:mm",
               "EEE MMM dd HH:mm:ss Z yyyy"
        };

    SDF_FORMATS = 
        Arrays.stream(formats)
            .map(pattern -> new SimpleDateFormat(pattern, Locale.ENGLISH))
            .collect(Collectors.toList());
}

public static java.util.Date parse(String input) {
  for (SimpleDateFormat sdf : SDF_FORMATS) {
    ParsePosition pos = new ParsePosition(0);
    java.util.Date d = sdf.parse(input, pos);
    if (pos.getErrorIndex() == -1) {
        return d;
    }
  }
  // log an error message
  return null; // or throw an exception
}

与try-catch-code相比,虽然不是非常引人注目,但可观察到性能提升。但是,一个重要的警告是,此代码是非线程安全。要在多线程环境中使用,您必须始终实例化SimpleDateFormat的新实例,或者您可以尝试使用ThreadLocal来最小化此类实例化。

Time4J

private static final MultiFormatParser<Moment> MULTI_FORMAT_PARSER;

static {
    String[] formats =
        {
               "yyyy-MM-dd'T'HH:mm:ss.SSSX", 
               "yyyy-MM-dd'T'HH:mm:ss.SSS-HH:mm",
               "EEE MMM dd HH:mm:ss Z yyyy"
        };

    List<ChronoFormatter<Moment>> formatters = 
        Arrays.stream(formats)
            .map(pattern -> 
                ChronoFormatter.ofMomentPattern(
                    pattern,
                    PatternType.CLDR,
                    Locale.ENGLISH,
                    Timezone.ofSystem().getID()))
            .collect(Collectors.toList());
    MULTI_FORMAT_PARSER = MultiFormatParser.of(formatters);
}

public static java.util.Date parse(String input) {
      ParseLog plog = new ParseLog();
      Moment m = MULTI_FORMAT_PARSER.parse(input, plog);
      if (plog.isError()) {
         // log an error message based on plog.getErrorMessage()
         return null; // or throw an exception
      } else {
         return TemporalType.JAVA_UTIL_DATE.from(m); // converted to old API
      }
}

这种方法是迄今为止解析多种格式的最快方法。自己尝试一下(也可以使用版本行3.x在Java-6或Android上使用Time4J,但是你必须在静态初始化程序中调整Java-8-streaming代码)。性能方面的改进是巨大的。而且代码也是线程安全的。

关于格式模式的一般评论

  • 我很担心看到“yyyy-MM-dd'T'HH:mm:ss.SSS-hh:mm”的模式,因为“h”代表12小时制(所以AM / PM丢失! !!)。
  • 我也担心看到“yyyy-MM-dd'T'HH:mm:ss.SSS'Z'”模式,因为除非你明确设置GMT-Zone,否则在输入中转义字面“Z”是错误的SimpleDateFormat - 实例上的(零偏移)。背景:ISO-8601定义了这样的模式,并始终将偏移UTC + 00:00分配给文字“Z”。通过转义,您将获得基于错误计算的结果(无例外或警告)。

答案 2 :(得分:1)

如果你想避免try / catch块,它是可能的,但它肯定会增加代码的大小。对于没有注释的空catch块,我遇到的最大问题是它们留下了&#34;我的意思是//做TODO:检查是否有例外?&#34;代码气味真的只适用于IMO,如果这样做是没有意义的,即如果你正在解析某事是否是数字,而不是使用isNumber方法。

您可以创建一个显式方法,用于检查是否可以解析它,然后返回值,如果它是可解析的。

boolean isParseable(Sting dateString, SimpleDateFormat format) {
    try {
        format.parse(dateString);
        return true;
    }
    catch(ParseException e) {
        return false;
    }
}

然后申请

for (String format : formats)
{
    SimpleDateFormat format = new SimpleDateFormat(format);
    if(isParseable(dateString, format))
        return format.parse(dateString);
}
return null;

根据您希望如何处理SDF对象,您可以选择将其实例化两次,传递它,或传回null / String值。

答案 3 :(得分:1)

如果您通过尝试一种接一种格式来格式化日期,那么合理的是您会有一堆例外,传播它们将无益,即使看到它们在大多数情况下都没有帮助。我要做的唯一改变是在DEBUG或TRACE级别记录异常(设置在你开发代码中运行代码时正常记录的位置下面),以防万一你想回来检查一下正在发生的事情只需要更改日志记录配置。

for (String format : formats)
{
    try
    {
        return new SimpleDateFormat(format).parse(dateString);
    }
    catch (ParseException e) {
        if (log.isTrace()) {
            log.trace("formatting failed for " + dateString 
            + " using format " + format, e);
        }
    }
}
return null;

答案 4 :(得分:0)

可以这样做。但在我看来(许多其他专业软件开发人员共享),空捕获块是一种反模式。您在代码中遇到了例外案例;为什么忽略它?这意味着您输入错误/无法使用。值得注意一些,你不同意吗?

所以,,你可以这样做。但实际上,真的问自己,你想要吗?或者你宁愿用糟糕的输入做一些更聪明的事情,而不是默默地扔掉它。让我给你一些建议:做你自己(以及其他任何使用你的代码的人)至少记录特殊数据的礼貌。不要记录异常;尝试在日志语句中捕获应用程序状态的错误/异常。在您的情况下,我建议记录失败日期字符串的内容。

最后,如果你想纯粹省略catch-block ......那么,你可以直接忽略异常。您可以在方法原型中包含throws Exception声明。但即便如此,使用您的API的任何人都必须抓住它。这只是检查异常如何在Java中工作的一部分。

答案 5 :(得分:0)

您说您不喜欢SimpleDateFormat提供的界面。您唯一的选择是将该接口包装在另一个提供您正在寻找的接口的接口中。如,

class MySimpleDateFormat {
  final String format;

  MySimpleDateFormat(format f) {
    this.format = f;
  }

  String format() {
    try {
      new SimpleDateFormat(format).format();
    } catch (Exception e) {
      return f.format();
    }
    return null;
  }
}

答案 6 :(得分:0)

我会使用类似org.apache.commons.lang3.time.DateUtils.parseDate(或者如果你需要匹配整个模式的parseDateStrictly)。它需要一个可能的模式列表。看一下这个例子:

Car

查看此文档:http://commons.apache.org/proper/commons-lang/javadocs/api-release/org/apache/commons/lang3/time/DateUtils.html

如果所有模式都不适用于输入,则会出现异常。空挡块是一种不好的做法。您可以通过记录错误或抛出错误或执行其他操作来处理异常。