团队维护同一个项目,坑队友是常有的事情。可是有些坑,真的是太尼玛坑了。本文主要结合最近遇到的一个坑来聊一聊防御性编程。

1。故事场景

公司某后台业务部门的Android客户端只剩下庐主和一个实习生小伙来做开发维护(是不是很爽呢),实习生小伙有事先走,结果在测试同学提Bug的时候,页面Crash了。哎哟我们的代码竟然有Bug?!顺便吐槽下负责黑盒测试的X同学,能否专业一点呢,虽然是做的黑盒测试,也不至于拿着一个刚弹出crash提示的手机,就跑过来说这里有bug,哎哟喔,都不把crash页面给我点掉,亲,我实在不知道这是哪个页面啊。吐槽完毕。
好戏开始了,我亲自点掉crash页面后,发现是实习小哥做的城市列表页面。那把可恶的手机插在电脑上面看看crash日志吧,结果发现了NullPointerException(果然是Java狗),定位到如下一段代码:

1
View nextSectionView = getChildAt(nextSectionPosition - firstVisibleItem);
final int bottom = mPinnedSection.view.getBottom() + getPaddingTop();
mSectionsDistanceY = nextSectionView.getTop() - bottom;

哎哟我一眼看到了那个nextSectionView的我家伙,好吧,就是你,getChildAt()这个方法在某些临界条件下会返回null。so,null.getTop()不尼玛空指针才怪!
于是改为下面的样子:

1
View nextSectionView = getChildAt(nextSectionPosition - firstVisibleItem);
if (nextSectionView == null) {
  return;
}
final int bottom = mPinnedSection.view.getBottom() + getPaddingTop();
mSectionsDistanceY = nextSectionView.getTop() - bottom;

问题解决。

2。防御性编程?

事实证明,防御性编程非常重要。不仅仅是为了保护程序本身,也是为了不坑队友(假设测试环境提供的数据没有引发bug,但是如果App上线了呢,做够proguard后,如果没有mapping文件,再去定位crash,那报错信息就是一坨屎,很难发现问题所在)。
我所理解的防御性编程,就是保护程序本身因为非法参数传入导致破坏。Design for failure or future *基本都是一个意思了。像上面故事场景里面空指针导致crash的问题,如果用TDD模式开发的话,测试用例那一关基本就gg了。
维基百科里对防御性编程的解释。主要的一段话:防御性编程是防御式设计的一种具体体现,它是为了保证,对程序的不可预见的使用,不会造成程序功能上的损坏。它可以被看作是为了减少或消除墨菲定律效力的想法。防御式编程主要用于可能被滥用,恶作剧或无意地造成灾难性影响的程序上。维基百科里面讲了软件缺陷或者漏洞可能会被黑客所利用。
抛开维基的解释,我们说个接地气的例子。其实防御性编程和非防御性编程最大的区别就是,防御性编程不会对输入做任何假设,就像心机婊A,遇见任何人都不会假设人本善,A有一套自己的策略来判定来着是否是坏人。因此,A在绝大多数情况下,都比较安全。那奶茶婊B呢,觉得人人都是善的,即便是个坏人过来,B也张开怀抱。。。,后面自己脑补吧。B最后成为受害人。恩。B也许运气好一点,一辈子都没有遇到坏人,但是,这种情况基本不成立。联系到programing上面就是,有两个函数A和B,A总会对传进来的参数做校验,只有符合条件才会进行下一步处理。而B呢,拿到参数直接进行处理,因此一些非法的参数也会被处理,这就导致程序本书的不健壮性,有可能导致系统漏洞。这些漏洞如果没有被良好的测试用例所覆盖,那么系统上线,这种潜在的风险可能会导致不可预估的后果,说俗一点,可能就是不可估计的经济损失。

3。如何写出鲁棒性高的代码

防御性编程的必要性和概念说的差不多了,那如何实践防御性编程呢?
让我们先来看一个例子,Java的Integer.parseInt()的实现:

1
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 || string.isEmpty()) {
            throw invalidInt(string);
        }

        char firstChar = string.charAt(0);
        int firstDigitIndex = (firstChar == '-' || firstChar == '+') ? 1 : 0;
        if (firstDigitIndex == string.length()) {
            throw invalidInt(string);
        }

        return parse(string, firstDigitIndex, radix, firstChar == '-');
}

看到了有没有?!对输入的参数,大神们对参数该做检查的做检查,该抛异常的抛异常
当然这只是一个方面,以Java为例的话,代码 OO Style(之前就被吐槽的很惨,讲真,当时不完全遵守OO主要是觉得自己完全驾驭的了Java这种编程模型,图方便而不想多写一堆模板代码,不过现在还是建议该遵守的要遵守,利人利己),做好权限控制,对象是否为空等等。。。
关于最佳实践,具体语言可能会有不同实践。按我的理解:防御性编程更多的是一种安全意识,或者是一种设计思想,是一种约定或者契约(如果不能从语言层面约束的话), 对于可能出现的异常做好检查,保护功能的健壮。
最后说说最近的一点感触:开发也要有测试的思维,TDD的开发模式可能会帮助比较大,但是不是所有的Project都是以TDD模式开展的,还是非常有必要写白盒测试,考虑好各种边界值,路径等,做好代码测试的覆盖。
就酱,如果你有关于防御性编程的实践,欢迎分享!