Как сжать этот код и объединить условие if и else

    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 соответствуют одному и тому же процессу кода, только функции и структура разные, как их объединить, чтобы код был меньше. Я имею в виду:

    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, поскольку это внешние функции. Типовая безопасность также важна.

Может быть несколько функций, таких как Featureproducts, Newproducts, я хочу создать общую функцию для возврата продуктов как json на основе динамически выбранной функции sql.


У вас проблема, десятки функций с одной и той же структурой кода - это плохо, по крайней мере, для меня это не имеет значения, читается он или нет, это просто дубликат копировать / вставлять без всякого смысла, в основном копировать / вставлять только с именем функции /SQLC изменяет имя функции и имя структуры, в остальном поток кода такой же.

SQLC генерирует автоматический код на основе запроса SQL, теперь это пустая трата времени на написание повторяющегося кода, чтобы просто преобразовать результат в JSON и вернуть его клиенту. Могут быть десятки функций SQL для возврата последних продуктов, рекомендуемых продуктов, продуктов в категории, продуктов из списка желаний, бла-бла-бла...

Все, что веб-сайт понимает, - это структура продукта, но SQLC возвращает разные структуры, поэтому нет единого типа dbResult. В любом случае сопоставление не представляет большого значения, используя отражение, мы можем проверять поля с тем же именем и преобразовывать SQL.NullString в строку и т. Д. В функции сопоставления.

Настоящая проблема для меня - это операторы if/else. Вы перенесли код в разные функции, но для меня в данной ситуации это не имеет смысла. Поскольку веб-обработчик в любом случае должен будет проверить, является ли запрос действительным или нет, определена ли категория или нет, затем проверьте, равна ли категория 0 или нет, а затем вызовите различные функции, а затем получите результат и вернитесь к клиенту. Для одной функции это может выглядеть лучше, но для реального производства это усугубит ситуацию, и вместо одной функции и блока if/else теперь у вас есть 3 функции для каждого API.

Я ищу, чтобы просто сопоставить результат SQLC с обработчиком маршрута. Поток кода всегда один и тот же, меняется только имя функции и имя структуры. Как сделать его динамическим, чтобы в моем обработчике http я мог просто написать:

return SQLCResult(c.Query("category"), GetAllFeaturedProducts, GetFeaturedProductsByCategory)

а затем на основе значения категории из c.Query("категория") 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 на основе логического условия и создаст сопоставление результата функции со структурой, переданной как 1-й аргумент, и вернет эту структуру обратно.

или может быть это конечная цель:

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"))
}

1 ответ

Есть что учесть, код действительно не имеет проблем, но его может быть трудно поддерживать, это может быть еще сложнее с более похожими сценариями, как задумано, он плохо масштабируется, и в долгосрочной перспективе может появиться больше правил, упрощающих его спагетти.

что мы хотим, так это разделение проблем, многоразовые похожие части и ясность. мы можем получить это без особых сложностей.

учитывая, что мы не можем изменить API репозитория - это может быть прямой подход - мы должны обернуть репозиторий, больше как Decorator или, в терминах Go, Shadowing.

// ProductHandler shadows product repository
type ProductHandler struct {
    *Repo
}

это позволяет нам лучше инкапсулировать интерес каждого звонка

func (ph ProductHandler) GetLatestProductsByCategory(ctx context.Context, cat int) ([]interface{}, error) {
    if cat == 0 {
        return nil, nil
    }

    l, err := ph.Repo.GetLatestProductsByCategory(ctx, cat)

    return ResultSet(&ProductsByCategory{}, l), err
}

func (ph ProductHandler) GetAllLatestProducts(ctx context.Context) ([]interface{}, error) {
    l, err := ph.Repo.GetAllLatestProducts(ctx)

    return ResultSet(&Products{}, l), err
}

при этом мы делегируем ответственность за получение или отключение категорий собственным методом и автоматически переносим набор результатов в его собственный тип, соответственно разделяя ответственность за отображение.

type Products struct {
    Id   string
    Name string
}

type ProductsByCategory struct {
    Category string
}

чтобы добиться преобразования набора результатов db в определенный тип, мы должны предоставить общий интерфейс, поэтому любому типу, который реализует этот интерфейс, разрешено преобразовывать (переводить, отображать, гидратировать являются синонимами) самого себя

type Transformer interface {
    Transform(interface{}) interface{}
}

теперь каждый тип может иметь собственное преобразование из -> в

func (p Products) Transform(i interface{}) interface{} {
    v, _ := i.(*dbresult)

    p.Name = v.RawName
    p.Id = v.RawId

    return p
}

func (p ProductsByCategory) Transform(i interface{}) interface{} {
    v, _ := i.(*dbresult)

    p.Category = v.CategoryName

    return p
}

с функцией, которая поможет нам преобразовать список данных, которые мы можем использовать повторно, когда захотим

func ResultSet(t Transformer, d []interface{}) []interface{} {
    result := make([]interface{}, len(d))
    for i, p := range d {
        result[i] = t.Transform(p)
    }

    return result
}

к настоящему времени наша реализация может просто выглядеть так, и все эти части можно использовать повторно

func main() {
    var category int
    // h.Repo
    repo := Repo{}
    ph := ProductHandler{&repo}

    pcat, _ := ph.GetLatestProductsByCategory(context.Background(), category)
    products, _ := ph.GetAllLatestProducts(context.Background())
    products = append(pcat, products...)

    for _, product := range products {
        fmt.Printf("%v\n", product)
    }
}

хотя код использует interface{}в этом нет ничего плохого, в конце концов, ваши данные уже поступают из базы данных вот так, и мы просто их передаем. Тип, утверждающий их, потенциально может быть дорогостоящим, если все сделано плохо, в данном случае это не так, до вызова json marshal.

вы можете найти рабочую копию здесь, там есть макет возможных вызовов ответа db для поддержки случаев. попытаться оценить category и посмотрим, что произойдет

Другие вопросы по тегам