代码精进之路
范学雷
Oracle首席软件工程师,Java SE安全组成员,OpenJDK评审成员
立即订阅
6350 人已学习
课程目录
已完结 47 讲
0/4登录后,你可以任选4讲全文学习。
开篇词 (1讲)
开篇词 | 你写的每一行代码,都是你的名片
免费
第一模块:代码“规范”篇 (16讲)
01 | 从条件运算符说起,反思什么是好代码
02 | 把错误关在笼子里的五道关卡
03 | 优秀程序员的六个关键特质
04 | 代码规范的价值:复盘苹果公司的GoToFail漏洞
05 | 经验总结:如何给你的代码起好名字?
06 | 代码整理的关键逻辑和最佳案例
07 | 写好注释,真的是小菜一碟吗?
08 | 写好声明的“八项纪律”
09 | 怎么用好Java注解?
10 | 异常处理都有哪些陷阱?
11 | 组织好代码段,让人对它“一见钟情”
12丨组织好代码文件,要有“用户思维”
13 | 接口规范,是协作的合约
14 | 怎么写好用户指南?
15 | 编写规范代码的检查清单
16丨代码“规范”篇用户答疑
第二模块:代码“经济”篇 (14讲)
17 | 为什么需要经济的代码?
18丨思考框架:什么样的代码才是高效的代码?
19 | 怎么避免过度设计?
20 | 简单和直观,是永恒的解决方案
21 | 怎么设计一个简单又直观的接口?
22丨高效率,从超越线程同步开始!
23 | 怎么减少内存使用,减轻内存管理负担?
24 | 黑白灰,理解延迟分配的两面性
25 | 使用有序的代码,调动异步的事件
26 | 有哪些招惹麻烦的性能陷阱?
27 | 怎么编写可持续发展的代码?
28 | 怎么尽量“不写”代码?
29 | 编写经济代码的检查清单
30丨“代码经济篇”答疑汇总
第三模块:代码“安全”篇 (14讲)
31 | 为什么安全的代码这么重要?
32 | 如何评估代码的安全缺陷?
33 | 整数的运算有哪些安全威胁?
34 | 数组和集合,可变量的安全陷阱
35 | 怎么处理敏感信息?
36 | 继承有什么安全缺陷?
37 | 边界,信任的分水岭
38 | 对象序列化的危害有多大?
39 | 怎么控制好代码的权力?
40 | 规范,代码长治久安的基础
41 | 预案,代码的主动风险管理
42 | 纵深,代码安全的深度防御
43 | 编写安全代码的最佳实践清单
44 | “代码安全篇”答疑汇总
加餐 (1讲)
Q&A加餐丨关于代码质量,你关心的那些事儿
结束语 (1讲)
结束语|如何成为一个编程好手?
代码精进之路
登录|注册

10 | 异常处理都有哪些陷阱?

范学雷 2019-01-25
上一讲中我们聊了聊怎么用好 Java 注解,今天我们谈谈怎么处理异常。
处理好异常状况是掌握一门编程语言的基础,也是我们编程离不开的基本功。相信你对异常处理的机制已经很熟悉了。异常处理便捷、灵活、好用。但是,越好用的东西,我们越容易忽视它的缺陷。异常处理就有很多我们容易忽视的陷阱。今天,我们来聊聊这些问题,以及该怎么处理这些问题。

异常就是非正常

异常状况,就是不正常的状况。
异常状况的处理会让代码的效率变低。一个流畅的业务,它实现代码的执行路径,理想的状况就是没有任何异常状况发生。否则,业务执行的效率就会大打折扣。既然这样,我们就不应该使用异常机制来处理正常的状况。
这一点不难理解。可是,由于一门语言无法理解什么样的状况是正常状况,什么样的状况又是异常状况,也就无法限制异常机制使用的具体场景。所以作为程序员,我们需要自己解决好这个问题,不要滥用了异常机制。
比如说,很多 API 的设计有检查参数有效性的方法。如果参数通过检验,就没有异常抛出,否则就会抛出异常。在使用这个方法的代码时,我们需要检查有没有抛出异常来确认参数是否有效。
/**
* Check if the user name is a registered name.
*
* @throws IllegalArgumentException if the user name is invalid or
* not registered.
*/
void checkUserName(String userName) {
// snipped
}
这是一个糟糕的设计!
在这个例子中,如果 userName 字符串不符合规范,这是一个异常状况; 如果 userName 不是一个注册用户,这通常是一个正常状况。 在正常状况下使用异常处理,无疑会降低系统的效率,以及编码的效率。
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《代码精进之路》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(14)

  • 极客不落🐒
    当方法中返回的数组或集合,会有为空的情况,不要直接返回 null(会强迫调用方需要进行判空处理,否则可能出现 NullPointerException),最好返回大小为 0 的数组或集合。

    其实分配一个空数组或集合所花费的时间和空间,性能损坏是很小到基本可以忽略不计的。同时,如果返回的空数组或者集合是 immutable(即不可变的)的话,可以进一步定义成 static final(对于数组而言)或者 Collections.emptyList()/emptyMap()/emptySet(),来公用同一个对象,减少性能影响。

    作者回复: 这个留言必须赞!

    2019-01-30
    1
    25
  • 木白
    “ 对于异常类名,我们要准确地选择异常类。” 老师,我们应该怎么去快速准确地知道需要抛哪个异常呢?因为有时候只是觉得应该做异常检测,但是属于哪种异常自己也描述不清楚。需要把jdk中的异常都记下来吗?我就是那种直接抛Exception异常的。

    作者回复: 坏消息是,即使我们记住了所有的JDK异常类,也仅仅是异常世界的一小部分,不够用的。 好消息是,没有人能记住即便是JDK这么少的异常类,所有我们也不要折磨自己去记住所有的异常。

    首先要记住,异常是一个可以扩展的类,需要时可以自己定义。所以,你的应用可以有自己定义的异常类。

    弄不清楚使用哪种异常,有两种情况比较典型,一种是不了解调用的类;另一种是不了解自己编写的代码的逻辑。这两条清楚了,异常就清楚了。这两条不清楚,代码也很难清楚。

    异常就不正常的状况。不正常的状况是什么,搞清楚了,异常该是什么也就知道了八九分。然后,去找合适的现存异常类,或者定义新的异常类,剩下的一两分工作也就凑齐了。

    比如说,我回复这条信息的时候,总是输错字母。要是用异常表示,这个不正常的状况就是“键盘输入信息错误”。 然后,在你使用的类库中,去寻找有没有表示这个不正常状况的异常,找到了就用。找不到,如果你想精确定义,就自己定义一个KeyboadInputException; 如果你不想定义新异常,就扩大概念,从“键盘输入信息错误"扩大到"输入信息错误"。然后IOException就是大家都常用的异常了,然后你就可以使用new IOException("键盘输入信息错误")来表示“键盘输入信息错误”这个不正常状况了。

    就这样,了解的代码,了解了不正常的状况到底是什么状况。

    2019-03-28
    2
  • 老杨同志
    1. 没找到结果应该是正常业务,不用抛出异常
    2. 缺失必要的参数校验

    import java.util.HashMap;
    import java.util.Map;

    class Solution {
        /**
         * Given an array of integers, return indices of the two numbers
         * such that they add up to a specific target.
         * return null if nums==null or nums.length==0 or result not found.
         */
        public int[] twoSum(int[] nums, int target) {
         if(nums==null || nums.length==0){
         return null;
         }
            Map<Integer, Integer> map = new HashMap<>();
            for (int i = 0; i < nums.length; i++) {
                int complement = target - nums[i];
                if (map.containsKey(complement)) {
                    return new int[] { map.get(complement), i };
                }
                map.put(nums[i], i);
            }
            return null;
        }
    }

    作者回复: 改的很赞!

    另外,有一个小技巧和你分享一下。如果返回值是空值(null),如果调用代码没有检查空值就调用,会抛除NullPointerException异常。如果返回空数组(int[0]), 就不会出现这个问题了。 这个小技巧可以减少调用代码的错误,这样设计的接口比较皮实耐用。如果返回值是数组或者集合,我们通常使用这一个技巧。

    2019-01-25
    2
  • Demon.Lee
    老师,请教一个问题,在写定时任务的业务时,一开始从表中提取一些记录,然后针对每条记录进行业务处理(业务处理有事务),如果业务处理成功,则更新这条记录状态为A,如果业务处理失败,则更新这条记录状态为B,此时我就感觉违背了“不要用异常处理业务逻辑”这条规则,老师有什么好建议。另外,我想到的是用两个线程处理 ,主线程更新状态,子线程处理业务逻辑,但是需要线程间同步。
    public void execute(){
    List<T> rows = list();
    for(T t:rows){
        try{
          deal(t);
          updateStatus("A");
        }catch(Exception ex){
           updateStatus("B");
        }
    }
    }

    作者回复: deal()能不能不抛出异常?或者不正常的时候抛出异常,正常的业务不抛出异常?

    使用线程处理更复杂,还不如捕获异常呢。

    2019-03-04
    1
  • 草原上的奔跑
    课后习题,1.使用异常处理正常的业务逻辑。2.异常没有在接口规范中标记清楚。3.异常类名感觉用在这里不合适

    作者回复: 找的都对。你想到怎么修改了吗?

    2019-01-25
    1
  • Sisyphus235
    异常和错误是必须要分离的部分,错误必须抛出,异常要分清类型处理。
    对于开发异常,应该及时抛出,在开发中解决;
    对于生产中异常,要做好 log,及时报警,比如用 Sentry 处理 500 的服务器异常,用 log 记录核心功能的状态
    2019-05-22
  • 北风一叶
    异常的使用要符合具体的场景,这一条非常虚,不具备可执行性

    作者回复: 哈哈,看来你喜欢干脆的结论。

    2019-03-08
  • 拉格朗日的忧桑
    从函数的单一职责规范来看,该方法for循环里面的逻辑可以提取出来,单独称为一个方法
    2019-02-22
  • 李星
    如果是异常没有被try-catch住的线程呢?

    作者回复: 我们先来看看线程的主方法声明“public void run()”。这个声明没有抛出检查型异常,只能抛出运行时异常。所以,检查型异常一定要在线程的实现中得到处理;否则的话,编译器应该报错的。这个方法可以抛出运行时异常。一个线程,像一个普通的方法一样(run()),抛出运行时异常后,线程就终止了。问题在于,线程通常共享资源,如果线程之间有联系,很多事情就会发生,依赖于线程的具体实现逻辑。比如说,如果一个线程要等待另一个线程的I/O,也许会阻塞。

    2019-02-12
  • 李星
    想问一下作者,在多线程情况下时,当某一个线程发生运行时异常,并且不处理时,是否真的会阻塞当前线程呢?使得这个线程被废掉。?

    作者回复: 当前线程是发生异常没处理的线程吗? 还是不同的两个线程? 没太明白问题。

    2019-02-12
  • pyhhou
    思考题:
    1. 对输入数组需进行预判
    2. 方法内如果有异常抛出,需进行标记描述
    3. 异常名称名不副实

    import java.util.HashMap;
    import java.util.Map;

    class Solution {
        /**
         * Given an array of integers, return indices of the two numbers
         * such that they add up to a specific target.
         */
        public int[] twoSum(int[] nums, int target) {
            if (nums == null || nums.length ==0) {
                return new int[0];
            }

            Map<Integer, Integer> map = new HashMap<>();
            for (int i = 0; i < nums.length; i++) {
                int complement = target - nums[i];
                if (map.containsKey(complement)) {
                    return new int[] { map.get(complement), i };
                }
                map.put(nums[i], i);
            }
            
            return new int[0];
        }
    }

    最后还想问下,如果说是 return 的不是 null 而是空数组,是不是就不需要在文档描述中标记写明?

    年过了一半了,看看自己拉下的章节还有不少,得抓紧了,祝老师新的一年心想事成,身体健康,工作生活一切顺利~ 今年励志一定要好好和你学写代码,稳扎稳打,快速成长

    作者回复: 新春快乐!

    JDK的习惯是,不论是null还是空的集合、数组,都在文档描述中标记清楚。这样,调用者能确切地知道该怎么检查。

    2019-02-09
  • 天佑
    大师,断言呢,使用场景是什么。

    作者回复: 简单的说,就是尽量不要使用断言。断言的设计是为了提高代码质量,可是断言一般有三个状态:断言是否启用;如果启用,断言是否成立。这让编码的逻辑很混乱,阅读的时候我们不一定能够把三个逻辑考虑周全,增加了编码错误的几率,降低了代码质量。

    2019-01-26
  • 我来也
    看不懂java 看这个异常处理有点吃力 🤦‍♂️

    作者回复: 有问题就留言,我们一起把疑惑的地方解决掉。

    2019-01-25
  • 王智
    这个小节看着懵懵的,看完之后完全不记得看了个啥,(╥╯^╰╥).还得看第二遍呀.

    后面的题目那个算是正常的业务,不应该用异常处理? 而且就算用异常处理了,方法的注释中也没有提及这个异常,那文档中就不会有这个异常的说明.

    我看了半天就看了这个,能力还是有点弱,得加油呀!!!

    作者回复: 练手题的问题找的都不错啊!

    2019-01-25
收起评论
14
返回
顶部