结果模式(Result Pattern)是一种编写能够处理不同类型结果(如成功、失败或异常)的错误容忍代码的方式。它经常被用在函数式编程语言中,但也可以使用自定义结构体、泛型和枚举在Go中实现。

结果模式可以避免使用异常进行控制流程,而是返回一个包含操作结果和任何相关信息(如消息或异常)的结构化对象。在Go中没有异常,而结果模式为开发者提供了一种友好的处理错误的方式。

通过结果模式可以轻松地检查操作的状态并相应地处理它。

目前在Go中,能够使用结果模式的方法,这要归功于函数的多返回值。Go操作通常以元组形式返回一个值和一个错误。

定义结果结构体

首先,创建一个只有两个值,SuccessFailure的枚举。通过枚举有助于清晰的获得执行的结果。

type State int

const (
	Success State = iota
	Failure
)

随后,创建模拟结果模式的结构体,定义三个私有属性,并且定义了获取和设置这些属性值的方法。

注意,该结构体已被定义为能处理泛型返回值和错误类型。这样做是为了使Result结构体在所有应用层和操作中都可以复用。

通过Fault泛型类型可以使用Go的error类型或者自定义错误。

对于状态,这里只获取当前值,当使用SetValue()赋值或者使用SetError()设置错误时,它就会被设定。

type Result[Output any, Fault any] struct {
	state State
	fault Fault
	value Output
}

func (result *Result[Output, Fault]) SetValue(value Output) {
	result.value = value
	result.state = Success
}

func (result *Result[Output, Fault]) Value() Output {
	return result.value
}

func (result *Result[Output, Fault]) SetFault(failure Fault) {
	result.fault = failure
	result.state = Failure
}

func (result *Result[Output, Fault]) Fault() Fault {
	return result.fault
}

func (result *Result[Output, Fault]) State() State {
	return result.state
}

这样也可以扩展错误处理的功能,例如,在SetError()函数中每次创建错误时都在应用日志中添加一条条目。

在Go的结果模式中使用自定义错误类型

为了展示在Go中使用自定义错误类型的结果模式的例子,创建了一个DomainError结构,它有两个属性,一个是DomainErrorType类型的,这是一个自定义枚举,另一个是使用Go生成的包含错误的error类型。

type DomainErrorType int

const (
	NetworkRequest DomainErrorType = iota
	JsonParsing
	Storage
)

// Our custom error type
type DomainError struct {
	Type  DomainErrorType
	Error error
}

现在来看一些使用示例,第一个是成功的操作,其他的则展示了带有自定义错误响应的失败和带有error响应的失败。

成功响应

如果一切顺利,函数会返回一个string

func testSuccessOperation() *Result[string, DomainError] {
	result := new(Result[string, DomainError])
	result.SetValue("Hola a todos, esto ha ido muy bien")

	return result
}

...

testOk := testSuccessOperation()

switch testOk.State() {
case Success:
	fmt.Printf("👍: %s\n", testOk.Value())
case Failure:
	fmt.Printf("🚨: %v\n", testOk.Fault())
}

控制台打印结果如下:

👍: Hola a todos, esto ha ido muy bien

自定义错误响应

如果在应用程序执行期间发生错误,则下面的函数会返回一个自定义的DomainError

func testFailureOperation() *Result[string, DomainError] {
	result := new(Result[string, DomainError])

	myError := DomainError{
		Type:  Storage,
		Error: errors.New("Database service unavailable"),
	}

	result.SetFault(myError)

	return result
}

...

testFailure := testFailureOperation()

switch testFailure.State() {
case Success:
	fmt.Printf("👍: %s\n", testFailure.Value())
case Failure:
	fmt.Printf("🚨: %v\n", testFailure.Fault().Error)
}

程序打印信息如下:

🚨: Database service unavailable

Go error返回

当程序发生未知error时,下面的代码会返回一个error类型。

func testFailureWithErrorType() *Result[string, error] {
	result := new(Result[string, error])

	goError := errors.New("This is an error using the Go `error` type")

	result.SetFault(goError)

	return result
}

...

testGoError := testFailureWithErrorType()

switch testGoError.State() {
case Success:
	fmt.Printf("👍: %s\n", testGoError.Value())
case Failure:
	fmt.Printf("🚨: %v\n", testGoError.Fault())
}

程序打印信息如下:

🚨: This is an error using the Go `error` type

小节

正如前面提到的,Go使用多返回值提供了结果模式的实现,但是如果需要向错误管理添加额外功能,可以使用带有泛型的结构来实现这个模式。



在Golang中使用结果模式(Result Pattern)处理错误插图

关注公众号:程序新视界,一个让你软实力、硬技术同步提升的平台

除非注明,否则均为程序新视界原创文章,转载必须以链接形式标明本文链接

本文链接:http://www.choupangxia.com/2024/01/01/golang-result-pattern/