【问题标题】:Advice on writing idiomatic Golang关于编写惯用 Golang 的建议
【发布时间】:2015-06-17 11:42:33
【问题描述】:

我正在掌握 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 的哲学是检查密钥的有效性 first 并恐慌,而不是试图盲目地使用该密钥。对吗?

我是围棋初学者,所以我可能对我使用的其他语言有包袱。在我看来,以先发制人的方式处理异常情况(在使用之前检查密钥)既不聪明也不高效。对吧?

【问题讨论】:

  • lumberjack 软件包可能有助于简化基于文件的日志记录(即文件轮换、最大大小等),如果您需要更严重的事情:github.com/natefinch/lumberjack

标签: go exception maps associative-array


【解决方案1】:

记录到文件

每次我想记录一些东西时,我都不会打开和关闭文件。在启动时,我只需打开它一次并将其设置为输出,然后在程序存在之前将其关闭。而且我不会使用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("Unknown function: " + name)
    }
    return nil
}

错误处理/报告

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

所以call() 函数应该有一个error 返回类型来表示如果找不到指定的函数。

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

var ErrInvalidFunc = errors.New("Invalid function!")

此解决方案的优点是call() 函数的调用者可以将返回的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
    }
}

【讨论】:

  • @DroidOS 在 Go 中的函数可以有多个返回值,因此在 Go 中你会通过返回 error 值来传播错误,即使函数通常有其他返回值。编辑 anwser 以显示它。
  • 哇!现在这就是我所说的 5* 答案。就像我参加了由专家举办的围棋大师班。再次感谢您!
猜你喜欢
  • 2013-11-11
  • 2015-03-02
  • 1970-01-01
  • 2010-10-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-11-30
相关资源
最近更新 更多