0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

Service层的异常处理

jf_ro2CN3Fa 来源:JavaEdge 2024-01-08 11:29 次阅读

来源:JavaEdge

0 前言

一般初学者学习编码和[错误处理]时,先知道[编程语言]有一种处理错误的形式或约定(如Java就抛异常),然后就开始用这些工具。但却忽视这问题本质:「处理错误是为了写正确程序」 。可是

1 啥叫“正确”?

「由解决的问题决定的。问题不同,解决方案不同。」

如一个web接口接受用户请求,参数age,也许业务要求字段是0~150之间整数。如输入字符串或负数就肯定不接受。一般在后端某地做输入合法性检查,不过就抛异常。

但归根到底这问题“正确”解决方法总是要以某种形式提示用户。而提示用户是某种前端工作,就要看界面是app,H5+AJAX还是类似于[jsp]的服务器产生界面。不管啥,你要根据需求去”设计一个修复错误“的流程。如一个常见的流程要后端抛异常,然后一路到某个集中处理错误的代码,将其转换为某个HTTP的错误(业务错误码)提供给前端,前端再映射做”提示“。如用户输入非法请求,从逻辑上后端都没法自己修复,这是个“正确”的策略。

2 报500了嘞!

如用户上传一个头像,后端将图片发给[云存储],结果云存储报500,咋办?你可能想重试,因为也许仅是[网络抖动],重试就能正常执行。但若重试多次无效,若设计了某种热备方案,可能改为发到另一个服务器。“重试”和“使用备份的依赖”都是“立刻处理“。

但若重试无效,所有的[备份服务]也无效,也许就能像上面那样把错误抛给前端,提示用户“服务器开小差”。从这方案易看出,你想把错误抛到哪里是因为那个catch的地方是处理问题最方便的地方。一个问题的解决方案可能要几个不同的错误处理组合起来才能办到。

3 NPE了!

你的程序抛个NPE。这一般就是程序员的bug:

要不就是程序员想表达一个东西”没有“,结果在后续处理中忘判断是否为null

要不就是在写代码时觉得100%不可能为null的地方出现了一个null

不管哪种,这错误用户总会看到一个很含糊的报错信息,这远远不够。“正确”办法是程序员自己能尽快发现它,并尽快修复。要做到这点,需要[监控系统]不断爬log,把问题报警出来。而非等用户找客服投诉。

4 OOM了!

比如你的[后端程序]突然OOM挂了。挂的程序没法恢复自己。要做到“正确”,须在服务之外的容器考虑这问题。

如你的服务跑在[k8s],他们会监控你程序状态,然后重启新的服务实例弥补挂掉的服务,还得调整流量,把去往宕机服务的流量切换到新实例。这的恢复因为跨系统所以不能仅用异常实现,但道理一样。

但光靠重启就“正确”了?若服务是完全无状态,问题不大。但若有状态,部分用户数据可能被执行一半的请求搞乱。因此重启要留意先“恢复数据到合法状态”。这又回到你要知道咋样才是“正确”的做法。只依靠简单的语法功能不能无脑解决这事。

5 提升维度

一个工作线程的“外部容器“是管理工作线程的“master”

一个网络请求的“外部容器”是一个Web Server

一个用户进程的“外部容器”是[操作系统]

Erlang把这种supervisor-worker的机制融入到语言的设计

Web程序很大程度能把异常抛给顶层,是因为:

请求来自前端,对因为用户请求有误(数据合法性、权限、用户上下文状态)造成的问题,最终基本只能告诉用户。因此抛异常到一个集中处理错误的地方,把异常转换为某个业务错误码的方法,合理

后端服务一般无状态。这也是软件系统设计的一般原则。无状态才意味着可随时随地安心重启。用户数据不会因为因为下一条而会出问题

后端对数据的修改依赖DB的事务。因此一个改一半的、没提交的事务不会造成副作用。

但这3条件并非总成立。总能遇到:

一些处理逻辑并非无状态

也并非所有的数据修改都能用一个事务保护

尤其要注意对[微服务]的调用,对内存状态「的修改是没有事务保护的」 ,一不留神就会搞乱用户数据。比如下面代码段

6 难以排查的代码段

try{
intres1=doStep1();
this.status1+=res1;
intres2=doStep2();
this.status2+=res2;
//抛个异常
intres3=doStep3();
this.status3=status1+status2+res3;
}catch(...){
//...
}

先假设status1、status2、status3之间需维护某种不变的约束(invariant)。然后执行这段代码时,如在doStep3抛异常,下面对status3的赋值就不会执行。这时如不能将status1、status2的修改rollback,就会造成数据违反约束的问题。

而程序员很难发现这个数据被改坏了。坏数据还可能导致其他依赖这数据的代码逻辑出错(如原本应该给积分的,却没给)。而这种错误一般很难排查,从大量数据里找到不正确的那一小段何其困难。

7 更难搞定的代码段

//controller
voidcontrollerMethod(/*参数*/){
try{
returnsvc.doWorkAndGetResult(/*参数*/);
}catch(Exceptione){
returnErrorJsonObject.of(e);
}
}

//svc
voiddoWorkAndGetResult(/*someparams*/){
intres1=otherSvc1.doStep1(/*someparams*/);
this.status1+=res1;
intres2=otherSvc2.doStep2(/*someparams*/);
this.status2+=res2;
intres3=otherSvc3.doStep3(/*someparams*/);
this.status3=status1+status2+res3;
returnSomeResult.of(this.status1,this.status2,this.status3);
}

难搞在于你写的时候可能以为doStep1~3这种东西即使抛异常也能被Controller里的catch。

在svc这层是不用处理任何异常,「因此不写[try……catch]天经地义」 。但实际上doStep1、doStep2、doStep3任何一个抛异常都会造成svc的数据状态不一致。甚至你一开始都可以通过文档或其他沟通确定doStep1、doStep2、doStep3一开始都是必然可成功,不会抛错的,因此你写的代码一开始是对的。

但你可能无法控制他们的实现(如他们是另外一个团队开发的[jar]提供的),而他们的实现可能会改成抛错。你的代码可能在完全不自知情况下从“不会出问题”变成“可能出问题”…… 更可怕的类似代码不能正确工作:

voiddoWorkAndGetResult(/*someparams*/){
try{
intres1=otherSvc1.doStep1(/*someparams*/);
this.status1+=res1;
intres2=otherSvc2.doStep2(/*someparams*/);
this.status2+=res2;
intres3=otherSvc3.doStep3(/*someparams*/);
this.status3=status1+status2+res3;
returnSomeResult.of(this.status1,this.status2,this.status3);
}catch(Exceptione){
//dorollback
}
}

你以为这样就会处理好数据rollback,甚至「觉得这种代码优雅」 。但实际上doStep1~3每一个地方抛错,rollback的代码都不一样。

得这么写

voiddoWorkAndGetResult(/*someparams*/){
intres1,res2,res3;
try{
res1=otherSvc1.doStep1(/*someparams*/);
this.status1+=res1;
}catch(Exceptione){
throwe;
}

try{
res2=otherSvc2.doStep2(/*someparams*/);
this.status2+=res2;
}catch(Exceptione){
//rollbackstatus1
this.status1-=res1;
throwe;
}

try{
res3=otherSvc3.doStep3(/*someparams*/);
this.status3=status1+status2+res3;
}catch(Exceptione){
//rollbackstatus1&status2
this.status1-=res1;
this.status2-=res2;
throwe;
}
}

这才是得到正确结果的代码,在任何地方出错都能维护数据一致性。优雅吗?

看起来很丑。比go的if err != nil还丑。但要在正确性和优雅性取舍,肯定毫不犹豫选前者。作为程序员不能直接认为抛异常可解决任何问题,须学会写出有正确逻辑的程序,哪怕很难且看起来丑。

为达成高正确性,你不能总将自己大部分注意力放在“一切都OK的流程“,而把错误看作是可随便应付了事的工作或简单的相信exception可自动搞定一切。

8 总结

希望程序员们对错误处理都要有敬畏之心。Java因为Checked Exception设计问题不得不避免使用,而Uncaughted Exception「实在是太过于弱鸡,是不能给程序员提供更好地帮助的」

因此,程序员在每次抛错或者处理错误的时候都要三省吾身:

这个错误的处理是正确的吗?

会让用户看到什么?

会不会搞乱数据?

不要以为自己抛了个异常就不管了。在[编译器]不能帮上太多忙的时候,好好写UT来保护代码脆弱的正确性。

审核编辑:汤梓红
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • JAVA
    +关注

    关注

    19

    文章

    2966

    浏览量

    104702
  • 代码
    +关注

    关注

    30

    文章

    4779

    浏览量

    68524
  • 程序员
    +关注

    关注

    4

    文章

    951

    浏览量

    29798
  • Service
    +关注

    关注

    0

    文章

    30

    浏览量

    13784

原文标题:Service 层的异常是抛到 Controller 层还是直接处理?

文章出处:【微信号:芋道源码,微信公众号:芋道源码】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    如何有效的处理空指针异常

    地遇到这个问题。 那么我们应该如何有效且优雅的处理空指针异常呢? 下面了不起将详细的介绍这个处理方案。 1、什么是空指针异常? 空指针异常
    的头像 发表于 09-30 10:25 1507次阅读

    嵌入式C编程常用的异常错误处理

    。 3. 中断服务程序 (Interrupt Service Routines, ISR) 在嵌入式系统中,中断是处理异常情况的常用方法。ISR用于处理硬件中断,并确保系统在
    发表于 08-06 14:32

    基于VxWorks的异常处理的研究和实现

    基于VxWorks的异常处理的研究和实现
    发表于 03-29 12:28 38次下载

    基于VxWorks的异常处理的研究和实现

    阐述了嵌入式软件系统中异常处理的必要性,然后基于嵌入式实时操作系统VxWorks,介绍了一种与具体处理器类型无关的异常处理方法,并且结合一种
    发表于 01-11 09:13 23次下载

    java异常处理的设计与重构

    在程序设计中,进行异常处理是非常关键和重要的一部分。一个程序的异常处理框架的好坏直接影响到整个项目的代码质量以及后期维护成本和难度。试想一下,如果一个项目从头到尾没有考虑过
    发表于 09-27 15:40 1次下载
    java<b class='flag-5'>异常</b><b class='flag-5'>处理</b>的设计与重构

    java异常处理设计和一些建议

    程序设计在程序设计中,进行异常处理是非常关键和重要的一部分。一个程序的异常处理框架的好坏直接影响到整个项目的代码质量以及后期维护成本和难度。试想一下,如果一个项目从头到尾没有考虑过
    发表于 09-28 11:48 0次下载
    java<b class='flag-5'>异常</b><b class='flag-5'>处理</b>设计和一些建议

    C语言的异常处理案例代码

    相信很多朋友在此之前可能根本没有使用或者听说过C语言的异常处理,印象中都是C++或者java才有的东西,C语言怎么会有异常处理呢?
    的头像 发表于 12-22 08:44 3817次阅读

    基于Python 异常的介绍以及异常处理的方法解析

    异常处理在任何一门编程语言里都是值得关注的一个话题,良好的异常处理可以让你的程序更加健壮,清晰的错误信息更能帮助你快速修复问题。在Python中,和不分高级语言一样,使用了try/ex
    的头像 发表于 01-31 14:20 6260次阅读
    基于Python <b class='flag-5'>异常</b>的介绍以及<b class='flag-5'>异常</b><b class='flag-5'>处理</b>的方法解析

    基于ARM处理器的高效异常处理解决方案

    嵌入式系统要求对异常及中断处理器能快速响应。文中分析了ARM体系结构下 异常处理 特点,提出一种基于 ARM处理器 的高效
    发表于 02-03 03:38 1401次阅读
    基于ARM<b class='flag-5'>处理</b>器的高效<b class='flag-5'>异常</b><b class='flag-5'>处理</b>解决方案

    Java程序设计教程之异常处理的详细资料说明

    本文档的详细介绍的是Java程序设计教程之异常处理的详细资料说明主要内容包括了:1 什么是异常,2异常处理机制,3
    发表于 02-22 10:27 13次下载
    Java程序设计教程之<b class='flag-5'>异常</b><b class='flag-5'>处理</b>的详细资料说明

    ARM异常中断的原因及处理措施

    当ARM异常中断发生时,系统执行完当前指令后,将跳转到相应的异常中断处理程序处执行。当异常中断处理程序执行完成后,程序返回到发生中断指令的下
    的头像 发表于 06-17 10:05 8083次阅读

    处理器中异常和中断解决

    异常是能够引起程序流偏离正常流程的事件,当异常发生时,正在执行的程序就会被挂起,处理器转而执行一块与该事件相关的代码(异常处理)。事件可以是
    的头像 发表于 10-12 17:14 5066次阅读

    替代try catch处理异常的优雅方式

    不过跟异常处理相关的只有注解@ExceptionHandler,从字面上看,就是 异常处理器 的意思,其实际作用也是:若在某个Controller类定义一个
    的头像 发表于 10-26 10:18 1078次阅读

    C++程序异常处理机制是什么

    那么C++设计了一套异常处理机制,一方面能够使得异常处理和正常运行代码进行分离,使得程序更加模块化;另一方面,C++的异常
    的头像 发表于 02-21 10:37 861次阅读
    C++程序<b class='flag-5'>异常</b><b class='flag-5'>处理</b>机制是什么

    PLC的异常类型和处理办法

    1.中央处理异常: 如果出现中央处理异常报警,应检查连接到中央处理器内部总线的所有设备。具体方法是依次更换可能导致故障的机 组,找出故障
    发表于 04-19 09:43 0次下载
    PLC的<b class='flag-5'>异常</b>类型和<b class='flag-5'>处理</b>办法