异常,天使还是魔鬼

异常,天使还是魔鬼?

我们写代码分两种:

  • 自信,愉快地写的,业务逻辑正常
  • 忐忑,苦恼地写的,小心处理各种错误和意外逻辑

为了缓解负面的情绪,OOP的异常机制横空出世!

try {
	//主体逻辑
}
catch(Exception e) {
	//异常逻辑
}
finally {
	//公用的清理工作,不要在这return
}

这个机制就像是伏妖袋一样的,捕到了,贼开心;没捕到呢???

三个江湖派别

将对异常的态度,对所有编程语言分一下类:

  • 嗤之以鼻派:C【用个毛】

  • 拥趸派:Java、C#【用的一层层地嵌套,不用不爽】

  • 吃瓜派:C++、Objective-C、Swift【支持,但很少用】

所以我们能看到,C一般不出错,要不就是重大错误;而Java虽然经常报错,至少能磕磕碰碰往前跑(但有些异常提醒画面,还不如崩溃了呢…)

异常的种类

大类上分:

  • 业务异常
  • 系统异常

业务异常

  • 定义:根据业务需要而定义的异常(比如你想请客,但钱不够)

  • 特点:

    • 业务特殊性
    • 一定有地方抛出,否则也没有存在的必要
    • 一定会在系统抛出,但不一定在本系统捕获

系统异常

  • 定义:程序运行中,依赖的底层模块或更底层的runtime(通用模块)抛出的异常

  • 特点:

    • 抽象程度高
    • 意料之外(靠经验发现)

我们千万别把此类系统异常,当作一个正常业务的逻辑分支!比如:

void OperationInQueue {
    while(status == Status.NORMAL) {
        try {
            String data = mg.pop(); // 从中间队列获取数据
            processData(data);
        } 
        catch(NoDataException e) {
            DoSomething();
        }
        catch(Exception e) {
            status = Status.ERROR;
        }
    }
}

我们千万别写成:

void OperationInQueue {
    while(status == Status.NORMAL) {
        try {
            if(mg.isEmpty()){
            	DoSomething();
            } else {
                String data = mg.pop(); // 从中间队列获取数据
            	processData(data);
            }           
        } 
        catch(Exception e) {
            status = Status.ERROR;
        }
    }
}

从某种角度来说,这段代码没有错,但是它的逻辑像是我们在“期待这个异常”,它不是业务逻辑啊!一定要分开处理!而且耦合度高的代码,不是我们要的吧…

此外,系统异常有个重要的分支——runtime异常,我们大部分人也是和这个东西打交道。(比如,数组越界,空指针等等常见异常)

throw——手榴弹什么时候扔

throw就像手榴弹,一旦被抛出,(往上抛)在它没被catch之前,整个程序进入紧张状态,直到捕获了才松懈

  • 本质:向所有模块宣布:“各单位注意!!我抛出了一个异常手榴弹!!!”
  • 目的:在开发和测试阶段确保它零概率被抛到最上层,也就是在被最终用户感知前,被中间层的代码处理掉

其实就是给你一个“亡羊补牢”的机会啊!我们来看一个例子:

public class TestException {
    public static void main(String[] args) {
        method1(); 
    } 
    private static void method1() {
        try {
            method2();
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    } 
    private static void method2() throws FileNotFoundException {
        File f = new File("D:/LOL.exe"); 
        System.out.println("试图打开 D:/LOL.exe");
        new FileInputStream(f);
        System.out.println("成功打开");
    }
}

method2中需要进行异常处理,但是method2不打算处理,而是把这个异常通过throws抛出去,那么method1就会接到该异常。 处理办法也是两种,要么是try-catch处理掉,要么也是抛出去,这里的选择是处理掉

问题来了:我们什么时候该把自定义异常往外抛呢?

记住,不要畏手畏脚,怕别人淦你,根据业务需求,该抛抛(比如你是负责饭馆的模块,只知道怎么收钱,但你不负责处理别人钱不够的情况,这就需要别人来处理顾客没钱的事实),做好沟通就行了(特别你是做底层的话)

catch——收炸弹的垃圾筐

catch的本质,其实就是带导航的goto,自动定位异常,而不是发散的

优势:

  • try-catch的机制使得主体逻辑和错误逻辑互相隔离,可读性强,且易于维护(这就是C没有的)

  • 借于OOP的体系,catch可以有大大小小不同的垃圾筐叠成,每个层级过滤不同种的垃圾,throw不差别抛出,但我catch有差别的接,从而让不同异常体现出不同的等级

  • Exception可以捕到所有它的子类异常,使得开发者可以提高编程效率

void personEat(Person p,Food f) {
    try {
        person.eat(f);
    }
    catch(NotEnoughMoneyException e){
        // 处理钱不够的异常
    }
    catch(Exception e){
        Log(e.Message); // 边边角角先放一边
    }
}

这个特性极大简化了逻辑,提高编程效率,避免程序员开发时因为边角分心

那我们catch到的异常,接着抛还是捕获了?——还是沟通,还是看业务

千万不要好心一人全揽了,导致异常消息队列数据大量流失嗷!

异常使用技巧

看病要趁早

关系到后面逻辑的要早抛,别等到方法深处才抛,看个付钱的例子:

public void pay(BigDecimal money) {
    if(money < needToPay){
        throw new NotEnoughMoney("Money is not enough!");
    }
    // 之后的逻辑是在钱够的情况下进行的,要提前检查钱够不够
}

不要加大catch负担

能在主体逻辑里避坑的,先处理掉,别太依赖异常啊!

try {
    // 并非多此一举,这是减少依赖,方便移植
	if(a != null)
		a.call(b);
    // 其他逻辑
}
catch (Exception e){
    Log(e.Message);
}

避免try花了眼

尽量不要在方法中写try-catch,不然到时候各种嵌套,眼花缭乱,不方便维护,实在要用,写个static工具类封装起来吧,求你了!

克制!不滥用!

给我记住几条铁则就好:

  • 最底下的model层不用考虑异常处理,抛就完了
  • 业务层尽量精确地按异常种类处理异常
  • 最上面的展示层捕获最终的所有漏网之鱼(比如用Exception爸爸)

总结

  • 异常不是给用户看的,是给我们看到的!

  • 异常机制的最大作用就是空间的隔离:主题体逻辑与错误逻辑分开

  • 啥时候抛,啥时候捕,全靠沟通(如果是自己个人的话,看上面的铁则做就行了)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值