Golang中为何可以忽略不带下划线的返回错误?

Golang中为何可以忽略不带下划线的返回错误? 也许这背后有一个简单的原因……但我不明白为什么编译器允许这样做:

package main

import (
	"errors"
	"fmt"
)

func hello() error {
	fmt.Println("hello")
	return errors.New("bad things")
}

func main() {
	hello()
}

在一个更复杂的场景中,忘记 hello 函数返回一个错误并不难。如果你只是在代码审查 main 函数(假设 hello 函数在某个库中),审查者很可能会忽略可能发生了错误这个事实。

运行示例:The Go Playground


更多关于Golang中为何可以忽略不带下划线的返回错误?的实战教程也可以访问 https://www.itying.com/category-94-b0.html

5 回复

谢谢

更多关于Golang中为何可以忽略不带下划线的返回错误?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


如果看到一个没有参数且不返回任何内容的函数调用,我可能会仔细检查它。我不认为 Go 编译器试图阻止你做某些事情,比如忽略错误。它只是试图让你有意为之。在我看来,这似乎是有意的。

在编写软件时,我遇到过很多问题。但可以说,调用一个名为 hello 的函数,却忽略它可能返回某些内容的情况,并不在其中。尤其是在 Go 中,如果可能出错,惯用的做法是返回错误。因此,检查错误也是惯用的做法。你试图防范的实际场景是什么?

啊!感谢分享。我在搜索时错过了这一点。我想这基本上解答了我的疑问——而且可以理解,在这一点上,它会破坏向后兼容性。

他们提到了这个包:GitHub - kisielk/errcheck: errcheck 用于检查你是否检查了错误。

我认为这或许能满足我关于捕获错误方面的顾虑。可能有点“吵闹”,但看起来你可以提供一个允许列表,列出那些你不关心的函数。

我会尝试一下,并关注那些提案,看看它们最终会如何发展。再次感谢!

我想我的例子确实有些刻意。我只是想用一个能引发我所提问题的函数签名来说明。这个问题的背景是,我一直在努力将Go语言引入我的工作团队——因此我一直在尝试学习这门语言中许多细微的边缘情况,并理解新的Go程序员可能会在哪些地方犯错。

我也可以反驳说,Go确实试图确保你不会犯简单的错误(也许不是特指错误,因为它们只是另一种值)。对我来说,我的hello示例与尝试这样做是同一回事(再次强调,这只是一个为了说明目的而刻意构造的例子):

package main

import "fmt"

func executeUpdate(count int) (int, error) {
	return count, nil
}

func main() {
	count := executeUpdate(10)
	fmt.Println(count)
}

这段代码当然无法编译。相反,编译器会提示:./prog.go:12:11: assignment mismatch: 1 variable but executeUpdate returns 2 values

那么,为什么我的hello示例没有因为类似的错误而编译失败呢(例如 assignment mismatch: 0 variables but hello returns 1 value)?

我原本期望需要写成 _ = hello() 编译器才会接受(这样写也可以……但并非必需)。

我应该指出,我认为没有人会否认这是糟糕的代码,也不符合Go语言的惯用写法。我只是试图理解为什么编译器不强制执行这一点,因为这似乎是一个能确保代码更清晰、更易读的简单情况。我也知道(没有确切的例子)我曾经复制/粘贴函数签名,却忘记处理错误,而当编译器没有报错时,我就忘记了。所以,这与其说是试图走捷径,不如说是试图捕捉简单的错误。

在Go语言中,编译器允许忽略不带下划线的返回错误,这是语言设计上的一个明确选择。Go团队认为,强制处理所有错误会导致代码冗余,特别是在某些场景下错误确实可以安全忽略(比如日志记录失败、清理操作等)。这种设计给予了开发者灵活性,但同时也要求开发者对错误处理保持警惕。

以下是一个示例,展示了何时可以安全忽略错误:

package main

import (
    "fmt"
    "os"
)

func writeLog(message string) error {
    // 尝试写入日志文件,但即使失败也不影响核心逻辑
    file, err := os.OpenFile("app.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
    if err != nil {
        return err
    }
    defer file.Close()
    
    _, err = file.WriteString(message + "\n")
    return err
}

func main() {
    // 忽略日志写入错误,因为即使日志失败,程序仍可继续运行
    writeLog("Application started")
    
    // 主逻辑
    fmt.Println("Running main logic...")
}

然而,对于关键错误(如数据库连接失败),必须显式处理。编译器不会阻止你忽略错误,但静态分析工具(如go vet)可以检测到未处理的错误并发出警告。例如:

package main

import (
    "database/sql"
    "fmt"
    _ "github.com/go-sql-driver/mysql"
)

func connectDB() (*sql.DB, error) {
    return sql.Open("mysql", "user:password@/dbname")
}

func main() {
    // 这里忽略了错误,但go vet会警告:Error return value is not checked
    db, _ := connectDB()
    defer db.Close()
    
    // 更好的做法是显式处理错误
    db, err := connectDB()
    if err != nil {
        fmt.Println("Database connection failed:", err)
        return
    }
    defer db.Close()
}

总之,Go语言将错误处理的决策权交给了开发者,而不是编译器。这种设计平衡了代码简洁性和安全性,但要求开发者在代码审查和测试中格外注意错误处理。

回到顶部