Go资源与出错处理

1、defer延迟语句

确保调用在函数结束时发生:延迟语句被用于执行一个函数调用,在这个函数之前,延迟语句返回

参数在defer语句时计算

defer列表为先进后出

error vs panic : 意料之中用error比如文件打不开,意料之外用panic比如数组越界

延迟函数

可在函数中添加多个defer语句,函数执行到最后,defer语句会按照逆序执行,最后该函数返回。

注:比如打开资源操作遇见错误需提前返回,返回前应当关闭相应资源,这时候可用defer。

注意:示例用到defer+panic+recover   用到Type Assertion   函数式编程很重要

例子1

创建目录:errorhandling下面再创建目录defer
//defer里面是相当于有一个栈 先进后出  defer好处:不怕中间有return 
func tryDefer() {  
  defer fmt.Println(1)
  defer fmt.Println(2)
  fmt.Println(3)
    //第一种:return
    panic("eror occurred") //第二种
    fmt.Println(4)
}//输出是3 2 1

其他

  • 延迟方法

defer也可以用于延迟一个方法调用。main函数中如果有defer将不会执行下面语句。

  • 延迟参数

延迟函数的参数在执行延迟语句时被执行。

func printA(a int) {
    fmt.Println("value of a in deferred function", a)
}
func main() {
    a := 5
    defer printA(a)
    a = 10
    fmt.Println("value of a before deferred function call", a)
}
输出:
value of a before deferred function call 10
value of a in deferred function 5
  • 堆栈延迟

一个函数有多个延迟调用时,它们被添加到一个堆栈中,并在后进先出中顺序中执行。

func main() {
    name := "Naveen"
    fmt.Printf("Orignal String: %s
", string(name))
    fmt.Printf("Reversed String: ")
    for _, v := range []rune(name) {
        defer fmt.Printf("%c", v)
    }
}

输出:
Orignal String: Naveen
Reversed String: neevaN

例子2

 详情参加functional  https://www.cnblogs.com/ycx95/p/9362175.html

package main

import (
	"fmt"
	"os"

	"bufio"

	"imooc.com/ccmouse/learngo/functional/fib"
)

func tryDefer() {
	for i := 0; i < 100; i++ {
		defer fmt.Println(i)
		if i == 30 {  //由于现进后出 输出就是:30 29 ... 1
			// Uncomment panic to see
			// how it works with defer
			// panic("printed too many")
		}
	}
}

func writeFile(filename string) {
	file, err := os.OpenFile(filename,
		os.O_EXCL|os.O_CREATE|os.O_WRONLY, 0666) //err只是一个值 可以针对值进行处理

	if err != nil { //错误处理  细致的错误处理  如果文件存在:输出会提示文件存在
		if pathError, ok := err.(*os.PathError); !ok {
			panic(err)
		} else {
			fmt.Printf("%s, %s, %s
",
				pathError.Op,
				pathError.Path,
				pathError.Err)
		}
		return
	}
	defer file.Close() //file写完了要关闭   

	writer := bufio.NewWriter(file)  // 直接file写文件很慢,因此用bufio包装一下
	defer writer.Flush()  //导进新创建的fib.txt文件里面

	f := fib.Fibonacci()
	for i := 0; i < 20; i++ {  //写前20个fib数列
		fmt.Fprintln(writer, f())
	}
}

func main() {
	tryDefer()
	writeFile("fib.txt")  //运行程序这里会创建一个fib.txt文件
}  

2. 错误处理

Go中错误也是一种类型,错误用内置的error类型表示。错误可存储在变量中,从函数中返回。

注:一个打开文件的功能函数 func Open(name string) (file *File, err error) 若文件成功打开,则将返回文件处理,否则返回一个非nil错误。

错误类型表示

type error interface {
    Error() string  //任何实现该接口的类型都可以作为一个错误调用  该方法提供了对错误的描述
}

错误处理一:人为处理

制造一个错误(程序会挂掉)

将原来的:
file,err := os.Create(filename)  //Create是openfile加了一些状态
直接修改为:
file, err := os.OpenFile(filename,
		os.O_EXCL|os.O_CREATE|os.O_WRONLY, 0666) //0666是一个权限 

处理挂掉的错误:如何对err进行细化处理(详见上面代码writefile的error处理部分

自己写error:err = errors.New("this is a custom error“) 其实error就是一个interface

错误处理:
file,err := os.Open("abc.txt")
if err!= nil {
      if pathError,ok := err.(*os.PathError);ok {
           fmt.Println(pathError.Err)
     }else{
             fmt.Println("unknown error",err)  
}

错误处理二:统一错误处理

新建目录:errorhanding目录下创建 filelistingserver 目录 

handler.go ( 位置 errorhandling/filelistingserver/filelisting)

package filelisting

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"strings"
)

const prefix = "/list/"

type userError string

func (e userError) Error() string {
	return e.Message()
}

func (e userError) Message() string {
	return string(e)
}

func HandleFileList(writer http.ResponseWriter,
	request *http.Request) error {
	fmt.Println()
	if strings.Index(
		request.URL.Path, prefix) != 0 {
		return userError(
			fmt.Sprintf("path %s must start "+
				"with %s",
				request.URL.Path, prefix))
	}
	path := request.URL.Path[len(prefix):]
	file, err := os.Open(path)
	if err != nil {
		return err  //有错就return出去外面有处理
	}
	defer file.Close()

	all, err := ioutil.ReadAll(file)
	if err != nil {
		return err
	}

	writer.Write(all)
	return nil
}

web.go(位置 errorhandling/filelistingserver)

package main

import (
	"log"   //这里视频中所写是 github.com/gpmgo/gopm/modules/log 
	"net/http"
	_ "net/http/pprof"
	"os"

	"imooc.com/ccmouse/learngo/errhandling/filelistingserver/filelisting"
)
//返回一个err
type appHandler func(writer http.ResponseWriter,
	request *http.Request) error
//包装errHandler返回的err,然后返回一个函数  特点:输入是一个函数 输出也是一个函数 
func errWrapper(  
	handler appHandler) func(
	http.ResponseWriter, *http.Request) {
	return func(writer http.ResponseWriter,
		request *http.Request) {
		// panic
		defer func() {
			if r := recover(); r != nil {
				log.Printf("Panic: %v", r)
				http.Error(writer,
					http.StatusText(http.StatusInternalServerError),
					http.StatusInternalServerError)
			}
		}()

		err := handler(writer, request)
                //处理err进行处理
		if err != nil {
			log.Printf("Error occurred "+
				"handling request: %s",
				err.Error())

			// user error
			if userErr, ok := err.(userError); ok {
				http.Error(writer,
					userErr.Message(),
					http.StatusBadRequest)
				return
			}

			// system 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)
		}
	}
}

type userError interface {
	error
	Message() string
}

func main() {
	http.HandleFunc("/",
		errWrapper(filelisting.HandleFileList))

	err := http.ListenAndServe(":8888", nil)  //开服务器  打开服务器输入:localhost:8888/list/fib.txt
	if err != nil {
		panic(err)
	}
}

//测试时候:管理员目录时候cp fib.txt fib2.txt  浏览器可以访问 让别人没有权限:chmod 500 fib2.txt  浏览器输出:Forbidden

errwrapper_test.go(位置 errorhandling/filelistingserver)  

package main

import (
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"os"
	"strings"
	"testing"
)

func errPanic(_ http.ResponseWriter,
	_ *http.Request) error {
	panic(123)
}

type testingUserError string

func (e testingUserError) Error() string {
	return e.Message()
}

func (e testingUserError) Message() string {
	return string(e)
}

func errUserError(_ http.ResponseWriter,
	_ *http.Request) error {
	return testingUserError("user error")
}

func errNotFound(_ http.ResponseWriter,
	_ *http.Request) error {
	return os.ErrNotExist
}

func errNoPermission(_ http.ResponseWriter,
	_ *http.Request) error {
	return os.ErrPermission
}

func errUnknown(_ http.ResponseWriter,
	_ *http.Request) error {
	return errors.New("unknown error")
}

func noError(writer http.ResponseWriter,
	_ *http.Request) error {
	fmt.Fprintln(writer, "no error")
	return nil
}

var tests = []struct {
	h       appHandler
	code    int
	message string
}{
	{errPanic, 500, "Internal Server Error"},
	{errUserError, 400, "user error"},
	{errNotFound, 404, "Not Found"},
	{errNoPermission, 403, "Forbidden"},
	{errUnknown, 500, "Internal Server Error"},
	{noError, 200, "no error"},
}

func TestErrWrapper(t *testing.T) {
	for _, tt := range tests {
		f := errWrapper(tt.h)
		response := httptest.NewRecorder()
		request := httptest.NewRequest(
			http.MethodGet,
			"http://www.imooc.com", nil)
		f(response, request)

		verifyResponse(response.Result(),
			tt.code, tt.message, t)
	}
}

func TestErrWrapperInServer(t *testing.T) {
	for _, tt := range tests {
		f := errWrapper(tt.h)
		server := httptest.NewServer(
			http.HandlerFunc(f))
		resp, _ := http.Get(server.URL)

		verifyResponse(
			resp, tt.code, tt.message, t)
	}
}

func verifyResponse(resp *http.Response,
	expectedCode int, expectedMsg string,
	t *testing.T) {
	b, _ := ioutil.ReadAll(resp.Body)
	body := strings.Trim(string(b), "
")
	if resp.StatusCode != expectedCode ||
		body != expectedMsg {
		t.Errorf("expect (%d, %s); "+
			"got (%d, %s)",
			expectedCode, expectedMsg,
			resp.StatusCode, body)
	}
}

3. panic

Go没有像Java一样的异常机制,不能抛出异常,而是使用了panic和recover机制。

注意:不能经常用

panic是一个内建函数,停止当前函数执行,一直向上返回,执行每一层的defer,如果没有遇见recover则程序退出

4.recover

与panic对应,可让进入令人恐慌的流程中的goroutine恢复过来,仅在延迟函数中有效。正常执行过程中,调用recover返回nil,无任何效果。

仅在defer调用中使用,获取panic的值,如果无法处理可重新panic

例子:

package main

import (
	"fmt"
)

func tryRecover() {
	defer func() { //这里写一个匿名函数 注意最后加的()
		r := recover()  //哈哈,这里是recover
		if r == nil {
			fmt.Println("Nothing to recover. " +
				"Please try uncomment errors " +
				"below.")
			return
		}
		if err, ok := r.(error); ok {
			fmt.Println("Error occurred:", err) //的确是err
		} else {
			panic(fmt.Sprintf(
				"I don't know what to do: %v", r))
		}
	}()

	// Uncomment each block to see different panic
	// scenarios.
	// Normal error
	//panic(errors.New("this is an error"))

	// Division by zero
	//b := 0
	//a := 5 / b
	//fmt.Println(a)

	// Causes re-panic
	//panic(123)
}

func main() {
	tryRecover()
}

  

原文地址:https://www.cnblogs.com/ycx95/p/9366750.html