防御性编程以及我的一些感想

Jun 11 2014

这得从我看到的这篇文章说起~

防御性编程小例,最开始我是关注了这个人的微信,才看到这篇文章的,最近也工作了一段时间了~对自己以及别人写的代码有了一些新的想法(因为我遇到过很多坑啊囧)。因此,本文来谈谈这个话题。

在公司,我们碰到的很大一部分问题都是NullPointerException。我常常就想:这段程序明明在我手机上运行好好的,为什么会出现这种情况呢?

因为,我们永远都无法预测用户使用App时会发生的各种情况。所以防御性编程可以让我们减少很大一部分错误。

先说一个故事

先来说一个我去年面试过的问题,面试官问我:请你用最熟悉的语言写一个atoi程序。

我心里一想:这么简单!!我要好好写!不要写出bug!我马上就写好了,并且用“12334”这种简单的字符串试了又试,没问题就交给他看。

public int atoi(String a){
    int len = a.length();
    int num = 0;
    for(int i = len - 1; i >= 0; i--) {
        num += (a.charAt(i) - '0') * Math.pow(10, len - i - 1);
    }
    return num;
}

面试官一看就问我,你找找有什么问题没,我看了好几遍(我都在找bug),说没问题啊!我又仔细一想:如果传进来一个负数怎么办呢?比如“-12333”,这段程序就错了!

我就和面试官说了~他说嗯,还有问题吗?我心里想还有啊?想了几遍都想不出!我说:效率问题?他不给面子直接就说:你先别管效率!

他说如果我传进来一个null会怎么样?我恍然大悟!!!我有太多东西没考虑到(我图样图森破啊囧!!)!

上面就是我的一个真实的故事~不知你看了有什么感想,反正我觉得这次面试可以让我反思很多我存在的问题。

自从看了前面提到的那篇文章后,我现在写代码就时刻装着“防御性编程”这5个字。

那么怎么写防御性代码呢?

请看Integer.parseInt(String)这个方法,好好看!我现在分析一下~JDK的大神们怎么写健壮的代码!(如果有错误请指正~ =.=)

public static int parseInt(String string, int radix) throws NumberFormatException {
    if (radix < Character.MIN_RADIX || radix > Character.MAX_RADIX) {
        throw new NumberFormatException("Invalid radix: " + radix);
    }
    if (string == null) {
        throw invalidInt(string);
    }
    int length = string.length(), i = 0;
    if (length == 0) {
        throw invalidInt(string);
    }
    boolean negative = string.charAt(i) == '-';
    if (negative && ++i == length) {
        throw invalidInt(string);
    }

    return parse(string, i, radix, negative);
}

上面这段代码出自java 1.7.0_51的java.lang.Integer类,JDK开发大神是如何写代码的呢?

可以看到这段代码最开始的一部分就是在验证每个参数的正确性(代码中radix表示进制数),这里最小的进制就是2,最大进制是36。如果进制数不满足要求,直接抛出异常。

然后判断传入字符串是否为null,如果字符串不为null,然后可以取字符串的长度。 后面再判断字符串是否一个负数, 当所有参数都验证过了以后再做正事——将字符串转换成一个数字

我现在得到的一个重要的经验就是:

当你写一个方法需要对传入的参数进行处理或者计算的时候,你必须要严格验证传入参数的正确性,如果不符合,就应当给出提示!

上面提到的那篇文章里说到:

这就是防御性编程的最基本规则:保护程序免遭非法输入数据的破坏。

这些都是我以前编程不会考虑的事情啊!

如果你的代码没有防御性措施,那么你一定会遇到各种坑的~只是时候未到~

但也不是说所有的程序都应该这么写。如果你在写一个private方法只供类里面使用,那么我觉得就不必写这种防御性代码了。当然没有绝对的事情,如果一个public方法接受外部传入的参数,这个参数又传入这个private方法,那么你在使用这个private方法时候就需要先验证参数的合法性,然后再调用这个private方法。

当你在写一个public方法可以接受来自任何地方的参数时,就必须要验证参数的合法性了!

那么为什么要防御性编程

我觉得最终的目的就是为了让你写的代码正确运行,当面对各种各样的参数时,同时要向外部提供参数错误的原因,可以快速找到bug。

在Android开发里面,主线程(UI线程)中的一个微小的问题都会导致程序的崩溃,可能是一不小心一个View对象传入某个方法的时候是一个null,也可能一个方法的返回值是null等等,各种坑会在隐藏的地方等着你来踩哦~

要知道当一个对象为null的时候(你肯定不知道它为null),然后调用它的方法时,就会发生程序崩溃,这是应该是程序崩溃最常见的原因之一了~

比如在我最开始写的那个atoi程序里,如果别人用我的程序不小心传入一个null,那么我的程序就崩溃了~