「Golang成長之路」錯誤處理與資源管理篇

yangkuang發表於2021-08-14

一、defer呼叫

介紹:
確保在函式呼叫結束時發生
defer就是在程式或函式呼叫結束後,然後執行defer後面的語句
例如:

package main

import "fmt"

func tryDefer(){

    fmt.Println(1)
    fmt.Println(2)
    fmt.Println(3)
    //列印:1、2、3

    defer fmt.Println(1)
    defer fmt.Println(2)
    fmt.Println(3)
    //列印:3、2、1

    defer fmt.Println(1)
    defer fmt.Println(2)
    fmt.Println(3)
    return
    fmt.Println(4)
    //列印:3、2、1

    defer fmt.Println(1)
    defer fmt.Println(2)
    fmt.Println(3)
    panic("error occurred")
    fmt.Println(4)
    //列印:3、2、1
    //panic: error occurred
}
func mian(){
    tryDefer()
}
  1. 在這個例子第二個列印中,前面加上defer關鍵字後,其後面的 fmt.Println(1)和 fmt.Println(2)都會該程式所有語句執行結束後執行,而defer裡面本身是一個棧(先進後出),所有會先列印2,然後列印1。
  2. 在這個例子的第三個列印中,我們加了關鍵字return其輸出結果還是1、2、3,這裡是因為return後程式就會退出,當程式退出前defer仍然會執行
  3. 在這個例子的第四個列印中,我們使用到函式panic()程式會報錯退出,但是在結束之前仍然會執行defer

還有一個例子:

func writeFile(filename string){
    file, err := os.Create(filename)  //os.Create()建立檔案
    if err != nil{  //err不為空,則檔案建立失敗,panic輸出err資訊
        panic(err)
    }

    defer File.close() //最後將檔案關閉

    writer := bufio.NerWriter(file)
    defer writer.Flush()

    f := fibonacci()
    for i := 0; i < 20; i++{
        fmt.Fprintln(writer, f())
    }
}

func fibonacci() func() int {
    a := 0, b := 1
    return func() int{
        a, b = b, a+b
        return a
    }
}

func mian(){
    writerFile("fib.txt")
}

二、錯誤處理

前面我們提到了panic()函式其實它也是用來做錯誤處理的,但是,使用panic程式會掛掉,這是我們不希望看到的。

file, err := os.Open("abc.txt")
if err != nil{
    panic(err)
}

像上面程式碼使用panic後程式就會把err中的內容輸出,並且掛掉

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

使用這種處理方式更好

三、panic&recover

  1. panic

「Golang成長之路」錯誤處理與資源管理篇

2.recover

「Golang成長之路」錯誤處理與資源管理篇

package main
import "fmt"

func tryRecover(){
    defer func(){
    r := recover()
    err, ok := r.(error)
    if ok{
        fmt.Println("Error occurred:", err)
    }else{
        panic("I don`t kown what to : %v", r)
    }
  }()

panic("123")

}

func mian(){
    tryRecover()

}

四、統一處理

伺服器統一出錯:
web.go:

package main

import (
   "learngo/errhandling/filelistingserver/handle"
 "log" "net/http" "os")

//定義一個函式型別
type Apphandle func(write http.ResponseWriter,
  request *http.Request) error

//函數語言程式設計,傳入一個函式Apphandle型別的函式型別,返回一個匿名函式func(http.ResponseWriter,*http.Request)
func errwarappler(handler Apphandle) func(http.ResponseWriter, *http.Request) {
   return func(writer http.ResponseWriter, request *http.Request) {
      //匿名函式func(http.ResponseWriter,*http.Request)內部構造:

  defer func(){
         r := recover()
         if r != nil{
            log.Printf("panic:%v\n", r)
            http.Error(writer, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
            return
  }

      }()

      err := handler(writer, request)

      if err != nil{
         log.Printf("Error handle request:%s", err.Error())

          if userErr, ok := err.(userError); ok{
            http.Error(writer, userErr.Massage(), http.StatusBadRequest)
            return
  }

         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
  Massage() string
}

func main() {
   //傳入部分
  http.HandleFunc("/",errwarappler(handle.Handler))

   err := http.ListenAndServe(":8888" , nil)
   if err != nil{
      panic(err)
   }
}

handle.go:

package handle

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

const perfix = "/list/"

type userError string

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

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

func Handler(write http.ResponseWriter, Request *http.Request) error{

   if strings.Index(Request.URL.Path, perfix) != 0 {
      return userError(fmt.Sprintf("path %s must start" +
         "whit %s",Request.URL.Path, perfix))
   }

   path := Request.URL.Path[len(perfix):]
   file , err := os.Open(path)
   if err != nil{
      //將錯誤資訊返回出去
  return err
   }
   defer file.Close()

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

   write.Write(all)
   return nil
}

注意:error和panic
在意料之外的情況:使用panic
在意料之內的情況:使用error

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章