Pull to refresh

Comments 44

как мне кажется, уж лучше использовать наработанные стереотипы от других языков. Вместо
try errIo, errNet {
// ..........
} catch {
// ..........
}

лучше использовать форму
try {
// ..........
} catch  errIo, errNet {
// ..........
}
Ага, и вместо

switch err {
	case net.Error1:
		doSomethingWithNetError()
	case net.Error2:
		doSomethingWithNetError()
	case io.Error1:
		doSomethingWithIoError()
	case io.Error2:
		doSomethingWithIoError()
}

использовать обычный подход:

try {
// ..........
} catch errNet {
// ..........
} catch errIo {
// ..........
}

И, внезапно, получаем стандартную инструкцию try/catch. Потому что она уже продумана и испытана во многих языках. Но, по какой-то причине, это не go-way.
Не по какой-то, а по вполне определённой — создатели языка верят, что использование исключений приводит к запутанному коду.
как мне кажется, уж лучше использовать наработанные стереотипы от других языков

Если ловить исключения, и делать это так, чтобы было максимально похоже на другие языки — возможно, но смысл конструкции с объявлением переменных сразу после try в том, что там происходит их инициализация. В противном случае мы в catch можем искать переменные, которые не инициализированы — до них просто очередь не дошла.

Ну это, конечно, если не ломать сам подход к обработке ошибок. Если ломать — придётся устроить большой холивар на тему «в какую сторону» и «нужно ли». И мне почему-то кажется, что за «ломать» будут в первую очередь те, кто на Go не пишут, а большинство среди тех, кто будет «против» — использует этот язык ежедневно.
Я возможно не совсем понял, но какой смысл в этой заметке, если это не работает? :)
Или это какой-то совет разработчикам языка?
Это заметка о «фиче» которая не помешала бы лично мне. Как минимум ещё 9 человек со мной согласились — день прожит не зря.
При необходимости на Go легко реализуется поведение, подобное try/catch, при чем без лишнего оверхеда по сравнению с теми языками, где это выделено в отдельную сущность. Но, по причине отсутствия явной поддержки исключений в языке, в Go это реализуется в более многословном виде без синтаксического сиропчика. Но в случае уместности такого подхода это вполне идиоматично и именно это и нужно делать и именно это и делается, в том числе в стандартной библиотеке языка.

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

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

Очень интересно было бы посмотреть на реализацию try/catch в Go руками. Пишу без сарказма, действительно интересно.
Мне на ум приходит только panic/defer/recovery, но этот подход требует создания оберток функций для блоков try/catch, что сильно усложняет код и портит его обозримость. Не говоря уж о том, что это неправильный подход для Go.
Главный вопрос в ловле panic — производительность. Мне кажется, она тут страдает.
Почему она не страдает в случае try…catch, но страдает в случае panic…defer?
> Почему она не страдает в случае try…catch

Очень даже страдает. В той же Java, например.
Немного потеоритезирую, но в случае имплементации моей версии try...catch компилятор после каждого присвоения нового значения указанным после try переменным типа error — сразу после того, как записал их значение (коим является 64-битный адрес объекта ошибки) в память, но до того, как вытеснил это значение из регистра — просто смотрит, является ли значение в регистре отличным от нуля. Если да — следует простой переход в блок catch, а все нужные значения при этом уже находятся на своих местах. Никаких других накладных расходов, вроде, не предвидится. Не думаю, что в данном варианте логики производительность сильно пострадает. Всего лишь одна проверка регистра на 0.

Полноценное исключение, насколько я его понимаю, является гораздо более масштабным мероприятием.
Хороший вопрос.
Я бы не ловил в данном случае. То есть catch ловит только внутри текущей goroutine.
ну то есть мало проверить на nil надо ещё тогда проверить кто его туда записал, значит надо записать куда-то кто его туда записал и так далее
Не надо ничего проверять. Это делает компилятор — для кода, относящегося к текущей goroutine, расставляет в местах инструкций для процессора переход по условию на точку входа в блок catch. Чуть выше я написал как именно он это делает.
А как-же замыкания?
Вы в контексте мою фразу прочитайте.
Блоки try/cath выглядят весьма уродливо. Они запутывают структуру кода и смешивают обработку ошибок с нормальной обработкой. Что мешает сделать как то так?
if err := client.Set("Fatal", " ", 0).Err(); err != nil {
			log.Print(err)
}
с одной стороны да, но пример из статьи так не записать без явного обявления переменных customVar и err
т.е. вот это менее уродливо?
client.Set(«Fatal», " ", 0).Err()
это redis либа если что. Где
client — индикаторный соединения
Fatal — имя ключа
" " — содержание.

Ну а если не нравиться можно написать свою либу. Благодаря Go я это могу сделать за 30 минут просто изучив команды редиса.
менее уродливо чем что?
Чем try/catch, в качестве альтернативы которому был приведён этот код.
Т.е. вызов метода Err() у результата void-метода Set(), по мнению, vGrabko99, меньше "запутывает структуру кода" и не "смешивает обработку ошибок с нормальной обработкой".
согласен! Пример не удачен. Вот обработка ошибок бд (не кривая либа с гитхаба, а коробочная)
if rows, err := DB.Query("SELECT * FROM users WHERE login=?", login);err !=nil {
log.Fatal(err)
}


Вы тупо вместо трех этажных конструкций всё делаете сразу в if.
В данном конкретном случае областью видимости rows будет блок if/else. Не всегда это удобно.
Все равно удобнее try/cath (я видел очень много этих try/cath и хочу сказать что даже передача rows в глобальную переменную для функции в else блоке будет намного более читаемо и понятно)
ну это не корректно с try/catch тут только if,err надо сравнивать, остальное дизайн библиотеки (конкретно эта возвращает чуть больше чем err)
то есть
> if err := client.Set(«Fatal», " ", 0); err != nil {
> log.Print(err)
> }

vs

> try {
> client.Set(«Fatal», " ", 0)
> } catch (Exception e){
> log.Println(e.getMessage())
> }
в этом случае вариант с if явно короче.

по поводу ошибки как значения против исключений можно холиварить много, но не надо тащить в Go практики работы с исключениями.
Адско плюсую. Правда кармы не хватает))
Вся суть обработки исключений в том, что не надо проверять результат после каждого библиотечного вызова.
По моему опыту, большинство функций вообще обходятся без try. Обработка ошибок не видна и не засоряет код.
В try заворачивается весь main или вся транзакция, или обработка взаимодействия с одним подключением (отвалилось — залогировали и забыли). В тех случаях, когда ошибки ожидаются, нужно вызывать фунции со статусом-результатом (TryParse вместо Parse, TryGetValue вместо обращения к Dictionary по индексу). Да, методов в библиотеках становится больше, зато писать код быстрее.

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

> Go не даёт выбора.
а ещё выбора не дает Rust
Вот это именно то к чему склоняют исключения

Они склоняют, а мы не поддаёмся. Исключения не должны быть единственным способом передачи ошибки, как я писал выше. Программист должен взвешенно выбрать, что использовать.
Конструкции типа try { client.Set(«Fatal», " ", 0); } catch (Exception e) это антипаттерн, не надо их приводить в пример.
Я не его приводил в пример, а просто написал эквивалентный вариант по Go коду.
В Rust и Go на уровне дизайна разделены случаи ошибок и исключительных ситуаций, зачем пытаться делать ещё одну сущность и собрать их вместе?
Как мне писать в этом случае код работайщий со сторонним кодом, когда у меня одна функция возвращает error, а вторая исключения, stack trace будет формироваться в обоих случаях? почему в с# это сделано только для некоторых классов но не для всех (уж не потому, что иметь 2х методов это пзцд)?
Почему в статьях про Rust — http://habrahabr.ru/post/242269/ большинство говорят, что да это классно, а в статьях про Go — как там могли придумать вообще, верните мои исключения
Я не его приводил в пример, а просто написал эквивалентный вариант по Go коду.

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

Тогда аналог на c#/java вообще бы не содержал бы никаких try/catch, ни одного лишнего символа в коде на обработку ошибок. Исключение прозрачно ушло бы наверх.

Как мне писать в этом случае код работайщий со сторонним кодом, когда у меня одна функция возвращает error, а вторая исключения, stack trace будет формироваться в обоих случаях?

На нагрузку на стек никак не влияет наличие try/catch, ведь trace надо будет собрать в любом случае, если где-то в глубине дерева вызовов случится panic.
Падает? Та вы мисье почитайте о panic log.Fatal и т.д. ))
Go не даёт выбора.

Для указанного случая — как раз таки даёт «из коробки». Только вместо try в main или транзакцию снизу кидается panic, которую наверху ловят через defer/recover.
Так надо вручную после каждого вызова кидать panic, если есть ошибка.
Что то мне это напоминает, ах да это же java а именно try-with-resources
    try (Statement stmt = con.createStatement()) {
        ResultSet rs = stmt.executeQuery(query);
        while (rs.next()) {
            System.out.println(---);
        }
    } catch (SQLException e) {
        JDBCTutorialUtilities.printSQLException(e);
    }


Ваш подход очень похож. И не так уж плох.
UFO just landed and posted this here
Этим можно пользоваться, если все нижележащие функции возвращают ошибку.
Как только мы написали foo!, наша функция начинает кидать исключение и к ней этот подход перестаёт работать.

Конечно, можно ловить исключение и преобразовывать его в ошибку, но программист никогда не будет уверен, то с чем он сейчас работает, не кидает ли скрытые исключения, просаживая производительность.
UFO just landed and posted this here
Sign up to leave a comment.

Articles