我在这家公司犯的错

其实这篇文章的提纲内容,我早就写了。我在写自己简历的时候,就总结出了我这一年来做过的东西,也顺便写出了我这一年犯过的所有错误。现在总结下来,希望(重音)以后能少犯同样的错误。

异常的处理

我在这家公司里做的第一个项目,根本没有统一的异常处理。所有的错误情况字段验证都是手动写的。这种重复的代码让我感觉像吃屎一样难受。

所以第一个我能控制的项目,我就按照spring的手册配置了整个错误处理。用@ExceptionHandler 这个注解来解决大部分异常的问题。而字段的验证,也使用了Spring 提供的插槽来实现了。我能想到的异常,都进行了自定义,然后使用注解去捕获去处理。

总之就是相比上一个项目,这个项目的错误处理非常优雅严谨。字段的验证,异常的跳出,跳出的异常返回什么样的内容。甚至连系统自带的ErrorController 我也实现用接口来实现了。总之我能想到的东西,我都尽力处理了。

然而时间证明,我以上的所作所为并没有产生足够优雅的效果。实际的情况并不总如我想象的一样。我仔细说一下到底怎么回事。

因为实际的项目中,各种异常情况总是越来越多的,然后再对每种异常的单独捕获和处理,这种重复的内容和之前吃屎的内容,一模一样。(具体来说就是定义一个异常,然后在异常处理的接口中加一个处理刚刚那个异常的带有ExecptionHandler标记的方法)

不过很幸运的是,我看过一个设计,有一个项目中处理异常,就只处理了Execption, 我当时并不理解,也不以为然。直到现在回想起来,才理解这是一个精巧的设计。

其实我还见过另一种错误处理方式,也很有趣。另一种错误处理使用的是AOP,拦截所有Controller的请求返回内容,如果出现异常,使用AOP来捕获,进行统一处理返回异常。

反思

从这件事情中,得到的启示是:重复的内容,尽量使用尽可能常规的处理,不要自己额外给自己增加麻烦。尽可能的减少通用的工作量。我这边的错误就在于,每次异常出现,都要额外补充一些一样的东西。

如果再让我重新做设计,我可能会这么做。依旧是每个错误定义一个异常。然后在异常中或者是什么位置,定义一个异常表,来把异常id和错误提示内容对应起来。而整个异常处理的流程中,要改造成,写一遍就不需要再写任何内容的了。经过这样的处理,既能优雅的新增异常,不增加过多的负担,又可以完整的实现功能。而其他位置出现的异常,都可以经过包装处理,转变成统一的格式。

强行用redis的无状态缓存设计

其实这个事情,并不是我开始提的,而是项目组组长最开始说这件事情。然后我觉得挺有意思的,就按照他的思路做了。

由于组长发现,网上用token+redis的人比较多,而且比较成熟。他觉得我们也可以追求这种有点时髦的东西,就让我做这件事情。

在做这个事情之前,我连session、token、各种验证都不太懂,拦截器也没写过。作为一无所知的小伙子接下了这个活。然后就按照组长的要求来做这件事情。而且在设计的时候,就出现了一个非常尴尬的错误。(我已经忘了哪里错了,不过印象中是有几个字段很尴尬)

经过了接近一年的各种业务的实现与完成,综合看来,这种 token 的设计很不合适。因为我们目前完全都是网页的应用,所有的token都要自己手动处理,有些位置没有session真的不方便。好在目前项目只是跟人员有关,其他的没有处理,如果很复杂,经常需要session处理的话,就很麻烦了。

反思

这个问题的主要错误原因是,从最开始就听从了组长的要求,要用这个做,恰好我也想使用这个做。我的私欲是我想试验,并学一下redis,组长的私欲是因为这个省事,网上到处都是,而且看起来很先进,可以吹,能有好名声。综合起来,做出了这么一个设计。

这个设计从最开始的目的上就是错的。session在一个web项目中,是非常必要的,用于追踪用户状态。即便是没有session,也要手动实现一个类似session的功能,还不如直接在原始session结构上进行扩展。如果不清楚改成token+redis的目的,还不如不改。

第一个错误就是设计目标上的错误。第二个就是忘了哪里错了(。

response 的返回值

整个response的返回对象是我写的,我提供了最常用的对象。这个东西,我在最开始做出来的时候,自我感觉还挺好的。

看过几个别人的设计之后,就没有这种感觉了。目前的bean是这个样子。

public class CommonResp<T> {

    public CommonResp() {
    }

    public CommonResp(String messages) {
        this.messages = messages;
    }

    public CommonResp(String messages, T data) {
        this.messages = messages;
        this.data = data;
    }

    private String messages;

    @ApiModelProperty("泛型参数,可能是string,可能是对象,也可能是list")
    private T data;
    //getter setter 。。。
}

核心就两个字段,一个消息实体,一段话。消息实体是给程序的,话是给人看的。

这个设计乍看起来没问题(反正我没看出来问题),但是在实际使用的时候,会出现一些限制。

我这里面的code ,完全使用由http协议定义的十几个。没有自己定义任何错误,我在实际构建这个的时候,就很尴尬了,总是找不出恰当的错误code,来表示业务系统的错误。当时根本没有感觉到是自己的CommonResponse 设计有问题。现在反思意识到。

其实看大平台的api文档,也能窥见一部分。他们的返回值给的东西是什么什么样的,都是经过设计的。他们带code是有理由的。

另一个设计错误就是,正确消息的默认值,尽量能提供默认值的都提供一个,方便嘛,平时用的时候也省事。

反思

这种设计,其实没啥可反思的,就是经验水平不够。在最开始设计的时候,压根就不知道自己的不知道,这真的是没办法的事情。

后记

这篇文章原记于 2019.03.05 放了很久也没有发出来。最近在整理blog嘛,顺便都整理下发出来。

实际上在现在这个时间点,看我半年前的反思,仍然有很多不足的地方。