Go错误处理和资源管理
第三章 错误处理和资源管理
3.1 defer调用
3.1.1 defer调用的特点
- 确保调用在函数结束时发生
- 参数在defer语句时计算
- defer列表为后进先出,遵循栈规则
示例1:
package main
import "fmt"
//defer中相当于有一个栈,遵循先进后出原则
func tryDefer() {
defer fmt.Println(1)
defer fmt.Println(2)
fmt.Println(3)
return
fmt.Println(4)
}
func main() {
tryDefer()
/*
控制台输出:
3
2
1
*/
}
defer其实就相当于Java中的try{}finally{}中finally用法。即使return和panic都不会影响defer的运行。
示例2:
package main
import (
"fmt"
)
//defer中相当于有一个栈,遵循先进后出原则
func tryDefer() {
for i := 0; i < 100; i++ {
defer fmt.Println(i)
if i == 10 {
panic("printed too many")
}
}
}
func main() {
tryDefer()
}
控制台输出:
10
9
8
7
6
5
4
3
2
1
0
panic: printed too many
goroutine 1 [running]:
main.tryDefer()
D:/go/go_workspace/src/learngo/errhandling/defer/defer.go:15 +0xc7
main.main()
D:/go/go_workspace/src/learngo/errhandling/defer/defer.go:38 +0x17
示例3:创建一个斐波那契数列前20位的txt文件
将第二章函数式编程中的斐波那契数列例子中的数列生成器调用后生成数列前20个数字,将其写入文件fibo.txt中。
package main
import (
"bufio"
"fmt"
"learngo/functional/fib"
"os"
)
func writeFile(filename string) {
file, err := os.Create(filename) //建立一个文件
if err != nil {
panic(err)//若建立出错则给出一个panic报错
}
defer file.Close() //一旦文件写完了就要关闭掉,利用defer特性在程序结束前最后关闭文件
writer := bufio.NewWriter(file)
defer writer.Flush() //一旦建立了NewWriter就要Flush
f := fib.Fibonacci()
for i := 0; i < 20; i++ {
fmt.Fprintln(writer, f())
}
}
func main() {
writeFile("fib.txt")
}
最后生成了一个fibo.txt文件,文件内容如下:
3.1.2 defer使用场景
- Open/Close,Open一个东西一般都要defer Close,意为在程序执行完后再利用defer的特性最后关掉文件
- Lock/Unlock
- PrintHeader/PrintFooter
3.1.3 defer和return谁先谁后
当defer和return同时出现在函数中,谁先触发呢?
package main
import "fmt"
func deferFunc() int {
fmt.Println("defer触发")
return 0
}
func returnFunc() int {
fmt.Println("return触发")
return 0
}
func returnAndDefer() int {
defer deferFunc()
return returnFunc()
}
func main() {
returnAndDefer()
}
控制台输出:
return触发
defer触发
控制台显示是return先触发defer后触发,解释起来也很容易理解,defer本身就是当前函数生命周期结束后才被出栈,换句话说,就是程序执行到函数最后一个“ } ”后defer才被执行,而return是在最后一个“ } ”之前,所以return先执行,defer后执行。
3.2 错误处理
错误处理方式示例:
package main
import (
"bufio"
"fmt"
"learngo/functional/fib"
"os"
)
func writeFile(filename string) {
file, err := os.OpenFile(filename, os.O_EXCL|os.O_CREATE, 0666) //给err赋值,创造出一个错误
//给出err的解决方法
if err != nil {
//OpenFile()注释中有一句话:If there is an error, it will be of type *PathError.
//所以当*PathError类型的error出现则进行一系列操作
pathError, ok := err.(*os.PathError)
if ok {
//若为*PathError类型的error则打印出三项内容
fmt.Printf("%s, %s, %s", pathError.Op, pathError.Path, pathError.Err)
} else {
panic(err) //若不是*PathError类型的error则给出panic报错
}
return //出现错误则中断程序
}
defer file.Close() //一旦文件写完了就要关闭掉
writer := bufio.NewWriter(file)
defer writer.Flush() //一旦建立了NewWriter就要Flush
f := fib.Fibonacci()
for i := 0; i < 20; i++ {
fmt.Fprintln(writer, f())
}
}
func main() {
writeFile("fib.txt")
}
控制台输出:
open, fib.txt, The file exists.
也可以自己创建error,创建格式如下:
err = errors.New("this is a custom error")
若将此自己创建的err替换掉上面的示例,程序则会识别出不是*PathError
类型的error,控制台输出如下:
panic: this is a custom error
goroutine 1 [running]:
main.writeFile({0x1c52d3?, 0xc00004c000?})
D:/go/go_workspace/src/learngo/errhandling/defer/defer.go:33 +0x69
main.main()
D:/go/go_workspace/src/learngo/errhandling/defer/defer.go:49 +0x25
3.3 服务器统一出错处理
如何实现统一的错误处理逻辑?
3.3.1 示例1,filelistserver
:创建功能为显示文件目录里的内容的webserver
func main() {
http.HandleFunc("/list/", func(writer http.ResponseWriter, request *http.Request) {
path := request.URL.Path[len("/list/"):]
//URL.Path[len("/list/"):]意为获取以/list/为开头的文件路径,[len("/list/"):]切片表示只获取/list/后面的路径
file, err := os.Open(path) //按照获取的路径path打开文件
if err != nil {
panic(err)
}
defer file.Close() //Open了就要Close
readAll, err := ioutil.ReadAll(file) //读取file文件
if err != nil {
panic(err)
}
writer.Write(readAll)
})
err := http.ListenAndServe(":8888", nil)//打开一个server监听 8888端口
if err != nil {
panic(err)
}
}
浏览器输入localhost:8888/list/fib.txt
:
打印成功,但是这个server在处理错误时直接panic(err)
了,如果出错页面会直接崩溃,例如输入http://localhost:8888/list/fib.txta
,页面就会显示“”该网页无法正常运作“”,这样会很难看。
3.3.1.1 webserver改进
文件代码结构:
handler.go
:
package filelisting
import (
"io/ioutil"
"net/http"
"os"
)
//HandleFileList()负责的功能:显示文件目录里的内容
//HandleFileList()只做其功能该做的事情,若出现错误则返回一个error,这个error在其调用的时候再传入专门解决error的函数中进行处理
func HandleFileList(writer http.ResponseWriter, request *http.Request) error {
path := request.URL.Path[len("/list/"):]
file, err := os.Open(path)
if err != nil {
//http.Error(writer, err.Error(), http.StatusInternalServerError) //若路径出错则会在网页显示错误原因
//return
return err
}
defer file.Close()
all, err := ioutil.ReadAll(file)
if err != nil {
//panic(err)
return err
}
writer.Write(all) //把文件内容写进ResponseWriter里去
return nil
}
web.go
:
package main
import (
"learngo/errhandling/filelistingserver/filelisting"
"log"
"net/http"
"os"
)
type appHandler func(writer http.ResponseWriter, request *http.Request) error
//将error传进errWrapper()进行统一处理
//errWrapper()输入是一个函数,输出也是一个函数,它把一个函数包装成了另外一个函数
func errWrapper(handler appHandler) func(http.ResponseWriter, *http.Request) {
return func(writer http.ResponseWriter, request *http.Request) {
//让handler去做业务逻辑,出现的error再通过switch进行统一处理
err := handler(writer, request)
if err != nil {
log.Printf("Error occurred handling request: %s", err.Error())
code := http.StatusOK
switch {
//文件不存在的情况
case os.IsNotExist(err):
code = http.StatusNotFound
//没有权限的情况
case os.IsPermission(err):
code = http.StatusForbidden
default:
code = http.StatusInternalServerError
}
http.Error(writer, http.StatusText(code), code)
//Error()传递的三个参数含义:向谁汇报error,字符串,error code(表示error的代码代号,比如StatusNotFound是404)
}
}
}
func main() {
http.HandleFunc("/list/", errWrapper(filelisting.HandleFileList))
//HandleFileList返回一个函数内部出现的error,再将此error传给errWrapper处理
err := http.ListenAndServe(":8888", nil) //开服务器,传入端口号8888,第二个参数一般都传nil
if err != nil {
panic(err)
}
}
网址输入错误时,如输入localhost:8888/list/fib.txta
,页面不会直接崩溃: