预期异常时避免空 catch 块

Avoid Empty Catch Blocks When Expecting Exception

我正在尝试使用 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,并且代码会继续循环,直到出现合适的找到日期格式,或 return 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?

可以做到这一点。但在我看来(许多其他专业软件开发人员也有同感),空的 catch 块是一种反模式。您在代码中遇到了 exceptional 情况;为什么忽略它?这意味着您得到了 bad/unusable 输入。这值得 一些 关注,你同意吗?

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

最后,如果你想完全省略 catch 块......好吧,你不能完全忽略 异常。您可以在方法原型中包含一个 throws Exception 声明。但即便如此,任何使用你的 API 的人都必须抓住它。这只是检查异常在 Java.

中工作方式的一部分

你的代码没问题。在这种情况下,当 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 块来避免处理应该处理的异常是一种错误的形式。那不是你正在做的——你的是不寻常的情况,在这种情况下正确的处理是什么都不做。

如果你想避免try/catch块,这是可能的,但它肯定会增加代码的大小。对于未注释的空 catch 块,我遇到的最大问题是它们留下了 "Did I mean to do //TODO: check for exceptions or not?" Code smell 的问题,IMO,如果这样做没有意义,即如果你正在解析到查看某物是否为数字,而不是使用 isNumber 方法。

您可以创建一个明确的方法来检查它是否可以被解析,然后 return 如果它是可解析的值。

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 值。

你是说你不喜欢 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;
  }
}

当您通过尝试一种又一种格式来格式化日期时,您会遇到一堆异常是合理的,传播它们将无济于事,即使在大多数情况下看到它们也无济于事。我要做的唯一更改是在 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;

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

简单日期格式

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 -静态初始化程序中的流代码)。性能方面的改进是巨大的。而且代码也是线程安全的。

关于格式模式的一般说明

  • 我担心看到模式 "yyyy-MM-dd'T'HH:mm:ss.SSS-hh:mm" 因为 "h" 代表 12 小时制(所以 AM/PM 不见了!!!)。
  • 我也担心看到模式 "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'",因为在输入中转义文字 "Z" 是错误的,除非您在 SimpleDateFormat 上明确设置 GMT 时区(零偏移) -实例。背景:ISO-8601 定义了这样一种模式,并且始终将偏移量 UTC+00:00 分配给文字 "Z"。通过转义,您将得到基于错误计算的结果(没有异常或警告)。

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

Date d = DateUtils.parseDate("23/10/2014T12:34:22", 
        new String[] {"yyyy/MM/dd'T'HH:mm:ss",
            "dd/MM/yyyy'T'HH:mm:ss"});

System.out.println(d);

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

如果 none 个模式应用于输入,您将得到一个异常。空的 catch 块是一种不好的做法。您应该通过记录错误或抛出错误或执行其他操作来处理异常。