LINUX.ORG.RU

golang - не хочу возвращать err, хочу паниковать!

 , ,


0

3

Какая-то секта с этими err. Код распухает в несколько раз. Идея с defer выглядит довольно здравой - я в своё время делал такой defer для 1C и для Delphi. Но паника лучше, чем возврат err-ов. Таковой возврат ничего не упрощает. Когда выпадает исключение, сразу виден весь стек. Сгенерированный err не показывает места своего возникновения, т.е. с помощью брекпойнтов нужно много итераций, чтобы локализовать ошибку. А на fatalpanic есть чуть ли не встроенный брекпойнт, во всяком случае, у меня на fatalpanic отладка сама по себе останавливается.

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

Я собираюсь попробовать в своих упражнениях максимально использовать панику. Труъ голангисты, разубедите!

★★★★★

Ответ на: комментарий от q0tw4

Причем иногда это верно даже для релиза.

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

den73 ★★★★★ ()
Ответ на: комментарий от den73

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

q0tw4 ★★★ ()
Ответ на: комментарий от den73

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

Вместо того, чтобы попытаться понять причины. Ты по ссылочкам-то почитай.

feofan ★★★★★ ()
Ответ на: комментарий от feofan

Причины могут быть разные: недостаток ресурсов, тараканы в голове, необходимость сделать вид, что придумано что-то новое. Здесь бизнес, декларируемые причины совершенно необязательно являются правдой.

Что тут понимать? Я всё же не вчера родился и имею какой-никакой опыт в программировании, что с исключениями, что без оных.

den73 ★★★★★ ()
Ответ на: комментарий от den73

«Errors are values» - здесь нет особой инновации. В любом ЯП со сборкой мусора логично сделать исключение объектом. То, что этот объект бросают, не должно создавать принципиальной разницы. Во всяком случае, в лиспе throw работает именно так - оно передаёт обычный объект вглубь стека. Про С++ не особо помню, там может быть нюанс со временем жизни исключения, но в целом, объект исключения - это тоже value, и в C++, и в Java. Объявлять отсутствующую инновацию присутствующей - это грубая ложь.

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

feofan ★★★★★ ()
Ответ на: комментарий от den73

Причины могут быть разные: недостаток ресурсов, тараканы в голове, необходимость сделать вид, что придумано что-то новое. Здесь бизнес, декларируемые причины совершенно необязательно являются правдой.

Да, да. Рептилоиды с нибиру навязывают доблестным программистам свою волю для порабощения человечества. Извини, но с таким образом мышления ты ошибся с выбором профессии. Программирование - это технологии для инженеров, а не обряды для шаманов. Ну то есть если правильно выполнять обряды что-то будет происходить. Но вот изобрести «новый обряд» при таком подходе уже нельзя.

feofan ★★★★★ ()
Ответ на: комментарий от hbee

А вот кстати, есть ведь на самом деле как минимум, 4 вида обработки ошибок в go:

  • err != nil - плохо то, что природа ошибки неизвестна. Т.е. кроме многословия, другого профита нет
  • паника. Вроде бы для «действительно серьёзных ситуаций». Но что это означает? Если программа написано неправильно, то лучше её завершить. Если ясно, что мы можем локализовать проблему в рамках горутины, то паника подходит - и ничем не хуже возврата кодов ошибок.
  • os.exit(1) - как я понял, этот вариант обходится скромным молчанием, с зря. В случае, если программа совсем неправильная, например, defer содержит безконечный цикл, может оказаться, что падение приложения прямо сейчас - это лучший выход. Опять же, нужен анализ, что мы наменяли во внешнем мире и к каким последствиям приведёт os.exit(1). Закрытием файлов уже занимается ОС.
  • kill -9 - может произойти в любой момент, но ведь и о нём нужно подумать.

Статья, может быть, и классная, и если бы в ней было хотя бы в 5 раз меньше букв, я бы её прочитал.

den73 ★★★★★ ()
Последнее исправление: den73 (всего исправлений: 2)
Ответ на: комментарий от den73

Ну кстати, если читать с конца, то и ничего. В общем-то, я не понял, чем эта статья отличается от моей точки зрения. Там приведён и пример фреймворка gin (24000 звёзд), который обрабатывает ошибки именно с помощью паники, перехватываемой внутри горутины.

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

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

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

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

den73 ★★★★★ ()
Последнее исправление: den73 (всего исправлений: 3)
Ответ на: комментарий от den73

err != nil - плохо то, что природа ошибки неизвестна. Т.е. кроме многословия, другого профита нет

Никто не мешает тебе делать нормально: кроме if есть еще, например, switch и https://golang.org/pkg/os/#IsNotExist с аналогами.

os.exit(1) - как я понял, этот вариант обходится скромным молчанием, с зря.

Внутри инициализации вполне используется - например написать в лог «не могу достучаться до базы» и завершиться с ненулевым эксит кодом.

kill -9 - может произойти в любой момент, но ведь и о нём нужно подумать.

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

feofan ★★★★★ ()
Ответ на: комментарий от den73

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

И ронять весь сервер, который обслуживает кроме проблемного клиента еще кучу? gin как раз для вебсерверов, там отправка в канал для, например, логирования, вполне оправдана. Обработку на месте при этом никто не запрещал. Наоборот, язык это всячески поощряет.

feofan ★★★★★ ()
Ответ на: комментарий от den73

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

Твое мнение совершенно внезапно совпало со спецификацией языка)

feofan ★★★★★ ()
Ответ на: комментарий от feofan

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

А это был как бы «убойный» аргумент против использования паники в мирных целях. Отлично, этот аргумент снят. Что ещё?

den73 ★★★★★ ()
Последнее исправление: den73 (всего исправлений: 1)
Ответ на: комментарий от feofan

И ронять весь сервер, который обслуживает кроме проблемного клиента еще кучу?

Например, есть ситуация, когда FFI библиотека повредила память приложения (не смотрел устройство FFI в голанге, но обычно это возможно). При обнаружении такой ситуации нужно ронять весь сервер, причём самым быстрым возможным методом, т.к. степень повреждённости памяти неизвестна. Мы можем положить ошибку в канал, но не факт, что её кто-нибудь оттуда достанет. А разыменование нулевого указателя я привёл для примера, возможно не совсем удачного.

den73 ★★★★★ ()
Ответ на: комментарий от feofan

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

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

den73 ★★★★★ ()
Ответ на: комментарий от feofan

Гугли graceful shutdown

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

den73 ★★★★★ ()
Последнее исправление: den73 (всего исправлений: 1)
Ответ на: комментарий от den73

Пример не самый подходящий, но аналогия понятна. Насчет «самым быстрым способом» - это про очень ограниченное число кейсов. Обычно нужно делать graceful shutdown.

feofan ★★★★★ ()
Ответ на: комментарий от den73

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

тут все плохо, а в советы я не верю.

Вот тебе ссылки почему

там много букв, не буду читать

Можно придумать много красивых слов, но в реальной жизни всегда есть место ситуации, когда «происходит какая-то хрень и мы не понимаем почему».

Такое бывало. Сначала исправить проблему каким-либо быстрым способом (откатиться), потом разбираться в причинах.

feofan ★★★★★ ()
Последнее исправление: feofan (всего исправлений: 1)
Ответ на: комментарий от feofan

Я на самом деле сейчас полно всего читаю, собирая кусочки для своей программы. Но в целом я твою статью тоже прочитал, и про graceful shutdown тоже просмотрел. Только в общем случае graceful shutdown невозможен. Либо таймаут (и часть данных в процесс обработки может пропасть), либо можно не дождаться шатдауна. Я полный чайник в вебе и думаю, что это решается путём распределения ролей в рамках комплекса программ, но SQL сервер базы данных «грациозно остановить» за фиксированное время невозможно. Именно та ситуация - либо часть транзакций пропадёт, либо время ожидания не ограничено.

den73 ★★★★★ ()
Последнее исправление: den73 (всего исправлений: 1)
Ответ на: комментарий от den73

Кстати, интересная тема, мне нравится, как в голанге устроена обработка паники. То, что except и finally совмещено, добавляет возможностей. Вот такой код сейчас я пишу (может быть, бред, потом пойму).

// RollbackIfActive rolls back transaction if it is still active.
// Defer this one if you're opening any transaction
// If failed to rollback, will panic. If already panicking, would ignore
// rollback error silently.
func RollbackIfActive(tx *sqlx.Tx) {
	err := tx.Rollback()
	if err == nil || err == sql.ErrTxDone {
		return
	}
	preExistingPanic := recover()
	if preExistingPanic == nil {
		panic(err)
	}
	log.Printf("Failed to rollback transaction while panicking. Err is %#v", err)
	panic(preExistingPanic)
}

Смысл в том, что мы можем узнать, есть ли уже сейчас исключение, и тем самым обработать ситуацию «исключение внутри исключения». В обычных языках в блоке finally недоступно ни исключение, ни информация о том, есть ли исключение. А except не выполняется, если исключения нет. Т.е. пришлось бы для такой ф-ии иметь и except, и finally, а также переменную, связывающую их, и продублировать код обработки ошибок. Если нигде не напутал, то получается здорово.

den73 ★★★★★ ()

А это не взаимозаменяемые вещи. Ошибки - ожидаемые, паники - реакция на неожиданное. И то и то должно быть в коде. Паника - это типа самопроверка встроенная в код, а обработка ошибок - часть алгоритма.

vlad9486 ()
Ответ на: комментарий от anonymous

Быдлокодить можно на любом языке. Нормально делай - нормально будет:

https://dave.cheney.net/practical-go/presentations/qcon-china.html#_error_han...

func CountLines(r io.Reader) (int, error) {
	var (
		br    = bufio.NewReader(r)
		lines int
		err   error
	)

	for {
		_, err = br.ReadString('\n')
		lines++
		if err != nil {
			break
		}
	}

	if err != io.EOF {
		return 0, err
	}
	return lines, nil
}

vs

func CountLines(r io.Reader) (int, error) {
	sc := bufio.NewScanner(r)
	lines := 0

	for sc.Scan() {
		lines++
	}
	return lines, sc.Err()
}
feofan ★★★★★ ()

Кстати, стал тут использовать gin. Эта тварь перехватывает любую панику в обработчике http запроса и превращает её в ошибку. Как теперь падать при каких-то фатальных вещах? Только log.Fatal. А что, если где-то в библиотеках by design паника означает фатальную ошибку?

den73 ★★★★★ ()
Последнее исправление: den73 (всего исправлений: 1)
Ответ на: комментарий от den73

Эта тварь перехватывает любую панику в обработчике http запроса и превращает её в ошибку.

Всё по заветам святых отцов. Лол. Говорят же, не будет тебе счастья с этим УГ.

anonymous ()
Ответ на: комментарий от den73

1. Не используй gin

2. Не используй такие библиотеки

3. Не роняй вебсервер. Лучше отдели часть, которую собираешься ронять в отдельный сервис, чтобы не ронять пользовательскую апишку.

4. Обрабатывай эти ошибки и, если нужно, роняй приложение

5. Не используй go

Вот тебе варианты, выбирай) При желании их можно комбинировать

feofan ★★★★★ ()
Последнее исправление: feofan (всего исправлений: 1)
Ответ на: комментарий от den73

Всё правильно делает. Обработчик не должен ронять сервер, сервер должен вернуть 500, что бы там ни случилось. Ты веб себе представляешь?

anonymous ()
Ответ на: комментарий от anonymous

Даже если конфиг-файл не найден, в котором, допустим, написано, на каком порту открываться? Или если закончилась память и даже в лог нельзя ничего записать? «Светить - и никаких гвоздей»?

den73 ★★★★★ ()
Последнее исправление: den73 (всего исправлений: 1)
Ответ на: комментарий от anonymous

Хотя блин, наверное, ты прав. Только тогда вся эта идея - писать приложение прямо со встроенным веб-сервером - вообще не работает.

den73 ★★★★★ ()
Последнее исправление: den73 (всего исправлений: 1)
Ответ на: комментарий от den73

Даже если конфиг-файл не найден, в котором, допустим, написано, на каком порту открываться?

Так этим ядро занимается. Но если обработчик фатально не смог проинициализироваться - вернет ошибку ядру, оно упадет если захочет. Это должно быть в API обработчика.

Или если закончилась память и даже в лог нельзя ничего записать?

Тут вряд ли что-то сделаешь. Но если обработчик выжрал память, а ядро смогло это пережить - тем лучше.

anonymous ()
Ответ на: комментарий от anonymous

Ну если мы говорим о штрафах, которые поисковые системы выдают за зависающую страницу, то получается, что любое сложное приложение должно стоять за каким-нибудь нгинксом, которые и будет сообщать поисковику, что «всё почти в порядке, но почему-то статус 500». И этот нгинкс должен стоять на другой машине, дабы кривое приложение не съело всю память, весь диск, весь CPU и т.п.

den73 ★★★★★ ()
Ответ на: комментарий от anonymous

Но если обработчик выжрал память, а ядро смогло это пережить - тем лучше.

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

den73 ★★★★★ ()

Владимир

Немножко об приемлемого для меня подхода к обработки ошибок.

Ни с кем спорить и доказывать прав или нет не буду /однозначно/.

Проверка успешности выполнения функций реализованf так.

API «сторонних» библиотек не использую «напрямую», а пишу к ним wrappers.
К примеру:
class EXPORT_FUNCTION CFileT : public WCRT::WCRTError {

В wrappers производится анализ кодов возврата
/а он бывает весьма многообразен и зависящий от значения параметров .../
Так вот WCRTError содержит данные об успешности выполнения функции /набор некоих полей (в случае необходимости текст message)/

Поэтому анализ кода возврата для любого API /именно любого/ прост, а главное единообразен. К примеру:

 bVp1 = CFileT::DuplicateHandle(                           // Creates a duplicate handle. The returned duplicate is in the caller's process space
  SrcCFile.m_hFile,
  &CFileTNew->m_hFile
 );

 ErrorDataCode( !bVp1, *CFileTNew )                        // Проверка на наличие ошибки

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

Конечно набор полей в WCRTError зависит от того в каком режиме собрана dll /release или debug/.

PS: Почему так?
Не хочу тратить время и помнить все тонкости обработок кодов возврата какого-либо API.
Конечно не большой оверхенд имеется.
А где его нет?

Рассказал весьма кратко /саму суть, а не реализацию/.

anonymous ()
Ответ на: комментарий от den73

любое сложное приложение должно стоять за каким-нибудь нгинксом

Вроде так обычно и бывает? Хотя скорее по другим причинам.

Пусть лучше падает, чем остаётся в неизвестном состоянии.

Ну, если ядру не хватило памяти, оно и упадет. А если не хватило только обработчику - вероятно, можно его переинициализировать.

anonymous ()
Ответ на: комментарий от anonymous

Ну, если ядру не хватило памяти, оно и упадет. А если не хватило только обработчику - вероятно, можно его переинициализировать.

Будет не так. В языках типа голанга постоянно выделяется новая память, т.к. далеко не всё хранится на стеке. Это может происходить неявно при любой деятельности. Кому-то первому не хватит памяти и начнутся глюки. Скорее всего, за время дефицита памяти успеет получить сколько-то отказов и ядро, и обработчик. Обработчик - хрен с ним, а ядро останется повреждённым.

den73 ★★★★★ ()
Ответ на: комментарий от den73

Владимир

Приведенный ранее метод обработки ошибок унифицирован для любого API.

Этот метод не совершенен, но он существенно упрощает и код и экономит время программиста.

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

А ныне все просто - получите exeption и делайте с ним что хотите.

anonymous ()
Ответ на: комментарий от anonymous

Всё правильно делает. Обработчик не должен ронять сервер, сервер должен вернуть 500, что бы там ни случилось.

Сфигали? Паника должна как раз всё громко ронять, на то она так и названа. При панике ядра ты как себе представляешь продолжение работы системы?

anonymous ()
Ответ на: комментарий от den73

Только тогда вся эта идея - писать приложение прямо со встроенным веб-сервером - вообще не работает.

Ну конечно это жопа. Веб-сервер, сервер приложений, и само приложение это разные сущности, и в одном флаконе не смешиваются.

anonymous ()
Ответ на: комментарий от anonymous

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

den73 ★★★★★ ()

После медитации над gin дошло, что должно быть. Должна быть, прежде всего, серёзность ошибки, например:

1. ошибка, которая должна рушить приложение с помощью exit(1) (нельзя записать в лог, кончилась память, повреждение памяти, ошибка программирования)

2. ошибка, которая должна рушить обработчик (ну не знаю)

3. ошибка, которую можно просто вывести в лог

При этом, казалось бы, 1 - это log.Fatal, 2 - это panic, а 3 - это err != nil, но вот хренушки. 1 - это да. Но некоторая паника, полученная в ходе нормальной логики, должна тоже рушить приложение. В моём случае это, например, неудачный коммит транзакции по неизвестной причине или ошибка при поиске регулярного выражения, забитого в коде программы (скорее всего, говорит об ошибке в самом выражении или о каком-то повреждении образа программы). Такая же ошибка может быть выражена и в виде err != nil. Т.е. серьёзность ошибки и способ её появления (паника или err), хоть и не ортогональны друг другу, но связаны достаточно мало.

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

Отсюда очередной раз выплывает гнилость идеологии, что «ошибки - это err, а паника - это что-то серьёзное».

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

den73 ★★★★★ ()