【发布时间】:2021-03-04 20:23:31
【问题描述】:
if category == 0 {
rows, err := h.Repo.GetAllLatestProducts(c.Context())
if err != nil {
return c.Status(fiber.StatusInternalServerError).SendString(err.Error())
}
result := make([]interface{}, len(rows))
for i, product := range rows {
result[i] = dbrow.ConvertToAllLatestProducts(product)
}
} else {
rows, err := h.Repo.GetLatestProductsByCategory(c.Context(), int16(category))
if err != nil {
return c.Status(fiber.StatusInternalServerError).SendString(err.Error())
}
result := make([]interface{}, len(rows))
for i, product := range rows {
result[i] = dbrow.ConvertToCategoryLatestProducts(product)
}
}
if和else条件都遵循相同的代码流程,只是functions和struct不同,如何合并,让代码更小。我的意思是:
var rows []postgres.GetAllLatestProductsRow
var rows []postgres.GetLatestProductsByCategoryRow
if category == 0 {
rows, err = h.Repo.GetAllLatestProducts(c.Context())
} else {
rows, err = h.Repo.GetLatestProductsByCategory(c.Context(), int16(category))
}
//Rest of the code ...
不能触摸 h.Repo.GetAllLatestProducts 或 h.Repo.GetLatestProductsByCategory,因为它们是外部函数。类型安全也很重要。
可以有多个函数,比如featuredproducts,newproducts,我想做一个通用函数,根据动态选择的sql函数将产品返回为json。
您遇到了问题,具有相同代码结构的数十个函数很糟糕,至少对我而言,它的可读性与否无关紧要,它只是复制/粘贴的东西,没有任何意义, 大部分是复制/粘贴,只有函数名/SQLC生成的函数名和结构名发生变化,其余代码流程相同。
SQLC 根据 SQL 查询生成自动代码,现在编写重复代码只是将结果转换为 JSON 并将其返回给客户端是浪费时间。 可能有几十个 SQL 函数来返回最新产品、特色产品、类别中的产品、愿望清单产品、等等等等……
网站所理解的只是 Product 结构,但 SQLC 返回不同的结构,因此没有单一的 dbResult 类型的东西。反正映射不是什么大事,使用反射我们可以在映射函数中检查同名字段,将SQL.NullString转换为字符串等。
对我来说,真正的问题是 if/else 语句。您已经在不同的函数中移动了代码,但对我来说,在这种情况下它没有意义。因为网络处理程序无论如何都必须检查 请求是否有效,是否定义了category,然后检查category是否为0,然后调用不同的函数,然后得到结果返回给客户端。 对于单个函数,它可能看起来更好,但对于实际生产,它会让事情变得更糟,而不是单个函数和一个 if/else 块,现在每个 API 都有 3 个函数。
我正在寻找它只是将 SQLC 结果映射到路由处理程序。代码流程总是一样的, 只有函数名称和结构名称发生变化。如何使其动态化,以便在我的 http 处理程序中,我可以简单地编写:
return SQLCResult(c.Query("category"), GetAllFeaturedProducts, GetFeaturedProductsByCategory)
然后根据来自 c.Query("category") 的类别值,SQLCResult 将自动调用 GetAllFeaturedProducts 或 GetFeaturedProductsByCategory。 类似于函数作为回调的东西,但函数签名不同,这是一个问题。
func (q *Queries) GetAllFeaturedProducts(ctx context.Context) ([]GetAllFeaturedProductsRow, error)
func (q *Queries) GetFeaturedProductsByCategory(ctx context.Context, idCategory int16)
映射函数不是必需的,因为在 SQLCResult 中,我们可以这样做:
MapDBStructToRestAPIStruct(&Product{}, &row, MapFields(&row))
这将创建一个字段名称和索引的映射,并传递 dbresult 行,它会使用反射将其转换为 Product 结构并返回相同的内容,即在修改其字段后返回第一个参数作为结果。
我仍在寻找如何编写 SQLCResult 函数,将 SQLC 函数名称作为输入然后返回结果,或者可以通过将 Product{} 结构本身放在 SQLCResult 函数中使其更通用,例如:
var result := SQLCResult(&Product{}, c.Query("category") == 0, GetAllFeaturedProducts, GetFeaturedProductsByCategory)
return c.Status(fiber.StatusOK).JSON(result)
SQLCResult 将根据布尔条件调用 GetAllFeaturedProducts 或 GetFeaturedProductsByCategory 并创建将函数结果映射到作为第一个参数传递的结构,并返回该结构。
或者可能是这样的最终目标:
func (h *Handlers) GetLatestProducts(c *fiber.Ctx) error {
if c.Query("category") == 0
return c.JSON(SQLCResult(&Product{}, GetAllLatestProducts)
else
return c.JSON(SQLCResult(&Product{}, GetLatestProductsByCategory, c.Query("category"))
}
func (h *Handlers) GetFeaturedProducts(c *fiber.Ctx) error {
if c.Query("category") == 0
return c.JSON(SQLCResult(&Product{}, GetAllFeaturedProducts)
else
return c.JSON(SQLCResult(&Product{}, GetFeaturedProductsByCategory, c.Query("category"))
}
【问题讨论】:
-
可读性强,书写方式清晰。为什么要改呢?
-
嗨 Burak,有两件事,首先是代码重复,if/else 中还有很多其他代码适用于结果。其次,如前所述,有很多类似的功能,例如特色产品,新产品,我想最终将所有功能合并为通用的。
-
嗨松饼,结果已经是接口{},这是 JSON 生成的。忽略 dbrow.ConvertToAllLatestProducts 中的部分。关于 GetAllLatestProductsRow 使变量类型动态化的部分是问题。
-
结果是 []interface{}。 convert 函数只是将一个结构映射到另一个结构。像 struct a { b: int } 到 struct c { b: int }。数据库结果在 SQLC 定义的结构中,我只是将它们映射到通用结构。
-
代码重复不是坏事。它使阅读变得更容易。如果你想抽象出代码的结构,你可以使用闭包来处理依赖类型的部分。它会更容易编写,但不一定更容易阅读。
标签: go refactoring go-fiber sqlc