文档章节

使用 Exception 写出优雅的代码

八月下沙
 八月下沙
发布于 2015/07/21 11:33
字数 1345
阅读 107
收藏 1

首先先来看一组代码:

Controller:

@RequestMapping
	public ModelAndView doCreate(HttpServletRequest request, Map<String, Object> out,
			Events event, String gmtStartStr, String gmtEndStr, String invitedAccountId){
		
		eventsService.initGmt(event, gmtStartStr, gmtEndStr);
		event.setUid(getSessionUser(request).getUid());
		
		Integer id = eventsService.saveEvent(event);
		
		if(id!=null && id.intValue()>0){
//			eventsService.appendJoiner(id, invitedAccountId);
			return new ModelAndView("redirect:index.do");
		}
		
		return new ModelAndView("/events/create");
	}

Service:

@Override
	public Integer saveEvent(Events event) {
		if(event == null){
			return null;
		}
		
		if(Strings.isNullOrEmpty(event.getName())){
			return null;
		}
		
		event.setStatusPublic(event.getStatusPublic()==null?Events.STATUS_DEFAULT:event.getStatusPublic());
		
		eventsMapper.save(event);
		
		return event.getId();
	}

这组代码应该算是比较典型的 controller 调用 service 实现业务功能的代码,以前的系统里一直也是这么干的,也有确实没有遇到什么特别严重的问题,但是在错误返回上总是会有那么点小纠结,有时候,错误是由于参数引起的,有时候是由于数据库操作引起的,在controller层其实不能特别准确的判断,特别是那些复杂的 service 方法,错误往往产生于众多逻辑中的一个,如果只是返回 null,controller 就只能知道方法是执行成功还是失败,但不知道失败的细节。另一个问题是 null 值判断被移到了 controller 里,这导致 controller 参与了业务,与 controller 应有的职责不符。

那么如何改进呢?

针对错误细节,我们可以把一个业务方法执行过程中,不可继续的环节认为是一种错误,只不过这种错误是开发人员自己可控的逻辑错误。错误在软件系统里也叫异常,所以,用异常来改进上述代码:

Controller:

@RequestMapping
	public ModelAndView doCreate(HttpServletRequest request, Map<String, Object> out,
			Events event, String gmtStartStr, String gmtEndStr, String invitedAccountId){
		
		eventsService.initGmt(event, gmtStartStr, gmtEndStr);
		event.setUid(getSessionUser(request).getUid());
		
		try{
		    Integer id = eventsService.saveEvent(event);
		    return new ModelAndView("redirect:index.do");
		}catch(Exception e){
		    out.put("error",e.getMessage());
                    return new ModelAndView("/events/create");
		}
	}

Service:

@Override
	public Integer saveEvent(Events event) throws Exception{
		if(event == null){
			throw new Exception("event can not be null.");
		}
		
		if(Strings.isNullOrEmpty(event.getName())){
			throw new Exception("event.name can not be null.");
		}
		
		event.setStatusPublic(event.getStatusPublic()==null?Events.STATUS_DEFAULT:event.getStatusPublic());
		
		eventsMapper.save(event);
		if(event.getId() ==null || event.getId()<=0){
		    throw new Exception("event save failure.");
		}
		
		return event.getId();
	}

以上改进的好处是在 controller 层很容易知道 service 出错的细节,同时也居然了处理 null 值判断问题,也可以在 controller 层做统一异常处理,比如跳转到错误页面,或返回错误的 JSON 等。

但是,实际的项目中,service 层业务并不总是这么简单,当 service 层中使用的工具或者类中本身就有异常抛出的时候,因为抛出的是 Exception,所以其他的异常信息被忽略了,在 controller 里仍然不能准确把握要处理的异常,这可能会导致前端页面无法正确提示不同的异常信息。

既然要区分不同的异常,那么把自己业务里的异常都用自定义异常类 ServiceException 来抛出不就可以解决问题了吗,代码改进如下:

Controller:

@RequestMapping
	public ModelAndView doCreate(HttpServletRequest request, Map<String, Object> out,
			Events event, String gmtStartStr, String gmtEndStr, String invitedAccountId){
		
		eventsService.initGmt(event, gmtStartStr, gmtEndStr);
		event.setUid(getSessionUser(request).getUid());
		
		try{
		    Integer id = eventsService.saveEvent(event);
		    return new ModelAndView("redirect:index.do");
		}catch(ServiceException e){
		    out.put("error",e.getMessage());
                    return new ModelAndView("/events/create");
		}
	}

Service:

@Override
	public Integer saveEvent(Events event) throws ServiceException{
		if(event == null){
			throw new ServiceException("event can not be null.");
		}
		
		if(Strings.isNullOrEmpty(event.getName())){
			throw new ServiceException("event.name can not be null.");
		}
		
		try{
    		    event.setStatusPublic(event.getStatusPublic()==null?Events.STATUS_DEFAULT:event.getStatusPublic());
		}catch(OtherException e){
		    throw new ServiceException("set status failure.", e);
		}
		
		eventsMapper.save(event);
		if(event.getId() ==null || event.getId()<=0){
		    throw new Exception("event save failure.");
		}
		
		return event.getId();
	}

嗯,到这里为止,代码看上去已经比较优雅了,让我们再来找找茬,首先是参数判定,虽然现在是用自己的异常类来做了处理,但 service 参数传递的错误应属于开发调用时就没正确调用导致,级别应属于Error级别,这种级别的错误应在开发时就被处理掉,正常情况下不应有这种情况发生,也不应该与业务上的逻辑错误归为一类,而 jdk 本身提供了参数较验的异常 IllegalArgumentException,这个异常继承自 RuntimeException,我们可以好好利用一下这个异常类。

另外一方面,现在 ServiceException 返回的错误信息对用户来说并不友好,应改进成用户可以理解的语言,比如改成国际化配置文件中的 key,这样可以直接在 controller 层得到有设定好的错误提示信息,也可以在 controller 层直接抛提供给前端,让前端转成国际化结果。

代码再次改进:

Controller:

@Resource
	private MessageSource messageSource;
	
	@RequestMapping
	public ModelAndView doCreate(HttpServletRequest request, Map<String, Object> out,
			Events event, String gmtStartStr, String gmtEndStr, String invitedAccountId, Locale locale){
		
		eventsService.initGmt(event, gmtStartStr, gmtEndStr);
		event.setUid(getSessionUser(request).getUid());
		
		try{
		    Integer id = eventsService.saveEvent(event);
		    return new ModelAndView("redirect:index.do");
		}catch(ServiceException e){
		    out.put("error",messageSource.getMessage(e.getMessage(), null, locale));
                    return new ModelAndView("/events/create");
		}
	}

Service:

@Override
	public Integer saveEvent(Events event) throws ServiceException{
		if(event == null){
			throw new IllegalArgumentException("event can not be null.");
		}
		
		if(Strings.isNullOrEmpty(event.getName())){
			throw new IllegalArgumentException("event.name can not be null.");
		}
		
		try{
    		    event.setStatusPublic(event.getStatusPublic()==null?Events.STATUS_DEFAULT:event.getStatusPublic());
		}catch(OtherException e){
		    throw new ServiceException("EVENT_SET_STATUS_FAILURE", e);
		}
		
		eventsMapper.save(event);
		if(event.getId() ==null || event.getId()<=0){
		    throw new Exception("EVENT_SAVE_FAILURE");
		}
		
		return event.getId();
	}

至此,代码优化暂时告一段落,代码的改进是无止境的,我们不应停留在原有的模式上,要不断寻求新的改进方法,而在改进过程中,更应注重 JDK,JAVA 本身所提供了各类工具和方法。


相关连接:

关于 null 判断的问题,importNews 上有一篇文章写得很好:http://www.importnew.com/13002.html


© 著作权归作者所有

共有 人打赏支持
八月下沙
粉丝 5
博文 12
码字总数 10423
作品 0
杭州
部门经理
私信 提问
加载中

评论(1)

Jimmy哥
Jimmy哥
受益良多,之前也一直迷糊,不知道如何将Service的错误返回到controller。
看完文章后,也有大概的思路了
每日 30 秒 ⏱ 优雅插入数组

简介 一天 30 秒 ⏱ 一段代码 ✍️ 一个场景 🖼 代码能运行起来就行了为什么要编写优雅的代码? 其实很多时候项目进度很赶、小姐姐不理你了、老板不给你加薪等等事情都会成为你今天偷偷把代...

zhangxiangliang
03/23
0
0
java向上转型妙用

Java向上转型的妙用 下面这段代码摘自《java编程思想》,内容稍有修改

alantuling_jt
2017/07/11
0
0
Java开发的30点心得

来,请喝杯Java 本文为自己在写Java代码中积累的一些心得,今天拿出来分享,不妥之处,请不吝赐教!若你有好的idea,也请评论区说说,我一并加上。 通用(General) 1.虽然有JVM帮你回收内存...

翱翔云端
2017/10/25
0
0
不要浪费时间写完美代码

一个系统的生命周期可以持续 5 年或者 10 年甚至是 20 年,但是对于特定的代码行,甚至特定的设计,寿命都会短很多:当你使用不同的解决方案进行迭代时,可能只有几个月或者几天甚至几分钟的...

oschina
2017/09/22
1K
7
TypeScript 数据模型层编程的最佳实践

虽然 TypeScript 主要用于客户端,而数据模型的设计主要是服务端来做的。 但是要写出优雅的代码,也还是有不少讲究的。 让我们从一个简单的我的文章列表 api 返回的数据开始,返回的文章列表...

banxi
2018/07/02
0
0

没有更多内容

加载失败,请刷新页面

加载更多

租房软件隐私保护如同虚设

近日,苏州市民赵先生向江苏新闻广播新闻热线025-84658888反映,他在“安居客”手机应用软件上浏览二手房信息,并且使用该软件自动生成的虚拟号码向当地一家中介公司进行咨询。可电话刚挂不久...

linux-tao
今天
1
0
分布式项目(五)iot-pgsql

书接上回,在Mapping server中,我们已经把数据都整理好了,现在利用postgresql存储历史数据。 iot-pgsql 构建iot-pgsql模块,这里我们写数据库为了性能考虑不在使用mybatis,换成spring jd...

lelinked
今天
4
0
一文分析java基础面试题中易出错考点

前言 这篇文章主要针对的是笔试题中出现的通过查看代码执行结果选择正确答案题材。 正式进入题目内容: 1、(单选题)下面代码的输出结果是什么? public class Base { private Strin...

一看就喷亏的小猿
今天
2
0
cocoapods 用法

cocoapods install pod install 更新本地已经install的仓库 更新所有的仓库 pod update --verbose --no-repo-update 更新制定的仓库 pod update ** --verbose --no-repo-update...

HOrange
今天
3
0
linux下socket编程实现一个服务器连接多个客户端

使用socekt通信一般步骤 1)服务器端:socker()建立套接字,绑定(bind)并监听(listen),用accept()等待客户端连接。 2)客户端:socker()建立套接字,连接(connect)服务器,连接上后...

shzwork
昨天
3
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部