try-catch,一个大家最常见不过的语法,但是有些人却用不好。当面对一个崩溃的时候,不管三七二十一先catch住再说,没错,老崩溃是被catch住了,但是新崩溃又出现了,想想都是坑啊,本文就和大家探讨下这个话题,此外买一送一,再和大家探讨下空指针异常。
乱用try-catch
写过C++的人都知道,那异常捕获特别不好用,甚至有些异常还捕获不了,在Java中,try-catch特别好使,如果一个人的代码没有try-catch,有时候会被大家质疑代码写的不健壮。
try-catch虽好,但不可乱用,原因在于try-catch可以隐藏代码缺陷,不利于bug排查。如果没有try-catch,那程序会崩溃,崩溃后从日志中可以一眼看出问题的原因。而如果用了try-catch以后,崩溃被catch住了,这个时候程序没有崩溃,但是程序运行会不正常。
我相信大家都明白:crash并不可怕,可怕的是crash后却没有找到日志,你就慢慢去查吧,这感觉谁查谁知道。
举个例子来说明下:
public class Example {
public static void main(String[] args) {
BusinessLogic bl = new BusinessLogic();
bl.doA();
bl.doB();
bl.doC();
bl.doD();
bl.doE();
}
}
class BusinessLogic {
public void doA(){
System.out.println("I am doing A");
}
public void doB(){
System.out.println("I am doing B");
}
public void doC(){
int i = 1/0;
System.out.println("I am doing C");
}
public void doD(){
System.out.println("I am doing D");
}
public void doE(){
System.out.println("I am doing E");
}
}
上面的程序有个问题,在doC函数中存在除0异常,程序运行后会崩溃,我们可以得到如下日志反馈:
I am doing A
I am doing B
Exception in thread "main" java.lang.ArithmeticException: / by zero
at BusinessLogic.doC(Example.java:27)
at Example.main(Example.java:7)
sandbox> exited with status 0
但是有的程序员,特别害怕崩溃,为了让自己的程序不会崩溃,直接在最外层进行try-catch:
public class Example {
public static void main(String[] args) {
try {
BusinessLogic bl = new BusinessLogic();
bl.doA();
bl.doB();
bl.doC();
bl.doD();
bl.doE();
} catch(Exception e) {
e.printStackTrace();
}
}
}
结果,程序是不会崩溃了,但是逻辑并没有正确地执行,这个时候如果让你去排查这个问题,你能第一时间定位到doC函数有问题吗?我觉得大部分人都不能。
虽然这个例子看起来有点可笑,但是在实际开发中确实看到过不少这种现象,更有甚者,连e.printStackTrace()这句话都懒得加,程序没有任何异常日志,想排查这种错误简直痛不欲生。
空指针强迫症
前面说到崩溃,大家知道排名前2的崩溃是什么吗?没错,那就是:
大家可以检查下自己的项目,看看这两个异常是不是占大头。所以,为了防止空指针异常,很多程序员会这样写代码:
public class Example {
public static void main(String[] args) {
BusinessLogic bl = new BusinessLogic();
Object param = new Object();
bl.doA(param);
}
}
class BusinessLogic {
public void doA(Object o){
if (o != null) {
System.out.println("I am doing A with param : " + o);
doB(o);
}
}
public void doB(Object o){
if (o != null) {
System.out.println("I am doing B with param : " + o);
doC(o);
}
}
public void doC(Object o){
System.out.println("I am doing C with param : " + o);
}
}
上面程序犯了一个错误,那就是重复判空,大量的冗余判断会影响程序的性能,上面的例子很简单,我相信大家都不会犯这个错误,但是当项目大了以后,当函数调用错综复杂的时候,你还能保证自己不犯错吗?很难!
大家还记得C语言的strcpy函数吗?
strcpy(char * __restrict to, const char * __restrict from)
{
char *save = to;
for (; (*to = *from); ++from, ++to);
return(save);
可以看到,C语言的strcpy函数,没有进行任何判断,比如你传递个空,或者说to的长度比from小,这些都会导致程序异常,但strcpy依然是不做任何判断,为的就是追求高性能。
对于strcpy来说,异常判断是上层调用方该考虑的事情,它作为底层API并不需要关注那么多,如果上层调用不合法,那就让程序挂掉就行了,谁叫你不按我的要求来呢!
这个涉及到架构设计的理念,我们在开发一个sdk的时候,其实是不需要每次都校验参数合法性的,明确对外暴露的API,如果说外部调用不合法,一定要让程序挂掉,而不是不回应,这样上层也更好排查问题,尤其是一些调用频次较高的底层sdk,更要注重这一点,性能和调用反馈是我们需要注意的。
这就好比搭积木,我们无需保证每块积木都是完全可靠的,但是每个积木之间无缝衔接,没有多一点,也没有少一点,而是刚刚好,它们共同构成了一个完整健壮的个体。写程序也是如此:
无需做诸多冗余的保证,只要各层级的代码能够无缝衔接就好