我是 LEE,老李,一个在 IT 行业摸爬滚打 17 年的技术老兵。
在开始今天的话题之前,我想先从这段代码开始今天的内容分享。在日常的开发过程中,我们经常遇到需要在业务逻辑代码中使用 if-else
语句。然而,随着业务的增长,这种 if-else
逻辑代码往往会变得越来越复杂,逻辑判断也会变得越来越多,导致代码的可读性和可维护性下降。因此,我们需要考虑如何优化这种 if-else
逻辑代码。
举个例子,假设我们正在使用 gin
框架开发一个 web service
,需要根据不同的请求参数来执行不同的逻辑来更改数据库中的数据。在这种情况下,我们通常会使用 if-else
语句来实现这个功能。
func UpdateData(c *gin.Context) {
var req struct {
ID int `json:"id"`
Name string `json:"name"`
}
// 解析请求参数,将请求参数绑定到结构体中
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// 解析出的参数有效性检查,当然这里已经很多库实现了参数校验,这里只是举例
if req.ID == 0 {
c.JSON(http.StatusBadRequest, gin.H{"error": "id is required"})
return
}
if req.Name == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "name is required"})
return
}
// 根据请求参数执行不同的逻辑, 根据结果返回不同的响应
if result, err := db.UpdateName(req.ID, req.Name); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
} else {
c.JSON(http.StatusOK, gin.H{"message": "success", "result": result})
}
}
这段代码展示了在使用 gin
框架开发 Handle
时常见的逻辑。然而,随着业务的增长,其中的 if-else
逻辑代码变得越来越复杂,逻辑判断也变得越来越多,导致代码的可读性和可维护性下降。
值得注意的是,c.ShouldBindJSON(&req)
的输出作为 db.UpdateName(req.ID, req.Name)
的输入,中间经过大量的 if-else
逻辑代码进行参数校验和错误处理。我们可以整理思路,将从 req
到 c.JSON
的逻辑视为一条具有多个可控分支的路径。
另外,我们再看一个例子:这段代码展示了一个经典的嵌套 if-else
逻辑代码(地域嵌套)。如果在其中发现了无法预料的错误,将会带来很大的困扰。
func doSomething(data Struct) error {
company := data.Commpay
if company.Name == "hw" {
techGroup := company.TechGroup
if techGroup.Type == "electronic" {
groupType := techGroup.GroupType
if groupType.Level == "A" {
groupResearch := groupType.Research
if groupResearch.Product == "phone" {
proVersion := groupResearch.Version
if proVersion.Version == "1" {
// do something
} else {
// do something
}
} else {
// do something
}
} else {
// do something
}
} else {
// do something
}
}
}
在 Golang
中,由于缺乏像 try
和 catch
这样的语法结构,我们通常使用 if-else
逻辑代码来实现参数校验、错误处理和业务逻辑等多种功能。然而,这种做法往往导致代码变得复杂,逻辑判断增多,可读性和可维护性下降。
那么,如何利用 Golang
的函数式编程来简化 if-else
逻辑代码呢?让我们开始今天的故事。
事件背景
2023 年底,一位同事向我求助,他在开发业务逻辑时遇到了频繁的错误,并且花费了整个周末都无法解决问题。我询问了他具体的情况,他说问题出现在一个复杂的 if-else
逻辑代码中,其中的逻辑判断过多,导致代码变得复杂难以理解,他不知道从何处着手进行优化。我表示愿意帮助他,并开始对代码进行审查。
经过仔细查看,我发现他的代码确实是一个典型的 if-else
逻辑代码,其中的逻辑判断过多,导致代码复杂且难以维护。我告诉他,他需要对代码进行优化。他认可这一点,但是不知道如何下手。
我们进行了一段时间的讨论,通过大量的断点调试和代码追踪,最终发现了一个逻辑判断错误,导致了后续大量嵌套的 if-else
逻辑代码无法执行。
为了解决这个问题,我决定从零开始编写一个 Golang
的通用库,用于优化复杂的 if-else
逻辑代码。这个库将提供链式调用、处理结果前后依赖、错误处理、兜底处理以及多种调用链之间的竞争等功能,以帮助开发人员优化复杂的逻辑判断流程。
通过反复的调研发现 JavaScript
中的 Promise
对于处理这样的操作非常方便,因此我决定借鉴 Promise
的概念,实现一个类似于 Promise
的函数式编程库,用于优化复杂的 if-else
逻辑代码。
于是,vowlink
诞生了。
痛点分析
如前文所述,随着业务的增长,复杂的 if-else
逻辑代码会变得越来越复杂,逻辑判断也会增多,导致代码的可读性和可维护性下降。为了解决这个问题,我们需要考虑优化这种逻辑代码的方法。
以下是需要解决的问题和痛点:
- 代码复杂:反复编写
if-else
逻辑代码导致代码复杂,难以理解。 - 代码重复:逻辑判断代码重复导致代码冗余,难以维护。
- 逻辑混乱:逻辑判断代码嵌套导致逻辑混乱,难以阅读。
- 风险未知:逻辑判断代码存在潜在风险,导致系统不稳定,难以维护。
为了解决以上问题,我们需要具备以下特点的工具:
- 简单易用:工具易于上手,学习成本低。
- 高效执行:工具能够快速、高效地处理各种逻辑控制任务。
- 逻辑控制:工具提供逻辑控制功能,简化复杂的逻辑判断。
- 错误处理:工具提供错误处理功能,处理各种错误信息。
- 兜底处理:工具提供兜底处理功能,处理最终的逻辑。
- 竞争关系:工具提供处理多种调用链之间竞争关系的功能。
项目介绍
vowlink
: github.com/shengyanli1…
vowlink
是一个基于 Golang
的函数式编程库,旨在优化复杂的 if-else
逻辑代码。它借鉴了 JavaScript
中 Promise
的概念,并提供了链式调用、处理结果前后依赖、错误处理、兜底处理以及多种调用链之间的竞争等功能。
vowlink
不是要取代 Golang
的 if-else
的写法,而是提供一种优化的选项,没有非黑即白。这里需要强调下:因为每一个开发者的习惯不同,这个库可能不适合你,但并不意味着 vowlink
不好。它是经过其他开发者的测试和试用后才开源的。
vowlink
的目标是提供一个简单易用的 Golang
库,用于优化复杂的逻辑控制流程,以提高代码的可读性和可维护性。
vowlink
提供以下功能来优化代码逻辑:
- 链式调用: 通过链式调用方式简化复杂的
if-else
逻辑代码。 - 处理前后依赖: 使用
Then().Then()
方法处理结果前后依赖,上次执行结果作为下一次执行的输入。 - 错误处理: 使用
Catch()
方法处理错误信息,提高代码的可读性。 - 兜底处理: 使用
Finally()
方法处理最终的逻辑,如资源释放等。 - 调用链竞争: 使用
Race()
、All()
、Any()
和AllSettled()
方法处理多种调用链之间的竞争关系。
在 vowlink
的世界中,每个 vow
都类似于 JavaScript
的 Promise
,它可以表示异步操作的结果或状态。vow
具有三种状态:Pending
、Fulfilled
和 Rejected
。一旦 vow
的状态发生改变,它将保持不变,与 JavaScript
的 Promise
的状态相同。
vow
与 Race()
、All()
、Any()
和 AllSettled()
函数没有必然的关联,它们可以独立运行。然而,在 vowlink
中,它们是一家人,都是 vow
的一种表现形式。简单来说,Race()
、All()
、Any()
和 AllSettled()
函数是更高级的容器,用于处理多个 vow
之间的竞争、并行和串行操作。
基于 vow
的特性,vowlink
提供了一系列函数,使开发者能够更方便地编写逻辑判断流程。同时,结合 Race()
、All()
、Any()
和 AllSettled()
函数,可以处理多个调用链之间的竞争关系。在大多数情况下,vowlink
可以替代复杂的 if-else
逻辑代码,提高代码的可读性和可维护性。
尽管在 GitHub
上可以找到许多类似的库,但我发现这些库要么功能过于复杂,要么功能过于简单,要么代码过于复杂,要么代码过于简单。因此,我决定从零开始,自己动手实现一个轻量级的函数式逻辑控制库,这就是 vowlink
。
设计初衷:
- 轻量化:
vowlink
是一个精简的库,代码量少,易于使用。 - 简单易用:
vowlink
提供简洁的接口,只需几行代码即可完成任务处理逻辑编写。 - 高效执行:
vowlink
执行效率高,能够快速、高效地处理各种逻辑控制任务。
状态机设计
为了让 vowlink
简单易用且高效执行,它的架构设计必须简洁可靠。
由于 vowlink
在某种程度上可以被视为 JavaScript
中 Promise
的实现,因此 vowlink
的状态机设计也参考了 JavaScript
中 Promise
的状态机设计。
Promise
流程图
Promise
状态定义
PromiseState
是一个枚举类型,它定义了 Promise
的三种状态:Pending
、Fulfilled
和 Rejected
。
// 定义 Promise 的三种状态:Pending、Fulfilled 和 Rejected
// Define the three states of a Promise: Pending, Fulfilled, and Rejected
const (
// Pending 代表一个 Promise 的 pending 状态,即 Promise 还在等待结果,既没有完成也没有被拒绝。
// Pending represents the pending state of a promise, which means the Promise is still waiting for the result, neither fulfilled nor rejected.
Pending PromiseState = iota
// Fulfilled 代表一个 Promise 的 fulfilled 状态,即 Promise 已经完成,有一个确定的结果值。
// Fulfilled represents the fulfilled state of a promise, which means the Promise is fulfilled and has a definite result value.
Fulfilled
// Rejected 代表一个 Promise 的 rejected 状态,即 Promise 已经被拒绝,有一个确定的错误原因。
// Rejected represents the rejected state of a promise, which means the Promise is rejected and has a definite reason for the error.
Rejected
)
Promise
结构体定义
Promise
是一个结构体,它代表一个 Promise。 内部结构也非常的简单,由状态,结果和错误信息组成。
// Promise 是一个结构体,它代表一个 Promise。Promise 是一种编程模式,用于处理异步操作。
// Promise is a struct that represents a Promise. A Promise is a programming pattern for handling asynchronous operations.
type Promise struct {
// state 是 Promise 的状态,它可以是 Pending、Fulfilled 或 Rejected。
// state is the state of the Promise, it can be Pending, Fulfilled, or Rejected.
state PromiseState
// value 是 Promise 的值,当 Promise 被 resolve 时,这个值会被设置。
// value is the value of the Promise, this value will be set when the Promise is resolved.
value interface{}
// reason 是 Promise 被拒绝的原因,当 Promise 被 reject 时,这个值会被设置。
// reason is the reason the Promise was rejected, this value will be set when the Promise is rejected.
reason error
}
接口设计
vowlink
的接口设计简洁明了,通过链式调用方式将内部的多个方法串联起来,实现逻辑控制。
方法接口
vowlink
提供了几个简洁易用的方法接口:
Then
: 用于处理成功的逻辑,类似于处理if
逻辑代码中的if err == nil
后的操作。Catch
: 用于处理失败的逻辑,类似于处理if
逻辑代码中的if err != nil
后的操作。Finally
: 用于处理最终的逻辑,类似于处理if
逻辑代码中的defer
后的操作。GetValue
: 用于获取当前处理结果。GetReason
: 用于获取当前处理的错误信息。
在提供的接口方法中,
Then
、Catch
、Finally
方法是执行方法,而GetValue
和GetReason
方法是结果方法。
GetValue
和GetReason
方法获取的结果是在Then
、Catch
、Finally
方法执行完成后的结果。
返回结果
当使用 vowlink
执行一个函数的逻辑控制时,经过多次判断和处理后,最终会返回一个新的 Promise
结构体,其中包含函数的执行结果和错误信息。可以通过 GetValue
和 GetReason
方法获取结果和错误信息。
举个例子:
func main() {
// 创建一个新的 Promise
// Create a new Promise
result := vl.NewPromise(func(resolve func(interface{}, error), reject func(interface{}, error)) {
// 这个 promise 直接解析为 "hello world"
// This promise is directly resolved to "hello world"
resolve("hello world", nil)
})
// 从 promise 中获取值并打印
// Get the value from the promise and print it
fmt.Println(result.GetValue())
}
使用示例
以下示例展示了如何使用 vowlink
优化 if-else
逻辑代码。通过创建两个不同状态的 vow
,一个成功的 vow
和一个失败的 vow
,然后使用 Then
方法处理成功的逻辑,使用 Catch
方法处理失败的逻辑。最后,使用 GetValue
和 GetReason
方法获取结果和错误信息。
更多的示例,可以参考 vowlink
项目中的 examples
目录里面的示例代码。 那里面已经基本覆盖了常见的用法。
举个例子:
package main
import (
"fmt"
vl "github.com/shengyanli1982/vowlink"
)
func main() {
// vowlink 像一个链条,你可以在链条上添加更多的 then() 来在 promise 解析后做更多的事情。
// vowlink is like a chain, you can add more then() to the chain to do more things after the promise is resolved.
result := vl.NewPromise(func(resolve func(interface{}, error), reject func(interface{}, error)) {
// 这个 promise 直接解析为 "hello world"
// This promise is directly resolved to "hello world"
resolve("hello world", nil)
}).Then(func(value interface{}) (interface{}, error) {
// 在 Then 方法中,我们将解析的值加上 " vowlink !!"
// In the Then method, we append " vowlink !!" to the resolved value
return value.(string) + " vowlink !!", nil
}, func(err error) (interface{}, error) {
// 如果 promise 被拒绝,我们将返回一个新的错误信息 "rejected."
// If the promise is rejected, we return a new error message "rejected."
return nil, fmt.Errorf("rejected.")
})
// 从 promise 中获取值并打印
// Get the value from the promise and print it
fmt.Println("Resolve:", result.GetValue())
// 这是一个被拒绝的 promise
// This is a rejected promise
result = vl.NewPromise(func(resolve func(interface{}, error), reject func(interface{}, error)) {
// 这个 promise 被拒绝,原因是 "error"
// This promise is rejected with the reason "error"
reject(nil, fmt.Errorf("error"))
}).Then(func(value interface{}) (interface{}, error) {
// 如果 promise 被解析,我们将解析的值加上 " vowlink"
// If the promise is resolved, we append " vowlink" to the resolved value
return value.(string) + " vowlink", nil
}, func(err error) (interface{}, error) {
// 如果 promise 被拒绝,我们将返回一个新的错误信息 "rejected."
// If the promise is rejected, we return a new error message "rejected."
return nil, fmt.Errorf("rejected.")
})
// 从 promise 中获取拒绝的原因并打印
// Get the reason for the rejection from the promise and print it
fmt.Println("Rejected:", result.GetReason().Error())
}
输出结果:
$ go run demo.go
Resolve: hello world vowlink !!
Rejected: rejected.
总结
vowlink
是一个轻量级的库,用于对函数进行逻辑控制。它的设计简单、易用且高效,使得开发人员能够以较低的学习成本快速上手。
通过使用 vowlink
,我们能够标准化重试操作,并实现逻辑代码的多处复用。这大大减少了重复编写代码的时间,提高了开发质量。使用 vowlink
,整个项目的代码看起来更加清爽、简洁而不简单。
如果您有任何问题或建议,请在 vowlink
的 GitHub
上提出 issue。我将尽快回复您的问题。