[翻译] effective go 之 Functions

pengfei_xue · · 2661 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

Functions

Multiple return values 返回多个值

One of Go's unusual features is that functions and methods can return multiple values. This form can be used to improve on a couple of clumsy idioms in C programs: in-band error returns (such as -1 for EOF) and modifying an argument.

Go的函数和方法可以同时返回多个值 这个特性可以改善C程序中的不太理想的多值返回形式 -- 参数传入指针 通过这个指针来修改参数 另外再返回一个值 比如-1 或者EOF

In C, a write error is signaled by a negative count with the error code secreted away in a volatile location. In Go, Write can return a count and an error: “Yes, you wrote some bytes but not all of them because you filled the device”. The signature of File.Write in package os is:

举个例子吧 C中的一个写文件的函数如下, 如果还未写就被其它的中断给打断了 会返回EINTR 如果已经写入了一部分数据 然后出现了错误 那么就返回已经写入的字节数 你需要加一个条件判断 来确实是否产生了错误

 #include <unistd.h>

 ssize_t write(int fd, const void *buf, size_t count);


但是Go可以直接返回已写入的字节数 以及相应的错误信息:
func (file *File) Write(b []byte) (n int, err error)

and as the documentation says, it returns the number of bytes written and a non-nil error when n != len(b). This is a common style; see the section on error handling for more examples.

就像文档中所描述的那样 这个函数返回写入的字节数 如果写入的字节数和b不一致 err中就是相应的错误信息


A similar approach obviates the need to pass a pointer to a return value to simulate a reference parameter. Here's a simple-minded function to grab a number from a position in a byte slice, returning the number and the next position.

类似的还有一个方法 不需要传入指针来模拟参数引用 下面这个函数从byte slice中取某个位置上的数据 并且返回改数据 和 它后继的位置

func nextInt(b []byte, i int) (int, int) {
    for ; i < len(b) && !isDigit(b[i]); i++ {
    }
    x := 0
    for ; i < len(b) && isDigit(b[i]); i++ {
        x = x*10 + int(b[i])-'0'
    }
    return x, i
}

You could use it to scan the numbers in an input slice b like this:

可以使用上面的函数来遍历slice:

    for i := 0; i < len(b); {
        x, i = nextInt(b, i)
        fmt.Println(x)
    }


Named result parameters 

The return or result "parameters" of a Go function can be given names and used as regular variables, just like the incoming parameters. When named, they are initialized to the zero values for their types when the function begins; if the function executes a return statement with no arguments, the current values of the result parameters are used as the returned values.

Go可以给返回值定义名字 和输入参数一样使用 定义命名返回值时 这些返回值变量会初始化成相应类型的零值 如果函数的return语句没有带参数 命令返回值变量的当前值会被当做返回值返回

The names are not mandatory but they can make code shorter and clearer: they're documentation. If we name the results of nextInt it becomes obvious which returned int is which.

当然 我们可以不使用命名返回值 不过使用命名返回值 代码会更加简洁 如下面这个函数声明 我们可以很明确地知道value 和 nextPost这两个返回值所代表的意思

func nextInt(b []byte, pos int) (value, nextPos int) {


Because named results are initialized and tied to an unadorned return, they can simplify as well as clarify. Here's a version of io.ReadFull that uses them well:

由于命名返回值变量会自动被初始化 并且在return没有带参数的情况下 会以当前值返回 使用起来简单明了 例如下面这个例子:

func ReadFull(r Reader, buf []byte) (n int, err error) {
    for len(buf) > 0 && err == nil {
        var nr int
        nr, err = r.Read(buf)
        n += nr
        buf = buf[nr:]
    }
    return
}


Defer 暂缓执行 

Go's defer statement schedules a function call (the deferred function) to be run immediately before the function executing the defer returns. It's an unusual but effective way to deal with situations such as resources that must be released regardless of which path a function takes to return. The canonical examples are unlocking a mutex or closing a file.

Go的defer语句 会在函数返回前被调用 它很诡异 但是在某些场合下非常有效 比如 不管函数是怎么样执行的 它的执行路径如何 在函数中获取的资源 必须要被释放 这时就可以加一个defer语句 这里有一个经典的例子 释放锁 或者关闭文件

// Contents returns the file's contents as a string.
func Contents(filename string) (string, error) {
    f, err := os.Open(filename)
    if err != nil {
        return "", err
    }
    defer f.Close()  // f.Close will run when we're finished.

    var result []byte
    buf := make([]byte, 100)
    for {
        n, err := f.Read(buf[0:])
        result = append(result, buf[0:n]...) // append is discussed later.
        if err != nil {
            if err == io.EOF {
                break
            }
            return "", err  // f will be closed if we return here.
        }
    }
    return string(result), nil // f will be closed if we return here.
}


Deferring a call to a function such as Close has two advantages. First, it guarantees that you will never forget to close the file, a mistake that's easy to make if you later edit the function to add a new return path. Second, it means that the close sits near the open, which is much clearer than placing it at the end of the function.

把例如像Close这样的函数暂缓执行 可以带来两个好处 首先它保证你不会忘记关闭这个文件 其次 你可以在打开文件后就立即使用defer来关闭文件 相比于在函数结尾处关闭文件 这样的方式更加明了

The arguments to the deferred function (which include the receiver if the function is a method) are evaluated when the defer executes, not when the call executes. Besides avoiding worries about variables changing values as the function executes, this means that a single deferred call site can defer multiple function executions. Here's a silly example.

传递给defer函数的参数会在defer执行时对其进行计算(如果被defer的函数接受的是另一个函数的返回值 那么这个内层的函数也会在这个时候被执行) 而不是在那个被defer的函数执行的时候才计算出来 这样既可以不必担心传递给defer的函数的参数在过程中被改掉 也可以对多个函数使用defer:

for i := 0; i < 5; i++ {
    defer fmt.Printf("%d ", i)
}

Deferred functions are executed in LIFO order, so this code will cause 4 3 2 1 0 to be printed when the function returns. A more plausible example is a simple way to trace function execution through the program. We could write a couple of simple tracing routines like this:

被defer的函数的执行顺序是后进先出 所以上面代码运行的结果就是 4 3 2 1 0 在看下面这个例子 使用trace函数来跟踪函数的执行过程:

func trace(s string)   { fmt.Println("entering:", s) }
func untrace(s string) { fmt.Println("leaving:", s) }

// Use them like this:
func a() {
    trace("a")
    defer untrace("a")
    // do something....
}

We can do better by exploiting the fact that arguments to deferred functions are evaluated when the defer executes. The tracing routine can set up the argument to the untracing routine. This example:

更进一步 我们可以利用defer函数的参数在defer执行的时候被初始化的特定来优化代码 :

func trace(s string) string {
    fmt.Println("entering:", s)
    return s
}

func un(s string) {
    fmt.Println("leaving:", s)
}

func a() {
    defer un(trace("a"))
    fmt.Println("in a")
}

func b() {
    defer un(trace("b"))
    fmt.Println("in b")
    a()
}

func main() {
    b()
}

prints 程序运行结果:

entering: b
in b
entering: a
in a
leaving: a
leaving: b


For programmers accustomed to block-level resource management from other languages, defer may seem peculiar, but its most interesting and powerful applications come precisely from the fact that it's not block-based but function-based. In the section on panic and recover we'll see another example of its possibilities.

对于习惯了其它语言中 比如python中的with方式的资源管理方法的程序员来说 defer看起来有点奇葩 但是他的魅力之处就在其是基于函数的 而不是基于块 在后续的panic和recover章节中 我们还将看到更多defer的身影


有疑问加站长微信联系(非本文作者)

本文来自:开源中国博客

感谢作者:pengfei_xue

查看原文:[翻译] effective go 之 Functions

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

2661 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传