Pull to refresh

Comments 24

Почему разработчики go сделали так неудобно? Неужели нельзя было сделать хотя бы нормальный стек-трейс? Ответ прост - из-за производительности.

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

Не для всех приложений. Выход из приложения будет только в случае простеньких cli утилит. А паника, если ошибся разработчик и нужно срочно бежать и править багу (если вы, конечно, не БД где иногда кидают панику, чтобы пропустить все if'ы).

Для большинства, я бы сказал. Вы же сами пишете:

Предположим ошибка возникает в репозитории при запросе какой-то сущности.

И что приложение или сервис в этом случае будут делать? Скорее всего выведут в лог/консоль и сделают os.Exit() или panic(), в зависимости от конкретных требований. Вот об этом и был мой комментарий.

В большинстве приложений ошибку пробросят на верх в слой контроллеров (если мы про какой-то сервер) и там отдадут клиенту

if err != nil {
    return err // никаких паник, os.Exit
}

Ну господи, суть-то от этого не меняется. В чем разница в этом контексте с os.Exit? Что же Вы так буквально понимаете. В обоих случаях работа сервиса прекращается. В одном случае управление будет передано обратно системе, а в другом случае - вышестоящему серверу. Смысл моего комментария в том, что возникновение ошибки - это не штатная ситуация. Т.е. это ситуация, которая возникает гораздо реже, чем не возникает. Т.е. чаще всего такая ситуация не возникает. И в этом случает аргументировать отсутствие стека скоростью - ну такая себе отговорка. Вот о чём был мой комментарий.

Вообще, это уже просто комментарий вслух, непоследовательность - это отличительная черта Go. Вот такое у меня мнение сложилось в процессе его использования и в процессе раскапывания issues в их репозитории на github. Те, кто его делал и делает, не заморачивались и не заморачиваются продумыванием наперёд. Такое вот ИМХО.

UFO landed and left these words here

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

Однако не все приложения будут завершаться паникой. Например RestAPI сервис вернёт 500-ю ошибку, запишет в лог и трейс.

UFO landed and left these words here

Спасибо за статью. Начали за здравие, а потом...

"Чтобы создать ошибку со всей информацией..." контекст конечно не нужен. Нужно перечитывать вот это снова, и снова, и снова...

https://go.dev/blog/go1.13-errors

Предположим ошибка возникает в репозитории при запросе какой-то сущности. Какая информация была бы полезна? ID сущности, возможно какие-то фильтры, если есть. Отлично, это есть всё в параметрах метода. Для этого добра можно создать свой тип ошибки.

А как в месте возникновения ошибки получить request-id, user-id и т.д.? Те переменные, которые совсем не нужны в параметрах функции репозитория, но которые важны? Пробросить их через контекст один из вариантов. И это нужно только в том случае, когда мы на верхних слоях не оборачиваем ошибку. Если оборачиваем, то можем информацию добавить и там.

А как в месте возникновения ошибки получить request-id, user-id и т.д.?

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

IMHO, контекст лучше обогащать теми же RequestID, UserID и прочими параметрами сразу по факту их появления, а не когда надо в лог закинуть

А можно обогатить и закинуть в лог. Если параметров много, то лучше в лог, а в контекст ошибки закинуть только request_id , например. Это ускорит поиски логов к тому же.

в лог можно уже из контекста вытащить

Под контекстом подразумевается не context.Context, а текст ошибки.

Например fmt.Errorf("send request by process_id %s: %w", pid, %w)

В лог имеется ввиду, добавить в виде параметров, например:

log.Info("some", zap.String("foo", bar))

А как в месте возникновения ошибки получить request-id, user-id и т.д.? Те переменные, которые совсем не нужны в параметрах функции репозитория, но которые важны?

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

А мне настолько не нравятся эти исключения, что пришлось даже написать реализацию Optional, чтобы избавить себя от вечного "if err != nil { return nil, err }.

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

пришлось даже написать реализацию Optional

О! А можно посмотреть? А то мне тоже приходилось своё на эту тему писать, хотелось бы полюбопытствовать, какие ещё варианты на эту тему есть.

Приведу бенчмарки из репозитория https://github.com/joomcode/errorx:

Тут из-за двоеточия ссылка получается невалидной.

По бенчмаркам видно, что сбор стека занимает ~1 мс в среднем при глубине вызова в 100. Цифра приличная, если рядышком у вас нет вызовов базы данных по 100-200 мс.

По таблице кажется, что худший случай это 0.5мс для наивной реализации. В случае эффективной реализации получается ~4мкс (BenchmarkStackTraceErrorxError100), что уже сложнее сравнивать с IO, мне кажется.

Недавно как раз записал видео про обработку ошибок в Golang, может кому-то будет полезно)

nit: в тексте ошибки лучше писать что вы делали, а не то, что сломалось, потому что потом после нескольких обертываний получится длинная простыня стенаний: "failed X: error doing Y: cannot Z: ….”

А то что это ошибка в принципе понятно из логлевела, например.

В go нет исключений. Разработчики, начинающие знакомиться с go, часто не знают как лучше всего обработать ошибку

И как они раньше на голом C писали...

Почему разработчики go сделали так неудобно? Неужели нельзя было сделать хотя бы нормальный стек-трейс?

Я обычно делаю fmt.Errorf и какое-то описание перед тем как вернуть ошибку выше. Тогда получается что-то такое: "get user error: read id from db error: init db error: something not found". И этой строки в большинстве случаев уже достаточно, чтобы понять, как по цепочке возвращалась ошибка и где именно она произошла, стектрейс необязателен.

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

Sign up to leave a comment.

Articles