文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

什么是好的错误消息? 讨论一下Java系统中的错误码设计

2024-12-02 05:25

关注

听起来还是有点抽象,能否给点代码? 刚好有一个 jdoctor 的项目,作者来自Oracle Labs[1] 样例代码如下:

ProblemBuilder.newBuilder(TestProblemId.ERROR1, StandardSeverity.ERROR, "Hawaiian pizza")
.withLongDescription("Pineapple on pizza would put your relationship with folks you respect at risk.")
.withShortDescription("pineapple on pizza isn't allowed")
.because("the Italian cuisine should be respected")
.documentedAt("https://www.bbc.co.uk/bitesize/articles/z2vftrd")
.addSolution(s -> s.withShortDescription("eat pineapple for desert"))
.addSolution(s -> s.withShortDescription("stop adding pineapple to pizza"));

这里的Problem理解为Error没有问题,核心主要包括以下几个字段:

有了这些具体的字段后,我们理解起来就方便多啦。

二、错误码(Error Code)的设计

各种错误处理上都建议使用错误码,错误码有非常多的优势:唯一性、搜索/统计更方便等,所以我们还是要讨论一下错误码的设计。网上也有不少错误码的设计规范,当然这篇文章也少不了重复造轮子,该设计提供给大家参考,大家自行判断啊,当然也非常欢迎留言指正。

一个错误码通常包含三个部分:

有了上述的规范后,让我们看一下典型的错误编码长什么样子:

我们采用应用名缩写, 组件名或者编码, 状态值,然后以中划线连接起来。中划线比较方便阅读,下划线有时候在显示的时候理解为空格。同时有了标准的HTTP Status Code支持,不用参考文档,你都能猜一个八九不离十。 错误码设计千万不要太复杂,试图将所有的信息都添加进去,当然信息非常全,但是也增加了开发者理解和使用成本,这个可能要做一个取舍,当然我也不是说目前这种一键三连(打赏、点赞加转发)的结构就最合理,你也可以自行调整。有没有做心里研究的同学来说一下,这种三部分组成的方式,是不是最符合人们的认知习惯?如果超过三部分,如4和5,人们能记住和使用的概率是不是就下降的非常多?

还记得前面说的error的context吗?这里error code其实就是启动context的作用,如 UIC-LOGIN-404,错误发生在哪里?错误码帮你定位啦。当时代码想干什么?错误码也说明啦。虽然说错误码不能完全代表错误的上下文,但是其承载的信息已经足够我们帮我们了解当时的上下文啦,所以这里error code就是起着context的作用。目前看来至少error code要比ProblemBuilder.newBuilder(TestProblemId.ERROR1, StandardSeverity.ERROR, "Hawaiian pizza") 中的Hawaiian pizza 作为context更具有说服力,也规范一些。

三、错误消息的编写格式

错误码设计完毕后,我们还不能用错误码+简短消息方式输出错误,不然就出现类似 ORA-00942: table or view does not exist这种情况,你一定会吐槽:"你为何不告诉哪个表或者view?"。所以我们还需要设计一个message格式,能够将错误的context, description, reason, document link, solutions全部包含进来,这样对开发者会比较友好。这里我拟定了一个Message的规范,当然大家可以发表自己的意见啊,如下:

long description(short desc): because/reason --- document link -- solutions

解释一下:

看一个具体的消息格式例子:

APP-100-400=Failed to log in system with email and password(Email login failed): can not find account with email {} --- please refer https://example.com/login/byemail  --- Solutions: 1. check your email  2. check your password

上述的APP-100-400的错误码对应的描述基本覆盖到jdoctor中需要的信息,可以说对一个错误的描述应该非常全啦,而且有一定的格式,也方便后续的日志分析。

四、组装和保存错误码 + Message

有了错误码和message的规范,接下来我们应该如何保存这些信息呢?如果是Java,是不是要创建对应的ErrorEnum,然后是一些POJO?这里个人建议使用properties文件来保存错误码和message的信息。文件名可以直接为ErrorMessages.properties,当然是在某一package下,文件样例如下:

### error messages for your App
APP-100-400=Failed to log in system with email and password(Email login failed): can not find account with email {0} --- please refer https://example.com/login/byemail --- Solutions: 1. check your email 2. check your password
APP-100-401=Failed to log in system with phone and pass(Phone login failed): can not find account with phone {0} --- please refer https://example.com/login/byphone --- Solutions: 1. check your phone 2. check your pass code in SMS

为何要选择properties文件来保存error code和message信息,主要有以下几个原因:

最后最关键的是IDE支持非常友好 , 以Java开发者使用的IntelliJ IDEA来说,对Properties文件的支持可以说是到了极致,如下:

快速查看:鼠标移上去就可以,按下CMD鼠标移上去也可以, Alt+Space也可以,当然点击直接定位就更不用说啦。

直接修改message的值:

总之IntellIJ IDEA对properties文件的支持到了极致,我们也没有理由不考虑开发者体验的问题,到处跳来跳去地找错误码,这种伤害程序员开发体验的事情不能做。 当然JetBrains的其他IDE,WebStorm等都有对proproperties文件编辑支持。

五、代码实现

看起来功能挺酷炫的,是不是这种方式错误管理要介入一个开发包啊?不需要,你只需要10行代码就搞定,如下:

import org.slf4j.helpers.MessageFormatter;

public class AppErrorMessages {
private static final String BUNDLE_FQN = "app.ErrorMessages";
private static final ResourceBundle RESOURCE_BUNDLE = ResourceBundle.getBundle(BUNDLE_FQN, new Locale("en", "US"));
public static String message(@PropertyKey(resourceBundle = BUNDLE_FQN) String key, Object... params) {
if (RESOURCE_BUNDLE.containsKey(key)) {
String value = RESOURCE_BUNDLE.getString(key);
final FormattingTuple tuple = MessageFormatter.arrayFormat(value, params);
return key + " - " + tuple.getMessage();
} else {
return MessageFormatter.arrayFormat(key, params).getMessage();
}
}
}

这样在任何地方如果你要打印错误消息的时候,这样log.info(AppErrorMessages.message("APP-100-400","xxx"));就可以。如果你还有想法和log进行一下Wrapper,如 log.info("APP-100-400","xxx"); ,也没有问题,样例代码如下:

public class ErrorCodeLogger implements Logger {
private Logger delegate;
private static final String BUNDLE_FQN = "app.ErrorMessages";
private static final ResourceBundle RESOURCE_BUNDLE = ResourceBundle.getBundle(BUNDLE_FQN, new Locale("en", "US"));

public ErrorCodeLogger(Logger delegate) {
this.delegate = delegate;
}

@Override
public void trace(@PropertyKey(resourceBundle = BUNDLE_FQN) String msg) {
delegate.trace(RESOURCE_BUNDLE.getString(msg));
}
}

接下来你就可以在log中直接整合error code,非常便捷。上述代码我已经写好,你参考文章末尾的项目地址即可。

最终的日志输出如下:

提醒:这里我们使用了slf4j的MessageFormatter,主要是方便后续的Slf4j的整合,而且slf4j的MessageFormatter比Java的MessageFormat容错和性能上更好一些。

六、FAQ

1. 为何选择3位的HTTP Status Code作为Error的Status Code?

大多数开发者对HTTP Status Code都比较熟悉,所以看到这些code就大致明白什么意思,当然对应用开发者也有严格的要求,你千万别将404解释为内部错误,如数据库连接失败这样的,逆正常思维的事情不要做。HTTP status code归类如下,当然你也可以参考一下 HTTP Status Codes Cheat Sheet[2]。

但是Error Status Code不局限在HTTP Status Code,你也可以参考SMTP, POP3等Status Code,此外你也自行可以选择诸如007,777这样的编码,只要能解释的合理就可以啦。

在日常的生活中,我们会使用一些特殊意义的数字或者和数字谐音,以下是一些友情提醒:

这种有特殊意义的数字或者数字谐音,如520,886,999,95等,如果能使用的恰当非常方便理解或更友好,如透传给用户UIC-REG-200(注册成功),如果调整为UIC-REG-520可能更温馨一些。总的来说使用这些数字要注意场景,当然比较保险的做法就是参考HTTP,SMTP等设计的status code。

2. properties文件存储error code和message,真的比enum和POJO好吗?

就Java和IntelliJ IDEA的支持来看,目前的配合还是比较好的,如i18n,维护成本等,而且这些ErrorMessages.properties也可以提交到中心仓库进行Error Code集中管理,如果是Java Enum+POJO对i18n和集中管理都比较麻烦,而且代码量也比较大,你从上述的jdoctor的problem builder的就可以看出。当然在不同的语言中也未必是绝对的,如在Rust中,由于enum的特性比较丰富,所以在Rust下使用enum来实现error code可能是比较好的选择。

#[derive(Debug)]
enum ErrorMessages {
AppLogin404 {
email: String,
},
AppLogin405(String),
}

impl fmt::Display for ErrorMessages {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
// extract enum name parameter
// output message from java-properties
write!(f, "{:?}", self)
}
}

3. 为何不在Error Code中提供错误级别

不少错误码设计中会添加错误级别,如 RS-001-404-9 这样,最后一位表示错误的严重级别。这样做没有问题,但是也要考虑现实因素,如下:

如果将错误的基本固化到error code中,这个后续你就没法调整啦,你如果调整了错误级别,那就是可能就是另外一个错误码,给统计和理解都会造成问题。我个人是建议错误码中不要包括严重级别这些信息,而是通过外围的文档和描述进行说明,当然你也可以通过诸如 log.info , log.error来确定错误的级别。

4. 能否提供共享库?

由于IntelliJ IDEA并不支持动态的properties文件名称,如果你用动态的properties文件名称,就不能进行代码提示,查找等功能也都不能使用,所以必须是这种 @PropertyKey(resourceBundle = BUNDLE_FQN) 静态的properties文件名方式。就一个Java类,你就受累Copy一下这个Java类,毕竟是一次性的工作,当然你想个性化调整代码也更方便,如和Log4j 2.x或自定也的logging框架整合也简单些。 日志是项目最基本的需求,所以你创建的项目的时候,就把Error Code对应的代码添加到项目模板中,这样项目创建后就自动包含logging和error code的功能。

5. 其他的考量

原文和Reddit上相关的讨论也进行了一些整理和说明:

七、总结

采用error code + 基于properties文件存储error message,这个设计其实就是一个综合的取舍。如果IDEA不能很好地支持properties文件,你看到一个Error Code,不能直接定位到错误的消息,相反还需要跳转来跳转去找对应的消息,那么Enum + POJO可能就是好的选择。此外error code的设计也非常偏向http status code方案,这个也是主要基于大家对HTTP都非常熟悉,基本上就能猜出大概的意思,相反随机编码的数字就没有这方法的优势,要去error code中心再去查找一下,无形中也是浪费开发人员的时间。

最后项目的Demo地址:http://gitlab.alibaba-inc.com/leijuan/java-error-messages-wizard

[1]https://github.com/melix/jdoctor

[2]https://cheatography.com/kstep/cheat-sheets/http-status-codes/

[3]https://www.morling.dev/blog/whats-in-a-good-error-message/

来源:阿里技术内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯