实习记录:复盘代码简洁性与可读性提高过程

本文为笔者实习期间个人学习记录

絮絮叨叨
要说实习这段时间印象最深的,不是认识了很多之前没接触过的技术,而是没完没了地修改controller和service层的代码…
现在项目结束了再回望,大致可分为三个阶段:

  1. 非常粗略的实现了增删改查的功能
  2. 代码完善与逻辑修改(含部分Bug修复)
  3. 公共部分抽取(工具类编写)

返工颇多,分析后主要是因为之前没有开发经验前期设计考虑不足,并且减少代码重复率的意识不强,一味地复制黏贴导致较多冗余.
原本抱有这样的想法:都是相似的代码,修改起来很快。【x】 然而 并 不 是.
6个模块,6个controller+6个service的抽象类和6个service的实现类+6个以上DTO+至少3*6个VO,有好几十个类都有可能进行重复的、类似的修改,一是修改起来很麻烦工作量大,二是非常枯燥且没有什么意义.
因此下面复盘总结一下自己重构整理代码的过程,以后少走弯路,减少返工.


仅以CategoryController.java与CategoryServiceImpl.java为例

1. 初步实现

第一遍写的代码非常粗糙,仅仅是实现了基本功能使得前后端能够调通,逻辑乱,存在较多Bug.

/**
 * @author : huangyuhui
 * @version : 1.0
 * @date : 2019/9/3
 */
@RestController
public class CategoryController {
	@Autowired
	private CategoryService categoryService;

	@GlobalExceptionLog
	@CrossOrigin
	@PostMapping("api/deleteCategories")
	@ApiLog
	public CommonResponse<String> delete(@RequestBody CommonRequest<List<CategoryDataItemVO>> commonRequest){
		List<CategoryDataItemVO> volist= commonRequest.getBody();
		if(volist!=null && volist.size()>0){
			//vo列表转dto列表
			List<CategoryDTO> dtos=new ArrayList<CategoryDTO>();
			CategoryDTO categoryDTO=null;
			for(CategoryDataItemVO vo: volist){
				categoryDTO=new CategoryDTO();
				BeanUtils.copyProperties(vo,categoryDTO);
				dtos.add(categoryDTO);
			}
			//调用service里面的对应方法进行删除
			int result=0;
			try {
				result = categoryService.delete(dtos);
			}catch (ServiceException serviceException){
				//抛出自定义异常
				throw new BusinessException(serviceException);
			}
			//返回CommonResponse给前端
			CommonResponse<String> response=new CommonResponse<>();
			ResponseHead head = new ResponseHead();
			head.setEncryption(0);
			head.setCode("0");
			//
			if(result>0){
				head.setMessage("删除类别成功");
			}else{
				head.setMessage("删除类别失败");
			}
			response.setResponseHead(head);
			return response;
		}
		return null;
	}

	@GlobalExceptionLog
	@CrossOrigin
	@PostMapping("api/addCategory")
	@ApiLog
	public CommonResponse<String> add(@RequestBody CommonRequest<CategoryDataItemVO> commonRequest ){
		CategoryDataItemVO vo=commonRequest.getBody();
		if(vo!=null) {
			CategoryDTO categoryDTO = new CategoryDTO();
			BeanUtils.copyProperties(vo, categoryDTO);
			int result = 0;
			try {
				result = categoryService.add(categoryDTO);
			} catch (ServiceException serviceException) {
				throw new BusinessException(serviceException);
			}
			//返回CommonResponse给前端
			CommonResponse<String> response = new CommonResponse<>();
			ResponseHead head = new ResponseHead();
			head.setEncryption(0);
			head.setCode("0");
			//
			if (result > 0) {
				head.setMessage("增加类别成功");
			} else {
				head.setMessage("增加类别失败");
			}
			response.setResponseHead(head);
			return response;
		}
		return  null;
	}

	@GlobalExceptionLog
	@CrossOrigin
	@PostMapping("api/updateCategory")
	@ApiLog
	public CommonResponse<String> update(@RequestBody CommonRequest<CategoryDataItemVO> commonRequest){
		CategoryDataItemVO vo=commonRequest.getBody();
		if(vo!=null) {
			CategoryDTO categoryDTO = new CategoryDTO();
			BeanUtils.copyProperties(vo, categoryDTO);
			int result = 0;
			try {
				result = categoryService.update(categoryDTO);
			} catch (ServiceException serviceException) {
				throw new BusinessException(serviceException);
			}
			//返回CommonResponse给前端
			CommonResponse<String> response = new CommonResponse<>();
			ResponseHead head = new ResponseHead();
			head.setEncryption(0);
			head.setCode("0");
			//
			if (result > 0) {
				head.setMessage("修改类别成功");
			} else {
				head.setMessage("修改类别失败");
			}
			response.setResponseHead(head);
			return response;
		}
		return  null;
	}


	@CrossOrigin
	@GetMapping("api/loadCategories")
	@ApiLog
	public List<CategoryDTO> list() throws Exception {
		CategoryDTO dto = new CategoryDTO();
		return  categoryService.queryByCondition(dto);
	}

		@GlobalExceptionLog
	@CrossOrigin
	@PostMapping("api/queryCategory")
	@ApiLog
	public List<CategoryDTO> query(@RequestBody CommonRequest<CategoryQueryConditionVO> commonRequest) {
			CategoryQueryConditionVO vo=commonRequest.getBody();
			if(vo!=null){
				CategoryDTO dto=new CategoryDTO();
				BeanUtils.copyProperties(vo,dto);
				System.out.println(dto);
				List<CategoryDTO> list=categoryService.queryByCondition(dto);
				return list;
			}
			return null;
	}

}
  • 其实一开始我们设计的是:以get方式请求load方法(因为load返回所有数据,不需要传递参数),以post方式请求query方法.但是后期这种方案就无意义了,因为前端传递给后端的数据统一成了CommonRequest,load方法也改成了有参的post请求,这样就没必要分两个方法写了,于是后期load被删去,统一用query进行查询.
  • 存在返回null的情况,这是不允许的,返回参数应该统一为CommonResponse,然后前端依据这个自定义报文的返回码判断是否执行成功或者异常.
/**
 * @author : huangyuhui
 * @version : 1.0
 * @date : 2019/9/2 0002
 */
@Service
public class CategoryServiceImpl implements CategoryService {

	@Autowired
	private CategoryDao categoryDao;

	@Autowired
	private DataSourceTransactionManager dataSourceTransactionManager;

	/**
	 * 增加题目类别
	 * @param categoryDTO
	 * @return
	 */
	public int add(CategoryDTO categoryDTO) {
		if(categoryDTO!=null){
			SnowFlake snowFlake=new SnowFlake(2,3);
			Date createdTime= DateUtils.getDate();
			//设置id(雪花算法获得)
			categoryDTO.setId(snowFlake.nextId());
			//更新时间和创建时间一样
			categoryDTO.setCreatedTime(createdTime);
			categoryDTO.setUpdatedTime(createdTime);
			//创建者和更新者后续从登录信息中获取
			categoryDTO.setCreatedBy((long)9527);
			categoryDTO.setUpdatedBy((long)9527);
			//设置版本
			categoryDTO.setVersion((long)1.0);
			try{
				categoryDao.insert(categoryDTO);
				return 1;
			}catch (Exception e){
				e.printStackTrace();
			}
		}
		return 0;
	}

	/**
	 * 删除题目类别
	 * @param categoryDtos
	 * @return
	 */
	public int delete(List<CategoryDTO> categoryDtos) {
		CategoryDTO categoryDTO=new CategoryDTO();
		if(categoryDtos!=null){
			for(CategoryDTO categoryDto : categoryDtos){
				categoryDTO=categoryDto;
				categoryDao.delete(categoryDTO);
			}
		}
		return 0;
	}

	/**
	 * 修改题目类别信息
	 * @param categoryDTO
	 * @return
	 */
	public int update(CategoryDTO categoryDTO) {
		//省略版本号对比

		Category category =new Category();
		BeanUtils.copyProperties(categoryDTO,category);
		category.setUpdatedTime(DateUtils.getDate());
		return categoryDao.updateByPrimaryKeySelective(category);
	}


	/**
	 * 通过id查找题目类别
	 * @param id
	 * @return
	 */
	public CategoryDTO getByPrimaryKey(Long id) {
		Category category=categoryDao.selectByPrimaryKey(id);
		CategoryDTO dto= new CategoryDTO();
		BeanUtils.copyProperties(category,dto);
		return dto;
	}

	/**
	 * 根据条件查找
	 * @param categoryDTO
	 * @return
	 */
	public List<CategoryDTO> queryByCondition(CategoryDTO categoryDTO) {
		Condition condition = new Condition(Category.class);
		Example.Criteria criteria=condition.createCriteria();
		if(!StringUtils.isEmpty(categoryDTO.getName())){
			criteria.andLike("name","%"+categoryDTO.getName()+"%");
		}
		List<Category> categories=categoryDao.selectByExample(condition);
		List<CategoryDTO> dtos=null;
		CategoryDTO dto=null;
		if(categories!=null){
			dtos=new ArrayList<CategoryDTO>(categories.size());
			for(Category category:categories){
					dto=new CategoryDTO();
					BeanUtils.copyProperties(category,dto);
					dtos.add(dto);
			}
		}else{
			dtos=new ArrayList<CategoryDTO>();
		}
		return dtos;
	}

	/**
	 * 查找所有题目类别
	 * @return List<CategoryDTO> 不会为null
	 */
	public List<CategoryDTO> queryAll() {
		List<CategoryDTO> dtos=new ArrayList<CategoryDTO>();
		List<Category> categories=categoryDao.selectAll();
		CategoryDTO dto=null;
		if(categories!=null){
			for(Category category:categories){
				dto=new CategoryDTO();
				BeanUtils.copyProperties(category,dto);
				dtos.add(dto);
			}
		}
		return dtos;
	}
}
  • 没有进行异常处理
  • 没有进行分页处理
  • 还存在未实现的,如版本对比和事务管理
  • 代码不规范
2. 封装前后端传递的参数

入参:CommonRequest<xxxVO>
返参:CommonResponse<xxx> 不会有返回为null的情况,约定报文头部的code为“0”时为表示成功,其余均为异常/失败

    @GlobalExceptionLog
	@CrossOrigin
	@PostMapping("/delete")
	@ApiLog
	public CommonResponse<String> delete(@RequestBody CommonRequest<List<CategoryDataItemVO>> commonRequest){
		//返回CommonResponse给前端
		CommonResponse<String> response=new CommonResponse<>();
		ResponseHead head = new ResponseHead();
		List<CategoryDataItemVO> volist= commonRequest.getBody();
		int result=0;
		if(volist!=null && volist.size()>0){
			//vo列表转dto列表
			List<CategoryDTO> dtos=new ArrayList<CategoryDTO>(volist.size());
			CategoryDTO categoryDTO=null;
			for(CategoryDataItemVO vo: volist){
				categoryDTO=new CategoryDTO();
				categoryDTO.setId(vo.getId());
				categoryDTO.setVersion(vo.getVersion());
				dtos.add(categoryDTO);
			}
			//调用service里面的对应方法进行删除
			result= categoryService.delete(dtos);
			if(result>0){
				head.setCode("0");
				head.setMessage("删除题目类别成功");
			}else {
				head.setMessage("删除题目类别失败");
			}
		}else {
			head.setMessage("请求参数为空");
		}
		head.setEncryption(0);
		response.setResponseHead(head);
		response.setBody(""+result);
		return response;
	}
  • 修改后,所有方法开头均为声明CommonResponse及其头部,然后取出CommonRequest的body部分进行处理.
  • api路径原来是方法上加注解@GetMapping(“api/loadCategories”)
    更改为,类上加@RequestMapping("/api/Category"),方法上只写描述该方法功能的路径,如:@PostMapping("/delete")
  • 还是有较多重复.
/**
	 * 删除题目类别
	 * @param categoryDtos
	 * @return
	 */
	@Override
	public int delete(List<CategoryDTO> categoryDtos) {
		int result=0;
		if(categoryDtos!=null){
			for(CategoryDTO categoryDTO : categoryDtos){
				Category category=categoryDao.selectByPrimaryKey(categoryDTO.getId());
				//判断数据是否存在
				if(StringUtils.isEmpty(category)){
					//如果为空则抛出不存在异常
					throw new ServiceException(BesDataExceptionEnum.CATEGORY_NON_EXISTENT);
				}
				//判断数据版本是否一致
				if(!category.getVersion().equals((categoryDTO.getVersion()))){
					throw new ServiceException(BesDataExceptionEnum.CATEGORY_VERSION_DISACCORD);
				}
				if(category.getStatus()==1){
					//数据正在被使用,不允许删除
					throw new ServiceException(BesDataExceptionEnum.CATEGORY_IN_USE);
				}
				try{
					int res=categoryDao.deleteByPrimaryKey(categoryDTO.getId());
					result+=res;
				}catch (Exception e){
					throw new ServiceException(BesDataExceptionEnum.CATEGORY_IN_USE);
				}
			}
		}
		return result;
	}
  • 添加了异常处理,如果异常,则封装成自定义的统一异常类抛出,由于Controller层方法前添加了 @GlobalExceptionLog注解,会对异常进行截获,将异常码和异常信息封装到CommonResponse的body后返回.
3. 查询返回分页数据
  • 上一版本:直接返回body为List<xxxVO>的CommonResponse
  • 修改后:使用PageHelper进行分页处理,返回的CommonResponse的body为PageInfo<xxxVO>
	@GlobalExceptionLog
	@CrossOrigin
	@PostMapping("/query")
	@ApiLog
	public CommonResponse<PageInfo<CategoryDataListVO>> query(@Valid @RequestBody CommonRequest<CategoryQueryConditionVO> commonRequest) {
		CategoryQueryConditionVO vo=commonRequest.getBody();
		CommonResponse<PageInfo<CategoryDataListVO>> response= new CommonResponse<>();
		ResponseHead head=new ResponseHead();
		CategoryDTO dto=new CategoryDTO();
		if(vo!=null){
			BeanUtils.copyProperties(vo,dto);
		}
		PageInfo<CategoryDTO> categoryDTOPageInfo=categoryService.queryByConditions(dto);
		PageHelper.clearPage();
		//将List<DTO>装换为List<VO>
		List<CategoryDataListVO> voList=new ArrayList<>();
		CategoryDataListVO categoryDataListVO=null;
		for(CategoryDTO categoryDTO:categoryDTOPageInfo.getList()){
			categoryDataListVO=new CategoryDataListVO();
			BeanUtils.copyProperties(categoryDTO,categoryDataListVO);
			voList.add(categoryDataListVO);
		}
		//PageInfo<DTO>转化为PageInfo<DataListVO>
		PageInfo<CategoryDataListVO> categoryDataListVOPageInfo=new PageInfo<>();
		BeanUtils.copyProperties(categoryDTOPageInfo,categoryDataListVOPageInfo);
		categoryDataListVOPageInfo.setList(voList);
		//设置返回数据格式
		head.setCode("0");
		head.setMessage("查询成功");
		response.setBody(categoryDataListVOPageInfo);
		response.setResponseHead(head);
		return response;
	}
/**
	 * 根据条件查找
	 * @param categoryDTO
	 * @return
	 */
	@Override
	public PageInfo<CategoryDTO> queryByConditions(CategoryDTO categoryDTO) {
		//设置查询条件
		Condition condition = new Condition(Category.class);
		Example.Criteria criteria=condition.createCriteria();
		if(!StringUtils.isEmpty(categoryDTO.getName())){
			criteria.andLike("name","%"+categoryDTO.getName()+"%");
		}
		if(categoryDTO.getParentId()!=null){
			criteria.andEqualTo("parentId",categoryDTO.getParentId());
		}
		if(categoryDTO.getId()!=null){
			criteria.andEqualTo("id",categoryDTO.getId());
		}
		List<Category> categories=null;
		PageHelper.startPage(categoryDTO.getIndex(),categoryDTO.getPageSize());
		try {
			 categories= categoryDao.selectByExample(condition);
		}catch (Exception e){
			throw new ServiceException(BesDataExceptionEnum.CATEGORY_QUERY_ERROR);
		}
		PageInfo<Category> categoryPageInfo=new PageInfo<>(categories);

		List<CategoryDTO> dtos=new ArrayList<>();
		CategoryDTO dto=null;
		for(Category category : categoryPageInfo.getList()){
				dto=new CategoryDTO();
				BeanUtils.copyProperties(category,dto);
				dtos.add(dto);
			}
		//将entity分页信息转化为DTO分页
		PageInfo<CategoryDTO> categoryDTOPageInfo=new PageInfo<>();
		BeanUtils.copyProperties(categoryPageInfo,categoryDTOPageInfo);
		//修改list对象
		categoryDTOPageInfo.setList(dtos);
		return categoryDTOPageInfo;
	}
4. 添加List转换类

由于存在大量的List<xxxVO>和List<xxxDTO>的转换,因此抽取出来编写了工具类TransformClass.java.
上一版本:

//vo列表转dto列表 			 
List<CategoryDTO> dtos=new ArrayList<CategoryDTO>(); 			
CategoryDTO categoryDTO=null; 			
for(CategoryDataItemVO vo: volist){
				categoryDTO=new CategoryDTO();
				BeanUtils.copyProperties(vo,categoryDTO);
				dtos.add(categoryDTO); 			
}

修改后:

List<CategoryDTO> dtos=TransformClass.convertList(volist,CategoryDTO.class);

原本五六行需要重复写的代码,使用了工具类以后只需一行就搞定了,而且逻辑清晰一目了然.

修改后的delete方法:

	@GlobalExceptionLog
	@CrossOrigin
	@PostMapping("/delete")
	@ApiLog
	public CommonResponse<String> delete(@RequestBody CommonRequest<List<CategoryDataItemVO>> commonRequest){
		//返回CommonResponse给前端
		CommonResponse<String> response=new CommonResponse<>();
		ResponseHead head = new ResponseHead();
		List<CategoryDataItemVO> volist= commonRequest.getBody();
		int result=0;
		if(volist!=null && volist.size()>0){
			//vo列表转dto列表
			List<CategoryDTO> dtos=TransformClass.convertList(volist,CategoryDTO.class);
			//调用service里面的对应方法进行删除
			result= categoryService.delete(dtos);
			if(result>0){
				head.setCode("0");
				head.setMessage("题目类别删除成功");
			}else {
				head.setMessage("题目类别删除失败");
			}
		}else {
			head.setMessage("请求参数为空");
		}
		head.setEncryption(0);
		response.setResponseHead(head);
		response.setBody(""+result);
		return response;
	}

TransformClass内部还需要注意什么呢?

  • 是否具有通用性. 这里可以采用泛型实现.
  • 是否进行了异常处理.
  • 是否覆盖了所有情况,是否对参数进行了判空等操作,是否一定有返回值.
5. 封装对入参和返参的处理工具类

在第2步的时候,已经将入参和返参规范起来了,统一为CommonRequest和CommonResponse,但是这样做仍然存在较多重复操作,而且对于从入参取得的body部分判空等操作逻辑也不够清晰,所以编写了统一处理的工具类CommonRequestUtils和CommonResponseUtils.

  • CommonRequestUtils:主要用于对入参进行检查,如头部检查以及body部分参数判空和List长度检查等等
  • CommonResponseUtils:用于封装返回成功的报文. 前面提到过,对于操作失败/异常的处理是使用注解进行注入返回的,因此controller本身代码只需声明返回成功的报文,遇到异常会被截获. 那既然都是一样的成功报文,也就不必要每次都在方法一开始声明报文和头部最后设code为“0”再返回,我们把这一样的操作封装成函数success(),参数为要返回的body及其类型.

下面来看修改后的controller代码:

	/**
	 * 删除题目类别
	 * @param commonRequest 前端请求报文[body:List<CategoryDataItemVO>]
	 * @return 响应报文[body:删除成功的记录数result]
	 */
	@GlobalExceptionLog
	//@CrossOrigin
	@PostMapping("/delete")
	@ApiLog
	public CommonResponse<String> delete(@RequestBody CommonRequest<List<CategoryDataItemVO>> commonRequest){
		CommonRequestUtils.checkCommonRequest(commonRequest);
		try {
			List<CategoryDTO> dtos=TransformClass.convertList(commonRequest.getBody(),CategoryDTO.class);
			//调用service里面的对应方法进行删除
			int result= categoryService.delete(dtos);
			return CommonResponseUtils.success(String.valueOf(result));
		} catch (ServiceException exception) {
			throw new BusinessException(exception);
		}
	}

简洁明了了许多,大大提高了代码的整洁度和可读性

6. service层公用字段设值改用注解

实体存在公用字段,如创建时间、修改时间、创建人、修改人等,可采用自定义注解进行注入,原本设置相关数据的代码可以删去.

ps:其实这里创建人和修改人的设置原本就未曾实现,就算不使用注解也需要修改成从redis中取得用户的id再设值,那么改成注解之后可以一并处理了.

	/**
	 * 增加题目类别
	 * @param categoryDTO
	 * @return
	 */
	@Override
	@SetCommonField(methodType= CommonFieldAspect.TYPE_INSERT)
	@Transactional(rollbackFor = {RuntimeException.class, Exception.class, ServiceException.class})
	public int add(CategoryDTO categoryDTO) {
		int result=0;
		if(categoryDTO!=null){
			if(categoryDao.selectByPrimaryKey(categoryDTO.getId())!=null){
				throw new ServiceException(BesDataExceptionEnum.CATEGORY_REPEAT);
			}
			//将DTO对象转换为ENTITY对象
			Category category=new Category();
			BeanUtils.copyProperties(categoryDTO,category);
			//设置id(雪花算法获得)
			SnowFlake snowFlake=new SnowFlake(2,3);
			category.setId(snowFlake.nextId());
			//设置版本
			category.setVersion(System.currentTimeMillis());
			try {
				result = categoryDao.insert(category);
			}catch (Exception e){
				throw new ServiceException(BesDataExceptionEnum.CATEGORY_INSERT_ERROR);
			}
		}
		return result;
	}
7.细节补充
  • 版本号对比
  • 事务注解
  • 增加radis
  • 增加注释

总结

可以看到,controller层代码经过了一个逐渐丰富完善到重构降低重复率的过程,最终一个方法只需十几二十行即可,且逻辑清晰明了,可读性高. 我认为这整个过程也许无法避免,但是可以尽量预见到.
如,一旦发现有经常重复的代码,可以抽象成工具类对其进行简化;巧用自定义注解进行异常捕获或常用/公用数据设值;一些必要的操作,如分页,能提前写好就不要留到后面修改,否则前端接因为收到的数据不同也要进行相应更改;养成随时写注释的习惯,不要到最后再补.
service层代码一般是越来越多的,因为逻辑都在这层,但也要注意过长的方法要尽量拆分成多个短方法.

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值