Pull to refresh

Comments 24

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

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

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

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

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

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

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

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

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

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

UFO just landed and posted this here

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

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

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

https://en.wikipedia.org/wiki/UNIVAC_I — 1951 год, первая обработка пробрасываемых исключений.

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

They’re [Googlers] not capable of understanding a brilliant language but we want to use them to build good software. So, the language that we give them has to be easy for them to understand and easy to adopt. – Rob Pike

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

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

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

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