> Вот уже началось — надо знать, что это net.OpError, потому что явно err.Timeout() не определён во всех ошибках. Или во всех-всех?
Не надо, в моем коде его нет, во всех пакетах стандартной библиотеки где генерируется свой тип ошибок (во всех 8), это [имя пакета].Error
> А за strings.Contains для фиксированных ошибок надо бить по наглой рыжей морде.
за тугодумство и over engineering я бы может тоже бил по… лу, но это не относится к теме
> По моему опыту, большинство функций вообще обходятся без try. Обработка ошибок не видна и не засоряет код.
Вот это именно то к чему склоняют исключения — генерировать ошибки, но не обрабатывать ошибки, а просто поймать их где-то в общем для всего приложения месте.
если скажите что пример с «network unreachable» был про Android — ок (надо бы глянуть конечно что там вернется на всякий), но типизация опять же не оч. помогла.
ну и там вроде есть встроенные способы проверять а подлючены мы во внешний мир или нет
http://habrahabr.ru/post/269909/#comment_8640805
>И я напомню речь про Go, его документацию и почему же в методе net.Listen явно не указано что там возвращается не просто error, а net.OpError и в каких реально кейсах эта информация важна.
Так вот по вашему комментарию. Для того чтобы проверить таймаут будет так (err у нас уже есть):
if err := err.(net.Error); err.Timeout() {
// это таймаут
}
то есть мы использовали ошибку из пакета, а в ней есть метод проверки а таймаут ли это, а вот network unreachable — так:
if strings.Contains(err.Error(), «network is unreachable») {
// это network is unreachable… на linux
}
да можно придумать логику любой сложности кто спорит то, но вот нужна ли она на самом деле?
во первых connect() это следствие, чтобы делать именно переподключение, обломаться сначала должно чтение или запись.
> если это timeout — попробовать другой сервер сразу
если это первый connect(), а что мешает сделать это сразу? timeout может быть по 100500 причинам на любом из узлов между вами и хостом назначения. если вы на удаленном сервере ничего не хранили, что нет на других и можете так легко делать переподлючения то не проще ли сразу пробовать другой сервер? если данные хранили, то не слишком ли после первого же таймаута их похерить и пробовать другой сервер?
> если network unreachable — увеличивающийся таймаут между попытками.
эта ошибка по сути означает что сеть куда вы идете не достижима в текущем состоянии интерфейсов, то есть грубо говоря уборщица вытащила кабель, какой смысл увеличивать таймаут?
ну это не корректно с 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 практики работы с исключениями.
прикольно, я видимо понял «я просто среагирую на ошибку» вы буквально что ли это восприняли?
То есть я уже 30 лет не сплю ни секунды и слежу за логом ошибок каждой своей программы? и они все с gui и я жму там кнопку переподключиться?
1) насколько я знаю SO_BINDTODEVICE не поддерживается в windows, а раз вы привели мне пример программы именно под нее, то я не понимаю почему вы говорите забиндится на «определенную сетевуху»
2) bind не подразумевает указания направления, сокет можно привязать и тот который слушает и тот который «подключается»
3) спасибо конечно… но я как бы догадался…
И я напомню речь про Go, его документацию и почему же в методе net.Listen явно не указано что там возвращается не просто error, а net.OpError и в каких реально кейсах эта информация важна.
И то что я говорил:
1) это информация не нужна в 99.9% случаях
2) она не поможет если вы попали в этот 0.1%
вот пример кода http://play.golang.org/p/vf0SO6__1S
e := err.(*net.OpError).Err.(*os.SyscallError).Err.(syscall.Errno)
вот такая конструкция получает первоначальную ошибку (при условии что мы ещё и в типы попали правильно, по хорошему это должно быть лесенка из 3х if), так это выглядит на linux:
99
cannot assign requested address
End
а если не кастить (тоесть вывести текст первой ошибки):
dial tcp 192.168.155.104:6000->192.168.155.104:5000: bind: cannot assign requested address
End
а теперь на windows:
10049
The requested address is not valid in its context.
End
То есть как видим и коды и текст разные.
А найти где же формируется текст ошибки, то для linux это исходники самого go, а для windows это система (через вызов FormatMessageW. хорошо хоть язык всегда английский)…
Так что даже зная все типы вам это не сильно то и помогает…
Как вы из этого:
> Если я делаю reconnect при обрыве сокета, то 99.9% мне всеравно почему это произошло, закрыл ли это соединение внешний сервер, произошел ли таймаут, ОС решила грохнуть и т.п. я просто среагирую на ошибку и сделаю переподключение.
сделали:
>Почему вы думаете, что у всех такой примитивный use case, когда можно просто грохнуться и ругнуться юзеру в консоль
КАК?!?!
Если вы пропускаете то что вам в 100 пикселях выше написано, то о чем тут вообще речь может идти?
Вы прикапались к документации, я вам 3 раза сказал, что во всех 8!!! случаях кастомная ошибка называется pkg.Error зачем эту информацию дублировать в каждом методе?, в других случаях (741) это стандартная ошибка из пакета errors. И описал 2 (на самом деле 1) пакета, где действительно нужно делать type assertion и смотреть на что-то отличное от err.Error()
Даже количественно вы делаете вывод о всей документации, по аргументу относящемуся к 1% всей стандартной библиотеки. Не говоря уже про смысл того что вы «предъявляете».
> Я понял, что надо лезть в исходник и, за неимением альтернатив, смирился с этим
Вообще самая лучшая документация это код, тут от ЯП не очень зависит. Простой понятный код. А дока может всегда быть не полной и т.п.
> Но это признак плохой и, попросту, отсутствующей документации. Ее просто нет.
Ещё раз голословное утверждение
> а другое дело runtime языка.
это не рантайм
А теперь по вашему примеру:
> Простой пример, у меня были задачи, когда биндинг происходит на конкретный адаптер.
На конкретный адрес наверно все же. Речь про исходящее соединение?
> Если этого адаптера нет
если логика именно такая, то есть вы таки проверяли существование такого адреса? хм… по тексту ошибки? или все таки перебрали все адреса и проверили есть он там или нет? что-то из этой серии http://play.golang.org/p/BDt3qEQ_2H
А если он все же есть, но этот порт занят, так и продолжали долбиться в него?
— В общем, вы абсолютно игнорируете то, что вам пишут, приписываете мне слова которых я не говорил, а также путаете рантайм и стандартную библиотеку…
Если я делаю reconnect при обрыве сокета, то 99.9% мне всеравно почему это произошло, закрыл ли это соединение внешний сервер, произошел ли таймаут, ОС решила грохнуть и т.п. я просто среагирую на ошибку и сделаю переподключение.
Если нужна доп. инфа, что именно произошло, то я вам привел ВСЕ места в стандартной либе где есть кастомный тип ошибки или интерфейс и указал, что это не сильно то и поможет. потому что если вы хотите отличить то, что порт занят другим процессом, от того что у пользователя прав не хватило забиндится на 0.0.0.0 (честно — не знаю зачем), то это только текст ошибки парсить. а так лучше не делать.
То что добавлено в компилятор должно там оставаться, а тут какие-то магические паттерны (как тут верно замечали go generate просто ищет подстроки), и внешняя тулза, может и потеряться в следующей мажорной версии.
А серьезно — не стоит делать чтобы компилятор сам запускал какие-то программы на неограниченном кол-ве пакетов (go generate я напоминаю надо запускать только в своих), там какой нить шутник может и rm -rf написать.
Так что правильно критиковать наличение go generate как такового.
Давайте посмотрим теперь на пример, что предлагает Пайк:
//go:generate go tool yacc -o gopher.go -p parser gopher.y
ну тут понятно, есть грамматика, создаем парсер, ок.
//go:generate stringer -type=Pill
то есть по сути попробовали добавить штуку, чтобы более менее одинаково люди писали свои обобщенно-программируемые кодогенерации, а не так например:
// +gen stringer
type Pill strunct {
…
}
Я вам во всех комментариях ответил по существу вопросов, вы продолжаете в стиле «в Go плохая документация». Это становится привычным. Аргументация?! Не не слышал
Это не 4 типа сообщать об ошибках, т.к. вы Err() посчитали дважды (или для вас bool в Scan и TokenType в Next() это 2 разных способа сообщать об ошибках?!) и токенайзеры на что-то существенное не бывают без типов токенов (а там где бывает там bool), при чем EofToken это стандартная практика их написания, чтобы делать процесс обработки проще. А т.к. в Go ошибки это значения и io.EOF это error то он становится ErrorToken.
Как думаете что основное в них, разобрать входной поток на токены/лексемы или вернуть ошибку? Может у вас есть пример как сделать дизайн токинизаторов (лексических парсеров) лучше?
> Ну да, типичное программирование по подсказкам IDE.
Ну ок, только: https://hsto.org/files/b95/67e/a22/b9567ea224c64ab7bed46d4d67cb1ffd.png
Какой бы ни был дизайн, все мы люди и задумался забыл и т.п. от языка не очень зависит.
Вот чуть расширенный подход, где возвращается тип токена, а не bool https://godoc.org/golang.org/x/net/html
так лучше, или все равно плохо?
> Да в том-то и дело, что нет.
да я про сам принцип писал, что он не 100% верен.
то есть
scanner := bufio.NewScanner(input)
for scanner.Scan() {
token := scanner.Text()
// process token
}
само появилось из воздуха, а то что там Err есть вы не догадались? То есть, нашли пакет, конструктор, у типа нашли метод, нужный, поняли что его надо в условие цикла поставить, и вызывать Token у сканера, не прочитав ни доки, ни описания к методам ни их кода. И с такой проницательностью не поняли что там есть Err?!
Вы понимаете, реально как человеку который пишет на Go мне слух режут все эти разговоры, о 100500 мега ппц важных проблем которых для меня как человека который почти каждый день на нем пишет на самом деле нет.
А из более менее реального кроме отсутствия дженериков (ну попробуй не заметь) никто ничего назвать не может.
> ошибки явны и их можно проигнорировать только специально
как обычно есть одно «но», когда вы проигнорировали вообще весь результат функции
> Здесь достаточно забыть вызвать Err — и никогда никогда об ошибке не узнает.
если никто не узнает то и проблемы считай нет.
А если серьезно, то формально вы вроде правы.
Но реально стандартные токенизаторы все так написаны это де факто уже соглашение, они конечно путаются, что делать с io.EOF, но вот .Err() уже считай стандарт. это во многом уходит во внутренности парсинга на Go, где проще в структуре ошибку сохранить чем в каждую функцию в результате возвращать.
Ну и в доке сразу правильный пример идет как ошибку обработать.
И декларация кстати в том, что ошибка идет вместе с результатом, чтобы вы их не разделяли. и тут получается что результат у нас токен, а он лежит внутри структуры парсера, там же где ошибка иии формально все классно…
ну, а так у авторов точно пунктик по поводу скорости, я думаю возврат условно byte вместо, byte + {pointer + pointer } (1 + 8 + 8) тут тоже свое сыграл.
ну да, оффициальное объяснение есть в статье:
> В существующем же дизайне API, пользовательский код таким образом выглядит более естественным: сначала пройтись до конца по токенам, а потом проверять и волноваться об ошибках. Обработка ошибок не прячет поток управления.
По поводу перечисления типов исключений в Go — в нем нет типов исключений, и по сути не очень то подразумевается различие в типах ошибок. если где-то есть что-то больше чем текст ошибки то это делается типом Error в пакете.
в Go комментарии это только текст, видимо не очень круто копипастить туда суда прототип интерфейса ошибок.
> Почему бы не задать вопрос, почему был выбран конкретный дизайн ошибок, если он объективно проигрывает тем же исключениями?
он не проигрывает, с исключениями проще раскручивать стек, например это не привычно при реализации парсеров, когда есть 100500 методов и по сути на один внешний вызов получается приличная вложенность вызовов внутренних,
но в принципе для ошибок это порождает несколько большее кол-во if'ов внутри реализации не более
значения-ошибки это быстро, очень, а ещё позволяет всю вашу программу заинлайнить, ну и реально это более явный способ говорить о том что этот метод может возвращать ошибку, чем исключения в большинстве языков
> Почему при этом так легко проигнорировать ошибку?
Официальный ответ, что выбор все равно должен оставаться за разработчиком. по сути есть только один случай когда можно сказать что это не так (когда вы не используете результат работы функции), но за запись
_, _ = fmt.Println(«Hello World»)
я думаю авторов бы прокляли куда больше
> Рандомизированный select, с ответа Пайка, именно в ключе «так захотелось» сделан
эм, а какой он должен быть? то есть у вас 2 процесса условно одновременно пишут в разные каналы, все равно какой-то пишет первее, планировщик переключает на код который ожидает это событие, пока он это делает там уже готово другое событие, но мы уже обрабатываем это или все процессоры были заняты и у нас 2 события ожидают обработки, по сути планировщик просто выберет одно из них.
Реальный кейс сможете привести когда это становится проблемой?
Не надо, в моем коде его нет, во всех пакетах стандартной библиотеки где генерируется свой тип ошибок (во всех 8), это [имя пакета].Error
> А за strings.Contains для фиксированных ошибок надо бить по наглой рыжей морде.
за тугодумство и over engineering я бы может тоже бил по… лу, но это не относится к теме
Вот это именно то к чему склоняют исключения — генерировать ошибки, но не обрабатывать ошибки, а просто поймать их где-то в общем для всего приложения месте.
> Go не даёт выбора.
а ещё выбора не дает Rust
ну и там вроде есть встроенные способы проверять а подлючены мы во внешний мир или нет
>И я напомню речь про Go, его документацию и почему же в методе net.Listen явно не указано что там возвращается не просто error, а net.OpError и в каких реально кейсах эта информация важна.
Так вот по вашему комментарию. Для того чтобы проверить таймаут будет так (err у нас уже есть):
if err := err.(net.Error); err.Timeout() {
// это таймаут
}
то есть мы использовали ошибку из пакета, а в ней есть метод проверки а таймаут ли это, а вот network unreachable — так:
if strings.Contains(err.Error(), «network is unreachable») {
// это network is unreachable… на linux
}
да можно придумать логику любой сложности кто спорит то, но вот нужна ли она на самом деле?
во первых connect() это следствие, чтобы делать именно переподключение, обломаться сначала должно чтение или запись.
> если это timeout — попробовать другой сервер сразу
если это первый connect(), а что мешает сделать это сразу? timeout может быть по 100500 причинам на любом из узлов между вами и хостом назначения. если вы на удаленном сервере ничего не хранили, что нет на других и можете так легко делать переподлючения то не проще ли сразу пробовать другой сервер? если данные хранили, то не слишком ли после первого же таймаута их похерить и пробовать другой сервер?
> если network unreachable — увеличивающийся таймаут между попытками.
эта ошибка по сути означает что сеть куда вы идете не достижима в текущем состоянии интерфейсов, то есть грубо говоря уборщица вытащила кабель, какой смысл увеличивать таймаут?
то есть
> 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 практики работы с исключениями.
То есть я уже 30 лет не сплю ни секунды и слежу за логом ошибок каждой своей программы? и они все с gui и я жму там кнопку переподключиться?
2) bind не подразумевает указания направления, сокет можно привязать и тот который слушает и тот который «подключается»
3) спасибо конечно… но я как бы догадался…
И я напомню речь про Go, его документацию и почему же в методе net.Listen явно не указано что там возвращается не просто error, а net.OpError и в каких реально кейсах эта информация важна.
И то что я говорил:
1) это информация не нужна в 99.9% случаях
2) она не поможет если вы попали в этот 0.1%
вот пример кода http://play.golang.org/p/vf0SO6__1S
e := err.(*net.OpError).Err.(*os.SyscallError).Err.(syscall.Errno)
вот такая конструкция получает первоначальную ошибку (при условии что мы ещё и в типы попали правильно, по хорошему это должно быть лесенка из 3х if), так это выглядит на linux:
99
cannot assign requested address
End
а если не кастить (тоесть вывести текст первой ошибки):
dial tcp 192.168.155.104:6000->192.168.155.104:5000: bind: cannot assign requested address
End
а теперь на windows:
10049
The requested address is not valid in its context.
End
То есть как видим и коды и текст разные.
А найти где же формируется текст ошибки, то для linux это исходники самого go, а для windows это система (через вызов FormatMessageW. хорошо хоть язык всегда английский)…
Так что даже зная все типы вам это не сильно то и помогает…
> Если я делаю reconnect при обрыве сокета, то 99.9% мне всеравно почему это произошло, закрыл ли это соединение внешний сервер, произошел ли таймаут, ОС решила грохнуть и т.п. я просто среагирую на ошибку и сделаю переподключение.
сделали:
>Почему вы думаете, что у всех такой примитивный use case, когда можно просто грохнуться и ругнуться юзеру в консоль
КАК?!?!
Если вы пропускаете то что вам в 100 пикселях выше написано, то о чем тут вообще речь может идти?
Вы прикапались к документации, я вам 3 раза сказал, что во всех 8!!! случаях кастомная ошибка называется pkg.Error зачем эту информацию дублировать в каждом методе?, в других случаях (741) это стандартная ошибка из пакета errors. И описал 2 (на самом деле 1) пакета, где действительно нужно делать type assertion и смотреть на что-то отличное от err.Error()
Даже количественно вы делаете вывод о всей документации, по аргументу относящемуся к 1% всей стандартной библиотеки. Не говоря уже про смысл того что вы «предъявляете».
> Я понял, что надо лезть в исходник и, за неимением альтернатив, смирился с этим
Вообще самая лучшая документация это код, тут от ЯП не очень зависит. Простой понятный код. А дока может всегда быть не полной и т.п.
> Но это признак плохой и, попросту, отсутствующей документации. Ее просто нет.
Ещё раз голословное утверждение
> а другое дело runtime языка.
это не рантайм
А теперь по вашему примеру:
> Простой пример, у меня были задачи, когда биндинг происходит на конкретный адаптер.
На конкретный адрес наверно все же. Речь про исходящее соединение?
> Если этого адаптера нет
если логика именно такая, то есть вы таки проверяли существование такого адреса? хм… по тексту ошибки? или все таки перебрали все адреса и проверили есть он там или нет? что-то из этой серии http://play.golang.org/p/BDt3qEQ_2H
А если он все же есть, но этот порт занят, так и продолжали долбиться в него?
— В общем, вы абсолютно игнорируете то, что вам пишут, приписываете мне слова которых я не говорил, а также путаете рантайм и стандартную библиотеку…
Если нужна доп. инфа, что именно произошло, то я вам привел ВСЕ места в стандартной либе где есть кастомный тип ошибки или интерфейс и указал, что это не сильно то и поможет. потому что если вы хотите отличить то, что порт занят другим процессом, от того что у пользователя прав не хватило забиндится на 0.0.0.0 (честно — не знаю зачем), то это только текст ошибки парсить. а так лучше не делать.
А серьезно — не стоит делать чтобы компилятор сам запускал какие-то программы на неограниченном кол-ве пакетов (go generate я напоминаю надо запускать только в своих), там какой нить шутник может и rm -rf написать.
Так что правильно критиковать наличение go generate как такового.
Давайте посмотрим теперь на пример, что предлагает Пайк:
//go:generate go tool yacc -o gopher.go -p parser gopher.y
ну тут понятно, есть грамматика, создаем парсер, ок.
//go:generate stringer -type=Pill
то есть по сути попробовали добавить штуку, чтобы более менее одинаково люди писали свои обобщенно-программируемые кодогенерации, а не так например:
// +gen stringer
type Pill strunct {
…
}
Как думаете что основное в них, разобрать входной поток на токены/лексемы или вернуть ошибку? Может у вас есть пример как сделать дизайн токинизаторов (лексических парсеров) лучше?
Ну ок, только: https://hsto.org/files/b95/67e/a22/b9567ea224c64ab7bed46d4d67cb1ffd.png
Какой бы ни был дизайн, все мы люди и задумался забыл и т.п. от языка не очень зависит.
Вот чуть расширенный подход, где возвращается тип токена, а не bool https://godoc.org/golang.org/x/net/html
так лучше, или все равно плохо?
да я про сам принцип писал, что он не 100% верен.
то есть
scanner := bufio.NewScanner(input)
for scanner.Scan() {
token := scanner.Text()
// process token
}
само появилось из воздуха, а то что там Err есть вы не догадались? То есть, нашли пакет, конструктор, у типа нашли метод, нужный, поняли что его надо в условие цикла поставить, и вызывать Token у сканера, не прочитав ни доки, ни описания к методам ни их кода. И с такой проницательностью не поняли что там есть Err?!
Вы понимаете, реально как человеку который пишет на Go мне слух режут все эти разговоры, о 100500 мега ппц важных проблем которых для меня как человека который почти каждый день на нем пишет на самом деле нет.
А из более менее реального кроме отсутствия дженериков (ну попробуй не заметь) никто ничего назвать не может.
как обычно есть одно «но», когда вы проигнорировали вообще весь результат функции
> Здесь достаточно забыть вызвать Err — и никогда никогда об ошибке не узнает.
если никто не узнает то и проблемы считай нет.
А если серьезно, то формально вы вроде правы.
Но реально стандартные токенизаторы все так написаны это де факто уже соглашение, они конечно путаются, что делать с io.EOF, но вот .Err() уже считай стандарт. это во многом уходит во внутренности парсинга на Go, где проще в структуре ошибку сохранить чем в каждую функцию в результате возвращать.
Ну и в доке сразу правильный пример идет как ошибку обработать.
И декларация кстати в том, что ошибка идет вместе с результатом, чтобы вы их не разделяли. и тут получается что результат у нас токен, а он лежит внутри структуры парсера, там же где ошибка иии формально все классно…
ну, а так у авторов точно пунктик по поводу скорости, я думаю возврат условно byte вместо, byte + {pointer + pointer } (1 + 8 + 8) тут тоже свое сыграл.
> В существующем же дизайне API, пользовательский код таким образом выглядит более естественным: сначала пройтись до конца по токенам, а потом проверять и волноваться об ошибках. Обработка ошибок не прячет поток управления.
в Go комментарии это только текст, видимо не очень круто копипастить туда суда прототип интерфейса ошибок.
> Почему бы не задать вопрос, почему был выбран конкретный дизайн ошибок, если он объективно проигрывает тем же исключениями?
он не проигрывает, с исключениями проще раскручивать стек, например это не привычно при реализации парсеров, когда есть 100500 методов и по сути на один внешний вызов получается приличная вложенность вызовов внутренних,
но в принципе для ошибок это порождает несколько большее кол-во if'ов внутри реализации не более
значения-ошибки это быстро, очень, а ещё позволяет всю вашу программу заинлайнить, ну и реально это более явный способ говорить о том что этот метод может возвращать ошибку, чем исключения в большинстве языков
> Почему при этом так легко проигнорировать ошибку?
Официальный ответ, что выбор все равно должен оставаться за разработчиком. по сути есть только один случай когда можно сказать что это не так (когда вы не используете результат работы функции), но за запись
_, _ = fmt.Println(«Hello World»)
я думаю авторов бы прокляли куда больше
> Рандомизированный select, с ответа Пайка, именно в ключе «так захотелось» сделан
эм, а какой он должен быть? то есть у вас 2 процесса условно одновременно пишут в разные каналы, все равно какой-то пишет первее, планировщик переключает на код который ожидает это событие, пока он это делает там уже готово другое событие, но мы уже обрабатываем это или все процессоры были заняты и у нас 2 события ожидают обработки, по сути планировщик просто выберет одно из них.
Реальный кейс сможете привести когда это становится проблемой?