本文将介绍 Golang 中的 stringer
工具,它可以帮助你自动生成 String()
方法,从而使代码更易于维护和理解。我们将从 Stringer
接口的基本用法入手,展示如何自定义类型的字符串表示,然后介绍 stringer
命令行工具,包括安装、使用方法和一些高级技巧。
使用 Stringer 接口自定义类型的字符串表示
在 Golang 开发中,我们经常需要将代码中的标识符,例如枚举常量,转换成易于理解的字符串形式。例如,你可能有一个表示状态码的整数类型,但你希望在日志或者用户界面上显示更友好的文本,例如 “Status OK” 而不是 “200”。
这时,fmt
包提供了一个名为 Stringer
的通用接口,它可以帮助我们自定义类型的字符串表示。许多包都会用到它,该接口只有一个 String()
方法。任何需要字符串形式标识符的包通常会寻找此接口的实现。
type Stringer interface {
String() string
}
因此,在打印任何标识符时,你可以通过为其定义一个 String()
方法来控制实际打印的内容。
自定义字符串表示的优势
假设你有一个名为 StatusCode
的整数类型和一些该类型的常量。如果使用 Println
(来自 fmt
或 log
)打印这些常量,你将看到打印的是状态码(200, 500 等)。
type StatusCode int
const (
statusOK StatusCode = 200
statusInternalServerErr StatusCode = 500
)
func main() {
fmt.Println(statusOK)
fmt.Println(statusInternalServerErr)
// Output:
// 200
// 500
}
但如果你想看到这些状态码的字符串表示,你可以通过简单地为 StatusCode
定义一个 String()
方法来实现 Stringer
接口。现在,Println
将打印 String()
函数返回的任何内容。
type StatusCode int
func (c StatusCode) String() string {
switch c {
case 200:
return "Status OK"
case 500:
return "Internal server error"
default:
return "Unknown status"
}
}
const (
statusOK StatusCode = 200
statusInternalServerErr StatusCode = 500
)
func main() {
fmt.Println(statusOK)
fmt.Println(statusInternalServerErr)
// Output:
// Status OK
// Internal server error
}
Stringer
在以下用例中非常有用:
- 遥测/日志记录:代码中的标识符(尤其是数字和结构体)很难在日志语句和指标标签中表示。数字可以直接使用,但对于那些对代码上下文了解较少或没有上下文的人来说,可能难以理解。
String()
方法可以帮助你为日志/指标标签中的这些标识符定义一个合理的表示形式。 - 用户侧:类似地,代码可能处理某些与用户看到或输入的值不同的值。
Stringer
接口在这方面也能提供帮助。
可能存在的问题
手动维护 String()
方法中的 switch case 语句非常繁琐。你创建的每个常量都需要一个 case。如果你忘记添加一个,可能会导致不一致的行为。
使用 stringer 命令行工具自动生成 String()
方法
为了解决手动维护 String()
方法可能存在的问题,Go 的标准工具链提供了 stringer
命令行工具,它可以自动生成这些 String()
方法。默认情况下,常量名称将用作字符串值,但也提供了一些标志来控制它。
安装 stringer 命令行工具
go install golang.org/x/tools/cmd/stringer@latest
stringer 工具用法示例
假设有以下代码:
package main
import "fmt"
type Status int
const (
statusOK Status = 200
statusBadRequest Status = 400
statusInternalServerErr Status = 500 // Something went wrong
)
func main() {
fmt.Println(statusOK)
fmt.Println(statusBadRequest)
fmt.Println(statusInternalServerErr)
}
将其粘贴到 main.go
文件中并直接运行,将得到以下输出:
200
400
500
现在,在与此文件相同的目录中,运行以下命令:
$ stringer --type Status
这将生成一个名为 status_string.go
的文件,其中包含自动生成的 String()
方法,生成的文件内容如下:
// Code generated by "stringer --type Status"; DO NOT EDIT.
package main
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[statusOK-200]
_ = x[statusBadRequest-400]
_ = x[statusInternalServerErr-500]
}
const (
_Status_name_0 = "statusOK"
_Status_name_1 = "statusBadRequest"
_Status_name_2 = "statusInternalServerErr"
)
func (i Status) String() string {
switch {
case i == 200:
return _Status_name_0
case i == 400:
return _Status_name_1
case i == 500:
return _Status_name_2
default:
return "Status(" + strconv.FormatInt(int64(i), 10) + ")"
}
}
再次运行 Go 程序现在应该给出如下输出:
statusOK
statusBadRequest
statusInternalServerErr
打印这些特定值是因为自动生成的 String()
返回的值默认基于标识符名称。--type
标志是必需的,它接受需要实现 Stringer
接口的类型名称。
--type
标志可以接受多个以逗号分隔的类型名称。
例如:--type Status,Method,Error
标识符以 status 为前缀,以指示它是 Status
类型。但在结果字符串值中,不需要这样做(因为类型在输出中并不真正相关)。可以使用 --trimprefix
标志将其删除。
$ stringer --type Status --trimprefix status
运行上述命令并再次运行 Go 程序。现在,输出应如下所示:
OK
BadRequest
InternalServerErr
如果想要自定义值,可以使用 --linecomment
标志,该标志允许你指定 String()
要返回的确切值。对代码进行以下更改:
const (
statusOK Status = 200
statusBadRequest Status = 400
statusInternalServerErr Status = 500 // Something went wrong
)
$ stringer --type Status --trimprefix status --linecomment
运行上述命令后,Go 程序应给出以下输出:
OK
BadRequest
Something went wrong
go:generate
:简化 stringer 的使用
你可以通过添加 //go:generate <shell command>
到你的 Go 文件中来自动化很多事情。
借助 go generate
,你可以自动化运行 stringer
命令。这样做将解决之前提到的所有问题:
不必为所有类型运行
stringer
。你可以通过运行以下命令为所有子包运行一次
stringer
:$ go generate ./...
在将整数类型声明为枚举时,只需在其上方添加此 go:generate
注释。在哪里添加此注释并不重要。但是将此注释放在类型声明的正上方将有助于读者理解将为此类型自动生成 String()
方法。
//go:generate stringer -type Status -trimprefix status
type Status int
const (
statusOK Status = 200
statusBadRequest Status = 400
statusInternalServerErr Status = 500
)
不必将生成的 _string.go
文件检入 git。你只需在 CI 中添加一个额外的步骤,就在构建步骤之前,运行 go generate ./...
。所有 String()
方法将自动生成。
注意事项
- 如果你有
go:generate
注释,请务必在运行go build
之前始终运行go generate
。因为,如果不运行go generate
,构建命令可能不会失败(如果未直接使用String()
函数)。但是最终可能导致不一致的行为。 go generate
不保证执行顺序。因此,如果你关心生成字符串方法的顺序,请确保编写自己的业务流程脚本。
总结
stringer
是一个能提升代码可维护性和可读性的实用工具。通过自动生成 String()
方法,它可以帮助开发者避免手动编写繁琐的 switch case 语句,减少出错的可能性。通过合理使用 stringer 工具,你可以编写出更清晰、更易于理解和维护的 Golang 代码。它和 gofmt, goimports, gopls, godoc 等工具一样,都是 Go 语言提供的强大工具,可以帮助你编写更高质量的代码。
希望本文能够帮助你更好地理解和使用 stringer 工具,并在你的 Golang 项目中发挥它的优势。