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

5 回复

在Go语言中,我们通常先处理错误情况吗?

我通常看到的情况是这样的。

错误即值 - Go编程语言 一文指出,既然错误是值,如果你愿意,可以编写更具体的错误处理代码。

更多关于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)
}

主要改进点:

  1. 错误变量命名:无需err2。在Go中,每次使用:=声明变量时,只要左边至少有一个新变量,就可以重用err。作用域是局部的,所以不会冲突。
  2. 返回值:直接返回string,而不是*string。在Go中,字符串是值类型,但它是不可变的,返回字符串的开销很小(只是一个指针和一个长度)。返回指针通常用于需要区分“零值”和“不存在/未设置”的场景(比如*string可以区分""nil)。这里直接返回字符串更简单。
  3. 错误包装:使用fmt.Errorf%w动词来包装错误。这为错误添加上下文信息(例如是哪个文件出错了),并且保留了原始错误类型,便于后续的errors.Iserrors.As检查。这是Go 1.13后处理错误的推荐方式。
  4. 导入路径:使用path/filepath代替pathfilepath包会处理不同操作系统(Windows/Unix)的文件路径分隔符问题,而path只处理斜杠/分隔的路径,常用于URL等。
  5. 主函数错误处理:使用log.Fatalf,它会在打印格式化错误信息后调用os.Exit(1)%v动词可以很好地打印出经过包装的错误链。
  6. 代码清晰度:将文件路径构造单独赋值给configPath,使代码意图更明确。
  7. 注释:将函数注释写在函数声明之上,这是Go文档注释的标准格式,go doc工具会识别它。

这个版本完全遵循了Go的惯用法:每次操作后立即检查错误,并为错误添加上下文后返回,直到在程序的某个层级(这里是main)决定如何处理它(这里是用log.Fatal终止程序)

回到顶部