接口返回值一定不允许使用枚举类型吗?
引言
在这一周的工作中
碰到一个业务场景,需要叫旁边的同事,李同学开了一个接口,发现给我的返回值中带了枚举类型的变量,我顺口提了一嘴,这种接口返回值最好不要用这种包含枚举类型的对象
李同学问我为什么,我就直接说了,在《Java 开发手册》中强制规定二方库里可以定义枚举类型,参数可以使用枚举类型,但是接口返回值不允许使用枚举类型或者包含枚举类型的 POJO 对象
但是,事后想了一想,孤尽老师在《手册》中写的的这条规定一定正确吗?
正文
杨小帅:不懂同学,你这个接口咋回事,序列化异常了?
不懂尽管心里慌的一批,但是表面仍然装作稳得一笔:不是吧,肯定是你调用的方式有误、你自己在回去检查检查、我都没动这个接口
不懂自信的三连击,让小帅疑惑的回到了座位
此时,不懂连忙打开 idea “我就改了一个接口的枚举值,咋会反序列化异常,不对,我记得手册中讲过 接口返回值不允许使用枚举类型或者包含枚举类型的 POJO 对象,完蛋完蛋,写代码的时候咋忘记了呢,让我快改回去,只要我改的够快,就没人发现我的 bug“
此时杨小帅已经悄悄的站在不懂的工位后,看见了不懂在改枚举,“不懂就是你,这个规范你都能忘记,你咋回事”
不懂略显羞涩的笑了一下:“失误,失误,可控,可控”
杨小帅:“像这种二方库接口返回值记得多操个心”
此时小美凑了过来:“啥是二方库?”
杨小帅甩了甩头发:二方库也称作二方包,一般指公司内部发布到中央仓库,可供公司内部其他应用依赖的库(jar 包);一方库是本工程内部子项目模块依赖的库;三方库为公司之外的开源库,比如像 fastjson、easyexcel 这种
小美:为啥这种接口返回值不允许使用枚举类型或者包含枚举类型的 POJO 对象?
杨小帅:是这样的,不懂改了枚举,就导致我们双方的枚举不同,在接口解析中就出现了,反序列化时出现异常
比如:你的本地枚举类,有一个天气 Enum:SUNNY, RAINY, CLOUDY,如果根据天气计算心情的方法:guess(WeatcherEnum xx),传入这三个值都是可以的
返回值为 Weather guess(参数),那么对方运算后,返回一个 SNOWY,本地枚举里没有这个值,就异常了
小美点了点头,原来是这样啊
此时,王经理走了过来,向大家问到:这条规范一定合适吗?
小美:我觉得小帅说的很有道理啊,接口返回值使用枚举的确会造成这样的异常呀
王经理:大家觉得枚举存在的意义是什么?
不懂:这题我会,就是顾名思义,见到就是知道这个字段是什么意思,不像 String 类型,假设传个 1、2、3,不知道传的啥意思
王经理:说的有道理,枚举,就是把已知的全部罗列出来。作为二方/三方库的提供者,我支持什么,你们就是用什么,这样是安全的。库版本升级后我支持了更多,你不知道情况下自然不会使用,反正我不支持的参数你不可能传递给我,所以作为输入,枚举简直就是安全保障,但作为返回值,情况就反过来了。我先告诉你这些这些可以有,然后你规定这些这些可以有,除此之外都没有。但是,是我说了算而不是你,所以你的规定狗屁不是。提供者偷偷升级,抛异常的可能性直趋百分百
那我们碰到,像不懂这样的坑货,偷偷升级怎么办,这个接口调用方少的可以通知同步升级,那么,要是一个接口有百十个调用方呢,可以做到同步升级吗?
那么可不可以在我们调用的时候,加强我们框架的健壮性,如果发序列化时发现了新的枚举值,则这个字段设置为 null,就像 Apache Thrift 代码里面写的那样?
王经理:好,今天就讲这些,你们思考思考
小美、不懂、小帅陷入了思考中.....
总结
其实呢
在二方库返回的接口中,如果该枚举的值是必须使用到枚举内部定义的方法和属性的,那么配置无法转换时就报错的策略
如果该枚举在业务中可以只处理认识的枚举,不认识的枚举不进行处理的,就配置转换为 null 的策略
如果该枚举在业务中不认识时当作某个默认值处理,就配置转换为默认值的策略
如果该枚举在本地业务根本没有使用,并不关心,那么可以在定义接收对象时直接定义为 int 或者 String 类型.
这样在整体序列化框架中进行自定义枚举类的转换对安全检查和扩展性的兼容性更加好,既可以兼容到枚举的好处,也可以健壮我们的 RPC 框架,那么能在顶层做的事情,是不是可以改良下,让开发者更舒服呢?
当然一切的出发点,还是基于我们目前的一个业务场景和实际情况来的,使用的时候可以多多思考
附上孤尽老师关于这条规定的回答
由于升级原因,导致双方的枚举类不尽相同,在接口解析,类反序列化时出现异常。Java 中出现的任何元素,在 Gosling 的角度都会有背后的思考和逻辑(尽管并非绝对完美,但 Java 的顶层抽象已经是天才级了),比如:接口、抽象类、注解、和本文提到的枚举。枚举有好处,类型安全,清晰直接,还可以使用等号来判断,也可以用在 switch 中。它的劣势也是明显的,就是不要扩展。可是为什么在返回值和参数进行了区分呢,如果不兼容,那么两个都有问题,怎么允许参数可以有枚举。当时的考虑,如果参数也不能用,那么枚举几乎无用武之地了。参数输出,毕竟是本地决定的,你本地有的,传送过去,向前兼容是不会有问题的。但如果是接口返回,就比较恶心了,因为解析回来的这个枚举值,可能本地还没有,这时就会抛出序列化异常。比如:你的本地枚举类,有一个天气 Enum:SUNNY, RAINY, CLOUDY,如果根据天气计算心情的方法:guess(WeatcherEnum xx),传入这三个值都是可以的。返回值:Weather guess(参数),那么对方运算后,返回一个 SNOWY,本地枚举里没有这个值,傻眼了。
版权声明: 本文为 InfoQ 作者【skow】的原创文章。
原文链接:【http://xie.infoq.cn/article/5d7dbb6b8864aa1e05e83e6e8】。
本文遵守【CC-BY 4.0】协议,转载请保留原文出处及本版权声明。
评论