Java业务开发常见错误100例
朱晔
贝壳金服资深架构师
立即订阅
7174 人已学习
课程目录
已完结 45 讲
0/4登录后,你可以任选4讲全文学习。
开篇词 (1讲)
开篇词 | 业务代码真的会有这么多坑?
免费
代码篇 (20讲)
01 | 使用了并发工具类库,线程安全就高枕无忧了吗?
02 | 代码加锁:不要让“锁”事成为烦心事
03 | 线程池:业务代码最常用也最容易犯错的组件
04 | 连接池:别让连接池帮了倒忙
05 | HTTP调用:你考虑到超时、重试、并发了吗?
06 | 20%的业务代码的Spring声明式事务,可能都没处理正确
07 | 数据库索引:索引并不是万能药
08 | 判等问题:程序里如何确定你就是你?
09 | 数值计算:注意精度、舍入和溢出问题
10 | 集合类:坑满地的List列表操作
11 | 空值处理:分不清楚的null和恼人的空指针
12 | 异常处理:别让自己在出问题的时候变为瞎子
13 | 日志:日志记录真没你想象的那么简单
14 | 文件IO:实现高效正确的文件读写并非易事
15 | 序列化:一来一回你还是原来的你吗?
16 | 用好Java 8的日期时间类,少踩一些“老三样”的坑
17 | 别以为“自动挡”就不可能出现OOM
18 | 当反射、注解和泛型遇到OOP时,会有哪些坑?
19 | Spring框架:IoC和AOP是扩展的核心
20 | Spring框架:框架帮我们做了很多工作也带来了复杂度
设计篇 (6讲)
21 | 代码重复:搞定代码重复的三个绝招
22 | 接口设计:系统间对话的语言,一定要统一
23 | 缓存设计:缓存可以锦上添花也可以落井下石
24 | 业务代码写完,就意味着生产就绪了?
25 | 异步处理好用,但非常容易用错
26 | 数据存储:NoSQL与RDBMS如何取长补短、相辅相成?
安全篇 (4讲)
27 | 数据源头:任何客户端的东西都不可信任
28 | 安全兜底:涉及钱时,必须考虑防刷、限量和防重
29 | 数据和代码:数据就是数据,代码就是代码
30 | 如何正确保存和传输敏感数据?
不定期加餐 (6讲)
加餐1 | 带你吃透课程中Java 8的那些重要知识点(一)
加餐2 | 带你吃透课程中Java 8的那些重要知识点(二)
加餐3 | 定位应用问题,排错套路很重要
加餐4 | 分析定位Java问题,一定要用好这些工具(一)
加餐5 | 分析定位Java问题,一定要用好这些工具(二)
加餐6 | 这15年来,我是如何在工作中学习技术和英语的?
结束语 (3讲)
结束语 | 写代码时,如何才能尽量避免踩坑?
结课测试 | 关于Java业务开发的100个常见错误,你都明白其中缘由了吗?
结课问卷获奖用户名单
答疑篇 (5讲)
答疑篇:代码篇思考题集锦(一)
答疑篇:代码篇思考题集锦(二)
答疑篇:代码篇思考题集锦(三)
答疑篇:设计篇思考题答案合集
答疑篇:安全篇思考题答案合集
Java业务开发常见错误100例
15
15
1.0x
00:00/00:00
登录|注册

22 | 接口设计:系统间对话的语言,一定要统一

朱晔 2020-05-05
你好,我是朱晔。今天,我要和你分享的主题是,在做接口设计时一定要确保系统之间对话的语言是统一的。
我们知道,开发一个服务的第一步就是设计接口。接口的设计需要考虑的点非常多,比如接口的命名、参数列表、包装结构体、接口粒度、版本策略、幂等性实现、同步异步处理方式等。
这其中,和接口设计相关比较重要的点有三个,分别是包装结构体、版本策略、同步异步处理方式。今天,我就通过我遇到的实际案例,和你一起看看因为接口设计思路和调用方理解不一致所导致的问题,以及相关的实践经验。

接口的响应要明确表示接口的处理结果

我曾遇到过一个处理收单的收单中心项目,下单接口返回的响应体中,包含了 success、code、info、message 等属性,以及二级嵌套对象 data 结构体。在对项目进行重构的时候,我们发现真的是无从入手,接口缺少文档,代码一有改动就出错。
有时候,下单操作的响应结果是这样的:success 是 true、message 是 OK,貌似代表下单成功了;但 info 里却提示订单存在风险,code 是一个 5001 的错误码,data 中能看到订单状态是 Cancelled,订单 ID 是 -1,好像又说明没有下单成功。
{
"success": true,
"code": 5001,
"info": "Risk order detected",
"message": "OK",
"data": {
"orderStatus": "Cancelled",
"orderId": -1
}
}
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《Java业务开发常见错误100例》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(14)

  • 梦倚栏杆
    1.第一个问题:可以参考http状态码的实现方式,一类错误以相同的数字前缀开头。我们实践的并不好(业务系统):透传上游错误码;或者是改编一下调用哪个服务哪个接口失败;理由:不管哪出了问题,都是端上的同学的先排查问题,为了节省时间,从错误信息可以方便的定位到底是哪出了问题。
    2.第二个问题:可维护性怎么办。我们当前使用的是spring MVC 或者jersey框架开发接口,IDEA都有现成的插件可以直接看接口。这种方式想知道完整的接口还需要了解apiVersion实现逻辑。可能会有人说看文档,但是文档和代码的一致性怎么保证呢

    作者回复: 1. 可以开发一个独立的错误码服务,实现转码、分类、收敛逻辑,甚至可以开发后台,让产品来录入需要的错误码提示消息。阿里的Java开发手册中也有提到相关错误码的一些推荐的最佳实践。
    2. 这确实是一个问题,所以框架如果做的好的话不仅仅要扩展功能部分还要打通文档,比如如果使用springdoc来什生成OpenAPI文档,那么直接把那部分生成URL的逻辑也改了

    2020-05-06
    3
    6
  • Darren
    一、其实有一定的作用,其实可以根据不同分类,前端其实在处理的时候,可以维护一个map,key时错误码的前1位或者2位,根据具体的业务,value时该状态码对应的处理器,就是工厂模式,这样的话其实不用处理if else的问题。
    二、第二个问题回答下“梦倚栏杆”童鞋的问题吧,其实自定义RequestMappingHandlerMapping的时候,可以把之前的也注册到registerHandlerMethod中,也就是说如果不写@APIVersion注解的话,不带版本号和带v1版本都是可以的匹配到的,若是真的有一天某个接口需要支持多版本,那么开发童鞋也就不得不看@APIVersion的说明文档了,其实也不算解决方式,算是稍规避了下吧;我们之前在使用swagger2的时候,好像swagger2生成的接口是默认带服务民称的,但是本地测试的时候其实是不需要的,导致请求不到,我们就自定义RequestMappingHandlerMapping,把带服务名和不带服务名的都加到mapping中。
    2020-05-06
    2
  • //null
    自动包装外层APIResposne响应的那边有一个问题,刚好我在做的时候遇到了,如果控制器返回的是String类型,那么返回APIResponse 对象会出现转换错误,因为我们在控制器返回的是String类型 springmvc会使用 StringHttpMessageConverter 转换器,这时候会报转换错误。

    作者回复: 是的,把MappingJackson2HttpMessageConverter放在最前面即可,或者在ResponseBodyAdvice里面判断类型,特殊处理

    2020-06-05
    1
  • 👽
    第一个问题我的解决是控制为更少的错误代码
    调用数据库是一种,调用服务失败是一种,业务层错误是一种
    返回到客户端的时候,客户端之判断错误类型是哪一类,从而做相应的处理。并不告知客户端详细的错误内容。详细的错误内容应该由后端工程师来把控。

    作者回复: 服务端需要做错误码归类收敛,特别是应该转换下游服务多少异常,否则客户端会不知所措。我们之前尝试过服务端驱动的方式,让服务端告知客户端如何处理,说白了客户端只需要照做即可,不需要感知错误码的含义,即便客户端显示错误码也只是用于排错,下面是相关介绍:

    说白了这种模式,就是在API的返回结果中包含驱动客户端去怎么做的信息,两个层次:

    1、交互驱动:比如包含actionType和actionInfo,actionType可以是toast、alert、redirectView、redirectWebView等,actionInfo就是toast的信息、alert的信息、redirect的URL等。由服务端来明确客户端在请求API后的交互行为的好处是:
    灵活:在紧急的时候还可以通过redirect方式进行救急,比如遇到特殊情况需要紧急进行逻辑修改可以直接在不发版的情况下切换到H5实现,甚至我们可以提供后台让产品或运营来配置交互的方式和信息
    统一:有的时候会遇到不同的客户端,iOS、Android、前端对于交互的实现不统一的情况,如果API结果可以规定这部分内容可以彻底避免这个问题
    2、行为驱动:更深一层的服务端驱动,可以实现一套API作为入口,让客户端进行调用,然后通过约定一套DSL告知客户端应该呈现什么,干什么。
    之前有两个这样的项目采用了类似的API设计方式:

    贷款审核:我们知道贷款的信用审核逻辑往往会变动比较大,还涉及到客户端的一些授权(比如运营商爬虫),而且App的发布更新往往比较困难(苹果App Store以及安卓各大应用商店的审核问题)。如果采用服务端驱动的架构来告知客户端接下去应该呈现什么界面做什么,那么会有很大的灵活性。
    客户端爬虫:我们知道如果采用服务端做爬虫很多时候因为IP的问题会被封,所以需要找很多代理。某项目我们想出了客户端共享代理的概念,使用手机客户端来做分布式代理,由服务端驱动调度所有的客户端,那么这个时候客户端需要听从服务端的指示来做请求然后上报响应。
    一般而言,对外的Web API是不会采用这种服务端驱动客户端的方式来设计API的。对于某些特殊类型的项目,我们可以考虑采用这种服务端驱动的方式来设计API,让客户端变为一个不含逻辑的执行者,执行的是UI和交互。

    2020-05-12
    1
  • Seven.Lin澤耿
    其实,我不是很认同用数字作为错误码,为何不用单词来做呢?就跟单元测试test1、test2一样,没什么意义,直接用类似`USER_NOT_EXIST`类似的不可以吗?

    作者回复: 引用自《阿里Java开发手册泰山版》
    12.【参考】错误码尽量有利于不同文化背景的开发者进行交流与代码协作。
    说明:英文单词形式的错误码不利于非英语母语国家(如阿拉伯语、希伯来语、俄罗斯语等)之间的开发
    者互相协作。

    2020-05-11
    1
    1
  • Demon.Lee
    1. 我们有一个code的字典表,就是excel中列出来,每新增一个,就在里面加一个,前端根据这个表格在代码中实现字典表查询,然后用友好的提示展示给用户。
    2. 回头补代码。

    有个疑问, “特殊情况下,比如收单服务内部处理不当,或是订单服务出现了额外的状态,虽然 success 为 true,但订单实际状态不是 Created,这时可以给予友好的错误提示。”
    -------我们没有这么干,我一时也有点接受不了,只要有错误,success肯定是false,所以想问问老师,有具体的案例可以分享下么,谢谢。其他小伙伴,你们呢,也是这么干么。

    作者回复: 特殊情况下,比如收单服务内部处理不当,或是订单服务出现了额外的状态,虽然 success 为 true,但订单实际状态不是 Created,这时可以给予友好的错误提示。》》这段我是指客户端的处理逻辑,客户端需要考虑到各种服务端响应的可能。

    2020-05-05
    1
    1
  • mickey
    请问老师,异步处理的结果应该不能保存在私有的HashMap里吧?因为后续的状态查询接口是新的一个线程,访问不到这个hashmap啊。

    private ConcurrentHashMap<String, SyncQueryUploadTaskResponse> downloadUrl

    作者回复: 可以访问到,又不是ThreadLocal的

    2020-07-16
    1
  • Asha
    老师和同学们,对于api接口的异常来说,可以使用老师的@RestControllerAdvice和@ExceptionHandler 和ResponseBodyAdvice的结合节省很多工作,那对于基于servlet filter的这种里面的异常比如token过期如何处理呢?多谢。
    2020-07-08
  • FelixFly
    1. 错误代码是需要的,方便快速定位问题以及有可能需要根据错误码进行相应的业务处理,若是给前端的话,一般有个错误类型,前端用于怎么展示这个信息,这个错误信息弄个统一的维护界面,数据放到缓存。
    2. 这个统一的配置是个好的方法,我们还是用最原始的方法,在每个url上进行定义,这样会出现配置错误的可能
    2020-05-12
  • Wiggle Wiggle
    对于同步接口,以文中的图片上传为例,假设实际上传需要较长时间,如果前端等待一定时间后断开连接(想取消上传),那么后端如何处理比较好?在 spring 框架下如何感知前端断开了连接?如何避免一条废数据上传到服务器呢?

    作者回复: 断开链接后,会出异常,比如对于文件上传可能就是

    java.io.EOFException: Unexpected EOF read on the socket

    在servlet层面就已经出错,不会进入业务逻辑处理,也不会存在废文件,对于tomcat可以把文件临时目录配置为一个固定目录,即便存在残留文件也可以定期清除

    2020-05-07
  • Mr WHO
    关于错误码我们目前的做法是内部的微服务间用异常透传错误码;外围服务的错误码放到message响应给上游,简单做下错误码转换
    2020-05-07
  • 殿小二
    老师 你好,问一下,针对非强制升级时,为了兼容老接口,只能复制一份,这类代码太重复了,有没有比较优化的解决方案

    作者回复: 新老接口总有公用逻辑的吧,把其中重复的部分提取出来

    2020-05-07
  • Geek_3b1096
    谢谢老师分享接口设计思维
    2020-05-06
  • 刘大明
    目前我们系统中是直接抛出自定义异常来去做异常提示信息.中途改造过一版,直接将code码和message提示信息写到配置管理中心里面.或者放到redis缓存里面.
    作为服务端我觉得有必要给出code错误码,方便问题排查
    2020-05-06
收起评论
14
返回
顶部