我对web应用“错误处理”的一些思考

我对web应用“错误处理”的一些思考

本文的是来源对于:我已经受够了“系统异常”!的思考

该文是从用户体验不好的“系统异常”说起,给出了一套基于异常处理的方案。

在我有限的开发时间里,也有过一套关于此类问题的处理方案,和上文略有不同,在这里简单的总结一下。

明确本文的适用场景

本文是以web应用的错误处理方案,并不局限与C#语言。部分场景也适合C/S开发。

明确用到名词的具体含义

上文:《我已经受够了“系统异常”!》
异常(类):特指各种语言使用的Exception类。
系统异常:由语言库带的基础类库,以及三方类库(数据库SDK,公司的内部SDK,公司内部微服务的调用,项目里一些与具体业务无关系的基础模块)抛出的异常。
业务异常:基于产品逻辑,需要抛出并打断整个业务流程的异常。
业务错误:基于产品逻辑设计出的一些错误返回。
业务返回对象:包含错误代码,错误消息,以及需要返回的数据对象。

用户看到的“系统异常”不仅仅是程序员的责任

用户看到的“系统异常”不仅仅是程序员的责任,还包含了产品经理的责任,“系统异常”仅能作为兜底条款存在,但具体的错误展示内容,需要产品经理与程序员一起协同来完成。

在产品设计之初,产品经理就需要根据产品的内容,定义出一些可能的错误,程序在实际开发过程中,会对这些错误做出更详细的补充。但一般来说,这类错误,归属于业务错误,业务模块开发过程中,这类返回通常是以函数对象返回业务对象里带的信息。

处理方案设计

方法实例

public ServiceModel1
{

    public Response GetUser(userName, pwd){

        if(userName == null)
            return new Response(ErrorCodeMap.ParamError, "参数错误");

        // ... 从数据库获取 user 数据

        var user = db.GetUser(userName);
        if)user == null)
            return new Response(ErrorCodeMap.LoginError, '用户不存在或者密码错误。');

        if(user.pwd != MarkPWD(pwd)){
            return new Response(ErrorCodeMap.LoginError, '用户不存在或者密码错误!');
        }

        return new Response(user);
    }

    public Response LoginXXX(userName, pwd, loginType){

        var ret = GetXX(param1, param2);
        if(ret.errorCode != 0){
            log.error("doXxx error. userName:{userName} pwd.length:{pwd?.Length}")

            return ret;
        }

        // ... 根据ret的user数据,再构建一个基础的用户数据返回

        var viewUser = new ViewUser(ret.data);

        return new Response(viewUser);
    }
}

这里的设计是传统的接口返回错误信息,而在接口外部,需要根据接口的返回信息再做进一步的处理。

方法里发生错误时,返回的错误就是我说说的业务逻辑错误,一些错误会在开发前期,由产品经理定义,如:登录错误。

一些,如用户没输入一些特定参数就提交了请求,返回的参数错误,则可能会在程序开发的时候定义。

这里和上文不同的地方,就是业务判断里不是都会抛出异常的。

当然,再系统的最外面一层还会由对未捕捉的异常做一个兜底的异常捕捉。

对象设计

public class Response
{
    public int code;

    public string message;

    public object data;
}

方法返回的Response对象没什么好说的,和上文的自定义Exception类似,包含 code 和 message。


public class ErrorCodeMap{

    public int Success = 0;

    public int ParamEror = 1001;

    public int LoginError = 2001;
}

关于CodeMap里的Code,那个值是否表示正确,曾经在公司里和同事有过探讨。

个人倾向于 0 表示正常,根据应用实际情况,将错误代码做一下号码段的分类。
例如,公共类基础错误,从1000开始,登录类的从2001开始,并在开始定义一个兜底的分类错误吗,然后再根据实际情况在号码段上扩充codeid。你没有看错,需要一个兜底的错误代码。

当然,正确的codeid,也不一定是0,例如,我们http请求返回正确的code是200。以及一些从c/c++升级过来的项目里,表示正确的code是1,表示错误的code是0,这些code需要系统在最初的时候就明确清楚。各项目之间也要互通,曾经做过的一个升级项目就是既有0,也有200作为正确的情况。当然对于这种情况,我们可以使用一些代码技巧来处理。

public class Response
{
    ... 

    public bool IsSuccess { get { return code == 0 || code == 200} }
}

为什么没做异常

上面的场景业务模块之间调用时的返回,如果系统一切正常,你能从业务方法调用返回的对象里,知道这次调用是否成功,都由你来判断是否要中止后面的处理流程以及是否对之前的业务做一些补偿。自然是不需加异常处理代码。

如果是发生了异常抛出,则是说明,底层内部觉得自己的问题实在太严重了,不得不终止,自然也不需要在业务层对这个进行处理。异常会直接抛到最外层去负责处理以及统一补偿。

当然,也不是说业务内部不能做异常捕捉,一些三方的调用,它们不像自己模块之间的调用约定,该加的异常处理也还是得加,并需要根据异常情况再考虑下一步处理。

弊端

可能是对开发人员要求会更高一些了。他需要属性每个接口的参数返回,需要知道其内部可能会出现怎样的错误返回,发生这些错误后,业务代码是直接返回错误出去的,还是再包装一次返回出去呢。写每个方法的时间又会变得更多一些了。

代码显得更加荣誉一些了,每个业务之间的调用都需要先判断错误,基于错误再写一些处理代码后,才能写正常流程的代码。估计这个也就是上文里不对业务错误单独处理,一律已异常的方式往外抛出吧。

我的方案还有优化空间吗?

我自己能改进的地方没多少了,这里可能还需要各位网友群策群力。

实际上软件的错误提示处理,一直都是一个很花费时间的工作。在开发阶段,就需要在代码里不断的埋入日志记录,单纯依靠异常抛出的堆栈信息是不够的。给界面展示用的错误消息也需要和产品经理不管沟通修改。

好的产品都是要时间磨出来的。当然要是时间不够,我也不介意在屎山上再堆上一些不可描述物品。

«   2023年9月   »
123
45678910
11121314151617
18192021222324
252627282930
网站分类
文章归档

Powered By Z-BlogPHP 1.6.5 Valyria

Copyright csharptools.cn Rights Reserved. 桂ICP备17007292号-1