Go language Bible - Chapter 5 functions - 5.8 defined functions

Keywords: Go Back-end Functional Programming

Chapter 5 functions

Function allows us to package a statement sequence into a unit, and then call it many times from other parts of the program. The mechanism of function allows us to decompose a large work into small tasks. We have touched on functions before, and we will discuss more features of functions in this chapter

5.8 defined function

In the findLinks example, we use the output of http.Get as the input of html.Parse. html.Prase can work normally only if the content of the URL is indeed in HTML format, but in fact, the URL points to rich content, which can be pictures, plain text or others. Passing content in these formats to HTML. Parse will have adverse consequences

The following example obtains the HTML page and outputs the title of the HTML page. The title function will check the content type field returned by the server. If it is found that the page is not HTML, it will terminate the function and return an error

func title(url string)error {
   resp,err := http.Get(url)
   if err != nil {
      return err
   }
   ct := resp.Header.Get("Content-Type")
   if ct 1!="text/html" && !strings.HasPrefix(ct,"text/html;"){
      resp.Body.Close()
      return fmt.Errorf("parsing %s as HTML:%v",url,err)
   }
   doc,err :=html.Parse(resp.Body)
   resp.Body.Close()
   if err != nil {
      return fmt.Errorf("parsing %s as HTML:%v",url,err)
   }
   visitNode := func(n *html.Node) {
      if n.Type == html.ElementNode && n.Data == "title"&&firstChild != nil{
         fmt.Println(n.firstChild.Data)
      }
   }
   for eachNode(doc,visitNode,nil)
   return nil
}

The following is the running result

$ ./title1 http://gopl.io
The Go Programming Language
$ ./title1 https://golang.org/doc/effective_go.html
Effective Go - The Go Programming Language
$ ./title1 https://golang.org/doc/gopher/frontpage.png
title1: https://golang.org/doc/gopher/frontpage.png has type image/png, not text/html

resp.Body.Close is called multiple times to ensure that the network link is closed under all paths (even if the function fails). As functions become more complex, more errors need to be handled, and it becomes more and more difficult to maintain and clean up logic. The unique defer mechanism of go language can make things easier

You only need to add defer before calling a function or method to complete the syntax required by defer. When the statement is executed, the function and parameter expressions are evaluated, but the function after defer will not be executed until the normal execution of the function containing the defer statement is completed, regardless of whether the function containing the defer statement is normally ended through return, Or the abnormal end caused by panic. You can execute multiple defer statements in a function in the reverse order of the declaration

Defer statements are often used to handle paired operations, such as opening and closing; Connecting and disconnecting links; Shackles and release locks; Through the defer mechanism, no matter how complex the function logic is, it can ensure that resources are released in any path. The defer that releases resources should directly follow the statement requesting resources. In the following code, a defer statement replaces the previous resp.Body.Close

func title(url string)error {
   resp,err := http.Get(url)
   if err != nil {
      return err
   }
   defer resp.Body.Close()
   ct := http.Header.Get("Content-Type")
   if ct != "text/html" && !strings.HasPrefix(ct,"text/html;"){
      return fmt.Errorf("%s has type %s, not text/html",url,ct)
   }
   doc,err := html.Parse(resp.Body)
   if err != nil {
      return fmt.Errorf("parsing as HTML: s%",url,err)
   }
   return nil
}

When dealing with other resources, you can also use the defer mechanism, such as the operation of files

func ReadFile(filename string)([]string,error) {
   f,err := os.Open(filename)
   if err != nil {
      return nil,err
   }
   defer f.Close()
   return ioutil.ReadAll(f)
}

Or handle mutexes

var mu sync.Mutex
var m = make(map[string]int)
func lookup(key string) int {
   mu.Lock()
   defer mu.Unlock()
   return m[key]
}

When debugging complex programs, defer mechanism is often used to record when to enter and exit functions. The following bigSlowOperation functions directly call the trace function to record the emptiness of the called. When bigSlowOperation is called, trace will return a function value, which will be called when bigSlowOperation exits

In this way, we can control the entry and all exits of the function through only one statement, and even record the running time of the function, such as start in the example

One thing to note: don't forget the parentheses after the defer statement, otherwise the operation that should be executed when entering will be executed when exiting, and the operation that should be executed when exiting will never be executed

func bigSlowOperation(){
   defer trace("bigSlowOperation")()
   time.Sleep(10*time.Second)
}
func trace(msg string) func() {
   start := time.Now()
   log.Printf("enter %s",msg)
   return func() {
      log.Printf("exit %s (%s)",msg,time.Since(start))
   }
}

Every time bigSlowOperation is called, the program will record the entry, exit and duration of the function (we use a time.Sleep to simulate a time-consuming operation)

$ ./trace
2015/11/18 09:53:26 enter bigSlowOperation
2015/11/18 09:53:36 exit bigSlowOperation (10.000589217s)

We know that the function in the defer statement will be executed after the return statement updates the return value. Because the anonymous function defined in the function can access all variables of the function, including the return value variable, the defer mechanism is adopted for the anonymous function to observe the return value of the function

Take the double function as an example

func double(x int)int{
   return x+x
}

We only need to name the return value of double first, and then add the defer statement, so that we can output the parameters and return value when double is called

func double(x int)(result int) {
   defer func() {
      fmt.Printf("double(%d) = %d\n",x,result)
   }()
   return x+x
}
_, = double(4)// 8

Maybe the double function is too simple to see the role of this trick, but it is not useful for functions with many return statements

The delayed anonymous function can even modify the return value returned by the function to the caller

func triple(x int)(result int){
   defer func() { result += x}()
   return double(x)
}
fmt.Println(triple(4)) //12

Pay special attention to the defer statement in the loop body, because these delayed functions will be executed only after the function is executed. The following code will cause the file description of the system to run out, because no file will be closed before all files are processed

for _,filename := range filenames {
   f,err := os.Open(filename)
   if err != nil{
      return nil
   }
}

One solution is to move the defer statement in the body of the loop to another function, which is called each time the loop

for _,filename := range filenames {
   if err != doFile(filename);err != nil {
      return err
   }
}
func doFile(filename string)error{
   f,err := os.Open(filename)'
   if err != nil{
      return err
   }
   defer f.Close()
}

The following code is an improved version of the previous code. We write the http response information to the local file instead of from the standard output stream. We propose the last paragraph of the url path as the file name through path.Base

func fetch(url string)(filename string,n int64,err error) {
   resp,err := http.Get(url)
   if err != nil {
      return "",0,err
   }
   defer resp.Body.Close()
   local := path.Base(resp.Request.URL.Path)
   if local == "/" {
      local = "index.html"
   }
   f,err := os.Create(local)
   if err != nil {
      return "",0,err
   }
   n,err = io.Copy(f,resp.Body)
   if closeErr := f.Close();err == nil {
      err = closeErr
   }
   return local, n, err
}

We have seen the resp.Body.Close delay before and will not repeat it here. In the above example, the os.Create clock in file is used for writing. When closing the file, we do not use the defer mechanism for f.close, because this will produce some errors. For many file systems, especially NFS, the errors when writing the file will be fed back when the file is closed. If the feedback information when the file is closed is not checked, it may lead to data loss, and we mistakenly think that the write operation is successful. If both io.Copy and f.close fail, we tend to feed back the error information of io.Copy to the caller, because it occurs before f.close, which is more likely to be close to the essence of the problem

Posted by shane0714 on Thu, 04 Nov 2021 22:23:30 -0700