还在为 Go 中繁琐 if-else 逻辑的烦恼? 那就用 vowlink 来解决

4,981 阅读12分钟

我是 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 逻辑代码进行参数校验和错误处理。我们可以整理思路,将从 reqc.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 中,由于缺乏像 trycatch 这样的语法结构,我们通常使用 if-else 逻辑代码来实现参数校验、错误处理和业务逻辑等多种功能。然而,这种做法往往导致代码变得复杂,逻辑判断增多,可读性和可维护性下降。

那么,如何利用 Golang 的函数式编程来简化 if-else 逻辑代码呢?让我们开始今天的故事。

事件背景

2023 年底,一位同事向我求助,他在开发业务逻辑时遇到了频繁的错误,并且花费了整个周末都无法解决问题。我询问了他具体的情况,他说问题出现在一个复杂的 if-else 逻辑代码中,其中的逻辑判断过多,导致代码变得复杂难以理解,他不知道从何处着手进行优化。我表示愿意帮助他,并开始对代码进行审查。

经过仔细查看,我发现他的代码确实是一个典型的 if-else 逻辑代码,其中的逻辑判断过多,导致代码复杂且难以维护。我告诉他,他需要对代码进行优化。他认可这一点,但是不知道如何下手。

我们进行了一段时间的讨论,通过大量的断点调试和代码追踪,最终发现了一个逻辑判断错误,导致了后续大量嵌套的 if-else 逻辑代码无法执行。

为了解决这个问题,我决定从零开始编写一个 Golang 的通用库,用于优化复杂的 if-else 逻辑代码。这个库将提供链式调用、处理结果前后依赖、错误处理、兜底处理以及多种调用链之间的竞争等功能,以帮助开发人员优化复杂的逻辑判断流程。

通过反复的调研发现 JavaScript 中的 Promise 对于处理这样的操作非常方便,因此我决定借鉴 Promise 的概念,实现一个类似于 Promise 的函数式编程库,用于优化复杂的 if-else 逻辑代码。

于是,vowlink 诞生了。

痛点分析

如前文所述,随着业务的增长,复杂的 if-else 逻辑代码会变得越来越复杂,逻辑判断也会增多,导致代码的可读性和可维护性下降。为了解决这个问题,我们需要考虑优化这种逻辑代码的方法。

以下是需要解决的问题和痛点:

  1. 代码复杂:反复编写 if-else 逻辑代码导致代码复杂,难以理解。
  2. 代码重复:逻辑判断代码重复导致代码冗余,难以维护。
  3. 逻辑混乱:逻辑判断代码嵌套导致逻辑混乱,难以阅读。
  4. 风险未知:逻辑判断代码存在潜在风险,导致系统不稳定,难以维护。

为了解决以上问题,我们需要具备以下特点的工具:

  1. 简单易用:工具易于上手,学习成本低。
  2. 高效执行:工具能够快速、高效地处理各种逻辑控制任务。
  3. 逻辑控制:工具提供逻辑控制功能,简化复杂的逻辑判断。
  4. 错误处理:工具提供错误处理功能,处理各种错误信息。
  5. 兜底处理:工具提供兜底处理功能,处理最终的逻辑。
  6. 竞争关系:工具提供处理多种调用链之间竞争关系的功能。

项目介绍

vowlink : github.com/shengyanli1…

1111.png

vowlink 是一个基于 Golang 的函数式编程库,旨在优化复杂的 if-else 逻辑代码。它借鉴了 JavaScriptPromise 的概念,并提供了链式调用、处理结果前后依赖、错误处理、兜底处理以及多种调用链之间的竞争等功能。

vowlink 不是要取代 Golangif-else 的写法,而是提供一种优化的选项,没有非黑即白。这里需要强调下:因为每一个开发者的习惯不同,这个库可能不适合你,但并不意味着 vowlink 不好。它是经过其他开发者的测试和试用后才开源的。

vowlink 的目标是提供一个简单易用的 Golang 库,用于优化复杂的逻辑控制流程,以提高代码的可读性和可维护性。

vowlink 提供以下功能来优化代码逻辑:

  1. 链式调用: 通过链式调用方式简化复杂的 if-else 逻辑代码。
  2. 处理前后依赖: 使用 Then().Then() 方法处理结果前后依赖,上次执行结果作为下一次执行的输入。
  3. 错误处理: 使用 Catch() 方法处理错误信息,提高代码的可读性。
  4. 兜底处理: 使用 Finally() 方法处理最终的逻辑,如资源释放等。
  5. 调用链竞争: 使用 Race()All()Any()AllSettled() 方法处理多种调用链之间的竞争关系。

vowlink 的世界中,每个 vow 都类似于 JavaScriptPromise,它可以表示异步操作的结果或状态。vow 具有三种状态:PendingFulfilledRejected。一旦 vow 的状态发生改变,它将保持不变,与 JavaScriptPromise 的状态相同。

vowRace()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 在某种程度上可以被视为 JavaScriptPromise 的实现,因此 vowlink 的状态机设计也参考了 JavaScriptPromise 的状态机设计。

Promise 流程图

vow-link-1.png

Promise 状态定义

PromiseState 是一个枚举类型,它定义了 Promise 的三种状态:PendingFulfilledRejected

// 定义 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: 用于获取当前处理的错误信息。

在提供的接口方法中,ThenCatchFinally 方法是执行方法,而 GetValueGetReason 方法是结果方法。

GetValueGetReason 方法获取的结果是在 ThenCatchFinally 方法执行完成后的结果。

返回结果

当使用 vowlink 执行一个函数的逻辑控制时,经过多次判断和处理后,最终会返回一个新的 Promise 结构体,其中包含函数的执行结果和错误信息。可以通过 GetValueGetReason 方法获取结果和错误信息。

举个例子:

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 方法处理失败的逻辑。最后,使用 GetValueGetReason 方法获取结果和错误信息。

更多的示例,可以参考 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,整个项目的代码看起来更加清爽、简洁而不简单。

如果您有任何问题或建议,请在 vowlinkGitHub 上提出 issue。我将尽快回复您的问题。