你好, 我是Alan, 作为一个程序员, 我们都非常希望代码不出现太大的bug, 那么异常处理就是必不可少的, 那么今天我们就来聊聊异常处理。
首先我们来看看两种不同编程语言的异常处理方式, 一种是Java的异常处理方式, 另外一种是Go语言的异常处理方式,
Go语言使用多返回值来返回错误信息
func echo(request string) (response string, err error) {
if request == "" {
err = errors.New("empty request")
return
}
response = fmt.Sprintf("echo: %s", request)
return
}
然后使用if err!=nil去处理异常
resp, err := echo(req)
if err != nil {
fmt.Printf("error: %s\n", err)
}
观察上面这段代码, 不知道你有没有想过为什么只判断err为不为空呢, 而不去判断response为不为空呢? Go语言老鸟会跟你说这是约定俗成, 但是实际情况可能是echo函数的第一个返回值response为空, 而第二个返回值err不为空, 或者两个都为空。
这样的多返回值+if判断就会给程序员造成非常大的困惑, 因为你每次看到一个函数返回异常, 都在想到底哪个为空? 而且这种逻辑上的混乱编译器是无法解决的。
Java采用了另外一种方式, 那就是check exception, 类似下面这种
String echo(String request) EmptyReqException {
if (request == null) {
throw new EmptyReqExpection();
}
return requset;
}
上面的echo函数表示了这样一种含义, 那就是这个函数只会返回一个值,值的类型要么是String, 要么是EmptyReqException。这样就有效避免了Go语言的多返回值+if判断所带来的逻辑混淆了。
同时在Java中, 如果一个函数返回了错误, 编译器就会强迫程序员去检查所有可能出现的错误之后, 才能去使用数据,
对比这两者显然Java的异常处理是更加严谨的。
下面就简单介绍一下Java异常处理的一些细节
Java中的异常处理有两个东西
exception, 其中又分为两种, 可检查(checked)和不可检查(unchecked)异常, 其中可检查异常必须在源代码中显式的进行捕获, 这是编译期的一部分。在下文中我就将checked exception简称为CE了。
error, 它是指在正常情况下, 不太可能出现的情况。
exception和err这两者又都继承了Throwable类。
介绍完这些细节之处, 就来看一些关于异常处理的最佳实践
(1) 不要"省略"else分支
在很多地方, 我看到很多人的"代码质量建议"的其中一条往往是, "省略else分支", 或者 "嵌套的else分支是有害的", 我想说这种思想是完全片面的, 因为无脑的省略else分支往往会出现逻辑遗漏。
if...else...的含义是, 如果if条件成立, 做某件事情, if的条件不成立, 那就要做另外一件事情, 也就是说当你考虑完正确路径(happy path)之后, 你是需要考虑错误路径sad path的, 而如果你想都不想, 直接的就把else分支给省略了, 那么就可能出现一些逻辑遗漏了。
其实我觉得写上else语句也没有什么大不了的, 等考虑好else中逻辑的处理, 再将它省略也是可以的。
if (response != null) {
return ...
} else {
return ...
}
同时我们也应该谨慎的去对待else出现的场景。
(2) 不要在try...catch...中包含太多代码
try {
A();
} catch(A e) {
}
try {
B();
} catch(A e) {...}
如果函数A出现异常A, 函数B也出现异常A, 那么应该尽可能的将这两者分开, 因为如果你使用了logger的话, 我们其实可以很快的判断是哪一个函数出现了问题, 这样很快就能定位到问题, 并将它解决。
尽量在catch中使用log或者把异常抛出来, 少写e.printStackTrace(), 因为在分布式项目中去打印堆栈信息, 并处理是一件很麻烦的事情。
(3) 不要使用Exception这样的通用异常, 而应该捕获特定异常。
很多人去批判Java的异常处理的一点就是程序员喜欢使用Exception去捕获异常, 我觉得这是典型的"厨师做不好菜说刀不行"。
使用Exception的坏处就是, 它会去捕获所有的异常, 这样有的异常就会被隐藏起来, 难以调试。一种正确的做法就是好好的去思考异常的范围, 将其不断的缩小。
(4) Java CE的本质
Java的CE其实本质上是一种union type
String echo(String request) EmptyReqException {
}
可以认为函数echo返回一个union类型: {String, EmptyReqException}。这种类型是比较严谨的, 因为它只有两种情况, 要么是正常, 要么是异常。
最后, 代码的质量需要我们程序员去保证的, 而不能只是的通过语言层面去提供, 虽然Java的异常处理很严谨, 但是依然可能会出现抛出80多个异常的情况, 所以我们就要具体问题具体分析了。

