如何编写符合惯用语法的Golang代码建议

7

我正在学习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的初学者,所以我可能会带着我使用的其他语言的包袱。在我看来,以预防的方式处理异常条件(在使用它之前检查密钥)既不聪明也不高效。对吗?


Lumberjack这个包可能有助于简化基于文件的日志记录(例如文件轮转,最大大小等)。如果您需要更为严肃的解决方案,请访问:https://github.com/natefinch/lumberjack。 - elithrar
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("Unknown function: " + name)
    }
    return nil
}

错误处理/报告

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

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

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

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
    }
}

1
在Go中,函数可以具有多个返回值,因此在Go中,通过返回“error”值来传播错误,即使该函数通常具有其他返回值。编辑答案以显示它。 - icza
4
哇!这才是我所说的五星级答案。就好像我参加了一位专家主持的围棋大师班一样。再次感谢你! - DroidOS

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接