Golang中如何编写简单地道的代码
Golang中如何编写简单地道的代码
大家好,我是Go语言新手。我想请大家对一个非常简单的Go函数提些意见,这个函数用于读取用户的配置文件,如果在打开/读取过程中出现任何问题,程序将以状态码1退出。显然,我最终会解析JSON,但作为Go新手,我想从简单的开始,看看我目前的写法是否符合Go语言的惯用法。这种写法是惯用的吗?即程序员需要不断地检查错误?我不喜欢err2这个变量名。我通常不会使用指针,除非必要,我更倾向于返回一个字符串。这是返回一个可选字符串的惯用方式吗?对我来说,这似乎过于冗长了。
欢迎任何评论。
package main
import (
"fmt"
"io/ioutil"
"log"
"os"
"path"
)
// 尝试读取用户主目录下的 .sinkerrc.json 文件,并返回指向字符串的指针。
func readSinkerRc() (*string, error) {
homdir, err := os.UserHomeDir()
if err != nil {
// 获取主目录时出现问题
return nil, err
}
dat, err2 := ioutil.ReadFile(path.Join(homdir, ".sinkerrc.json"))
if err2 != nil {
// 读取文件时出现问题。
return nil, err2
}
ret := string(dat)
// 返回数据的指针,这样在错误情况下我们也可以返回nil。
return &ret, err
}
func main() {
dat, err := readSinkerRc()
if err != nil {
log.Fatal("读取您的 .sinkerrc.json 文件时出现问题: " + err.Error())
}
fmt.Println(*dat)
}
更多关于Golang中如何编写简单地道的代码的实战教程也可以访问 https://www.itying.com/category-94-b0.html
更多关于Golang中如何编写简单地道的代码的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
谢谢!
我明白了,既然我们只检查错误,就不需要为字符串返回 nil,使用空字符串可以简化代码,并且允许我们避免使用指针,同时将第一个返回值类型声明为字符串。
有趣的是,在流程中,有些特殊的条件会先被处理。例如,大多数时候系统都能读取用户的主目录。这与我通常被教导的做法有些相反,通常我们会先处理正常情况。
在 Go 语言中,我们通常先处理错误情况吗?你也可以先检查 err 是否为 nil,进行计算,然后在最后处理它不为 nil 的情况,但通常不这样做,是吗?
一些想法:你不需要重命名 err,但确实,你应该在每个可能发生错误的地方检查错误。我还去掉了指针,并确保在每个点都返回字符串。
func readSinkerRc() (string, error) {
homdir, err := os.UserHomeDir()
if err != nil {
// problem getting homedir
return "", err
}
dat, err := ioutil.ReadFile(path.Join(homdir, ".sinkerrc.json"))
if err != nil {
// Problem reading file.
return "", err
}
return string(dat), nil
}
func main() {
dat, err := readSinkerRc()
if err != nil {
log.Fatal("Problem reading your .sinkerrc.json file: " + err.Error())
}
fmt.Println(dat)
}
package main
import (
"fmt"
"io/ioutil"
"log"
"os"
"path/filepath"
)
// readSinkerRc 读取用户主目录下的 .sinkerrc.json 文件。
func readSinkerRc() (string, error) {
path, err := os.UserHomeDir()
if err != nil {
return "", err
}
path = filepath.Join(path, ".sinkerrc.json")
data, err := ioutil.ReadFile(path)
if err != nil {
return "", err
}
return string(data), err
}
func main() {
data, err := readSinkerRc()
if err != nil {
log.Fatal("读取 .sinkerrc.json 文件时出错: " + err.Error())
}
fmt.Println(data)
}
这是一个很好的问题,也是每个Go新手都会遇到的困惑。你的代码已经抓住了Go的核心思想,但确实有一些可以改进的地方,使其更地道。
首先,直接回答你的核心问题:是的,在Go中,程序员需要不断地、显式地检查每一个可能返回错误的操作。 这不是冗长,而是Go语言设计哲学的一部分——显式优于隐式。它强制你处理每一个错误,避免了错误被意外忽略,从而编写出更健壮的程序。
下面是一个更地道的版本,它解决了你提到的几个问题(err2变量名、不必要的指针、主函数中的错误处理):
package main
import (
"fmt"
"io/ioutil"
"log"
"os"
"path/filepath"
)
// readSinkerRc 尝试读取用户主目录下的 .sinkerrc.json 文件。
// 成功时返回文件内容的字符串,失败时返回错误。
func readSinkerRc() (string, error) {
homeDir, err := os.UserHomeDir()
if err != nil {
return "", fmt.Errorf("获取用户主目录失败: %w", err)
}
configPath := filepath.Join(homeDir, ".sinkerrc.json")
data, err := ioutil.ReadFile(configPath)
if err != nil {
return "", fmt.Errorf("读取配置文件 %s 失败: %w", configPath, err)
}
return string(data), nil
}
func main() {
configData, err := readSinkerRc()
if err != nil {
log.Fatalf("初始化配置失败: %v", err)
}
fmt.Println(configData)
// 后续可以在这里添加JSON解析逻辑: json.Unmarshal([]byte(configData), &myConfig)
}
主要改进点:
- 错误变量命名:无需
err2。在Go中,每次使用:=声明变量时,只要左边至少有一个新变量,就可以重用err。作用域是局部的,所以不会冲突。 - 返回值:直接返回
string,而不是*string。在Go中,字符串是值类型,但它是不可变的,返回字符串的开销很小(只是一个指针和一个长度)。返回指针通常用于需要区分“零值”和“不存在/未设置”的场景(比如*string可以区分""和nil)。这里直接返回字符串更简单。 - 错误包装:使用
fmt.Errorf和%w动词来包装错误。这为错误添加上下文信息(例如是哪个文件出错了),并且保留了原始错误类型,便于后续的errors.Is或errors.As检查。这是Go 1.13后处理错误的推荐方式。 - 导入路径:使用
path/filepath代替path。filepath包会处理不同操作系统(Windows/Unix)的文件路径分隔符问题,而path只处理斜杠/分隔的路径,常用于URL等。 - 主函数错误处理:使用
log.Fatalf,它会在打印格式化错误信息后调用os.Exit(1)。%v动词可以很好地打印出经过包装的错误链。 - 代码清晰度:将文件路径构造单独赋值给
configPath,使代码意图更明确。 - 注释:将函数注释写在函数声明之上,这是Go文档注释的标准格式,
go doc工具会识别它。
这个版本完全遵循了Go的惯用法:每次操作后立即检查错误,并为错误添加上下文后返回,直到在程序的某个层级(这里是main)决定如何处理它(这里是用log.Fatal终止程序)。

