Advice on writing idiomatic Golang

huangapple go评论75阅读模式
英文:

Advice on writing idiomatic Golang

问题

我正在努力理解Golang的工作方式。首先是一些示例代码:

package main

import (
	"log"
	"os"
)

func logIt(s string) {
	f, _ := os.OpenFile("errors.log", os.O_RDWR|os.O_CREATE|os.O_APPEND,
		0666)
	defer f.Close()

	log.SetOutput(f)
	log.Println(s)
}

type iAm func(string)

func a(iam string) { logIt(iam + " A") }

func b(iam string) { logIt(iam + " B") }

func c(iam string) { logIt(iam + " C") }

var funcs = map[string]iAm{"A": a, "B": b, "C": c}

func main() {
	funcs["A"]("Je suis")
	funcs["B"]("Ich bin")
	funcs["A"]("Yo soy")
	funcs["D"]("Soy Yo")
}

解释

  • 我将所有的日志输出都重定向到一个文件中,以便以后监控。这是正确的重定向方式吗?
  • 我想根据用户输入在运行时确定要调用的正确函数。为此,我将这些函数打包成了一个Golang映射(在PHP中我会使用关联数组)。这样做是有效的吗?
  • 最后,你会注意到我实际上在映射中没有一个D键。最后一个funcs调用会导致Go抛出一个错误。在另一种语言中,我会将这些调用包装在一个try...块中,以避免出现问题。根据我所了解的,Go的哲学是首先检查键的有效性,然后才会引发panic,而不是盲目地使用该键。这是正确的吗?

作为一个Go初学者,我可能会受到我使用的其他语言的影响。在我看来,以预防性的方式处理异常情况(在使用之前检查键)既不聪明也不高效。对吗?

英文:

I am in the process of getting to grips with the Golang way of doing things. First some sample code:

package main

import (
	"log"
	"os"
)

func logIt(s string) {
	f, _ := os.OpenFile("errors.log", os.O_RDWR|os.O_CREATE|os.O_APPEND,
		0666)
	defer f.Close()

	log.SetOutput(f)
	log.Println(s)
}

type iAm func(string)

func a(iam string) { logIt(iam + " A") }

func b(iam string) { logIt(iam + " B") }

func c(iam string) { logIt(iam + " C") }

var funcs = map[string]iAm{"A": a, "B": b, "C": c}

func main() {
	funcs["A"]("Je suis")
	funcs["B"]("Ich bin")
	funcs["A"]("Yo soy")
	funcs["D"]("Soy Yo")
}

Explanations

  • I am channeling all my log out put to a file so I can monitor it later. Is this the right way to channel?
  • I want to identify the right function to call at run time based on user inputs. To that end I have packed the functions as a Golang map - In PHP I would have used an associative array. This works. However, is this an efficient way to do things.
  • Finally, you will note that I don't actually have a D key in my map. That last funcs call causes Go to throw a wobbly. In another language I would have wrapped those calls in a try... block and avoided the problem. From what I have understood the Go philosophy is to check the validity of the key first and panic rather than trying to blindly use that key. Is that correct?

I am a Go beginner so I probably have baggage from the other languages I use. To my mind dealing with exceptional conditions in a pre-emptive way (check the key prior to using it) is neither smart nor efficient. Right?

答案1

得分: 18

写入文件日志

我不会每次想要记录日志时都打开和关闭文件。在启动时,我只会打开它一次并将其设置为输出,然后在程序退出之前关闭它。我也不会使用logIt()函数:只需使用log包的函数进行日志记录,这样您就可以使用格式化日志记录,例如log.Printf()等。

动态函数选择

函数映射完全可以,而且在性能方面表现良好。如果您需要更快的速度,可以根据函数名称进行switch,并直接在case分支中调用目标函数。

检查键的存在性

map中的值是函数值。函数类型的零值是nil,您不能调用nil函数,因此必须在调用之前检查该值。请注意,如果使用不存在的键索引映射,则返回值类型的零值将返回,对于函数类型来说是nil。因此,我们只需检查该值是否为nil。还有另一种逗号-OK习惯用法,例如fv,ok := funcs[name],其中ok将是一个布尔值,告诉我们该键是否在映射中找到。

不过,您不必在每次调用中都重复这样做:

func call(name, iam string) {
    if fv := funcs[name]; fv != nil {
        fv(iam)
    }
}

注意:

如果您选择使用switchdefault分支将处理无效的函数名称(当然,这里您将不需要函数映射):

func call(name, iam string) error {
    switch name {
    case "A":
        a(iam)
    case "B":
        b(iam)
    case "C":
        c(iam)
    default:
        return errors.New("未知函数:" + name)
    }
    return nil
}

错误处理/报告

在Go中,函数可以具有多个返回值,因此在Go中,通过返回一个error值来传播错误,即使该函数通常具有其他返回值。

因此,call()函数应该具有一个error返回类型,以表示是否找不到指定的函数。

您可以选择返回由errors.New()函数创建的新error值(因此它可以是动态的),或者您可以选择创建一个全局变量并具有固定的错误值,例如:

var ErrInvalidFunc = errors.New("无效的函数!")

这种解决方案的优点是call()函数的调用者可以将返回的error值与ErrInvalidFunc全局变量的值进行比较,以知道这种情况并相应地采取行动,例如:

if err := call("foo", "bar"); err != nil {
    if err == ErrInvalidFunc {
        // "foo"是无效的函数名称
    } else {
        // 其他错误
    }
}

完整的修订程序如下:

<sup>(稍微压缩以避免垂直滚动条。)</sup>

package main

import (
    "errors"
    "log"
    "os"
)

type iAm func(string)

func a(iam string) { log.Println(iam + " A") }
func b(iam string) { log.Println(iam + " B") }
func c(iam string) { log.Println(iam + " C") }

var funcs = map[string]iAm{"A": a, "B": b, "C": c}

func main() {
    f, err := os.OpenFile("errors.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
    if err != nil {
        panic(err)
    }
    defer f.Close()
    log.SetOutput(f)

    call("A", "Je suis")
    call("B", "Ich bin")
    call("C", "Yo soy")
    call("D", "Soy Yo")
}

func call(name, iam string) error {
    if fv := funcs[name]; fv == nil {
        return errors.New("未知函数:" + name)
    } else {
        fv(iam)
        return nil
    }
}
英文:

Logging to file

I wouldn't open and close the file each time I want to log something. At startup I would just open it once and set it as output, and before the program exists, close it. And I wouldn't use a logIt() function: just log using the functions of the log package, so you can do formatted logging e.g. with log.Printf() etc.

Dynamic function choosing

A function map is completely OK, and does well performance-wise. If you need something faster, you can do a switch based on the function name, and call directly the target function in the case branches.

Checking the existence of the key

The values in the map are function values. The zero value of a function type is nil and you can't call a nil function, so you have to check the value before proceeding to call it. Note that if you index a map with a non-existing key, the zero-value of the value type is returned which is nil in case of function type. So we can simply check if the value is nil. There is also another comma-ok idiom, e.g. fv, ok := funcs[name] where ok will be a boolean value telling if the key was found in the map.

You can do it in one place though, you don't have to duplicate it in each call:

func call(name, iam string) {
	if fv := funcs[name]; fv != nil {
		fv(iam)
	}
}

Note:

If you would choose to use a switch, the default branch would handle the invalid function name (and here you would not need the function map of course):

func call(name, iam string) error {
	switch name {
	case &quot;A&quot;:
		a(iam)
	case &quot;B&quot;:
		b(iam)
	case &quot;C&quot;:
		c(iam)
	default:
		return errors.New(&quot;Unknown function: &quot; + name)
	}
	return nil
}

Error handling / reporting

In Go functions can have multiple return values, so in Go you propagate error by returning an error value, even if the function normally has other return value(s).

So the call() function should have an error return type to signal if the specified function cannot be found.

You may choose to return a new error value created by e.g. the errors.New() function (so it can be dynamic) or you may choose to create a global variable and have a fixed error value like:

var ErrInvalidFunc = errors.New(&quot;Invalid function!&quot;)

The pros of this solution is that callers of the call() function can compare the returned error value to the value of the ErrInvalidFunc global variable to know that this is the case and act accordingly, e.g.:

if err := call(&quot;foo&quot;, &quot;bar&quot;); err != nil {
    if err == ErrInvalidFunc {
        // &quot;foo&quot; is an invalid function name
    } else {
        // Some other error
    }
}

So the complete revised program:

<sup>(Slightly compacted to avoid vertical scroll bars.)</sup>

package main

import (&quot;errors&quot;; &quot;log&quot;; &quot;os&quot;)

type iAm func(string)

func a(iam string) { log.Println(iam + &quot; A&quot;) }
func b(iam string) { log.Println(iam + &quot; B&quot;) }
func c(iam string) { log.Println(iam + &quot; C&quot;) }

var funcs = map[string]iAm{&quot;A&quot;: a, &quot;B&quot;: b, &quot;C&quot;: c}

func main() {
	f, err := os.OpenFile(&quot;errors.log&quot;, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
	if err != nil {
		panic(err)
	}
	defer f.Close()
	log.SetOutput(f)

	call(&quot;A&quot;, &quot;Je suis&quot;)
	call(&quot;B&quot;, &quot;Ich bin&quot;)
	call(&quot;C&quot;, &quot;Yo soy&quot;)
	call(&quot;D&quot;, &quot;Soy Yo&quot;)
}

func call(name, iam string) error {
	if fv := funcs[name]; fv == nil {
		return errors.New(&quot;Unknown funcion: &quot; + name)
	} else {
		fv(iam)
        return nil
    }
}

huangapple
  • 本文由 发表于 2015年6月17日 19:42:33
  • 转载请务必保留本文链接:https://go.coder-hub.com/30890591.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定