【湿货】 代码优化

339 阅读6分钟

代码优化

这里无限期做更新。 还是由于本科生比较菜的原因吧,学校里没人教这些,只能偶尔跟小伙伴聊到某些很奇怪的地方怎么写的时候,才能学到。毕竟老师都懒得看你的代码。 可能自己逛github、stackoverflow少了吧orz。 因为自己主要写Java,所以下面就拿Java做例子吧

数据层

1. 数据接收Request

数据接收就比较简单了,因为 Spring 有自带的注解,@RequestBody,@ReuqestParam和@RequestHeader,所以只要打上注解就好了。

但是实际上,这里有个巨坑,一直无法理解,就是@ReuqestBody与@ReuqestParam无法一起使用,也就是说,当你希望通过一个实体对象接收数据,用到@ReuqestBody Data data,的时候,你就没有办法使用@RueqestParam接收其他参数了。 于是找了一种解决方法,通过原生的HttpServletRequest的getParam方法来接收,且这些参数必须待在URL上,否则后台将会接不到。于是有了下面的代码。

public Result cloneActivity(@RequestBody ActivityDto activityDto,
                               @RequestHeader("Authorization") String token,
                               HttpServletRequest request) throws Exception {
        String acid = request.getParameter("acid");
        Integer options = Integer.valueOf(request.getParameter("options"));
        return activityCloneService.cloneActivity(acid, activityDto, options);
}

期初呢感觉一点毛病都没有的,因为实在没办法了,接不到参数啊。 实际上只是因为自己懒得再封装多一层数据层,因为觉得每一个接口都要自定义一个数据进行@RequestBody的接收,感觉要命的啊。 但是自己封装了个,这样的类之后,好像这个就没啥毛病了。

public class RequestData<T> {
    private T data;
    private Map<String, Object> map;
    
    public static <E> getMapParameter(String key, Class<E> clz) throw ReuqestDataException {
        Object obj = map.getOrDefault(key, null);
        if (obj == null) {
            throw new ReuqestDataException(Result.fail("参数不能为空", ErrorCode.BAD_REQUEST));
        }
        if (clz.isInstance(obj)) {
            return clz.cast(obj);
        }
        throw new ReuqestDataException(Result.fail("参数类型不正确", ErrorCode.BAD_REQUEST));
    }
    
    // get set ...
}

2. 数据返回Response

返回层其实相对比较简单。但是这上面也是经过了很头疼的脑洞时间 最初写项目的时候,返回的数据报有:

// 正确返回案例
return "返回数据成功"

然后发现没有错误案例,就成了:

// 正确返回案例
Map<String, Object> map = new HashMap<>();
map.put("msg", "成功返回");
map.put("anyData", data); // 注意这里的anyData,这个变量名是自定义的
return map;
// 错误返回案例
Map<String, Object> map = new HashMap<>();
map.put("errMsg", "错误返回");
map.put("errCode", "错误码");
return map;

总觉得这样好像,没啥毛病了。 但是头疼的地方来了,当时这个项目是两个人写的。竟然errorMessage写出了。不知道多少种写法 有:errMsg,errorMsg,errorMessage.. 本人竟然某天晚上一个个搜把所有的这些都改统一了。改统一了!!?? 不得不说当时有多蠢。

直到又换了个项目,才把这里给妥妥的封装成了:

public static Result Success = success(null);

public static Result False = fail("接口未实现", ErrorCode.INTERFACE_NOT_FINISH);

@ApiModelProperty(value = "结果说明 true = 成功 false = 失败 ", allowableValues = "true,false", required = true)
//参数说明 参数肯定会存在
private boolean status;

@ApiModelProperty("提示信息")
private String message;

@ApiModelProperty("错误信息")
private String errorMsg;

@ApiModelProperty("错误代码")
private int errorCode;

@ApiModelProperty("操作返回的数据结果")
private Object data;

就大概的省略一下自定义封装的构造函数吧 感觉也还是不错的是吧。 但是问题又来了。这些数据究竟实在服务层(service)上返回呢,还是在控制层(Controller)返回呢。 这个问题我纠结了很久,但是因为有业务错误情况呀,如果我在控制层返回,那么我错误的业务逻辑,应该如何判断呢,那么我的控制层是不是又会多很多奇奇怪怪的东西呢。毕竟控制层只是相当于转发路由的。 于是服务层代码大概就这样的:

public Result deleteActivityAtlas(String acid, String pid) throws Exception {
    ActivityAtlasDto activityAtlasDto = checkActivityAtlasBelongToActivity(acid, pid);
    if (activityAtlasDto == null) {
        return Result.fail("没有操作权限", ErrorCode.FORBIDDEN);
    }
    activityAtlasRepository.delete(activityAtlasDto);
    return Result.success("删除图片成功");
}

这个砖就这么把项目基本搬完了。也没人觉得他很怪异。只是看起来Service层的复用性,不太好,耦合度有那么点高。 直至某一天跟小伙伴(开发大佬)聊天,问他这一块怎么做的。 他说,我直接把业务当异常抛出来啊。orz 抛出来??!然后全局异常处理器一接,美滋滋。 然后代码差不多就成了这样了。

public String updateActivityAtlas(ActivityAtlasDto activityAtlasDto) throws ResultException, Exception {
    ActivityAtlasDto correctActivityAtlasDto = checkActivityAtlasBelongToActivity(activityAtlasDto.getAcid(), activityAtlasDto.getPid());
    if (correctActivityAtlasDto == null) {
         throw new ResultException(Result.fail("没有操作权限", ErrorCode.FORBIDDEN));
    }
    UpdateUtil.copyNonNullProperties(activityAtlasDto, correctActivityAtlasDto);
    activityAtlasRepository.save(correctActivityAtlasDto);
    return "更新图片成功";
}

emmm,大概服务端的Reuqest和Response就那么多了吧。 如果还有能优化的地方,待补充ORZ

数据实体

如果平时大家用MyBits或者Hibernate,那么应该一定会有这样的情况吧。 某些字段,我不想传给前端。 某些字段,我不想从后台搜出来。 那么这种时候,从数据设计上,就出现了DTO和VO。DO(Entity) 这里就不做解释了吧。就是元数据对象。

元数据为DO,而DTO和VO都是可以直接从DO中继承的。 DTO是专门跟数据库持久层打交道的数据封装对象。 VO是专门跟前台打交道的数据封装对象。

对于VO

  1. 不需要的属性 对于不需要的属性,这边的做法是继承后,把那个属性在VO类中重写了,并加上@JsonIgnore这个注解,即可在该类序列化的时候将其无视。不传给前端
  2. 从DTO转过来的属性 往往从DTO转过来的属性很奇怪,比如一些属性是个对象,对象里的属性需要显示到前台。 栗子如下:
public class DataDto {
    private ChildrenDataDto childrenDataDto;
    private String message;
    // set get ...
    }
class ChildrenDataDto {
    private String childrenMessage;
    // set get ...
}

你给前端的时候,前端总不希望,他要childrenDataDto.childrenMessage把这个属性取出吧。 这时候,我们可以做个mapper文件,将childrenDataDto.childrenMessage 映射成 childrenMessage。然后通过反射的原理,将DTO转成VO,这样一想,是不是美滋滋。 然而这种想法,早有大神实现了,Dozer工具类(基于BeanUtils)。 只需要通过

dozerFactory.convert();

这个方法即可实现转换。

这样子设计是不是顿时感觉,VO原来那么好做

对于DTO

DTO就不太好优化了,现在的优化思想有两个。 首选我们要了解清楚,DTO到底是干啥的,简单来说,就是跟数据库打交道的。 单表查询的DTO其实基本上可以等同于DO,但是连表查询的时候,DTO就会变得极其复杂。 而往往,我们需要的DTO是各式各样的,从数据库中搜出来的也是各式各样。 我对DTO的优化想法如下

  1. 子类继承 父类只提供基础的一些数据 而子类则实现更多的属性。更为具体
  2. 通过@Transient注解 这个注解可以无视掉某些字段不进入数据库的SQL查询。 可以同VO一样。继承下来再重写修改啥的。 其他的DTO。就老老实实写吧哈哈哈哈哈哈哈。