返回(可能隐藏的)变量的惯用方法,这些变量已被定义为单独的多变量返回的一部分



正常情况

在编写具有命名返回值的函数时,通常可以使用裸返回(是否应该单独讨论)。它们可能看起来如下:

func add(x, y int) (z int) {
    z = x + y
    return
}

此处的returnreturn z 含义相同

有问题的情况

然而,下面的节略片段。。。

func loadModule(moduleName, fileRoot string) (module []byte) {
    if strings.HasSuffix(moduleName, ".md") {
        module, err := readConvertMDFile(fileRoot + "htdocs/md/" + moduleName)
        if err != nil {
            log.Println(err)
        }
        return module
    } else {
        module = []byte{}
        return
    }
}

(这个片段运行良好,是我目前解决问题的方法)

如果只有CCD_ 5而不是CCD_。这是因为module是(第二次)与err一起声明的,必须声明CCD_7,因为它在这个范围内还不存在。

可能的解决方案

  1. 按照我所做的操作,显式命名返回变量。虽然这不是一个糟糕的解决方案,但我觉得应该有一种方法来安排代码,使其以裸返回的方式正常运行。其他人评论说,这种显式的返回会导致"代码气味"。

  2. 在开头添加var err error,并使用多重赋值而不是声明。可能是一个更好的解决方案,但为了一致性和减少不必要的行,我更喜欢在可能的情况下使用隐式赋值。

  3. 使用临时moduleT变量,然后分配module = moduleT。。。这让人觉得既混乱又多余。

虽然我可以得到我想要的编译结果,但我希望有人能提出一种清晰、惯用的写作方式。

我总是使用您建议的解决方案2-添加一个额外的var语句。

func loadModule(moduleName, fileRoot string) (module []byte) {
    var err error
    if strings.HasSuffix(moduleName, ".md") {
        module, err = readConvertMDFile(fileRoot + "htdocs/md/" + moduleName)
        if err != nil {
            log.Println(err)
        }
        return
    } else {
        // no need for this as module will be nil if it isn't written to
        // a nil slice is perfectly legal and has len()=0
        // and can be appended to etc
        // module = []byte{}
        return
    }
}

选项2也是最有效的解决方案。请记住,go返回堆栈上的所有值,因此命名的返回值相当于堆栈分配的变量。

如果在选项1或选项3中没有裸返回,那么其中无论如何都有一个隐含的module = modulemodule = moduleT语句。

不幸的是,变量阴影会在一段时间后折磨每个Go程序员。我非常希望编译器不允许函数中的所有阴影,因为它是真正错误的来源。

在我写这个问题时,我的函数如下所示:

(主要用于展示其冗长性)

func loadModule(moduleName, fileRoot string) (module []byte) {
    if strings.HasSuffix(moduleName, ".md") {
        module, err := readConvertMDFile(fileRoot + "htdocs/md/" + moduleName)
        if err != nil {
            log.Println(err)
        }
        return module
    } else if strings.HasSuffix(moduleName, ".html") {
        module, err := ioutil.ReadFile(fileRoot + "htdocs/html/" + moduleName)
        if err != nil {
            log.Println(err)
        }
        return module
    } else if strings.HasSuffix(moduleName, ".js") {
        module, err := ioutil.ReadFile(fileRoot + "htdocs/js/" + moduleName)
        if err != nil {
            log.Println(err)
        }
        return module
    } else if strings.HasSuffix(moduleName, ".css") {
        module, err := ioutil.ReadFile(fileRoot + "htdocs/css/" + moduleName)
        if err != nil {
            log.Println(err)
        }
        return module
    } else {
        module = []byte{}
        return
    }
}

这使用了我建议的解决方案1。它有很多重复的代码(我还是个初学者)。如果我改为使用建议的解决方案2(但不是按照我最初想到的方式),将var err error放在then函数的顶部,代码将从两个方面得到改进:

func loadModule(moduleName, fileRoot string) (module []byte) {
    var err error
    switch {
    case strings.HasSuffix(moduleName, ".md"):
        module, err = readConvertMDFile(fileRoot + "htdocs/md/" + moduleName)
    case strings.HasSuffix(moduleName, ".html"):
        module, err = ioutil.ReadFile(fileRoot + "htdocs/html/" + moduleName)
    case strings.HasSuffix(moduleName, ".js"):
        module, err = ioutil.ReadFile(fileRoot + "htdocs/js/" + moduleName)
    case strings.HasSuffix(moduleName, ".css"):
        module, err = ioutil.ReadFile(fileRoot + "htdocs/css/" + moduleName)
    default:
        module = []byte{}
    }
    if err != nil {
            log.Println(err)
        }
    return
}

不再有阴影变量,错误日志和返回都可以从每个if语句中移出,从而生成更清晰的代码。

很可能有办法改进这一点。编辑:。。。根据@ANisus的建议,if-else链已被switch语句替换。

最新更新