掘金 后端 ( ) • 2024-05-09 13:49

这一章节涵盖了以下内容:

  • 按照Go语言的惯用法组织我们的代码。
  • 高效地处理抽象概念:接口和泛型。
  • 关于如何构建项目的最优实践。

以一种干净、符合Go语言习惯且易于维护的方式组织Go代码库并不是一项容易的任务。它需要经验和甚至犯错来理解所有与代码和项目组织相关的最佳实践。需要避免哪些陷阱(例如,变量遮蔽和嵌套代码滥用)?我们如何构建包?何时何地使用接口或泛型、初始化函数和工具包?在本章中,我们将检查常见的组织错误。

2.1 非预期的变量遮蔽

变量的作用域指的是变量可以被引用的地方:换句话说,就是应用程序中一个名称绑定有效的部分。在Go语言中,一个在块(block)中声明的变量名可以在内部块中被重新声明。这个原则称为变量遮蔽(variable shadowing),它容易引发常见的错误。

以下示例展示了由于变量遮蔽导致的一个非预期的副作用。它根据一个追踪布尔值的不同,以两种不同的方式创建了一个HTTP客户端:

var client *http.Client // 声明一个HTTP客户端变量
if tracing {
    client, err := createClientWithTracing() // 创建了一个启用了追踪的HTTP客户端.(在这个代码块中,client变量被遮蔽)
    if err != nil {
      return err
    }
    log.Println(client)
} else {
    client, err := createDefaultClient() // 创建了一个默认的HTTP客户端。(在这个代码块中,client变量也被遮蔽了)
    if err != nil {
      return err
    }
    log.Println(client)
}
// Use client

这段代码能够编译,因为内部的 client 变量在日志记录调用中被使用了。如果不是这样,我们会遇到编译错误,例如“声明了 client 但未使用”。

我们如何确保原始的 client 变量被赋予了一个值?有两种不同的选择。

第一个选项使用临时变量,在内部代码块中这样使用:

var client *http.Client
if tracing {
    c, err := createClientWithTracing() // 创建一个临时变量 c
    if err != nil {
      return err 
    }
    client = c // 将这个临时变量 c 赋值给 client
} else {
    // Same logic
}

在这里,我们将结果赋值给一个临时变量 c,该变量的作用域仅限于 if 代码块内部。然后,我们将其重新赋值给 client 变量。同时,我们对 else 部分也执行相同的操作。

第二种选项在内部代码块中使用赋值操作符(=)直接将函数结果赋值给 client 变量。然而,这需要创建一个错误变量,因为只有当变量名已经被声明时,赋值操作符才能工作。例如:

var client *http.Client 
var err error  // 声明一个 err 变量
if tracing {
    client, err = createClientWithTracing() // 使用赋值操作符将返回的 *http.Client 直接赋值给 client 变量
    if err != nil {
      return err 
    }
} else {
    // Same logic
}

我们可以直接将结果赋值给 client,而不是首先将结果赋值给一个临时变量。

两种选择都是完全有效的。这两种选择之间的主要区别在于,在第二种选择中,我们只执行一次赋值,这可能被认为更易于阅读。此外,在第二种选择中,我们可以将错误处理统一化,并将其实现在 if/else 语句之外,正如这个示例所展示的:

if tracing {
    client, err = createClientWithTracing()
} else {
    client, err = createDefaultClient()
}
if err != nil {
  // Common error handling
}

变量遮蔽发生在内部代码块中重新声明了一个变量名时,但我们看到这种做法容易出错。是否强制规定禁止变量遮蔽取决于个人偏好。例如,有时重用现有变量名,如用于错误的 err,可能是方便的。然而,通常情况下,我们应该保持谨慎,因为我们现在知道,我们可能会遇到这样的情况:代码可以编译,但是接收值的变量并不是预期的那一个。在本章后面的部分,我们还将看到如何检测遮蔽变量,这可能有助于我们发现潜在的错误。