
Комментарии 24
Почему разработчики go сделали так неудобно? Неужели нельзя было сделать хотя бы нормальный стек-трейс? Ответ прост - из-за производительности.
Вот это вот смешно. Особенно, если учесть, что на самом верхнем уровне, в результате получения ошибки, будет или выход из приложения, или вообще паника. Конечно, скорость тут очень важна.
Не для всех приложений. Выход из приложения будет только в случае простеньких cli утилит. А паника, если ошибся разработчик и нужно срочно бежать и править багу (если вы, конечно, не БД где иногда кидают панику, чтобы пропустить все if'ы).
Для большинства, я бы сказал. Вы же сами пишете:
Предположим ошибка возникает в репозитории при запросе какой-то сущности.
И что приложение или сервис в этом случае будут делать? Скорее всего выведут в лог/консоль и сделают os.Exit() или panic(), в зависимости от конкретных требований. Вот об этом и был мой комментарий.
В большинстве приложений ошибку пробросят на верх в слой контроллеров (если мы про какой-то сервер) и там отдадут клиенту
if err != nil {
return err // никаких паник, os.Exit
}
Ну господи, суть-то от этого не меняется. В чем разница в этом контексте с os.Exit? Что же Вы так буквально понимаете. В обоих случаях работа сервиса прекращается. В одном случае управление будет передано обратно системе, а в другом случае - вышестоящему серверу. Смысл моего комментария в том, что возникновение ошибки - это не штатная ситуация. Т.е. это ситуация, которая возникает гораздо реже, чем не возникает. Т.е. чаще всего такая ситуация не возникает. И в этом случает аргументировать отсутствие стека скоростью - ну такая себе отговорка. Вот о чём был мой комментарий.
Вообще, это уже просто комментарий вслух, непоследовательность - это отличительная черта Go. Вот такое у меня мнение сложилось в процессе его использования и в процессе раскапывания issues в их репозитории на github. Те, кто его делал и делает, не заморачивались и не заморачиваются продумыванием наперёд. Такое вот ИМХО.
Дело конечно же не в скорости. Go это компиляция десятилетнего опыта полученного при создании предшествующих ему технологий и языков программирования, задолго до появления исключений.
Однако не все приложения будут завершаться паникой. Например RestAPI сервис вернёт 500-ю ошибку, запишет в лог и трейс.
Спасибо за статью. Начали за здравие, а потом...
"Чтобы создать ошибку со всей информацией..." контекст конечно не нужен. Нужно перечитывать вот это снова, и снова, и снова...
Предположим ошибка возникает в репозитории при запросе какой-то сущности. Какая информация была бы полезна? ID сущности, возможно какие-то фильтры, если есть. Отлично, это есть всё в параметрах метода. Для этого добра можно создать свой тип ошибки.
А как в месте возникновения ошибки получить request-id, user-id и т.д.? Те переменные, которые совсем не нужны в параметрах функции репозитория, но которые важны? Пробросить их через контекст один из вариантов. И это нужно только в том случае, когда мы на верхних слоях не оборачиваем ошибку. Если оборачиваем, то можем информацию добавить и там.
А как в месте возникновения ошибки получить request-id, user-id и т.д.?
В тексте ошибки лучше расширить контекст, чтоб было понятней где именно она произошла, а значения параметров засунуть в лог, где их можно легко отыскать, например в ELK, и полноценно работать с ними.
IMHO, контекст лучше обогащать теми же RequestID, UserID и прочими параметрами сразу по факту их появления, а не когда надо в лог закинуть
А как в месте возникновения ошибки получить request-id, user-id и т.д.? Те переменные, которые совсем не нужны в параметрах функции репозитория, но которые важны?
Не знаю. Я ошибку никогда не обрабатываю в месте возникновения, я передаю её наверх, прям самый верх, "где всё началось". А там обычно доступна вообще вся информация. А репозиторий в итоге занимается только своим делом, не знает, кто и как в нём смотрит.
А мне настолько не нравятся эти исключения, что пришлось даже написать реализацию Optional, чтобы избавить себя от вечного "if err != nil { return nil, err }.
Жалко ещё, что нет нормальных дженериков, но, наверное, конструкция языка их не подразумевает
Приведу бенчмарки из репозитория 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". И этой строки в большинстве случаев уже достаточно, чтобы понять, как по цепочке возвращалась ошибка и где именно она произошла, стектрейс необязателен.
Обработка ошибок в go в 2023 г