null?对象?异常?到底应该如何返回错误信息
这篇文章记录我的一些思考。在工作了一段时间之后。
问题的核心很简单:到底如何返回错误信息。
学生时代,见到过当时的老师的代码:
- if (foo() == null) {
- }
当然,这位老师是一位比较擅长c/c++的老程序员,所以他的代码其实使用c写的。但是意思和这段代码类似。当时,我很好奇为什么要对一个方法的返回值是不是null进行判断。现在当然很清楚了:在很多win32的API里面,是通过返回值为null来传递“函数调用失败”这一种信息的。
那么,这么做好吗?
我翻看了很多的博客,大致上说这种写法不好的占多数。最重要的理由如下:
- 无法展示更详细的错误信息
- 容易让调用者忽略
这种说法基本还是有道理的。因为返回值为null,确实很明显得存在这两种问题。
对于第一点,其比较关键的影响就是,既然不知道更多信息,也就无法在日志中体现。从而不能针对可能出现的多种异常进行不同的日志打印和处理。曾经也发生过因为缺失了日志打印,导致花很多时间来定位一个问题的情况。尤其是线上环境不比本地随意debug,一个简单的问题经过几次这种“信息丢失”之后,就有可能成为一个“疑案”。
而第二点,更是严重影响系统安全性、稳定性。人都有不小心的时候,如果忘了对某个接口的返回值进行校验,就会导致代码逻辑是按照“这件事已经发生并且正确发生”写的,但是实际执行过程中发生了错误。一方面比较难排查,第二方面是可能导致一个问题在另外的地方爆发出来。
那么是不是绝对不能这么做呢?其实也不尽然。简单来说,如果调用方在系统内部、代码执行逻辑可控、在返回null之前有正确的日志打印、调用方对这个异常确实没有办法处理。满足这几个条件就可以使用返回null。但是还是具有危险性。
这个问题的改进方案是返回一个对象。比如写一个Result对象。
- public class Result {
- private Integer code;
- private String message;
- private Object data;
- // getter and setter
- }
这样就有一个优化:能够返回错误的信息和一些数据。本次方法成功了吗?如果失败需要打印什么信息?如果成功,是不是需要一些内容?
但是这种写法也有问题。
最主要的是其适用性问题。就是说如果很多方法使用同一个Result,能保证都能通过这种方式返回其需要的数据吗?一些特别的方法返回的东西不能够使用一个Object进行转化。(或者说这样做代价不小)
其次,很多代码业务逻辑极其简单,如果强行使用结果类进行封装,反倒有画蛇添足之嫌。
最后,如果要严格使用这个类,代码开发的代价很大。
那么这种做法适合什么场景呢?一句话:对外接口。一方面我们要按照一个统一的规则去返回信息(正确或者错误)。第二方面,很难要求调用方去捕获我们的异常。如果他们忘了捕获,是不是就有可能会造成更严重的情况?所以,这种情况下,我们需要告知调用方失败信息。但是又不能要求对方做出多少改变。
最后一种解决方案是抛异常。
Effective Java里面用一句话解释什么时候需要抛异常,什么时候不需要。“当你对异常什么也做不了的时候,就不要抛异常”(大意,有可能记错了)。
这句话是很有道理的。因为如果你显式地抛出异常,那么调用方就需要去捕获。如果要求对方捕获,可是却对这种异常什么也做不了。那么抛异常是不是就不那么有意义了?
这个地方的关注焦点在于,调用方如何处理这个异常?假如说,调用方只是打印日志,算不算这个异常有用?在最开始的时候,我曾经认为这种情况可以认定为这个异常对调用方没有帮助。而现在我的想法产生了变化。简单来说和具体业务有关。
再进一步来说:如果调用方在得到这个错误信息之后,代码逻辑会改变,就适合抛异常。而如果调用方即使得知了错误信息,也不需要改变代码运行逻辑,就可以更简化得处理。比如这个接口是删除数据。那么在失败的情况下,就必须通过抛异常或者别的方式,告知调用方数据删除失败。否则调用方的代码可能会忽视数据删除失败。这里就不适合返回null,因为返回null的检查不是强制性的,调用方如果没有进行判断,就可能导致后面的逻辑完全错误。
第二种情况就在于对一些缓存的查询。如果失败,或者没查到,抛异常就不再是一个必须的选项了。当然,如果抛异常,能让调用方更清晰地了解为啥没查到缓存,是网络问题,还是参数不正确,或者确实没有这个缓存。这里的重点在于,调用方即使知道了这些信息,恐怕也无法做更多的事,即使知道了是网络问题,也很难通过代码解决。而对于打印日志,这种“没查到缓存”的情况是否有必须在调用端强调其原因,就看具体的业务了。不过我个人觉得,这里的日志打印交给服务提供方比较好,其一是提供方肯定比调用方知道的信息更全。其二是,提供方本来就有责任去维护系统的稳定性。所以对于日志,能做更多。
除了这些理由之外,还有一个点。就是在项目结构的关键层级,是否需要补货所有异常。比如controller层,如果抛出异常,就会直接给用户造成严重影响。当然如果每个函数都简单地try catch住所有异常,则也是不负责任的,这样不利于排查问题。
总结起来,是否抛异常,还是通过什么方式返回错误信息。主要取决于:
- 调用方拿到错误信息能做什么
- 业务上调用方是否需要获取具体的错误信息
- 是否有必要捕获所有异常
null?对象?异常?到底应该如何返回错误信息的更多相关文章
- SpringBoot+SpringSecurity+Thymeleaf认证失败返回错误信息踩坑记录
Spring boot +Spring Security + Thymeleaf认证失败返回错误信息踩坑记录 步入8102年,现在企业开发追求快速,Springboot以多种优秀特性引领潮流,在众多使 ...
- 发生了Post错误:错误代码40005,微信返回错误信息:invalid file type
给客户部署 PxxCms, 使用群发功能发送图文的的时候提示: 发生了Post错误:错误代码40005,微信返回错误信息:invalid file type, 没学过php伤不起 ... Google ...
- 使用jQuery异步传递Model到控制器方法,并异步返回错误信息
需要通过jquery传递到控制器方法的Model为: public class Person { public string Name { get; set; } public int Age { g ...
- API返回错误信息的最佳实践
使用HTTP Status区分不同消息返回 最基础的三个状态200 OK, 400 Client Error, 500 Server Error 这些应该是够的, 如果客户端可以处理更细的划分, 可以 ...
- Spring中抛出异常时,既要要返回错误信息,还要做事务回滚
情况一:如果没有在程序中手动捕获异常,如下代码事务会回滚 情况二:如果在程序中自已捕获异常未往外抛,如下代码事务不会回滚 如果doDbStuff2()这个操作数据库的方法抛出异常,因为将异常捕获未往外 ...
- HttpWebRequest抓取网页数据返回异常:远程服务器返回错误: (503) 服务器不可用
解决方法: HttpWebRequest request = (HttpWebRequest)WebRequest.Create(webURL); //声明一个H ...
- Spring boot +Spring Security + Thymeleaf 认证失败返回错误信息
[Please make sure to select the branch corresponding to the version of Thymeleaf you are using] Stat ...
- 设置RobotFramework的ftplibrary中,将Upload_file操作的异常改为回显错误信息。
测试中需要通过FTP通道,将数据发送给服务器,而这个上传的数据要被阻断.在结合RobotFramework测试中,安装的ftplibrary,使用upload_file操作,如果上传动作失败,会抛出异 ...
- asp.net web api 向客户端返回错误信息
1使用Http状态码 ASP.NET Web Api框架提供了Http状态码的值,如下图所示. 虽然有这些预定义的状态码,但在实际项目中使用自定状态码结合预定义状态码更有优势. 通过在适当的位置抛出异 ...
随机推荐
- python的元组数据类型及常用操作
Python的元组与列表类似,不同之处在于元组的元素不能修改. 元组使用小括号,列表使用方括号. 元组创建很简单,只需要在括号中添加元素,并使用逗号隔开即可. 如下实例: tup1 = ('physi ...
- java程序执行命令行,解锁数据库表
有些表锁的时间长或其他原因,在plsql中不能解锁,只能用命令行解锁. 有些功能跨平台系统的交互偶尔会锁表,就需要自动解锁. 下面是解锁的代码: package com.lg.BreakOracleU ...
- java 时间转换去杠
public static String minusHyphen(String dateParam){ if(dateParam ==null) return null; if(dateParam.i ...
- 【linux基于Postfix和Dovecot邮件系统的搭建】
一:PostFixe和Dovecot的简单介绍 Postfix postfix是Wietse Venema在IBM的GPL协议之下开发的MTA(邮件传输代理)软件.postfix是Wietse Ven ...
- Layabox进阶之资源加载
资源加载失败,图片资源默认类型是image 如果是sprite可能找不到. 资源的加载顺序,场景被加载出来时,要判断该场景的资源是否都已经加载到. 点击A界面弹出来B界面,A界面的资源要在B界面之前加 ...
- C语言实现简易扫雷
首先,写代码之前要将整体思路写出来: 扫雷游戏:1.需要两个二维数组,一个用来展示,一个用来放雷; 2.整体骨架在代码中都有注释说明; 3.游戏难度比较简单,适合初学者观看,如果有大佬看明白,可以指点 ...
- linux-课题练习1
1.创建组testgroup: 2.创建用户a2012,先采用默认设置创建,然后使该用户加入testgroup组. 3.创建用户a2013,其用户主目录为/tmp/a2013,其主组为testgrou ...
- (数据科学学习手札32)Python中re模块的详细介绍
一.简介 关于正则表达式,我在前一篇(数据科学学习手札31)中已经做了详细介绍,本篇将对Python中自带模块re的常用功能进行总结: re作为Python中专为正则表达式相关功能做出支持的模块,提供 ...
- LeetCode高频题目(100)汇总-Java实现
LeetCode高频题目(100)汇总-Java实现 LeetCode高频题目(100)汇总-Java实现 目录 第01-50题 [Leetcode-easy-1] Two Sum [Le ...
- c++返回引用
#include <iostream> #include <ctime> using namespace std; double vals[] = {10.1, 12.6, 3 ...