Go Senior Engineer Lecture (MOOC) 002

go (2)

string

package main

import (
    "fmt"
    "unicode/utf8"
)

func main() {
    s := "Yes我爱Go语言"
    fmt.Println(len(s))
    for _, b := range []byte(s) {
        fmt.Printf("%X ", b)
    }
    fmt.Println()
    for i, ch := range s { // ch is a rune 4字节的 将utf-8解码再转成unicode
        fmt.Printf("(%d %X) ", i, ch)
    }
    fmt.Println()
    fmt.Println("Rune count:", utf8.RuneCountInString(s))
    bytes := []byte(s)
    for len(bytes) > 0 {
        ch, size := utf8.DecodeRune(bytes) // 一个字符一个字符解
        bytes = bytes[size:]
        fmt.Printf("%c ", ch)
    }
    fmt.Println()

    for i, ch := range []rune(s) {
        fmt.Printf(" %d->%c ", i, ch)
    }
    fmt.Println()
}

The strings package provides common string operations

  • Fields, Split, Join
  • Contains, Index
  • ToLower, ToUpper
  • Trim, TrimRight, TrimLeft

Structs and Methods

Go language only supports encapsulation, not polymorphism or inheritance
There are no classes, only structs
No need to know whether it's created on the heap or stack;

package main

type TreeNode struct {
    value       int
    left, right *TreeNode
}

func main() {
    // Instantiate node
    var root TreeNode
    root = TreeNode{value: 3}
    root.left = &TreeNode{}
    root.right = &TreeNode{5, nil, nil}
    root.right.right = new(TreeNode{value:0}) // Use . to access members, regardless of whether it's an address or a struct.
    root.left.right = createNode(2)
}

// There are no constructors, we can create instances using factory functions.
func createNode(value int) *TreeNode {
    return &TreeNode{value: value}
}
  • To change content, a pointer receiver must be used.
  • Consider using a pointer receiver for large structs as well; copying data consumes resources.

Value receivers are unique to the Go language.

Encapsulation

  • Uppercase first letter: public
  • Lowercase first letter: private
  • Packages are based on directories (directory and package names can differ, but one directory has one package name, meaning one directory can only contain one package); the main package contains the executable entry point.
  • Methods defined for a struct must be in the same package, but can be in different files.

Refactor the directory structure

  • tree
  • entry
    • entry.go
  • node.go
package tree

import (
    "fmt"
)

type Node struct {
    Value       int
    Left, Right *Node
}



// There are no constructors, we can create instances using factory functions.
func CreateNode(value int) *Node {
    return &Node{Value: value}
}

// Creating a method for a struct can be seen as a regular function, e.g., func Print(node TreeNode){}
// Note value copy
func (node Node) Print() {
    fmt.Println(node.Value)
}

// Note that without *, the value of node cannot be modified, because all passes are value copies, hence (node *TreeNode)
func (node *Node) SetValue(val int) {
    if node == nil {
        fmt.Println("Setting value to nil node. Ignored.")
        return
    }
    node.Value = val
}

// In-order traversal: Left, Root, Right
func (node *Node) Traverse() {
    if node==nil {
        return
    }
    node.Left.Traverse()
    node.Print()
    node.Right.Traverse()
}

// entry.go
package main

import "gobasic/tree"

func main() {
    // Instantiate node
    var root tree.Node
    root = tree.Node{Value: 3}
    root.Left = &tree.Node{}
    root.Right = &tree.Node{5, nil, nil}
    root.Right.Right = new(tree.Node) // Use . to access members, regardless of whether it's an address or a struct.
    root.Left.Right = tree.CreateNode(2)
    //root.Print()
    root.Traverse()
}

Extend with a post-order traversal using this package

package main

import (
    "fmt"
    "gobasic/tree"
)
// Lowercase means not for external use
type myTreeNode struct {
    node *tree.Node
}

// Custom private post-order traversal
func (myNode *myTreeNode) postOrder() {
    if myNode == nil || myNode.node == nil {
        return
    }
    left := myTreeNode{myNode.node.Left} // cannot call pointer method on myTreeNode literal
    right := myTreeNode{myNode.node.Right}
    left.postOrder()
    right.postOrder()
    myNode.node.Print()
}

func main() {
    // Instantiate node
    var root tree.Node
    root = tree.Node{Value: 3}
    root.Left = &tree.Node{}
    root.Right = &tree.Node{5, nil, nil}
    root.Right.Right = new(tree.Node) // Use . to access members, regardless of whether it's an address or a struct.
    root.Left.Right = tree.CreateNode(2)
    //root.Print()
    root.Traverse()
    fmt.Println()
    myRoot := myTreeNode{&root}
    myRoot.postOrder()
    fmt.Println()
}

Wrapping a Queue

package queue

type Queue []int

func (q *Queue) Push(v int) {
    // q is a pointer, *q is the object itself, the following changes the object itself
    *q = append(*q,v)
}

func (q *Queue) Pop() int  {
    head:=(*q)[0]
    *q = (*q)[1:]
    return head
}

func (q *Queue) IsEmpty() bool  {
    return len(*q) == 0
}

GOPATH Environment Variable

Default~/go(unix,linux) %USERPROFILE%\go(windows)
Official recommendation: all projects and third-party libraries should be placed under the same GOPATH, or each project can be placed under a different GOPATH.

  • go get to fetch third-party libraries
  • go get -v github.com/gpmgo/gopm can be used to pull resources from Google servers, which was previously done by bypassing the firewall with dep (quite silly).
  • This way we can get goimports gopm get -g -v golang.org/x/tools/cmd/goimports. Because it was previously installed via go get or dep with a VPN, the installation failed, prompting that a golang.org library already exists.
  • The first main file should be placed in a separate directory.

Interfaces

duck typing
The user defines the interface

// main.go
package main

import (
    "fmt"
    "gobasic/retriever/moke"
    real2 "gobasic/retriever/real"
)

type Retriever interface {
    Get(url string) string
}

func download(r Retriever) string {
    return  r.Get("www.baidu.com")
}

func main()  {
    var r Retriever
    r = moke.Retriever{"====This is a fake content==="}
    fmt.Println(download(r))
    r = real2.Retriever{}
    fmt.Println(download(r))
}

// moke/mockretriever.go If it has a Get method (returning string), it means it implements this interface.
package moke

type Retriever struct {
    Contents string
}

func (r Retriever) Get(url string) string  {
    return r.Contents
}
// real/reltriever.go
package real

import (
    "net/http"
    "net/http/httputil"
    "time"
)

type Retriever struct {
    UserAgent string
    TimeOut   time.Duration
}

func (r Retriever) Get(url string) string {
    //panic("implement me")
    resp, err := http.Get("http://www.baidu.com")
    if err != nil {
        panic(err)
    }
    result, err := httputil.DumpResponse(resp, true)
    resp.Body.Close()
    if err != nil {
        panic(err)
    }

    return string(result)
}

What an interface variable points to

  • Interface variable
  • Implementer type
  • Implementer pointer --> Implementer
    Interface variables come with pointers; interface variables are also passed by value, so there's almost no need to use pointers to interfaces; pointer receivers can only implement pointer methods, while value receivers can use either values or addresses.

Any type can be specified as interface{}

Inspecting interface variables

  • Represents any type interface{}
  • Type Assertion
  • Type Switch

Some standard Go interfaces

  • Stringer
  • Reader
  • Writer

Functional Programming

  • Functions are first-class citizens; parameters, variables, and return values can all be functions.
  • Higher-order functions
  • Functions --> Closures

Orthodox functional programming: immutability (no state, only constants and functions), functions can only have one parameter,

package main

import "fmt"

func adder() func(int) int {
    sum:=0
    return func(i int) int {
        sum+=i
        return sum
    }
}

func main()  {
    a:=adder()
    for i:=0;i<=10;i++{
        fmt.Printf("0+1+...+%d = %d\n",i,a(i))
    }
}

Fibonacci

package main

import "fmt"

// 1,1,2,3,5,8,13,21
//   a,b
//     a,b
func fibonacci() func() int {
    a,b:=0,1
    return func() int {
        a,b=b,a+b
        return a
    }
}

func main() {
    f:= fibonacci()
    for i:=0;i<10;i++ {
        fmt.Printf("%v \t",f())
    }
}

Applications of Closures

  • More natural, no need to specify how to access free variables.
  • No lambda expressions, but there are anonymous functions.

Refactoring tree node

package tree

import "fmt"

// In-order traversal: Left, Root, Right
func (node *Node) Traverse() {
    /*if node==nil {
        return
    }
    node.Left.Traverse()
    // Can only be used for printing,
    node.Print()
    node.Right.Traverse()*/

    node.TraverseFunc(func(n *Node) {
        n.Print()
    })
    fmt.Println()
}

// Refactor it
func (node *Node) TraverseFunc(f func(*Node)) {
    if node == nil {
        return
    }
    node.Left.TraverseFunc(f)
    f(node)
    node.Right.TraverseFunc(f)
}

defer

Resource management, such as file opening and closing, resource management, and error handling. defer ensures that actions occur when the function finishes.

package main

import "fmt"

func tryDefer()  {
    // No fear of panic or return in between
    defer fmt.Println(1)
    defer fmt.Println(2)
    fmt.Println(3)
    panic("error occoured") // For normal programs, don't panic, but handle panics.
    fmt.Println(4)
}

func main() {
    tryDefer()
}

Writing the Fibonacci sequence to fib.txt

package main

import (
    "bufio"
    "fmt"
    "gobasic/functional/fib"
    "os"
)


func WriteFile(filename string) {
    file, err := os.Create(filename)
    if err != nil {
        panic(err)
    }
    defer file.Close() // Close the file
// First write content to the buffer
    writer:=bufio.NewWriter(file) // When opening a resource, think about closing it.
    defer writer.Flush() // Write a close statement nearby.
    f:=fib.Fibonacci()
    for i:=0;i<20;i++{
        fmt.Fprintln(writer,f())
    }
}

func main() {
    //tryDefer()
    WriteFile("fib.txt")
}

It will execute whether there's a return, a panic, or the function simply finishes.

defer file.Close()
defer is a stack
Ensures calls happen when the function finishes
Arguments are evaluated when the defer statement is encountered, not when exiting.
The defer list is Last-In, First-Out (LIFO)

  • When to call
  • Open/Close
  • Lock/Unlock
  • PrintHeader/PrintFooter

Error Handling

panic actually crashes the program. To avoid this, we can print error logs and return. err is just a value.

package main

import (
    "bufio"
    "fmt"
    "gobasic/functional/fib"
    "iris_source/core/errors"
    "os"
)

func WriteFile(filename string) {
    //file, err := os.Create(filename)
    /*
        O_CREATE int = syscall.O_CREAT  // If it doesn't exist, create a new file
        O_EXCL   int = syscall.O_EXCL   // Used with O_CREATE, file must not exist
    */
    file, err := os.OpenFile(filename, os.O_EXCL|os.O_CREATE, 0666)
    // Self-declared error
    err = errors.New("this is a custom error") // If the self-declared error is not a PathError, it will panic.
    if err != nil {
        // panic(err) // Crashing would look bad
        // fmt.Println("file already exists.")
        // fmt.Println("Error: ", err.Error())
        if pathError, ok := err.(*os.PathError); !ok {
            panic(err) // If we really don't know what the error is, let's panic.
        } else {
            fmt.Printf("%s,%s,%s\n", pathError.Op, pathError.Path, pathError.Err)
        }
        return
    }
    writer := bufio.NewWriter(file)
    defer file.Close() // Close the file
    // First write content to the buffer. After the function finishes, the defer stack needs to be executed.
    defer writer.Flush()
    f := fib.Fibonacci()
    for i := 0; i < 20; i++ {
        fmt.Fprintln(writer, f())
    }
}

func main() {
    WriteFile("fib.txt")
}

Unified Error Handling

package main

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

func main() {
    http.HandleFunc("/list/", func(writer http.ResponseWriter, request *http.Request) {
        path:= request.URL.Path[len("/list/"):]
        file,err:=os.Open(path)
        if err != nil{
            panic(err)
        }
        defer file.Close()
        all,err:=ioutil.ReadAll(file)
        if err!=nil {
            panic(err)
        }
        writer.Write(all)
    })
    err:=http.ListenAndServe(":8888",nil)
    if err!=nil {
        panic(err)
    }
}

When you enter a wrong URL or one that is not /list/

//filelistingserver/web.go
package main

import (
    "gobasic/errhandling/filelistingserver/filelisting"
    "net/http"
    "os"

    "github.com/gpmgo/gopm/modules/log"
)

// Define a type
type appHandler func(writer http.ResponseWriter, request *http.Request) error

func errWrapper(handler appHandler) func(writer http.ResponseWriter, request *http.Request) {
    return func(writer http.ResponseWriter, request *http.Request) {
        err := handler(writer, request)
        if err != nil {
            log.Warn("Error 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)
        }
    }
}

func main() {
    http.HandleFunc("/list/", errWrapper(filelisting.HandleFileList))
    err := http.ListenAndServe(":8888", nil)
    if err != nil {
        panic(err)
    }
}
// filelistingserver/filelisting/handler.go similar to controller

panic & recover

Stops current function execution, returns upwards, executing defer at each level. If no recover is encountered, the program exits;
recover is only used in defer calls to get the panic value. If it cannot be handled, it can panic again.

package main

import (
    "fmt"
)

func tryRecover()  {
    defer func() {
        r:=recover()
        if err,ok:=r.(error);ok{
            fmt.Println("Error occurred: ",err)
        }else{
            panic((r))
        }
    }()

    // panic(errors.New("this is an error."))
    // panic(123) 123 is not an error, it will trigger a re-panic.
    b:=0
    a:=5/b
    fmt.Println(a)
}

func main() {
    tryRecover()
}

What happens if we change the request path to / instead of /list/?

func main() {
    http.HandleFunc("/", errWrapper(filelisting.HandleFileList))
    err := http.ListenAndServe(":8888", nil)
    if err != nil {
        panic(err)
    }
}
// runtime error: slice bounds out of range
// handler
package filelisting

import (
    "io/ioutil"
    "iris_source/core/errors"
    "net/http"
    "os"
    "strings"
)

const prefix = "/list/"

func HandleFileList(writer http.ResponseWriter, request *http.Request) error { // Return error, let the upper layer handle it, just return.
    if strings.Index(request.URL.Path, prefix) != 0 {
        return errors.New("path must start with " + prefix)
    }
    path := request.URL.Path[len(prefix):]
    file, err := os.Open(path)
    if err != nil {
        // panic(err) // Although this doesn't crash the program, the user prompt is not very friendly. We can further optimize it.
        // http.Error(writer,err.Error(),http.StatusInternalServerError) // This is also not appropriate; we need to divide errors into internal and user-facing parts.
        return err
    }
    defer file.Close()
    all, err := ioutil.ReadAll(file)
    if err != nil {
        //panic(err)
        return err
    }
    writer.Write(all)

    return nil
}
// web
package main

import (
    "gobasic/errhandling/filelistingserver/filelisting"
    "log"
    "net/http"
    "os"
)

// Define a type
type appHandler func(writer http.ResponseWriter, request *http.Request) error

func errWrapper(handler appHandler) func(writer http.ResponseWriter, request *http.Request) {
    return func(writer http.ResponseWriter, request *http.Request) {
        err := handler(writer, request)
        defer func() {
            if r:=recover();r!=nil {
                // log.Error("panic: %v", r)
                log.Printf("panic: %v",r)
                http.Error(writer, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
            }
        }()
        if err != nil {
            // log.Warn("Error handling request %s",err.Error())
            log.Printf("Error 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)
        }
    }
}

func main() {
    http.HandleFunc("/", errWrapper(filelisting.HandleFileList))
    err := http.ListenAndServe(":8888", nil)
    if err != nil {
        panic(err)
    }
}

Distinguish between what the user sees and what they don't see.

package main

import (
    "gobasic/errhandling/filelistingserver/filelisting"
    "log"
    "net/http"
    "os"
)

// Define a type
type appHandler func(writer http.ResponseWriter, request *http.Request) error

func errWrapper(handler appHandler) func(writer http.ResponseWriter, request *http.Request) {
    return func(writer http.ResponseWriter, request *http.Request) {
        err := handler(writer, request)
        defer func() {
            if r:=recover();r!=nil {
                // log.Error("panic: %v", r)
                log.Printf("panic: %v",r)
                http.Error(writer, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
            }
        }()
        if err != nil {
            // log.Warn("Error handling request %s",err.Error())
            log.Printf("Error handling request %s",err.Error())
            // Handle userError
            if userError,ok:=err.(userError);ok{
                http.Error(writer,userError.Message(),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)
        }
    }
}

// Define user-facing error
type userError interface {
    error // For the system
    Message() string // For the user
}

func main() {
    http.HandleFunc("/", errWrapper(filelisting.HandleFileList))
    err := http.ListenAndServe(":8888", nil)
    if err != nil {
        panic(err)
    }
}
// handler.go
package filelisting

import (
    "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 { // Return error, let the upper layer handle it, just return.
    if strings.Index(request.URL.Path, prefix) != 0 {
        //return errors.New("path must start with " + prefix)
        return userError("path must start with " + prefix)
    }
    path := request.URL.Path[len(prefix):]
    file, err := os.Open(path)
    if err != nil {
        // panic(err) // Although this doesn't crash the program, the user prompt is not very friendly. We can further optimize it.
        // http.Error(writer,err.Error(),http.StatusInternalServerError) // This is also not appropriate; we need to divide errors into internal and user-facing parts.
        return err
    }
    defer file.Close()
    all, err := ioutil.ReadAll(file)
    if err != nil {
        //panic(err)
        return err
    }
    writer.Write(all)

    return nil
}

Interface definitions are self-contained; they don't need to see each other.
Expected errors: use error. E.g., file cannot be opened. Unexpected errors: use panic. E.g., array out of bounds.

主题测试文章,只做测试使用。发布者:Walker,转转请注明出处:https://walker-learn.xyz/archives/6734

(0)
Walker的头像Walker
上一篇 13 hours ago
下一篇 Feb 26, 2025 17:05

Related Posts

EN
简体中文 繁體中文 English