关于编写地道的 Golang 的建议
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 哲学是先检查密钥 的有效性 并恐慌,而不是试图盲目地使用该密钥。对吗?
我是 Go 初学者,所以我可能对我使用的其他语言有一些包袱。在我看来,以先发制人的方式处理异常情况(在使用之前检查密钥)既不明智也不高效。对吗?
记录到文件
每次我想记录一些东西时,我不会打开和关闭文件。在启动时,我只会打开它一次并将其设置为输出,然后在程序存在之前关闭它。而且我不会使用 logIt()
函数:只需使用 log
package, so you can do formatted logging e.g. with log.Printf()
等
的函数进行记录
动态函数选择
功能映射完全可以,并且在性能方面做得很好。如果你需要更快的东西,你可以根据函数名做一个switch
,直接调用case
分支中的目标函数。
检查密钥是否存在
map
中的值是函数值。函数类型的零值是 nil
并且您不能调用 nil
函数,因此您必须在继续调用它之前检查该值。请注意,如果您使用不存在的键索引映射,则值类型的零值是 returned,在函数类型的情况下是 nil
。所以我们可以简单地检查值是否为 nil
。还有另一个逗号可以的成语,例如fv, ok := funcs[name]
其中 ok
将是一个布尔值,指示是否在地图中找到了键。
虽然你可以在一个地方完成,但你不必在每次调用时都重复它:
func call(name, iam string) {
if fv := funcs[name]; fv != nil {
fv(iam)
}
}
注:
如果您选择使用 switch
,default
分支将处理无效的函数名称(当然这里您不需要函数映射):
func call(name, iam string) error {
switch name {
case "A":
a(iam)
case "B":
b(iam)
case "C":
c(iam)
default:
return errors.New("Unknown function: " + name)
}
return nil
}
错误处理/报告
在 Go 中函数可以有多个 return 值,所以在 Go 中你通过 returning 一个 error
值来传播错误,即使函数通常有其他 return ] 值。
所以 call()
函数应该有一个 error
return 类型来表示找不到指定的函数。
您可以选择 return 一个新的 error
值,例如errors.New()
函数(因此它可以是动态的)或者您可以选择创建一个全局变量并具有固定的错误值,例如:
var ErrInvalidFunc = errors.New("Invalid function!")
此解决方案的优点是 call()
函数的调用者可以将 returned error
值与 ErrInvalidFunc
全局变量的值进行比较以了解是这种情况并采取相应的行动,例如:
if err := call("foo", "bar"); err != nil {
if err == ErrInvalidFunc {
// "foo" is an invalid function name
} else {
// Some other error
}
}
所以完整的修改程序:
(稍微压缩以避免垂直滚动条。)
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("Unknown funcion: " + name)
} else {
fv(iam)
return nil
}
}
我正在掌握 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 哲学是先检查密钥 的有效性 并恐慌,而不是试图盲目地使用该密钥。对吗?
我是 Go 初学者,所以我可能对我使用的其他语言有一些包袱。在我看来,以先发制人的方式处理异常情况(在使用之前检查密钥)既不明智也不高效。对吗?
记录到文件
每次我想记录一些东西时,我不会打开和关闭文件。在启动时,我只会打开它一次并将其设置为输出,然后在程序存在之前关闭它。而且我不会使用 logIt()
函数:只需使用 log
package, so you can do formatted logging e.g. with log.Printf()
等
动态函数选择
功能映射完全可以,并且在性能方面做得很好。如果你需要更快的东西,你可以根据函数名做一个switch
,直接调用case
分支中的目标函数。
检查密钥是否存在
map
中的值是函数值。函数类型的零值是 nil
并且您不能调用 nil
函数,因此您必须在继续调用它之前检查该值。请注意,如果您使用不存在的键索引映射,则值类型的零值是 returned,在函数类型的情况下是 nil
。所以我们可以简单地检查值是否为 nil
。还有另一个逗号可以的成语,例如fv, ok := funcs[name]
其中 ok
将是一个布尔值,指示是否在地图中找到了键。
虽然你可以在一个地方完成,但你不必在每次调用时都重复它:
func call(name, iam string) {
if fv := funcs[name]; fv != nil {
fv(iam)
}
}
注:
如果您选择使用 switch
,default
分支将处理无效的函数名称(当然这里您不需要函数映射):
func call(name, iam string) error {
switch name {
case "A":
a(iam)
case "B":
b(iam)
case "C":
c(iam)
default:
return errors.New("Unknown function: " + name)
}
return nil
}
错误处理/报告
在 Go 中函数可以有多个 return 值,所以在 Go 中你通过 returning 一个 error
值来传播错误,即使函数通常有其他 return ] 值。
所以 call()
函数应该有一个 error
return 类型来表示找不到指定的函数。
您可以选择 return 一个新的 error
值,例如errors.New()
函数(因此它可以是动态的)或者您可以选择创建一个全局变量并具有固定的错误值,例如:
var ErrInvalidFunc = errors.New("Invalid function!")
此解决方案的优点是 call()
函数的调用者可以将 returned error
值与 ErrInvalidFunc
全局变量的值进行比较以了解是这种情况并采取相应的行动,例如:
if err := call("foo", "bar"); err != nil {
if err == ErrInvalidFunc {
// "foo" is an invalid function name
} else {
// Some other error
}
}
所以完整的修改程序:
(稍微压缩以避免垂直滚动条。)
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("Unknown funcion: " + name)
} else {
fv(iam)
return nil
}
}