Golang中构造函数/初始化函数失败处理方案探讨

Golang中构造函数/初始化函数失败处理方案探讨 我正在编写一个库,用于与一个不太知名的云存储服务的Web服务进行交互。该库中的一个函数是 NewUserClient,顾名思义,它创建一个新的 Client 对象,用于与该Web服务交互。此调用需要与API进行一些来回通信,将用户名和密码转换为API令牌,该令牌可以包含在请求头中以授权后续请求。由于这种与 http.Client 的交互,可能会返回错误。我并不是一个经验丰富的Go程序员,但根据我接触过的几个Go包来看,让 NewX 函数返回 (X, error) 似乎并不常见。这让我思考是否应该:

  • 避免从 NewX 函数返回多个值;要么:
    • 将任何错误推迟到第一个在其返回值中包含错误的函数
    • 使用 panic(我不认为这是正确的方式,但这在Python/C#/Java中很常见(同时我也知道Go不是Python/C#/Java 🙂))
    • 在返回的对象上添加一个 Err() 函数或类似的东西,以获取最近的错误
  • 使用不同的命名约定(例如 CreateX

我不知道这些选项中哪一个适合,或者是否有其他我没有考虑到的选项?是否有某个选项是默认的或事实上的标准,我应该优先选择,除非有理由不这样做?


更多关于Golang中构造函数/初始化函数失败处理方案探讨的实战教程也可以访问 https://www.itying.com/category-94-b0.html

5 回复

谢谢Jakob,我想我会采用这个方案。我喜欢这种在初始化失败时甚至无法获取Client值的思路。

更多关于Golang中构造函数/初始化函数失败处理方案探讨的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


skillian:

创建一个返回 (X, error) 的 NewX 函数似乎并不常见

我经常看到并采用这种做法,对此完全不会感到意外。这在标准库中也非常普遍。

func main() {
    fmt.Println("hello world")
}

将认证功能设计为类似 Authenticate(username, pw string) (*Client, error) 的形式可能更合理,因为这样能明确表示在返回客户端前需要先验证数据有效性。

另一种方案是让 NewClient 返回一个包含 Authenticate 方法的客户端,并在验证失败时通过该方法返回错误。

func main() {
    fmt.Println("hello world")
}

感谢您的回复,Jon。我很喜欢使用除NewX之外的其他函数名称来明确表示这不仅返回一个X,但与此同时,我认为像Authenticate这样的名称并不能让我明显看出它会给我一个新的Client,除非我将来查看自己的文档(假设我不经常使用这个包)。如果可能的话,我希望能够自然而然地达到“成功之路”!

出于同样的原因,我并不太喜欢使用单独的Authenticate方法来验证客户端,因为API用户可能会在构建Client后忘记使用此函数。我希望客户端的构建/初始化是一个单一的函数调用(从公共API中),以消除错误使用客户端的可能性。

在Go语言中,从构造函数返回 (T, error) 是处理初始化失败的标准做法。这是Go社区广泛接受和推荐的模式,因为它明确地处理了错误,避免了隐藏的失败状态。你的 NewUserClient 函数应该返回 (*Client, error),而不是推迟错误或使用 panic。

以下是一个示例实现:

package yourlibrary

import (
    "errors"
    "net/http"
)

type Client struct {
    token string
    // 其他字段...
}

// NewUserClient 创建一个新的客户端实例,返回客户端和可能的错误
func NewUserClient(username, password string) (*Client, error) {
    if username == "" || password == "" {
        return nil, errors.New("username and password cannot be empty")
    }
    
    // 模拟与API的交互来获取令牌
    token, err := authenticateWithAPI(username, password)
    if err != nil {
        return nil, err
    }
    
    client := &Client{
        token: token,
    }
    
    return client, nil
}

func authenticateWithAPI(username, password string) (string, error) {
    // 这里实现实际的HTTP请求逻辑
    // 如果请求失败,返回错误
    resp, err := http.Post("https://api.example.com/auth", "application/json", nil)
    if err != nil {
        return "", err
    }
    defer resp.Body.Close()
    
    if resp.StatusCode != http.StatusOK {
        return "", errors.New("authentication failed")
    }
    
    // 解析响应获取令牌
    return "example-token", nil
}

使用示例:

func main() {
    client, err := yourlibrary.NewUserClient("user", "pass")
    if err != nil {
        // 处理初始化错误
        log.Fatalf("Failed to create client: %v", err)
    }
    
    // 使用client进行后续操作
    // ...
}

这种模式的优点:

  • 错误处理明确,调用方必须检查错误
  • 符合Go的标准库约定(如 os.Opensql.Open 等)
  • 避免了对象处于无效状态的风险
  • 代码可读性强,意图清晰

避免使用 panic,因为在Go中panic通常用于不可恢复的错误。也不要使用 Err() 方法推迟错误检查,因为这会导致对象在无效状态下被使用。保持 NewX 命名,因为这是Go中构造函数的惯用命名方式。

回到顶部