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

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

Какие характеристики могут быть у ошибки, чтобы предоставить команде как можно больше информации? Я бы ответил, что такая ошибка сообщит вам, что именно не так в системе, даже если она выдает клиенту общие ошибки. Это ошибка, которая не требует поиска нескольких конкретных строк в разрозненном, бесконечном файле журнала. Наконец, такая ошибка укажет вам прямо на проблему в вашем коде, надеюсь, используя очень подробный стек.

Разные языки предоставляют разные наборы инструментов для обработки ошибок. Здесь, в Rose Rocket, мы используем GoLang в качестве основного языка для нашей серверной системы. К сожалению, GoLang (https://golang.org/pkg/errors/) имеет довольно простой интерфейс ошибок, с которым можно работать, и со временем вы можете начать обнаруживать, что он слишком упрощен для больших проектов.

Давайте посмотрим на очень упрощенный пример:

FriendlyError := errors.New("Uh oh system could not process your request")
type Truck struct {
    ID       string `json:”id”`
    Name     string `json:”name”`
}
func ValidateTruck(truck *Truck) error {
    var err error
    if truck.Name == "" {
        err = errors.New("Name of the Truck is incorrect")
        fmt.Errorf("%v\n", err)
        return err
    }
    // some extra business logic we need to do with our model
    // we are calling “some api” that might return an error
    err = extraChecks(truck)
    if err != nil {
        fmt.Errorf("%v\n", err)
        return err
    }
    return nil
}
func CreateTruck(w http.ResponseWriter, r *http.Request) {
    var err error
    var truck Truck
    if err = ReadJSONBody(r, &truck); err != nil {
        fmt.Errorf("%v\n", err)
        http.Error(w, FriendlyError.Error(), http.StatusBadRequest)
        return
    }
    if err = ValidateTruck(&truck); err != nil {
        fmt.Errorf("%v\n", err)
        if err.Error() == "Name of the Dock is incorrect" {
            http.Error(w, err.Error(), http.StatusInternalServerError)
        } else {
            http.Error(w, FriendlyError.Error(), http.StatusInternalServerError)
        }
        return
    }
    if _, err = DBCreate(&truck); err != nil {
        fmt.Errorf("%v\n", err)
        http.Error(w, FriendlyError.Error(), http.StatusInternalServerError)
        return
    }
    json.NewEncoder(w).Encode(truck)
}

Болевые точки

1. Прежде всего, это возможность распечатать подробный стек ошибок. Важно знать последние функции, которые были выполнены при возникновении ошибки. В приведенном выше примере мы хотели бы узнать, что функция CreateTruck () завершилась ошибкой, потому что она вызвала функцию ValidateTruck (), которая вернула ошибку.

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

3. Со временем мы обнаружили, что почти каждую ошибку, сгенерированную функцией, никогда не нужно возвращать клиенту. Скорее, мы хотим скрыть настоящую причину ошибки за неким дружественным общим сообщением, которое возвращается клиенту, давая ему представление о том, что система обнаружила проблему. Маскировка - идеальный метод для этого. Любая ошибка может иметь дружественного родственника ошибки, который используется для функции err.Error (), предотвращая попадание реальной проблемы на клиента.

4. Возможность добавить дополнительный уровень данных, обработанных для ошибки, может быть потенциально полезной для клиентов или для внутренних систем (например, что-то вроде Twitter errorCodes: https: // developer .twitter.com / en / docs / basics / response-codes.html).

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

Возможное решение?

xerrs (https://github.com/RoseRocket/xerrs)

FriendlyError := errors.New("Uh oh system could not process your request")
func ValidateTruck(truck *Truck) error {
    if truck.Name == "" {
        return xerrs.New("name of the Truck is incorrect")
    }
    if err := extraChecks(truck); err != nil {
        return xerrs.MaskError(err, FriendlyError)
    }
    return nil
}
func CreateTruck(w http.ResponseWriter, r *http.Request) {
    var err error
    defer func() {
        // Details returns cause error, mask if specified, and the stack
        // In this example only 5 last stack function calls will be printed out
        fmt.Println(xerrs.Details(err, 5))
    }()
    var truck Truck
    if err = ReadJSONBody(r, &truck); err != nil {
        err = xerrs.MaskError(err, FriendlyError)
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }
    if err = ValidateTruck(&truck); err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }
    if _, err = DBCreate(&truck); err != nil {
        err = xerrs.MaskError(err, FriendlyError)
        xerrs.SetData(err, "ErrorCode", 123) // set custom error value
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    json.NewEncoder(w).Encode(truck)
}

Как видите, этот обновленный код не полагается на то, что разработчики печатают ошибки после каждой отдельной функции - вместо этого они могут сосредоточиться на печати ошибок только в верхней функции, где это наиболее важно (CreateTruck () в этом примере ). Честно говоря, всегда записывать ошибки - не лучший выход. Ошибки - это ценности, особенно в Go, и их следует рассматривать как таковые; Есть много ситуаций, когда ошибка может привести к другому пути выполнения, но не обязательно регистрировать.

fmt.Println (x.Details (5)) печатает стек, который представляет собой один большой двоичный объект выходных данных и не требует поиска разрозненных строк журнала.

У каждой ошибки есть маска, которая используется для ошибки, с которой сталкивается клиент. Эта функция может быть использована для любой функции настолько глубокой, насколько вы хотите. Вам не нужно иметь большие операторы «если» в ваших основных функциях, чтобы вы могли определить, нужно ли вам возвращать клиенту более общую ошибку вместо исходной.

xerrs использует встроенный интерфейс ошибок, который предотвращает массовую перезапись кода в проекте. Каждая функция по-прежнему может возвращать ошибку вместо какой-то специальной собственной структуры.

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

Любые альтернативы?

Существуют альтернативы, которые могут оказаться полезными для ваших проектов, например xerrs.

juju / errors (https://github.com/juju/errors) Изначально мы пробовали juju / errors для наших проектов, но это не совсем подходило для наших нужд здесь, в Rose Rocket.

pkg / errors (https://github.com/pkg/errors) Это небольшая лицензионная библиотека BSD, которая имеет меньше функций, но может соответствовать вашим потребностям.

(Https://go.googlesource.com/proposal/+/master/design/go2draft.md) В сообществе GoLang уже высказываются некоторые идеи и предложения по обработке ошибок, которые могут изменить способ работы разработчиков Go с ошибками. Cегодня.

В заключение

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

Ваше здоровье!