Comments 287
"у меня возникло пару мыслей"… ндэ...
Спасибо за благодарность за проделанный перевод
Так вы не удовольствия ради это делаете? Ну что ж: низкий поклон Вам за проделанную работу.
и вежливое указание на опечатку в приватном сообщении.
Стесняетесь собственных ошибок? А я даже не знал, что есть какие-то там сообщения. В свою очередь, позвольте Вас поблагодарить за все хорошее.
природа методов в Go означает, что функции тоже могут иметь методы
Что это значит? Не структуры ли данных могут иметь методы?
и интернет благодаря ним стал лучше
им.
Что это значит? Не структуры ли данных могут иметь методы?
Из спецификации языка:
A type may have a method set associated with it.
Вот пример https://play.golang.org/p/9ouVP44MHl
Ну или упомянутый в статье http.HandlerFunc
A type may have a method set associated with it.
И каким образом это соответствует вашему
функции тоже могут иметь методы
?
Это как раз соответствует моему
структуры данных могут иметь методы
И каким образом это соответствует вашему. Это как раз соответствует моему
В Go есть не только структуры, но и другие типы — базовые типы, массивы, слайсы, функции, мапы(map), указатели, каналы. На любой из них можно повесить свой метод (через type MyType map/chan/func...
. понятное дело).
В Go есть не только структуры
Под «структуры данных» я не имел в виду конкретно структуры ака записи. Скорее сложные типы данных, просто выразился неточно.
функции, мапы(map), указатели, каналы. На любой из них можно повесить свой метод
Имеется в виду функциональный тип, когда передается ссылка(указатель) на функцию? Тогда понятно; но все равно это не будет «функция может иметь метод». Функция в таком случае — это не тип данных, типом данных будет нечто, ссылающееся на функцию, а не она сама. Нет?
когда передается ссылка(указатель) на функцию
Не очень понимаю, что вы под этим понимаете. Куда передаётся?
Если вы пишете:
type MyType func() bool
то MyType
это именно тип — вы можете его засовывать в другие типы, создавать переменные этого типа, присваивать их, передавать по каналам и тд.
Функция — это коробка, которая выполняет работу, она не является типом данных. Так же, как не является т.д., скажем, оператор.
Но вы можете создать тип данных, который указывает на функцию с вот такой-то сигнатурой, что и сделано строкой
type MyType func() bool
ru.wikipedia.org/wiki/%D0%A4%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D0%BE%D0%BD%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D0%B9_%D1%82%D0%B8%D0%BF
А с сигнатурой
type MyType string
тип указывает на строку в вашей терминологии?
Мне кажется, вы вместо того, чтобы прочесть спецификацию и понять, как устроены типы в Go, пытаетесь объяснить это своими словами, какими-то удобными концепциями из вашего предыдущего опыта. Отсюда и путаница про "указатели", "ссылки" и "коробки, выполняющие работу".
А с сигнатурой
Слово «сигнатура» мною было применено к функции, «функция с такой-то сигнатурой» — т.е. набором параметров и типом (пами) возвращаемых значений.
тип указывает на строку в вашей терминологии?
Вы путаете тип данных и конкретное значение. Функция — это значение.
type MyType string
описывает тип данных «строка», и переменная такого типа может принимать разные значения — конкретные строки.
type MyType func() bool
описывает функциональный тип данных, и переменная такого типа может принимать разные значения — конкретные функции (технически — ссылки на функции, но это только деталь реализации) с вот такой вот сигнатурой.
Мне кажется, вы вместо того, чтобы прочесть спецификацию и понять, как устроены типы в Go, пытаетесь объяснить это своими словами, какими-то удобными концепциями из вашего предыдущего опыта. Отсюда и путаница про «указатели», «ссылки» и «коробки, выполняющие работу».
Почему своими? Есть устоявшаяся терминология, ссылку на википедию с описанием см. выше.
Вы путаете тип данных и конкретное значение.
Нет, не путаю.
технически — ссылки на функции, но это только деталь реализации
Откуда вы это взяли?
Почему своими? Есть устоявшаяся терминология, ссылку на википедию с описанием см. выше.
Ну в Go не оперируют словами "тип, который передает ссылку на функцию". Я именно поэтому и предлагал познакомиться со спецификацией языка (она небольшая), чтобы говорить на одном языке, а не своими словами пытаться объяснить.

10 лет непрерывных побед.
Я реально слабо представляю себе кейс когда человек решит перейти с C#/Java/D/Swift на Go и сможет внятно объяснить свое решение.
Особенно занятно читать вранье про простоту Go. Каким местом он простой? Что на нем делается проще чем на других языках? Такой бред могут нести только люди никогда в жизни ничего серьезного не писавшие. Вся эта простота (а по факту примитивность) приводит лишь к тому что на мало-мальски сложных проектах начинается дикое костылестроение т.к. в языке не оказывается каких-то элементарных синтаксических конструкций. Итогом является закономерный Го-внокод.
я заметил интересную особенность, что на Go активно переходят только PHP-шнки
А на скольких из этих полумиллиона Go программистов вы построили свой вывод? Подозреваю, что статистически ваша выборка очень нерепрезентативна. Смею полагать, что моя выборка будет побольше, и мои наблюдения разительно отличаются с вашими.
Я реально слабо представляю себе кейс когда человек решит перейти с C#/Java/D/Swift
Вы бы ещё R или Julia назвали. По моим наблюдениям, чаще переходят с C/C++, Ruby/Node.js/Python/PHP.
Особенно занятно читать вранье про простоту Go. Каким местом он простой? Что на нем делается проще чем на других языках?
Слово "простота" очень легко трактуется с точностью до наоборот разными людьми. Тут глупо спорить. Кто-то считает, что если учить 4 года Haskell, то потом любая программа помещается в 3 строчки и это "простота языка", но с инженерной точки зрения простота это противоположность сложности. Почитайте "Мифический человеко-месяц" Брукса, ну, или хотя бы его же "Серебрянной пули нет", там хорошо про сложность разложено.
Так, стоп. Причем тут Haskell. Вопрос был в том чем Go проще C#/Java/D/Swift/Python. Можете пример показать? Ну вот допустим я в Python могу проверить вхождение так:
if x in arr
На D это будет:
if (arr.canFind(x))
На других языках тоже примерно так же.
Вы будете утверждать, что на Go это проще делается?
По факту больше кода, больше ошибок, дольше разработка, сложнее рефакторинг, тестирование, отладка и тд.
Так ГДЕ Go простой??
Вы путаете простоту и количество символов, и на этом строите свои представления о том, как происходит рефакторинг и тестирование в Go.
Так ГДЕ Go простой??
Везде — в грамматике, спецификации, количестве концепций, которые нужно выучить, количестве способов сделать одно и тоже, количестве телодвижений чтобы написать тест или бенчмарк, количестве усилий, необходимых, чтобы поддерживать документацию, количестве времени на рефакторинг, количестве времени, необходимом, чтобы понять код, написанный другим программистом, и угадать, что же он попытался упаковать в те 5 символов, истинную сложность котороых он спрятал(а) в своей голове, думая, что все знают, что он(а) имел(а) ввиду, и так далее.
Да ладно врать то. Вы примеры покажите чем Go проще других языков.
>количестве способов сделать одно и тоже
Поверьте, как только возникнет более или менее серьезная задача, то программисты на Go будут дико костылить просто потому что в других языках проблему тщательно обдумали и взвешивали кучу за и против, а на Go решением будет бездумный гуглеж кода по принципу «вроде работает и хрен бы с ним». Отсюда поддержка этого кода будет просто адовой.
>и угадать, что же он попытался упаковать в те 5 символов
Ага, к примеру по `canFind` или `Сontains` ну совершенно не очевидно что они делают и приходится гадать.
Вы примеры покажите чем Go проще других языков.
Вот держите примитивный пул воркеров. Жду ваших примеров такой же простой задачи на других языках. Если ваши рассуждения верны, то это должно быть 7 строчек на всё про всё (так чтобы я смог скопировать отсюда и запустить у себя, и чтобы это было читабельно). Жду ваших примеров.
package main
import (
"fmt"
"time"
)
func worker(id int, jobs <-chan int, results chan<- int) {
for j := range jobs {
fmt.Println("worker", id, "started job", j)
time.Sleep(time.Second)
fmt.Println("worker", id, "finished job", j)
results <- j * 2
}
}
func main() {
jobs := make(chan int, 100)
results := make(chan int, 100)
for w := 1; w <= 3; w++ {
go worker(w, jobs, results)
}
for j := 1; j <= 5; j++ {
jobs <- j
}
close(jobs)
for a := 1; a <= 5; a++ {
<-results
}
}
Поверьте, как только возникнет более или менее серьезная задача
Я пишу "более или менее серьезные задачи" на Go уже больше 4 лет, и общаюсь с огромным количеством Go программистов по всему свету, включая ребят из Google, Uber, Dropbox, Cloudflare и Docker. Мои наблюдения очень отличаются от ваших. Расскажите, пожалуйста, сколько "более или менее серьезных задач" на Go вы написали.
Простите, но IMHO это лукавство — вы приводите пример не простой задачи, а той, на которой Go специализируется. Давайте приведем действительно простую задачу, и посмотрим насколько проще этот код будет выглядеть на Go:
echo Hello>out.txt
package main
import (
"io/ioutil"
)
func main() {
ioutil.WriteFile("out.txt", []byte("Hello"), 0664)
}
но IMHO это лукавство — вы приводите пример не простой задачи, а той, на которой Go специализируется.
Попросили же показать пример, где Go проще других языков.
Вот вам пример на python:
from multiprocessing import Pool
def f(x):
return x*x
if __name__ == '__main__':
with Pool(5) as p:
print(p.map(f, [1, 2, 3]))
Куда проще?
Хаха, вы опять пришли потроллить? Код который вы написали (или скопировали из первого результата гугла, как вы это любите делать)
а) запускает 5(!) разных процессов
б) не совсем тот же, код который просили написать
в) не запускается :)
$ python main.py
Traceback (most recent call last):
File "main.py", line 7, in <module>
with Pool(5) as p:
AttributeError: __exit__
б) «Вот держите примитивный пул воркеров» — это примитивный пул воркеров. Нет?
в) Потому что я скопировал этот пример из третьего питона, попробуйте на нем.
Если уж вам нужен примитивный пул воркеров, которые будут работать на корутинах — то так сразу нужно было и писать.
SirEdvin ах, другая версия нужна, ну окей. Надеюсь, что вы всё же сознательно троллите — в примере на Go я спокойно могу запустить 10000 воркеров, а ваш пример на 10000 процессов положат систему.
Ладно, серьезно, в этом посте количество комментариев не актуально, поэтому ваши услуги профессионального тролля-хейтера не нужны. Я вас позову, если нужно будет, спасибо.
А какой вообще смысл в в тысячах воркеров? Обычно число воркеров оптимизируют по производительности и оно порядка числа ядер в системе. Число тасков может быть любым.
При том, что это решение граничит с невозможностью использования. Нет, я понимаю, что тем, кто пишет только на Python, или есть legacy код, который нужно хоть как-то распараллелить — это, возможно, единственный выход, недостатки которого они будут терпеть до последнего.
Но всё же человек попросил написать, в чём Go проще других языков, и я привёл пример. Мне, зачем-то начали показывать костыли на других языках, которые запускают множество процессов. Давайте может ещё k8s кластер поднимем и одной строчкой на баше задеплоим пул воркеров и обсудим?
И да, мне ничего не надо доказывать, я прекрасно знаю ограничения и возможности основных мейнстримовых языков, тем более питона.
Обычно число воркеров оптимизируют по производительности и оно порядка числа ядер в системе.
Согласен. Поинт был в том, что горутины и OS процессы это разные лиги вообще.
SirEdvin ах, другая версия нужна, ну окей. Надеюсь, что вы всё же сознательно троллите — в примере на Go я спокойно могу запустить 10000 воркеров, а ваш пример на 10000 процессов положат систему.
Внезапно, но нет. Просто программа упадет с ошибкой.
Traceback (most recent call last):
File "test.py", line 7, in <module>
with Pool(10000) as p:
File "/usr/lib/python3.6/multiprocessing/context.py", line 119, in Pool
context=self.get_context())
File "/usr/lib/python3.6/multiprocessing/pool.py", line 174, in __init__
self._repopulate_pool()
File "/usr/lib/python3.6/multiprocessing/pool.py", line 239, in _repopulate_pool
w.start()
File "/usr/lib/python3.6/multiprocessing/process.py", line 105, in start
self._popen = self._Popen(self)
File "/usr/lib/python3.6/multiprocessing/context.py", line 277, in _Popen
return Popen(process_obj)
File "/usr/lib/python3.6/multiprocessing/popen_fork.py", line 20, in __init__
self._launch(process_obj)
File "/usr/lib/python3.6/multiprocessing/popen_fork.py", line 66, in _launch
parent_r, child_w = os.pipe()
OSError: [Errno 24] Too many open files
А уж если вам настолько сильно нужны корутины, то можете запустить вот это (только вам нужно будет установить библиотеку gevent).
from gevent.pool import Pool
def f(x):
return x*x
if __name__ == '__main__':
pool_1 = Pool(10000)
print(pool_1.map(f, list(range(0, 10000))))
Надеюсь, вас не смущает в этом плане внешняя зависимость, ведь для golang очень часто приходится добавлять внешнею зависимость для обработки ошибок от dropbox.
Просто программа упадет с ошибкой.
Удивительно эффективное решение вы написали.
ведь для golang очень часто приходится добавлять внешнею зависимость для обработки ошибок от dropbox.
Вы себе сами придумываете эти фантазии или кто-то помогает?
Ну вот kubernetes и moby используют. traefik использует. А часть других проектов типа gin пилит для этого свои костыли. Некоторые обходятся без этого, но не все.
> Удивительно эффективное решение вы написали.
В первоначальном задании не было ничего о «а запустить сколько угодно воркеров». Какое тз — такое и решение.
:) так а в чем профит-то?
Новый язык можно создавать по нескольким причинам:
1) just for fan! ну типа я такой крутой и вот что я могу!
2) вынужденная необходимость и отсутствие альтернатив
3) улучшить существующее положение дел
Чем вот это:
for j := 1; j <= 5; j++ {
Отличается от Сишного:
for( j = 1;j <= 5; j++) {
?
Или вот это:
func worker(id int, jobs <-chan int, results chan< — int)
от Сишного:
void worker(int id, const int *jobs, int *results)
Тут возможно что-то не так с jobs и results, т.к. мутки со стрелочками не совсем понятны, хотя названия переменных наводят на мысли что jobs это входные данные, results — выходные.
мутки со стрелочками не совсем понятны
Дайте я вам плюсов в карму подкину, это прекрасно :D
Чем вот это Отличается от Сишного:
В этом была и идея, что язык не должен радикально отличаться от известной базы, чтобы облегчить вход. Большинство людей, знакомых с С, осваивают Go за одни выходные, и это big deal.
В этом была и идея, что язык не должен радикально отличаться от известной базы
Я, конечно, извиняюсь, но по-моему, Go радикально отличается от C и C++. Даже не знаю, что там может быть общего.
Я, конечно, извиняюсь, но по-моему, Go радикально отличается от C и C++. Даже не знаю, что там может быть общего.
Извиняю, но я даже не знаю, как можно не видеть общего между ними. Боюсь, я тут не помогу в таком случае.
То, что я вижу в Go vs C:
- есть интерфейсы;
- декларация переменной — порядок имени и типа противоположный;
- инклудов, макросов нет;
- синтаксис control statements другой;
- ...
Что общего с C/C++ вы нашли в Go?
Вот, кстати, тут человек не видит различий между C и Go вообще
Там речь не о различиях вообще, а о том, что Go проще, чем C.
Думаю там видно, что:
func worker(id int, jobs <-chan int, results chan<- int)
отличается от:
void worker(int id, const int *jobs, int *results)
и «сишник» этот код на Go не поймет и будет спотыкаться при его написании (хотя бы из-за смены порядка указания имя/тип).
Вы троллите, да?
Ответ: «и C, и Go являются тьюринг-полными языками программирования», конечно, правильный, но я хотел бы услышать что-то более конкретное.
using System;
using System.Linq;
using System.Threading;
namespace Go_comparison
{
class Program
{
static void Main(string[] args)
{
var jobs = Enumerable.Range(1, 5);
jobs.AsParallel().WithDegreeOfParallelism(3)
.Select(x =>
{
Console.WriteLine($"Starting work on iten {x}");
Thread.Sleep(1000);
Console.WriteLine($"Finished work on iten {x}");
return x * 2;
})
.ForAll(Console.WriteLine);
Console.ReadLine();
}
}
}
dotnetfiddle.net/cQqT5O
На Elixir:
1..10000
|> Enum.map(&Task.async(fn ->
IO.puts("worker #{&1} started job")
:timer.sleep(1000)
IO.puts("worker #{&1} finished job")
end))
|> Enum.map(&Task.await/1)
if _, ok := arr[x]; ok {...}
если же у вас там массив то в цикле проверять придется.
В общем так же как в плюсах.
if _, ok := arr[x]; ok {...}
Читается лучше чем к примеру:
if (arr.canFind(x))
>если же у вас там массив то в цикле проверять придется.
А ну вообще прелестно! Проще некуда!
Вот видите даже в каких-то стерильных условиях типа: «Если ваш 'arr' это мэп то» код на Go оказывается куда менее читабельный чем на других языках.
if (arr.end() != arr.find(x)) { ... }
и да, вариант на го мне нравится больше.
Поиск же элемента в массиве в плюсах будет точно таким же — полный перебор всех значений в цикле.
Ну и у меня встречный вопрос — как вы полагаете может быть сделана проверка вхождения элемента в контейнер без учета типа контейнера. Учтите, что массив не являетеся классом, это просто указатель, из него нельзя вызывать функций.
Но тут то вся соль в том, что на Go нужно костыли городить уже в простых случаях.
if (map.count(key) > 0) { ;; }
find имеет смысл если сохранять итератор и потом его использовать для доступа к элементу — экономится один поиск по дереву. То есть это дополнительная функциональность С++, которая не всегда легко доступна в других языках, но пользоваться её не обязательно и не всегда имеет смысл.
Вы путаете простоту и выразительность. Питон выразительный, т.к. имеет довольно много сахара. Но относительно простой, т.к. там обычно один очевидный способ сделать что-то. Го, в свою очередь простой, т.к. там мало концепций, относительно простых для понимания, и не топчущихся друг другу по ногам. Однако, го не слишком выразителен, что приводит к boilerplate, в т.ч. на ряде частых и простых сценариев.
Да, я думаю, что между "выразительностью" и "понятностью, что автор имел ввиду", у Go приоритет смещён на последнее.
Нет. Знаю порядка сотни go разработчиков. Php, C — чуть ли не в равной доле. Много perl ребят нашло себя в go. Также не редкость java, node.js, c#.
Пишем уже два года. И переходить назад вообще не хочется. Проект, кроссплатформенный. В том числе и на мобильных устройствах.
Раздражает только один момент. Паника при выходе за массив или слайс. Меня бы устроила просто обработка ошибки. Сейчас приходится проверки ставить перед каждым обращением.
Особенно занятно читать вранье про простоту Go. Каким местом он простой? Что на нем делается проще чем на других языках? Такой бред могут нести только люди никогда в жизни ничего серьезного не писавшие. Вся эта простота (а по факту примитивность) приводит лишь к тому что на мало-мальски сложных проектах начинается дикое костылестроение т.к. в языке не оказывается каких-то элементарных синтаксических конструкций. Итогом является закономерный Го-внокод.
Потому что когда кое-кто переходил с зоопарка C/C++ кода с кучей систем сборок, синтаксисов, редакций и прочего на милый уютный Golang, количество синтаксического сахара к котором стремится к нулю, люди возрадовались, потому что вдоволь наелись проблем, которые порождает слишком гибкий язык. Другое дело, что потом они рано или поздно осознают к тому, что golang порождает еще больше проблем из-за отсутствия значительных частей этого синтаксического сахара в виде кучи кривых конструкций и просто синтаксического мусора в коде в духе if err!=nil
— прокинуть и поймут, что не зря раньше от этого отказались, и займутся всерьез внутренними соглашениями по коду вместо беготни по языкам.
Опять же, если вам нужна более строгая система ошибок, то ничего не мешает сделать так, как это сделали в Java, что бы нужно было или указывать у метода throws с типами ошибок, которые он может возвращать или же обрабатывать их в методе.
Если вас беспокоит оверхед от сохранения стека вызова — не сохраняйте его по умолчанию и дайте пользователям возможность его включать там, где нужно.
Основная моя претензия к этой синтаксической конструкции — ее можно было полностью вынести в объявления функции и более того, сделать ее типизированной, что бы она давала больше информации.
Отказ от эксепшенов, насколько я знаю, был продиктован скорее желанием упростить реализацию, чем какими-то соображениями синтаксиса. Дело в том, что сделать нормальные экспешены довольно сложное и муторное занятие полное компромисов, поэтому авторы решили их вообще выбросить из концепции.
Не совсем. В Google в целом пришли к выводу, что польза от эксепшенов меньше, чем проблемы, которые они приносят. https://google.github.io/styleguide/cppguide.html#Exceptions
А в купе с идеологией языка — просто, но не проще, чем необходимо, это удачно сочиталось, да.
Извините, вы сами читали, что по вашей ссылке написано? Там как раз написано что
On their face, the benefits of using exceptions outweigh the costs, especially in new projects.
Пожалуйста, прекратите ссылаться на Google Style Guide как аргумент против исключений.
Извиняю, конечно читал. Я указал на этот документ не как "аргумент против исключений" (там есть аргументы и за, и против), а как аргумент того, что в Google не используют эксепшены, что, естественно, повлияло на выбор способ обработки ошибок в Go. Тоесть "сложность адекватной реализации" не была первопричиной. Главными причинами являются как раз те моменты, которые описаны в Cons в Google Style Guide — и они в идеологии языка Go имели больший вес.
Т.е. если в плюсах исключения сделаны через одно место, то их теперь везде использовать нельзя?
Т.е. если в плюсах исключения сделаны через одно место, то их теперь везде использовать нельзя?
Нет, но те минусы исключений, которые упоминаются в Go FAQ не имеют отношения к конкретной реализации в плюсах, а к самой концепции в целом.
Нет, но те минусы исключений, которые упоминаются в Go FAQ
Но ссылку вы дали не на Go FAQ, а на руководству по стилю для плюсов, где исключение и спроектированы, и реализованы, и применялись криво.
В Go есть полный эквивалент throw-catch-finally в виде panic-recover-defer. Так что концепция в целом никуда не делась, несмотря на всю критику.
Разве что про вышеупомянутую эквивалентность в блоге Golang деликатно не упоминается.
Ещё раз — ссылка была дана, чтобы показать, что некая культура "ухода от эксепшенов" уже была в Google ещё до Go. И те проблемы, который озвучены в Go FAQ как раз не относятся к реализации С++, и не касаются panic-recover-defer.
В частности, главный аргумент это все таки то, что эксепшены стимулируют:
а) неправильно их использовать (panic/recover не используют для события "юзер не найден", это надо прям совсем удариться, чтобы так использовать)
б) отношения к обычным ошибкам (которые обязательно произойдут), как неважному коду, который нужно спрятать подальше, чтобы он не "захламлял" хороший код.
в) уменьшают понятность error-path кода (понятность что происходит, без прыгания по другим файлам и функциям)
Только давайте не превращать этот тред в очередной спор про плюсы и минусы эксепшенов, мы все о них в курсе, надеюсь. Я лишь делаю акцент на том, что первопричина отсутствия эксепшенов в Go это не "сложность реализации", а более практические соображения.
И те проблемы, который озвучены в Go FAQ как раз не относятся к реализации С++, и не касаются panic-recover-defer.
В чем принципиальная разница между throw-catch-finally и panic-recover-defer?
Почему проблемы первого варианта внезапно перестали касаться второго?
эксепшены стимулируют:
а) неправильно их использовать (panic/recover не используют для события "юзер не найден", это надо прям совсем удариться, чтобы так использовать)
Не понимаю. Что именно в исключениях стимулирует ошибки, а в панике — нет?
б) отношения к обычным ошибкам (которые обязательно произойдут), как неважному коду, который нужно спрятать подальше, чтобы он не "захламлял" хороший код.
И здесь абсолютно нет ясности, почему исключения провоцируют такое отношение, а паника — нет.
в) уменьшают понятность error-path кода (понятность что происходит, без прыгания по другим файлам и функциям)
И здесь у паники никакого различия с исключениями не видно, по крайней мере лично мне.
Я лишь делаю акцент на том, что первопричина отсутствия эксепшенов в Go это не "сложность реализации", а более практические соображения.
А я делаю акцент на то, что исключения в Go есть, просто их зачем-то переименовали в panic, после чего формально верно заявили что никаких исключений у нас нет.
В чем принципиальная разница между throw-catch-finally и panic-recover-defer?
В том что panic-recover это дополнительный способ обработки действительно критических ситуаций, а throw-catch-finally — основной и единственный.
Что именно в исключениях стимулирует ошибки, а в панике — нет?
Ошибки ошибкам рознь, подход с исключениями стимулирует выбрасывать исключения даже тогда, когда никакого исключения нет, а есть абсолютно ожидаемое поведение (файл не найден, неверный ввод и т.д., например). В Go никто не будет для такого использовать панику, потому что есть более простой и удобный способ обработать такую "ошибку".
И здесь абсолютно нет ясности, почему исключения провоцируют такое отношение, а паника — нет.
Ещё раз — потому что паника — это не единственный способ реагировать на ошибку, а дополнительный, четко с четко озвученным кейсом крайних случаев, когда действительно надо падать.
А я делаю акцент на то, что исключения в Go есть, просто их зачем-то переименовали в panic
Потому что подавляющее количество "ошибок" в программе никакие не исключения. И когда у вас для обработки ошибок есть только исключения, то как в ситуации с молотком, всё начинает казаться гвоздём. Паника это как раз "исключения здорового человека".
В том что panic-recover это дополнительный способ обработки действительно критических ситуаций, а throw-catch-finally — основной и единственный.
Неправда. Вы сами опровергли собственный тезис, приведя ссылку на руководство по стилю программирования в плюсах. Никаким "основным и единственным" способом исключения не являются. По крайней мере для гугла, где го и придумали.
Ошибки ошибкам рознь, подход с исключениями стимулирует выбрасывать исключения даже тогда, когда никакого исключения нет, а есть абсолютно ожидаемое поведение (файл не найден, неверный ввод и т.д., например). В Go никто не будет для такого использовать панику, потому что есть более простой и удобный способ обработать такую "ошибку".
"Более простой и удобный способ" с кодами ошибок и так поддерживается абсолютно везде. Это в общем-то исторически первый способ.
Ещё раз — потому что паника — это не единственный способ реагировать на ошибку, а дополнительный, четко с четко озвученным кейсом крайних случаев, когда действительно надо падать.
Если надо именно падать — никакой recover не нужен. А так то же самое исключение вид сбоку — раскрутка стека с возможностью перехвата на более высоком уровне.
И когда у вас для обработки ошибок есть только исключения, то как в ситуации с молотком, всё начинает казаться гвоздём.
Вы не могли указать, у кого конкретно "для обработки ошибок есть только исключения"?
Паника это как раз "исключения здорового человека".
То есть все разговоры про "в Go нет исключений" оказались чистым маркетингом, построенном на переименовании?
Неправда
Правда. Обычно в языках в которых есть много способов сделать одно и тоже, появляются лагери сторонников того или иного подхода. Как правило, либо "мы используем эксепшены", либо "мы не используем эксепшены", как в Гугле, но "в одной функции используем, в другой не используем" — такого не происходит.
с кодами ошибок и так поддерживается абсолютно везде.
В Go нет кодов ошибок.
Если надо именно падать — никакой recover не нужен.
Есть ситуации, когда нужен. А так да, panic() чаще используется без recover().
То есть все разговоры про "в Go нет исключений" оказались чистым маркетингом, построенном на переименовании?
Нет. Вы зачем-то пытаетесь самого себя убедить в истинности этого утверждения, но нет.
Обычно в языках в которых есть много способов сделать одно и тоже, появляются лагери сторонников того или иного подхода. Как правило, либо "мы используем эксепшены", либо "мы не используем эксепшены", как в Гугле, но "в одной функции используем, в другой не используем" — такого не происходит
Так и в го есть два способа обработки ошибок: коды ошибок и исключения под псевдонимом "паника". И что характерно, вы сами привели несколько примеров, когда предпочтительнее один способ, а когда — другой.
Мало того, на хабре есть и другие иллюстрации:
https://habrahabr.ru/post/118898/
Для реального примера использования panic и recover смотри пакет json из стандартной библиотеки Go. Он декодирует закодированные в JSON данные с помощью набора рекурсивных функций. Когда на вход поступает неправильно сформированный JSON, парсер вызывает panic, чтобы развернуть стек к верхнему вызову, который восстанавливается после паники и возвращает подходящий код ошибки (смотри функции «error» и «unmarshal» в decode.go). Похожий пример такой техники в процедуре Compile пакета regexp. Существует соглашение, что в библиотеках Go, даже если пакет использует panic внутри, его внешнее API возвращает явные коды ошибок.
В Go нет кодов ошибок.
Судя по цитате выше, другие гоферы об этом не в курсе.
Нет. Вы зачем-то пытаетесь самого себя убедить в истинности этого утверждения, но нет.
Не понял. Вы сначала сами признали что panic — это исключения. А теперь сами же это отрицаете. Взаимоисключающие параграфы.
Так и в го есть два способа обработки ошибок
Об этом я вам и говорил. Вы сами с собой спорите уже.
Судя по цитате выше, другие гоферы об этом не в курсе.
Вы сейчас используете чьи-то посты на хабре, чтобы продолжать называть вещи неверными именами? Окей, продолжайте называть как хотите.
Не понял. Вы сначала сами признали что panic — это исключения. А теперь сами же это отрицаете
Я не успеваю следить за тем, как вы придумываете новые интерпретации своим и моим словами. Извините, но вы пытаетесь что-то себе доказать, и ещё и убедить других в этом. Мне такой формат дискуссии не интересен. Если захотите спросить что-то про Go, спрашивайте, а рассказывать самому себе про "коды ошибок" и про "чистый маркетинг" — это можете делать самостоятельно.
Окей, продолжайте называть как хотите.
Вы уже 33 раза сказали, что я использую неверное имя. Я в 33 раз прошу — назовите верное.
Я не успеваю следить за тем, как вы придумываете новые интерпретации своим и моим словами.
Вы окончательно перешли от предмета дискуссии к обсуждению личности оппонента, причем без единого пруфлинка. И еще жалуетесь на каких-то "троллей". Нехорошо.
"Более простой и удобный способ" с кодами ошибок и так поддерживается абсолютно везде.
Не совсем так. Конечно в Java вы тоже можете написать библиотеку, которая вместо FileNotFound будет возвращать некий код (как в C), но так не делают, ибо так не принято.
Отсюда растут две проблемы.
- визуально код перестаёт быть компактным и распадается на два, иногда три блока, но это терпимо, если писать аккуратно.
- в некоторых языках/платформах код становится медленнее при большом числе событий, поскольку сами эксепшены относительно медленная вещь.
А вот в Go не принято кидать panic из библиотеки. Считается, что паниковать имеет права только ваше приложение. Т.е. эксепшену отведена совсем другая роль.
Есть у такого подхода ахилесова пята. Когда все делаешь идеально, ты по идее должен захендлить все возможные экспешены и коды ошибок в каждой точке вызова. Разумеется так никто не делает. Но если в Java по дефолту исключение найдет путь наверх и вы получите трейс, то в Go возвратный код по дефолту просто замьютят и вы ничего не узнаете о ошибке.
так не принято
"Так не принято" — это соглашение, а не язык, как меня здесь упорно убеждал автор статьи.
В .NET для API библиотек принято иметь оба варианта. Приложение выбирает удобный для своей семантики.
Но сам язык опять-таки ничего в этом плане не навязывает.
как меня здесь упорно убеждал автор статьи.
Зная, что вам приходится по 33 раза одно и тоже объяснять. повторю ещё раз — автор статьи Роб Пайк, а не я.
И я вас ни в чём не убеждал — вы сами себя в чём то убеждали, оперируя своими терминами, приписывая это мне. Не вводите людей в заблуждение.
В .NET для API библиотек принято иметь оба варианта.
А как это реализуется? В смысле выбор.
Есть определенные соглашения по сигнатуре вызова.
Например, при ошибке парсинга:
- Метод Parse бросит исключение,
- Метод TryParse вернет флаг как результат (и распарсенное как out-параметр при успехе)
- Метод ParseOrDefault вернет null
Считается хорошим тоном дать вызывающему коду выбор, как именно обработать ошибку.
В го сам язык не мешает при желании делать точно так же.
Конечно в Java вы тоже можете написать библиотеку, которая вместо FileNotFound будет возвращать некий код (как в C), но так не делают, ибо так не принято.
Да ладно!
Вот, например: «boolean File.delete()», «boolean File.mkdir()» и т.п.
Ну ок, не знал. Много таких библиотек в общей массе?
Довольно много размазано по различным кускам стандартной библиотеки. Та же банальная работа с коллекциями часто имеет подобные методы для частых случаев (например, j.u.Map#get(key)
). Инстанцирование exception'а достаточно дорогая операция из-за сбора стектрейса, если специально не выкидывать его сбор. И, соответственно, exception'ы, как правило, не используются для control flow.
Не совсем. В Google в целом пришли к выводу, что польза от эксепшенов меньше, чем проблемы, которые они приносят.Ну вот зачем вы перевираете. Отказались исключительно в «C++».
Гляньте на гитхабе в публичные гугловые исходники для Java… Посчитайте сколько там исключений… Инересно, почему от них не отказались?
Или посмотрите на проекты на Python — тоже исключения повсеместно (в том числе кастомные).
Ещё эта конструкция мне удобна тем, что если нужно обработать ошибку в зависимости от кода ошибки, то я это делаю сразу в нужном месте. И если нужно передать ошибку наверх то могу добавить что то к уже имеющемуся коду. Например какая именно функция выше уровнем не смогла открыть файл. Конечно же это можно сделать и с использованием эксепшена. Но это будет как раз замусоривание кода.
Я не говорю, что вот "впилите экспешены, вот что бы прямо как в java", но почему прокидывание err наверх нельзя было сделать поведением по умолчанию, если другое не указано?
Например, что бы можно было бы написать
func t1(x int, y int) (int, error){
r1 = t2(x,y) // неявная ошибка
return r1;
}
вместо
func t1(x int, y int) (int, error){
r1, err = t2(x,y)
if err != nil {
return nil, err
}
return r1, nil;
}
func t1(x int, y int) (r1 int, err, error){
r1, err = t2(x,y)
if err != nil {
return
}
return
}
Если у вас именованные переменные в возврате, то в return не обязательно их указывать.
Я понимаю что пример упрощённый и в данном случае вообще можно if не делать.
А если нужно сделать так?
func t1(x int, y int) (r1 int, err, error){
r1, err = t2(x,y)
if err != nil {
return errors.New("Имеенно это не могло вызвать t2 при причине:",err )
}
return
}
}
или так
func t1(x int, y int) (err, error){
r1, err = t2(x,y)
if err != nil {
r3, err = t3(x,y)
if err != nil {
return Err
}
// Gthtl тем как вернуться на верх сделать ещё несколько проверок и вызовов.
}
return
}
}
То есть, если обработки нет и функция возвращает error, то прокидывать вставлять инструкции, которые будет прокидывать ошибку наверх. А если обработка есть, то, следовательно, поведение по умолчанию не нужно. Я думал над таким сценарием и мне он кажется в целом весьма нормальным. Возможно, я упустил какие-то возможные проблемы?
У такого решения есть два недостатка:
- На каждую строку полезного целевого кода добавляется три с шаблонным кодом обработкой ошибок. Это сильно ухудшает читаемость и затрудняет сопровождение.
- Компилятор ловит далеко не все случаи, когда обработка ошибок пропущена.
Согласен, но за это вы получаете:
- Понятность (особенно другими людьми, читающими ваш код), что происходит в случае ошибки
- Отношение к error-path с таким же уважением, как и к happy-path коду (в отличие от эксепшенов)
- Простоту и понятность обработки ошибки в программе в целом — это простая переменная: получил ее, обработал. Никакой магии и талмудов, о том как правильно пользоваться, никакого "да эти 78% программистов просто не научились правильно пользоваться эксепшенами".
Понятность (особенно другими людьми, читающими ваш код), что происходит в случае ошибки
Обычно происходит return nil, error что не добавляет ровно никакой полезной информации. Или ошибка вкладывается в _ и игнорируется — очень опасный вариант. Или ошибка не вкладывается в _ но все равно игнорируется — еще хуже.
Отношение к error-path с таким же уважением, как и к happy-path коду (в отличие от эксепшенов)
Целевой код решает поставленную задачу — без него вообще ничего не будет.
Код в error-path минимизирует убытки — это очень важная отдельная ответственность, ее логично и реализовать отдельно, без смешения и копипасты.
Простоту и понятность обработки ошибки в программе в целом — это простая переменная: получил ее, обработал.
Наоборот — нет никакого единого механизма обработки ошибок в контексте, каждый отдельный метод обязан решать все на месте. Для понимания каждого конкретного случая в масштабе программы надо обязательно просмотреть кучу условных операторов вверх по стеку. В случае недосмотра с кодом ошибки в период выполнения имеем систему с тихо поломанными инвариантами, которая якобы работает (на самом деле нет). В случае недосмотра с исключениями имеем панику и подробную информацию что, где и когда полыхнуло.
Отказ от более сложных и мощных средств никак не уменьшает сложность задачи.
Как результат в Go есть механизм panic-recover-defer, делающий в точности то же самое, что и исключения. Следовательно, все равно придется решать, когда уместна паника, а когда код ошибки. Такие дела.
Обычно происходит return nil, error что не добавляет ровно никакой полезной информации.
Обычно добавляется полезная информация и ошибка заворачивается в что-то вроде return nil, fmt.Errorf("decode: %s", err)
или заворачивается в свой тип, реализующий интерфейс error — return nil, DecodeError{line: line, err: err}
, но во многих ситуациях достаточно и просто передать ошибку наверх — это нормально. Суть здесь в том, что ваш коворкер или вы через 3 месяца, читая этот код, мгновенно будете видеть, что происходит при возникновении ошибки, уменьшая таким образом количество догадок, ошибок в понимании workflow программы и подкапотной магии в целом.
Код в error-path минимизирует убытки — это очень важная отдельная ответственность, ее логично и реализовать отдельно
Ну это подход тех, кто привык к эксепшенам. Авторы Go же считают, что error path код, не нужно "реализовывать отдельно", и стимулируют к этому относиться как такому же важному коду, как и happy path. Написал if, напиши и else, другими словами. Вы можете быть с этим не согласны, но это тот подход, который выбран в Go и который, на самом деле, миллионы программистов считают более правильным и удобным.
Для понимания каждого конкретного случая в масштабе программы надо обязательно просмотреть кучу условных операторов вверх по стеку.
Это вы как раз эксепшены описали. Прото открыв код в гитхабе, у вас нет возможности понять, что тут могло выброситься или не могло, и как будет обработана ошибка и будет ли вообще. В подходе Go такой проблемы нет.
Отказ от более сложных и мощных средств никак не уменьшает сложность задачи.
А оно и не должно. Сложность задачи (essential complexity) средствами не уменьшается, а вот выбор средств с меньшей сложностью (accidental complexity) это как раз то, к чему вся софтверно инжинерная индустрия и стремится (ну, не везде, правда))).
Следовательно, все равно придется решать, когда уместна паника, а когда код ошибки.
Именно, и паника очень редко когда на нужна по факту. И, к слову, вы написали "код ошибки", но коды ошибки это в С. В Go error не код, это интерфейсный тип, и это, мягко говоря, несравнимо.
Обычно добавляется полезная информация и ошибка заворачивается в что-то вроде return nil, fmt.Errorf("decode: %s", err) или заворачивается в свой тип, реализующий интерфейс error — return nil, DecodeError{line: line, err: err},
Полный эквивалент перехвата исключений на границе абстракций. Понимания требуется столько же.
во многих ситуациях достаточно и просто передать ошибку наверх — это нормально.
В точности с теми же проблемами, что и при использовании исключений. Но еще и с кучей шаблонного кода в нагрузку.
вторы Go же считают, что error path код, не нужно "реализовывать отдельно", и стимулируют к этому относиться как такому же важному коду, как и happy path.
Сможете показать, где это стимулируется средствами языка, а не просто руководством по стилю?
Error path НЕ такой же важный код как happy path по определению.
Без happy path у вас вообще ничего нет, без error path вы можете нести бОльшие убытки при нештатных ситуациях.
При использовании кодов ошибок их обработка перемешана с бархатным путем похлеще чем спагетти, занимает больше места (три строчки на одну), содержит массу копипасты, требует читать весь код при анализе ошибочной ситуации.
Сложность задачи (essential complexity) средствами не уменьшается, а вот выбор средств с меньшей сложностью (accidental complexity) это как раз то, к чему вся софтверно инжинерная индустрия и стремится (ну, не везде, правда))).
Задача обработки ошибок — это accidental complexity, никакой сущностной сложности в ней нет.
А вот happy path — это как раз essential complexity.
Исключения позволяют обработать ошибки концентрированно, централизовано, без копипасты, с максимально читаемым целевым кодом.
Это вы как раз эксепшены описали. Прото открыв код в гитхабе, у вас нет возможности понять, что тут могло выброситься или не могло, и как будет обработана ошибка и будет ли вообще. В подходе Go такой проблемы нет.
Да неужели? Если у вас случилась нештатная ситуация и она не совсем верно обрабатывается с помощью кодов ошибок, то вам придется
- Узнать о проблеме (необработанные коды ошибок молчат как партизаны)
- Предположить, где она возникла
- Пройти по всему стеку, проверив, как ее отрабатывает каждый метод в цепочке вызовов.
- Если предположение оказалось неверным вернуться к пункту 2
С исключениями:
- У вас будет стек с указанием места, где конкретно возникла ошибка.
- Проверять нужно только места перехвата исключений (а не всю цепочку вызовов)
миллионы программистов считают более правильным и удобным.
Миллионы леммингов не могут ошибаться. У человека чисто биологически есть масса систематических ошибок мышления, поэтому отсылка к миллионам сторонников релевантна только при голосовании.
Именно, и паника очень редко когда нужна по факту.
Ну вот вы и начали составлять талмуд о правильном использовании исключений. Кстати, а когда нужна паника по вашему мнению? В Go FAQ этот вопрос почему-то обойден молчанием.
И, к слову, вы написали "код ошибки", но коды ошибки это в С. В Go error не код, это интерфейсный тип, и это, мягко говоря, несравнимо.
"Код ошибки" — это роль, а не конкретный тип вроде целого числа. Вопрос с передачей полиморфного типа в качестве информации об ошибке давно решен положительно и как раз по опыту работы с исключениями.
Полный эквивалент перехвата исключений на границе абстракций. Понимания требуется столько же.
Ни разу не полный. При явном возврате, вы тут же видите что происходит в error path. В случае исключений вам нужно скроллить вниз функции, держа в голове типы исключений, которые мог выбросить данный вызов (и это тоже невозможно узнать, просто глянув на строку вызова).
Error path НЕ такой же важный код как happy path по определению.
Это вы в это верите, и, почему-то считаете это какой-то абсолютной истиной. В Go это убеждения очень не приветствуется и язык вас заставит начать относиться к error-path как к такому же важному коду и сделает это новым определением.
требует читать весь код при анализе ошибочной ситуации.
Именно. Читая весь код, вы сможете понять, что на самом деле имел ввиду программист, писавший код до вас.
Исключения позволяют обработать ошибки концентрированно, централизовано
Ага, при этом увеличивая когнитивную нагрузку на процесс понимания этой самой обработки, стимулируя пренебрежительное отношения к обработке ошибок и ещё пачку проблем. Не сомневайтесь, я в курсе плюсов эксепшенов, также как и их минусов.
обрабатывается с помощью кодов ошибок, то вам придется. У вас будет стек с указанием места, где конкретно возникла ошибка.
Давайте вы немного всё же познакомитесь с обработкой ошибок в Go, прежде чем спорить, как вам идея? В Go нет кодов ошибок, и при необходимости в ошибках в Go может содержаться и стектрейс и информация о файле/строке и что угодно вообще.
Кстати, а когда нужна паника по вашему мнению?
Когда ошибка критична и программа (или модуль программы) не могут дальше продолжать из-за этой ошибки и должна прекратить выполнение no matter what.
"Код ошибки" — это роль, а не конкретный тип вроде целого числа.
Это не роль, это именно число, по крайней мере так это подразумевается остальными. Вы уже выше написали совершенно ложные предположения из-за того, что продолжаете путать error тип в Go с "кодом ошибки".
Ни разу не полный. При явном возврате, вы тут же видите что происходит в error path. В случае исключений вам нужно скроллить вниз функции, держа в голове типы исключений.
Не вниз функции, а до ближайшей секции except.
Типы исключений в голове нужно держать ровно в той же степени, как и конкретный тип, реализующий интерфейс error. Если вам не нужен второй — то и первый не нужен точно так же.
Коды ошибок требует обработать ошибку сразу, исключения позволяет это сделать в специально отведенном месте.
Единственный плюс за коды ошибок — легче увидеть необработанное при ревью кода.
Это вы в это верите, и, почему-то считаете это какой-то абсолютной истиной. В Go это убеждения очень не приветствуется и язык вас заставит начать относиться к error-path как к такому же важному коду и сделает это новым определением.
Вы только что сами утверждали, что язык ничего не заставляет. В нем же есть "исключения здорового человека" AKA panic.
Давайте уж определимся, кто именно заставляет — язык или руководство по стилю?
Именно. Читая весь код, вы сможете понять, что на самом деле имел ввиду программист, писавший код до вас.
Не телепат и могу понять лишь то что написано. С исключениями для понимания читать придется меньше.
Ага, при этом увеличивая когнитивную нагрузку на процесс понимания этой самой обработки, стимулируя пренебрежительное отношения к обработке ошибок и ещё пачку проблем. Не сомневайтесь, я в курсе плюсов эксепшенов, также как и их минусов.
Не сомневайтесь, что и я в курсе. И обращаю внимание не только на достоинства симпатичного вам подхода.
Давайте вы немного всё же познакомитесь с обработкой ошибок в Go, прежде чем спорить, как вам идея? В Go нет кодов ошибок, и при необходимости в ошибках в Go может содержаться и стектрейс и информация о файле/строке и что угодно вообще.
Может, но как и в любом варианте с кодами ошибок, такая информация должна добавляться вручную. Исключения дают все это из коробки автоматически.
Когда ошибка критична и программа (или модуль программы) не могут дальше продолжать из-за этой ошибки и должна прекратить выполнение no matter what.
И зачем тогда нужен recover, если no matter what?
Это не роль, это именно число, по крайней мере так это подразумевается остальными.
Остальными это кем? Структурно рекомендуемый паттерн обработки ошибок в Go — это коды ошибок, т.е. специальные возвращаемые значения, содержащие информацию об ошибке. Если вы знаете общепринятый (и не специфичный для Go) термин, давайте пользоваться им, мне не критично.
Исключения тоже могут быть вовсе не объектами — и ничего, термин от этого не меняется.
Вы уже выше написали совершенно ложные предположения из-за того, что продолжаете путать error тип в Go с "кодом ошибки".
Будьте любезны дать конкретные примеры с указаниями в чем именно ложность.
Не вниз функции, а до ближайшей секции except.
А где обычно "ближайшая секция except"? Никогда не видел вверху функции или сразу под вызовом. Давайте по сути.
Единственный плюс за коды ошибок — легче увидеть необработанное при ревью кода.
А вы специально продолжаете называть возврат ошибок в Go "кодами ошибок"?
Давайте уж определимся,
Да вы никак не можете определиться, коды или не коды ошибок в Go и что такое исключения. Давайте пока вы с этим не определитесь, с другими вещами пока что рано определяться.
С исключениями для понимания читать придется меньше.
И понимать меньше, да.
Исключения дают все это из коробки автоматически.
И это ещё одна глупость — стектрейс далеко не во всех ошибках нужен. Но у вас же нет других способов, поэтому вы тратите память и такты процессора, чтобы гонять стекйтрейсы по поводу и без повода, даже не отдавая в этом отчет. Это не только лишний минус к производительности, но и просто неправильно. Почти каждая Python программа, например, вместо понятного сообщения для в консоль вроде "Ctrl-C pressed, aborting" или "File not found" вываливает стектрейс, но очень многие привыкшие к этому даже не понимают, почему это абсурдно. В Go вы получаете стектрейс только когда это надо.
И зачем тогда нужен recover, если no matter what?
Типичный случай, ваша библиотека предоставляет возможность использовать функции пользователя. По определению, у вас нет возможности знать заранее, что пользователь будет делать там, какие он будет функции передавать в ваш код. Может он там на ноль собрался делить, но ваша программа не должна при этом падать.
С точки зрения пользователя — его код это как отдельная программа, с точки зрения вашей библиотеки — его код это отдельная функция — черный ящик. В таком случае имеет смысл на всякий случай поставить recover и красиво обработать панику, если вдруг что.
Типичный пример — net/http
библиотека и HandlerFunc.
Структурно рекомендуемый паттерн обработки ошибок в Go — это коды ошибок
Вы же троллите, да? Коды ошибок — это устоявшийся термин из С и С++, где возвращаются действительно коды ошибок, и далее по ним делается switch или подобные проверки, что в купе с единственным значением возврата, делает механизм возвратов кодов ошибок очень неудобным. Go этих недостатков лишен и интерфейсный тип ошибки даёт гибкость несравнимую с "кодами ошибок". Но вы продолжаете всё равно называть это на свой лад, только заводя дискуссию в тупик.
А вы специально продолжаете называть возврат ошибок в Go "кодами ошибок"?
Да, вы пока не предложили никакого другого варианта. Использовать термин "возврат ошибок в Go" принципиально неверно — специальные значения для ошибок используются везде, ничего специфического для го в таком паттерне нет.
Да вы никак не можете определиться, коды или не коды ошибок в Go и что такое исключения. Давайте пока вы с этим не определитесь, с другими вещами пока что рано определяться.
Вы так убедительно писали про особенности англо- и испаноязычной культуры общения. Очевидно, лично к вам это не относится никак. Попытайтесь все-таки не хамить собеседнику, который с вами всего лишь не согласен по далеко не самому важному вопросу во вселенной.
И попробуйте дать ответ на прямо поставленный вопрос. Он совершенно точно не из категории риторических. Лично я не вижу препятствий к использованию исключений со стороны языка го.
И понимать меньше, да.
Понимать — больше. Пока один изучает весь код всех методов по цепочке вызовов другой уже заканчивает решать текущую задачу и занимается новой.
И это ещё одна глупость — стектрейс далеко не во всех ошибках нужен.
Вы снова хамите. Зачем? Думаете, это добавит популярности вашему любимому языку?
Конечно, стектрейс нужен далеко не всегда. Как и исключения. Но с ними у вас будет стек без малейших дополнительных усилий. Кстати, в яве можно и без стека при желании сделать. Часто исключения все равно никто бросать не будет — на то они и исключительные ситуации.
Коды ошибок — это устоявшийся термин из С и С++, где возвращаются действительно коды ошибок, и далее по ним делается switch или подобные проверки, что в купе с единственным значением возврата, делает механизм возвратов кодов ошибок очень неудобным. Go этих недостатков лишен и интерфейсный тип ошибки даёт гибкость несравнимую с "кодами ошибок". Но вы продолжаете всё равно называть это на свой лад, только заводя дискуссию в тупик.
Коды ошибок появились задолго до си и тем более плюсов, и даже любого языка высокого уровня вообще.
Замена типа кода ошибки на интерфейс (или базовый класс) позволяет иметь больше информации (это придумали задолго до го) и гибче ее обрабатывать, но сам паттерн обработки от этого нисколько не меняется. Вы так и не дали подходящего общего термина, поэтому я использую самый близкий из тех, что известны мне. Это корректнее, чем говорить только про го, в котором ничего уникального по части обработки ошибок нет.
И это ещё одна глупость — стектрейс далеко не во всех ошибках нужен.
Вы снова хамите. Зачем?
Вам кажется хамством утверждение, что не во всех ошибках нужен стектрейс? Я думал я уже ничему тут не удивляюсь :)
И попробуйте дать ответ на прямо поставленный вопрос.
Вам его уже дали много раз. Но вы используете свою собственную терминологию, воинственно отвоевывая право не понимать, что вам объясняют. Если два человека не могут прийти к общему значению слов, то каким образом они могут прийти к общему значению предложений составленных из этих слов? Именно поэтому я вас просил перестать кому-то что-то доказывать и выйти на диалог, но вы доказали, что это невозможно.
Вы так и не дали подходящего общего термина
Дал, несмотря на то, что вы сами и не попытались взять. Хотите писать простыни комментариев про то как вас никто не заставит назвать вещи своими именами — пишите на здоровье. Мне это больше не интересно.
Обычно добавляется полезная информация и ошибка заворачивается в что-то вроде return nil, fmt.Errorf(«decode: %s», err) или заворачивается в свой тип, реализующий интерфейс error — return nil, DecodeError{line: line, err: err}
Очевидно, что полезность «return nil, fmt.Errorf('decode: %s', err)» стремится к нулю, т.к. эта ошибка для пользователя (причем, без учета i18n), а не программы, ее нельзя категоризировать и далее каким-либо осмысленным образом обработать.
А возвращение в стиле «return nil, DecodeError{line: line, err: err}», по-моему, упрется в то же, что и упираются исключения — лень программиста завести «DecodeError» и написать правильно.
Мило. Вы это Go программистам пытаетесь доказать, что в Go обработка ошибок стремиться к нулю, или тем, кто Go не знает? :)
Не передергивайте, речь шла про конструкцию return nil, fmt.Errorf('decode: %s', err)
а не про обработку ошибок в общем.
Почему вы отвечаете за другого человека и объясняете мне, что вы считаете, что он имел ввиду? Куда вы дели из его ответа конструкцию return nil, DecodeError{line: line, err: err}
и фантазии автора о том, во что оно упрётся?
Или вы тут один человек под разными аккаунтами?
Что же до строчки `return nil, DecodeError{line: line, err: err}` — то при чем тут она? Про полезность стремящуюся к нулю говорилось в контексте первой строчки, а не этой. Второй вариант как раз нормальный.
Мило. Вы это Go программистам пытаетесь доказать
Ну я тоже программирую на Go, решая те задачи, которые для него хорошо подходят с моей точки зрения: небольшие переносимые утилиты командной строки.
Кроме этого, упомянутая проблема «стремления пользы к нулю» справедлива не только для Go, а вообще для любой системы, в которой будет использоваться подобная конструкция (строка ошибка). В C++ это будет:
throw «some error useless description»;.
https://stackoverflow.com/questions/6248404/c-exceptions-is-throwing-c-string-as-an-exception-bad
упомянутая проблема «стремления пользы к нулю»
Я всё же позволю не согласиться с этим — во многих случаях как раз важно дать пользователю четкое описание того, что произошло, и строковое описание ошибки, дополненное на каждому уровне стека оказывается именно тем, что нужно. Напирмер, если пользователь запускает программу, и получает в ответ строку Can't load config: open ~/.config/myapp: insufficient permissions
— это почти образец того, как подобная ошибка и должна быть обработана и подана юзеру. Тут не нужны стектрейсы. не нужна метаинформация — простая строка, объясняющая чуть возникшей ситуации, которая трактуется программой, как проблема.
То, что это не для всех случаев нужно — это правда. Для других случаев, вам больше подойдут другие паттерны обработки и представления пользователю ошибки, иногда будет удобней создавать свои типы с метаинформацией, включая файл и строку где ошибка произошла или даже весь стектрейс. В некоторых случаях будет удобней аккумулировать ошибку и делать текстовое представление всех ошибок, а в некоторых вообще держать канал ошибок, и передавать ошибки через канал. Go это позволяет делать под каждый необходимый кейс. И говорить, что польза одного из кейсов стремится к нулю — это не очень корректно.
Сообщения об ошибках, также как и любые сообщения, нужно отображать локализованными. Вот только такие простые конструкции как приведенная вами не позволяют локализовать сообщение об ошибке.
Сообщения об ошибках, также как и любые сообщения, нужно отображать локализованными.
Не обязательно.
Вот только такие простые конструкции как приведенная вами не позволяют локализовать сообщение об ошибке.
Элементарно позволяют.
return nil, fmt.Errorf("%s: %s", i18n("decode"), err)
return nil, DecodeError{line: line, err: i18n(err)}
А откуда такая абсолютная убежденность, что все ошибки необходимо локализовать? Спрашиваю чисто из практического опыта — в серверах, например, часто единственный пользователь, который будет читать ошибки, это технический персонал (разработчики, девопс, и тд). и проблем с пониманием английского нет.
Вот после таких решений (я о первом) и появляются "локализованные" сообщения вида "Ошибка декодировать: открыть файл: нехватка разрешений".
return nil, fmt.Errorf("%s: %s", i18n(«decode»), err)
Ха-ха-ха. Встречал я подобный код, только вместо i18n был стразу MessageBox. Чего мелочится-то? :-)
В 99% случаях это нарушение роли компонента в системе.
О чём вы сейчас спорите?
О пользе типизированных ошибок, которые часто ленятся использовать программисты с качестве возвращаемого результата и в качестве объекта исключения.
О пользе типизированных ошибок
Ясно. А кто-то говорил, что они не полезны?
Да, это вы говорили:
Я всё же позволю не согласиться с этим — во многих случаях как раз важно дать пользователю четкое описание того, что произошло, и строковое описание ошибки, дополненное на каждому уровне стека оказывается именно тем, что нужно. Напирмер, если пользователь запускает программу, и получает в ответ строку Can't load config: open ~/.config/myapp: insufficient permissions — это почти образец того, как подобная ошибка и должна быть обработана и подана юзеру. Тут не нужны стектрейсы. не нужна метаинформация — простая строка, объясняющая чуть возникшей ситуации, которая трактуется программой, как проблема.
Да, это вы говорили:
В каком месте из приведенной цитаты следует, что "типизированные ошибки бесполезны"?
С первых же слов — по контексту беседы, вы не соглашаетесь с необходимостью типизированных ошибок.
> ценность А стремится к нулю
я> не согласен, есть много случаев когда А имеет смысл
> значит вы утверждаете, что Б бесполезно
Извините, но я не вижу как можно продолжать дискуссию с такими нарушениями базовой логики.
кому важно удобство и кто в состоянии воспользоваться эксепшенами
Я слушаю эти сказки про "удобство" и "в состоянии выучить" эксепшенов уже лет 10. Практика показывает, что подавляющее большинство кода на эксепшенах написано через одно место и создает много проблем. Последний раз, когда я спорил о пользе эксепшенов и слушал о том, что "это программисты были идиоты", спор закончился тем, что мне пришлось переводить сервис этого человека в докер-контейнер, и там было как раз важно ловить ошибки, что не так (порт не открыт например, или файл не найден или еще что) — и ни одна, ни одна из действительно ошибочных ситуаций не была корректно обработана. Эксепшены стимулировали "выбросил и словил наверху", такой подход в целом заставляет считать ошибки чем-то не особо важным, чем-то "а, потом займусь", и результат был на лицо. Спасло только использование strace. Спрашивается — зачем был вообще этот механизм ошибок "не для идиотов, а для элитных программистов, смотрящих свысока", который "очень удобный для использования", если всё равно толку от него ноль? Можно было просто делить на ноль и падать — было бы эффективнее. И я такого уже насмотрелся достаточно.
Более того, было тут исследование статическим анализом 400000 репозитороев на Java, в котором оказалось, что 78% проектов просто выбрасывают GeneralException и ловят его в самом верху. Ладно, вы можете не верить моему опыту и опыту сотен других, но с цифрами и статистикой сложно спорить. Поэтому лично я рад, что хотя бы в одном языке к вопросу подошли к практической и эмпирической точки зрения, а не с теоретических фантазий.
Поэтому воздерживайтесь, конечно, вас никто не заставляет же. Пишите, на чем удобно.
Я слушаю эти сказки про "удобство" и "в состоянии выучить"
А я читаю их в вашем исполнении прямо сейчас. У вас есть ссылки, сколько программистов неверно обрабатывали коды ошибок и когда? Если да, то можете их привести?
Если нет, то исключения хороши еще и тем, что качество их использования можно легко оценить статическим анализом.
У вас есть ссылки, сколько программистов неверно обрабатывали коды ошибок
Причём тут коды ошибок в Go? Почитайте всё же про реализацию ошибок в Go, прежде чем спорить, там не так много читать.
Если нет, то исключения хороши еще и тем, что качество их использования можно легко оценить статическим анализом.
Ну хоть какой-то плюс, и то надуманный. Статический анализ обработки ошибок в Go гораздо проще благодаря простой грамматике языка и тому факту, что ошибки в Go — такие же значения и переменные, как любые другие, что позволяет легко писать статические анализаторы по поводу и без.
Статический анализ обработки ошибок в Go гораздо проще благодаря простой грамматике языка и тому факту, что ошибки в Go — такие же значения и переменные, как любые другие, что позволяет легко писать статические анализаторы по поводу и без.
У вас есть результаты статического анализа обработки ошибок на Go по большому набору проектов?
У вас есть результаты статического анализа обработки ошибок на Go по большому набору проектов?
Нет, но это кстати, одна из тем, которая мне интересна и я работаю над проектом, для статического анализа всей экосистемы open-source Go.
Нет, но это кстати, одна из тем, которая мне интересна и я работаю над проектом, для статического анализа всей экосистемы open-source Go.
Следовательно, вы не можете использовать статистику по исключениям явы как аргумент.
У вас просто нет статистики по го для сравнения.
Более того, у вас так же вообще нет статистики по обработке с помощью кодов ошибок (уж извините, но другого термина вы пока не предложили).
И результаты для явы очень легко объяснить: львиной доле программистов нужен результат, а не тщательная обработка ошибок.
Исключения тут просто индикатор, а не проблема сами по себе.
Статический анализ обработки ошибок в Go гораздо проще благодаря простой грамматике языка и тому факту, что ошибки в Go — такие же значения и переменные, как любые другие, что позволяет легко писать статические анализаторы по поводу и без.
Если ошибки — такие же значения, переменные и конструкции, как и остальные, то вам объективно сложнее вычленить обработку ошибок из остального кода для анализа.
Следовательно, вы не можете использовать статистику по исключениям явы как аргумент.
Как аргумент того, что эксепшены чаще используются некорректно — могу.
У вас просто нет статистики по го для сравнения.
У меня есть эмпирический опыт, да. Меня почему так порадовал тот paper по джаве, потому что мой опыт с эксепшенами примерно говорил тоже самое. Но опытом сильно не поаргументируешь, в отличие от цифр. Так что я был счастлив порадовать свой confirmation bias. С Go же я работаю уже 4 года и пока что вижу только то, что Go действительно заставляет людей начинать ценить ошибки, и код. где ошибки не еффективно обрабатываются, или уж тем более игнорируются — это редкость, которая долго не живет, особенно в опен-сорс мире. Можете меня ругать, что я недостаточно доступно могу объяснить почему и как Go это получилось достичь — я признаю, что сам могу не понимать всех механизмов — но всё таки, читая чужой код на Go я почти всегда вижу адекватную, понятную и правильную обработку ошибок.
кодов ошибок (уж извините, но другого термина вы пока не предложили).
Называйте это просто "возвратом ошибок".
львиной доле программистов нужен результат, а не тщательная обработка ошибок.
Хахаха. В рамочку фразу просто. Кому же нужна "тщательная обработка ошибок", если язык стимулирует думать про ошибки как что-то "исключительно", прячет это с глаз подальше и стимулирует думать о них в последнюю очередь.
то вам объективно сложнее вычленить обработку ошибок из остального кода для анализа.
Вам может и да, а мне нет — я люблю тулзы для статического анализа писать в Go — в нем парсер AST и анализатор типов в стандартной библиотеке идут, одно удовольствие. Особенно благодаря простой грамматике, я могу за выходные написать проект, который для более сложных языков в принципе даже бы и не взялся писать. Что как бы ещё один не очень очевидный плюс. Вобщем, выделить интерфейсный тип error среди остальных типов статическим анализом не представляет никакой проблемы.
Как аргумент того, что эксепшены чаще используются некорректно — могу.
Не можете даже этого: возможно, для тех проектов такого примитивного использования исключений было достаточно.
У меня есть эмпирический опыт, да.
У меня тоже. Я к нему принципиально не апеллирую
Меня почему так порадовал тот paper по джаве, потому что мой опыт с эксепшенами примерно говорил тоже самое. Но опытом сильно не поаргументируешь, в отличие от цифр.
Цифрами тоже непросто, если не пытаться подгонять их под убеждения. У вас нет никаких данных против заведомо более простых объяснений статистики по яве.
я признаю, что сам могу не понимать всех механизмов
Но твердо верите что дело в языке и плохих исключениях. Хотя очень легко проверить, что это не так, причем по вашим же ссылкам.
читая чужой код на Go я почти всегда вижу адекватную, понятную и правильную обработку ошибок.
Верю, но совершенно очевидно, что дело тут не в языке.
Кому же нужна "тщательная обработка ошибок", если язык стимулирует думать про ошибки как что-то "исключительно", прячет это с глаз подальше и стимулирует думать о них в последнюю очередь.
Это вы про яву с ее checked exceptions? Серьезно?
Дело не в инструментах — в го есть полный аналог исключений.
И если им не злоупотребляют — дело не в языке, а в решаемых задачах.
в нем парсер AST и анализатор типов в стандартной библиотеке идут,
Для статистики по исключениям (или panic в го) с приемлемой точностью анализ по AST скорее всего не нужен от слова совсем. Такие дела.
но совершенно очевидно, что дело тут не в языке.
Совершенно очевидно, что именно в нём.
Дело не в инструментах — в го есть полный аналог исключений.
И если им не злоупотребляют — дело не в языке
Вы вольны верить в то, что хотите верить и продолжать игнорировать то, что вам говорят люди, больше разбирающиеся в предмете спора.
Для статистики по исключениям (или panic в го) с приемлемой точностью анализ по AST скорее всего не нужен от слова совсем.
Что вы этим пытаетесь сказать? Пожалуйста, постарайтесь доносить свои мысли более понятно и, желательно, использовать те же понятия, которыми оперируют другие люди, а не ваши собственные определения. Спасибо.
Вы сами оформили всё своими словами про "идиотов" и "тем, кто хочет удобства и умным программистам не дают свободы" и в такой формулировке, конечно, это всех отталкивает.
а вы уже «идиотов» приняли на свой счёт.
Я не принимал ваше "идиотов" на свой счёт.
«пусть неудобно, зато никто не ошибётся»
Опять же, вы сами так это оформили, ещё и "эмоционально раскрасили". Неудобством это кажется только с непривычки, но "непривычно" и "неудобно" это разные вещи.
Ну это же самый популярный аргумент сторонников эксепшенов, который звучал и будет звучать тысячи раз — о том, что Go сделали для "тупых разработчиков, которые не могут научиться правильно пользоваться эксепшенами", а "умных элитных разработчиков рок-звезд" Go заставляет чувствовать себя ненужными и не рок-звездами. Так что да, я вполне естественно эту риторику могу увидеть и ваших комментариях, признаюсь.
Ну это же самый популярный аргумент сторонников эксепшенов, который звучал и будет звучать тысячи раз
Так это вы отвечали тем самым людям, что тысячи раз озвучивали этот аргумент или мне? Обычно если ваш комментарий под моим с отступом — это означает, что вы отвечаете мне, а я (если мне не изменяет память) ничего такого не писал. Я лишь сказал, что для меня это неудобно — про музыкальных кумиров и тупых разработчиков — это вы уже похоже сами додумали.
Боюсь представить эту вашу практику, которая показывает на какое-то одно место о котором все говорят, но никто не может объяснить конкретнее.
Ваша позиция по отношению к го мне примерно ясна из предыдущего диспута, можно было не писать ещё один такой длинный комментарий.
Это число из paper-а по исследованию Java exceptions. Последний раз когда я его находил (на Хабре в каждом посте про Go приходят комментаторы, рассказывающие про счастье эксепшенов, и дискуссия доходит до этого пейпера) — там была битая ссылка, но если хотите, могу найти оригинал, он у меня где-то есть.
Ваша позиция по отношению к го мне примерно ясна из предыдущего диспута, можно было не писать ещё один такой длинный комментарий.
Если честно, то ваша позиция мне тоже ясна из первого комментария, и его можно было вообще не писать, сообщая всем вокруг как вам не интересен Go, обвиняя в этом аргументацию кого-то из комментаторов. Вот серьезно.
Вот подумайте сами, если бы го был мне неинтересен, зачем бы я стал заходить в эту статью? А я её прочёл да ещё и вместе с комментариями, видимо какой-то интерес есть? Проблемы только две. Мне кажутся некоторые решения этого языка немного странным и я пытаюсь понять, чем они обусловлены, но когда вижу такую аргументацию, то мне кажется, что я все же чего-то не понимаю. И второе, что вечно преследует го — это хейтеры. У языка похоже что-то не чисто с кармой, ему просто катастрофически не везёт с хейтерами, они наводняют любой пост и не дают любителям писать на го наслаждаться их любимым языком.
если бы го был мне неинтересен, зачем бы я стал заходить в эту статью?
Не принимайте на свой счёт, но тут большинство комментаторов — именно такие. Из года в год пишут одно и тоже, так и не удосужившись попробовать Go.
Мне кажутся некоторые решения этого языка немного странным и я пытаюсь понять, чем они обусловлены
В Go масса моментов, которые разительно отличаются от "общепринятых" норм. Многие из них и для меня были явными WTF — как такое вообще можно было придумать. Но мне лично помогло то, что называется appeal to authority — которое часто является логической ошибкой, но не всегда. У меня было большое уважение к Кену Томпсону и Робу Пайку (надеюсь, не нужно рассказывать, кто это), и я просто доверился, что их практический опыт больше моего. Если они считают, что "неиспользуемый импорт это плохо и компилятор должен ругнуться", то возможно это имеет смысл, даже если мне это непривычно и всеми фибрами я этому сопротивляюсь. На удивление, очень быстро эти моменты стали нормой и сейчас я не понимаю, как можно было раньше жить без go fmt, с тоннами импортов, которые не используются, с классами, с миллионами способами сделать одно и то же и т.д.
К сожалению, этот же аргумент меня отталкивает от других языков — например, если я вижу, что какой-то язык был создан молодым студентом, который любил писать компиляторы в свободное время, но как-то у меня не вызывает это доверия, и моё "wtf" тут будет иметь больше вес.
В целом, основное отличие в Go в том, что они забили на новые веяния в PLT, поскольку видели, как все новые достижения приводили не только к прогрессу, но и к созданию нового класса проблем и сложности (эксепшены, наследование, shared memory concurrency и тд) и оставили в языке только минимум ортогональных концепций, которые себя зарекомендовали уже несколько десятилетий как.
Плюс сделали акцент на социальный аспект программирования (читабельность кода важнее, тулинг решает, convention over configuration — минимум файлов настроек, тесты и бенчмарки должны быть из коробки и отнимать у программиста минимум телодвижений, для документации не нужно ничего учить и тоже из коробки и т.д.)
Резюмируя — Go построен на практическом опыте того, что реально важно в real-world программировании, а не в академических исследованиях новых фишек.
И второе, что вечно преследует го — это хейтеры.
Это специфика рунета, в англоязычном интернете тоже троллей хватает, но там как-то поадекватней. Главное от чего такой хейт и пригорание — от того, что настолько "объективно примитивный язык" по их мнению, достиг таких высот. Особенно в этом плане показательно сообщество хаскеллистов — переводил как-то статью о том, где человек рассказывал о том, какой прекрасный Хаскель и какой ужасный Go, при этом признаваясь, что он может попросить коллегу выучить за неделю Go и закончить проект, а попросить выучить Хаскель и законтрибьютить не может, потому что у него у самого ушло 4 года чтобы выйти на какой-то достойный уровень, — и это да, решает.
Резюмируя — Go построен на практическом опыте того, что реально важно в real-world программировании, а не в академических исследованиях новых фишек.
Особенно важно для реального мира было сохранить ту самую "ошибку на миллиард долларов"?
Особенно важно для реального мира было сохранить ту самую "ошибку на миллиард долларов"?
В Go есть понятие zero value, и nil лишь один из частных его случаев. Но мне нравится, как вы пытаетесь всеми силами любые шаблоны и манипулятивные фразы привлечь, чтобы самому себе что-то доказать. Очень показательно для C# коммьюнити.
Мне кажется, что основная беда го — это не хейтеры, а фанатики. Они готовы продать все что угодно за уникальные достоинства языка, включая то, что реализовано задолго до и много где. И даже недостатки будут отрицаться или продаваться за достоинства.
Да я-то вас понял.
Но должен же был кто-то в качестве жеста доброй воли объяснить эту автору статьи.
Поэтому лично я рад, что хотя бы в одном языке к вопросу подошли к практической и эмпирической точки зрения, а не с теоретических фантазий.
Не только в одном. В Rust-е так же нет исключений, но обработка ошибок там на порядок лучше реализованы чем в Go.
А как вы замерили "на порядок лучше"? Как раз "практичность и эмпиричность" означает проверенность временем и обкатанность реальными программистами на реальных проектах, а не теоретические размышления, что будет лучше. Тоесть я не спорю, вполне может быть, так как вы написали, но ровно с такой же вероятностью может оказаться фантазией, разбившаяся о реалии человеческой психики. Я не сильно слежу за миром Rust, но из нескольких свежих статей сложилось впечатление, что Rust-программисты злоупотребляют .unwrap()
.
Так что решение Rust ну никак нельзя назвать "прагматичным и эмпиричным", и вопрос на самом деле в том — не будет ли это "на порядок хуже" на практике. Если будет лучше — это будет же сказка, следующие языки смогут извлечь пользу.
А как вы замерили "на порядок лучше"
На личном опыте написания программ на языках Rust и Go.
Как раз "практичность и эмпиричность" означает проверенность временем и обкатанность реальными программистами на реальных проектах, а не теоретические размышления, что будет лучше.
Вы хотите сказать, что программы на Rust не обкатаны на реальных проектах?
Я не сильно слежу за миром Rust, но из нескольких свежих статей сложилось впечатление, что Rust-программисты злоупотребляют .unwrap()
Если судить по статьям, то Go-программисты злоупотребляют пропуском обработки ошибок. Вы же понимаете, что статьи пишутся не для того, чтобы досконально все ошибки описывать. Лучше судить по коду на гитхабе. И не любительских проектов, а тех что работают на продакшене.
Вы хотите сказать, что программы на Rust не обкатаны на реальных проектах?
Нет я хочу сказать, что когда в совершенно новый язык добавлятся новая фича и объявляется как "на порядок лучше" — это всегда наивно и преждевременно. Настоящее испытание "фич" происходит годами, по мере того, как программисты массово начинают её использовать и на практике становится понятно, насколько это работает так.
Вы же понимаете, что статьи пишутся не для того, чтобы досконально все ошибки описывать.
Вы не поняли, я не про статьи с примерами кода без обработки ошибок, а про статьи, в которых пишут, что .unwrap() everywhere
.
Нет я хочу сказать, что когда в совершенно новый язык добавлятся новая фича и объявляется как "на порядок лучше" — это всегда наивно и преждевременно. Настоящее испытание "фич" происходит годами, по мере того, как программисты массово начинают её использовать и на практике становится понятно, насколько это работает так.
Вас похоже задело фраза «на порядок лучше».
Давайте я перефразирую.
В Rust, так же как и в Go, нет исключений. После написания программ на Go и Rust, я считаю (это ключевой момент, это моё мнение), что обработка ошибок в Rust сделана на порядок лучше.
Фича обработки ошибок в Rust уже испытана более чем двумя годами прошедшими со дня релиза первой версии и программисты массово её используют, т.к. программа без обработки ошибок не скомпилируется.
При этом unwrap не рекомендуется для использования в продакшене, т.к. необработанная ошибка упадёт в рантайме с паникой. Использовать unwrap в Rust, равносильно пропуску обработки ошибки в Go.
Справедливости ради, хочу добавить, что после Rust на Go программировать приятнее благодаря простоте этого языка и экосистема Go на порядок лучше развита (специально здесь использую данное словосочетание).
Мне нравится простота и развитость Go и нравится безопасность и функциональные возможность Rust.
Почему в го нет исключений?
panic-recover-defer полностью эквивалентен throw-catch-finall с точностью до синтаксического сахара.
Нет, ну если хотите, называйте panic-recover-defer исключениями :)
Но используется этот механизм не так как в других языках.
Например, в других языках, библиотеки пишут так, что отсутсвие записи в базе выбрасывает исключение.
Или библиотеки валидации пользовательского ввода выбрасывают исключения на ошибки валидации.
Или, например, чтобы отдать пользователю по HTTP вместо 200 статуса, какой-нибудь 400-тый выбрасывают исключение.
И у меня отвращение от такого использования исключений.
В Go для таких случаев не будут применять panic-recover-defer.
Как используется — вопрос не наличия инструмента, а соглашений.
Говорить что в го нет исключений очевидно неверно.
Говорить что они не используются тоже очевидно неверно — варианты есть здесь же в комментариях.
Можно сказать, что они используются иначе чем в других языках — но даже это неверно: гугл точно так же (хотя и по несколько иным причинам) избегает использования исключений в плюсах.
В 100-й раз вам пишу одно и то же, но у понятия "исключения" есть конкретный общепринятый смысл — это конкретный механизм языка для обработки ошибок. В Go есть panic() для быстрого падения, есть recover() для случаев, когда нужно словить паники из кода, который не контролируешь, и есть defer() для случаев, когда нужно выполнить код при выходе из скопа функции.
В принципе, эти три конструкции можно соединить вместе и получить некое похожее подобие костыльных "исключений". Но это неверно говорить, что "в Go есть исключения", потому что соединив воедино три отдельные совершенно отличные конструкции языка можно соорудить нечто напоминающее исключения в языках с эксепшенами.
Вас похоже задело фраза «на порядок лучше».
Давайте я перефразирую.
…
я считаю (это ключевой момент, это моё мнение), что обработка ошибок в Rust сделана на порядок лучше.
Да вы мастер перефразирования.
Использовать unwrap в Rust, равносильно пропуску обработки ошибки в Go.
Это было бы супер, если бы оказалось правдой!
Использовать unwrap в Rust, равносильно пропуску обработки ошибки в Go.
Не равносильно — необработанная ошибка в го никакой паники сама по себе не вызовет, работа программы будет молча продолжена.
Под равносильностью я имел в виду, то что писать заключения вроде «программисты на Rust злоупотребляют unwrap», равносильно тому что «программисты на Go злоупотребляют пропуском обработки ошибок», механизм работы, конечно же, отличается.
работа программы будет молча продолжена
Будет молча продолжена до первого обращения к результату и выскочит какой-нибудь NPE. Вряд ли программа написана так, что с результатом никакой последующей работы не ведётся.
Под равносильностью я имел в виду, то что писать заключения вроде «программисты на Rust злоупотребляют unwrap», равносильно тому что «программисты на Go злоупотребляют пропуском обработки ошибок»,
Даже это не равносильно, хотя и то и другое без пруфлинков всего лишь бездокательные оценочные суждения.
Пропуск ошибки на го выглядит безобидно — ну пропустили вы одно возвращаемое значение из кортежа, что тут такого?
Unwrap же прямо и недвусмысленно заявляет: "здесь все быстро и грязно, падение сразу после ошибки приветствуется".
Будет молча продолжена до первого обращения к результату и выскочит какой-нибудь NPE. Вряд ли программа написана так, что с результатом никакой последующей работы не ведётся.
Это обращение может быть сколь угодно далеко от места и времени сбоя плюс вся дополнительная информация о локализации и причинах сбоя будет утеряна. Да и null при ошибке не обязателен.
Создатели раста понимают необходимость решения проблемы возвращаемых ошибок
Какие умнички. Удачи им, буду болеть за них, чтобы решили, при этом не пожертвовав решениями других, гораздо более серьезных проблем.
Помимо unwrap-а как минимум есть варианты использовать цепочки обработки результата
А создатели Rust понимают, к чему обычно приводит наличие нескольких способов сделать одно и тоже?
гораздо более серьезных проблем
Что-то мне подсказывает, что вы недооцениваете масштаб этой проблемы. Лично для меня при всей моей симпатии к гоу, есть пара нюансов (один из которых обилие кода, который бы я хотел написать в одном месте, но не могу из за этой особенности языка) которые лично мне не дают расслабиться когда я пишу на нем код, потому выбирать его для продакшена я пока не тороплюсь.
которые лично мне не дают расслабиться когда я пишу на нем код
прекрасно вас понимаю, Go тяжело некоторым людям заходит, они нутром сопротивляются после других языков. каждый раз, когда видят, что нужно писать проверку на ошибку, вспоминают, как бы это было "проще" в другом знакомом языке, и вот эта opportunity cost отталкивает.
но Go меняться не будет и он очень предвзят к тому, что хорошо и что плохо, поэтому обработка ошибок сделана именно такой — простой, понятной и с минимумом магии — и сделана такой нарочно.
да, приходится "больше писать", но это с лихвой окупается, тем что ошибки на самом деле начинают обрабатываться, язык приучает к ним относиться так же, как к любым другим значениям (можете мне не верить, но это то что я слышал десятки раз от разных людей по всему миру — "Go научил меня уважать ошибки"). я уже должен быть привыкнуть, но все равно каждый раз поражаюсь, когда на совершенно новом проекте в полмиллиона строк кода, время на вход и понимание кодовой базы (включая того, что происходит при ошибках) занимает меньше суток. вот просто приходишь на новый проект — и в первый день уже коммитишь.
из моего опыта, это бесценно.
А это надо еще и замерять?
В го при использовании рекомендуемого способа обработки ошибок надо делать ревью кода, во время исполнения пропущенная ошибка будет молча проигнорирована.
При использовании исключений пропущенная ошибка в период выполнения всплывет и прямо о себе заявит, с блекджетом и стектрейсами.
В расте же
- наличие обработки ошибок проверяется компилятором;
- есть сахар, с которым не надо разбавлять целевой код 1:3 шаблонным;
- даже для "быстро и грязно" есть готовый маркер, заметный за километр
А это надо еще и замерять?
Когда кто-то говорит "А на порядок больше Б", то как-бы, да, подразумевается некая количественная характеристики. Систему исчисления для "порядка" тоже неплохо бы озвучить.
В го… надо делать ревью кода
Ревью кода надо делать во всех языках.
В расте же
Это прекрасно, но Rust делался всё же более теоретиками, чем практиками, поэтому вполне ожидаемо получить это:
Статьи о том, что вся идея обработки ошибок в Rust обламывается на том, что все просто используют unwrap()
, как бы на это намекает.
Я не думаю, что даже могу вспомнить какие это были статьи конкретно — просто то, что отложилось в памяти из прочитанного за последние пару лет.
Не отрицаю, что статьи могли быть biased, я тоже biased, но вобщем-то логично. Я искренне только порадуюсь, если Rust действительно сделает в этом практический шаг вперёд — будьте уверены, другие языки это в будущем заберут себе, если оно действительно так хорошо на практике, как и в теории.
Я на раст тоже поглядываю уже который год, равно как и на Swift, правда между тем, как я моргаю, версия языка обновляется с пятнадцатой на восемьдесят девятую, что слегка смущает. Ну и посты в стиле "я фанат раста, два месяца зубрил, но так и не въехал, и не смог написать относительно простую программу" как бы подтверждают моё убеждение, что простота языка это слишком важный фактор, на который в Rust забили полностью.
Мне то всё равно какой язык — я просто хочу быстро и эффективно писать код, который другие люди могут быстро и эффективно понимать и улучшать. Rust, увы, не из этой категории.
Это прекрасно, но Rust делался всё же более теоретиками, чем практиками, поэтому вполне ожидаемо получить это:
А чтобы теоретикам небыло скучно, в перерывах между придумыванием теоретического языка, они писали экспериментальный WEB-движок.
они писали экспериментальный WEB-движок
Теоретический ;)
И что, много написали за 5 лет? И да, servo был и есть исследовательским экспериментом, хотя я всеми руками за то, чтобы проект удался.
Просто это ж несравнимо — что Грейдон, что Нико — кто они такие по сути? Просто талантливые ребята, которые увлекались теорией языков программирования, любили хачить языки и компиляторы. Это прекрасно, только это нельзя даже близко поставить рядом с Робом Пайком и Кеном Томпсоном, которые с 80х написали такое количество разнообразного софта, и проработали в таком количестве команд, всех размером и сортов, что ну это просто разные лиги.
И я сейчас да, совершенно сознательно апеллирую к авторитету — это часто считается логической ошибкой, но если персона, о которой идёт речь, действительно имеет абсолютно реальный авторитет в теме — то это имеет смысл. Опыт, которые Кен Томпсон и Роб Пайк накопили за десятилетия практики в итоге был ключевым в дизайне Go. И тут не только количество лет, конечно, но и то, что это одни из самых крутых пионеров computer science — ну, рассказывать, кто они такие, не нужно, думаю.
Long story short, "практики" дизайнившие Go сказали — "мы возьмем в язык то, что действительно работает из нашего практического опыта, что действительно важно в реальных процессах и командах", а "теоретики", дизайнившие большинство остальных языков (не только Rust, увы) звучат иначе — "мы возьмем, то что было, и переделаем по новому, добавим много совершенно новых подходов и фич и будет круто". И часто оно и бывает круто, пока не приходят реальные программисты, реальные проекты и процессы. И дело не в одном или двух проектах, а в действительно масштабном использовании, когда десятки и тысяч программистов пишут софт на этом языке — вот тогда обнаруживаются совершенно непредвиденные паттерны использования и проблемы. А сумашествие в виде "ничего, поломаем язык и пофиксим" только усугубляет ситуацию.
Вобщем, это если кратко про "теоретиков" vs "практиков".
И да, servo был и есть исследовательским экспериментом, хотя я всеми руками за то, чтобы проект удался.
Не знаю, насколько проект удался, но Firefox Quantum основанный на servo уже в бете и в ноябре планируется релиз.
https://blog.mozilla.org/blog/2017/09/26/firefox-quantum-beta-developer-edition/
Не знаю, насколько проект удался, но Firefox Quantum основанный на servo уже в бете и в ноябре планируется релиз.
Круто, не знал. Но за 5 лет… авторами языка… в ноябре планируется релиз. Слабенько тянет на "практику". Впрочем, очень хорошо отражает нишу Rust — проекты, где нужна максимально высокая производительность, на которые есть уйма времени, и над которыми работают люди, которые потратили годы на этот язык.
Но за 5 лет
Справедливости ради стоит упомянуть, что от стабильного релиза языка прошло чуть больше двух лет.
Слабенько тянет на "практику".
Складывается ощущение, что вы думаете, что на Rust написан только servo и что пишут на нём только авторы языка.
Складывается ощущение, что вы думаете
Нет, я отвечал на конкретное приведение в пример servo.
от стабильного релиза языка прошло чуть больше двух лет.
Что тоже не очень комплимент "практическому опыту" авторов — писать сложнейший проект на языке, который ещё несколько лет как не достиг стабильного релиза.
Много раз видел на хедхантере вакансии Go/Python, причем Go/PHP — ни разу (может мало мониторил?). Складывается ощущение, что на Gо часто пишут проекты, где раньше был питон. Но это лишь наблюдение
Кстати, один я заметил интересную особенность, что на Go активно переходят только PHP-шнки т.е. в большинстве своем те люди, которые привыкли писать лапшеобразный код.
Перешел на Go с С/С++, Java и Python (для новых проектов). Очень счастлив что это сделал, теперь трачу больше времени на написание кода приложений, чем на отладку и попытки понять почему у меня огнестрельная рана в ноге. Веб приложения не пишу, лапшу не пишу (по-крайней мере стараюсь не писать). Я что-то делаю не так?
Итогом является закономерный Го-внокод.
Мне кажется Вы приписываете языку то, что относится к самим программистам. Если программист пишет говнокод на PHP и JS, то переход на тот-же D это не исправит.
Пожалуйста, перестаньте переходить на личности в каждом посте, приписывать мне ваши собственные суждения и публично меня в этом обвинять. Вы этим дискредитируете Java-сообщество.
Вам можно, а мне — нет?
Какой смысл адекватно вести с вами беседы или как-то разговаривать, если весь разговор с вами укладывается в паттерн чат-бота:
- Вот эта фича в Go откровенно непродумана, потому что ....
- Нет, она отличная, потому-что <субъективное суждение/ссылка на авторитет/придирка к какой-то мелочи в объяснении>
- Но это же не так <пример/аргумент/опровержение>
- <обвинение в троллинге/попытка принизить знания собеседника/попытка троллинга/еще больше придирок>
Вся ваша аргументация строится на каком-то абсолютном бреде в духе "ну, когнитивная нагрузка меньше и ошибки теперь уважают" как будто вы сравниваете go не с современными языками, а с C99.
Попытки объяснить необходимость той или иной фичи вы сводите к тому, что "эта фича не нужна, потому что без нее можно обойтись", закрывая глаза на то, что можно обойтись вообще только ассемблером.
Это еще ухудшается постоянным потоком наркоманских "историй успеха", когда крупные компании накопив огромный технический долг, вместо того, что бы его решать, решают переписать все на Go и получают какие-то космические результаты. О да, это не ваша статья, но и у вас такие тоже есть.
И какой толк с вами говорить, если у вас все даже запущеннее чем у javascript'еров, они хотя бы признают, что у их языка есть крупные проблемы, но они с ними борются.
PS: Как это я могу дискредитировать сообщество языка, на котором уже года три не писал?)
если весь разговор с вами укладывается в паттерн чат-бота:
Вот эта фича в Go откровенно непродумана, потому что ....
Именно, спасибо за аналогию, ваши все комментарии — это маленький чат бот о том. как Go не продуман и последующая болезненная реакция на утверждения о том, что тот или иной момент более чем продуман и так и задумывался. И в каждом посте вы не делаете никаких выводов и продолжаете писать одно и то же, даже не пытаясь понять, что вам отвечают.
asm0 — MIPS
asm5 — ARM
asm6 — AMD64
asm9 — PPC64
У «go» как названия скорее кучу недостатков можно найти, а не «оправданий», начиная от того, что у поисковиков с «go» не так всё гладко (недавно ещё и «Android Go» появился, который никак с Go не связан), заканчивая тем, что это название уже было занято другим языком программирования.
«golang» в запросах и тегах помогает отбросить неоднозначность, но это костыль.
Всё же надеюсь, что некоторые элементы в Go 2 введут. Я бы лично не откзался от четырёх
- Generics — дабы иметь инструменты переиспользования кода
- Result-like обработка ошибок, дабы не иметь проблем с "вернули не-окей и забыли обработать", вкупе с кодом забитым
val, err := func() if err != nil { return <default value>, err }
- Non-nullable ссылки по умолчанию. Nil non-nil interfaces это честно говоря жёсткий косяк. Вопрос того, ссылки в Го или указатели, на самом деле дискуссионный.
- Деструкторы в каком-то вменяемом виде. defer не очень способствует надёжному коду.
По-моему опыт C++ и Rust показывает, что без этого не очень хорошо живётся.
Моё мнение не отражает мнение авторов/сообщества Go, но «Вирусные» возможности языков программирования выглядят менее вероятными кандидатами.
С деструкторами конечно проблемка, т.к. они требуют сильно подпилить семантику — либо вводить "волшебный тип-обёртку". Как вариант обхода — маркировать "закрывабельные" типы и при их наличии в скопе делать анализ потока управления, если может утечь — ругаться и требовать дефер. Но это отдельный геморрой. Считайте, что список выше в порядке "хотения".
Я бы лично не откзался от четырёх
Вы, наверное, читали https://habrahabr.ru/post/333346/? Там важный момент в том, что каждый может написать доступно — как именно вы не смогли "переиспользовать код" и к чему это привело из-за "отсутствия дженериков" в Go, или как нынешняя обработка ошибок приводит к проблемам в коде, и авторы Go это гарантированно прочтут и будут лучше понимать, куда смотреть. Только, как вы понимаете, посты из серии "я так привык, мне так удобней" вряд ли какой-то эффект принесут.
Деструкторы в каком-то вменяемом виде. defer не очень способствует надёжному коду.
Может ещё конструкторы, ещё и с перегрузкой? :) Посмотрите на SetFinalizer(), но это для очень специфических случаев. Обычно если "хочется деструкторов", то это вы ещё не отвыкли от предыдущих языков, а не "defer не очень способствует надежному коду" )) Defer совершенно другую задачу решает.
Хорошо, давайте попробую привести примеры. Сам код, увы, сейчас мне недоступен.
как именно вы не смогли "переиспользовать код" и к чему это привело из-за "отсутствия дженериков"
Стейт-прокси над стейтлесс сервером. Часть, ответственная за кеширование, была отделена от других кусков кода. Снаружи пришлось "просовывать" объект в виде interface{} и лямбду, которая его кастила к правильному типу и дёргала. Да, это непривычно для меня — я отвык от тайп-кастов по поводу и без с тех пор как перестал писать на голом С. Более общий пример — в Го любая не-встроенная структура данных будет построена на interface{}. Опять тайп-касты. В Java такая ситуация была до 1.5, после чего дженерики, хоть и урезанные, таки ввели.
как нынешняя обработка ошибок приводит к проблемам в коде
Как я сказал, конкретный код привести сейчас не могу. Был код такого вида, способный "обломаться" на любом этапе:
val1, err := op1()
if err != nil { return nil, err; }
val2, err := val1.op2()
if err != nil { return nil, err; }
val3, err := val2.op3()
if err != nil { return nil, err; }
В добавок, если я забуду обработать ошибку, компилятор мне слова не скажет. И у меня будет NPE паника или сегфолт.
Для сравнения, на любом языке с исключениями:
return op1().op2().op3;
На Rust, который тоже работает через возвращаемые значения
op1()?.op2()?.op3()?
Может ещё конструкторы, ещё и с перегрузкой?
Вот кстати с отсутствием конструкторов как "специальных функций" я полностью согласен. Ловля нетривиальных проблем с порядком инициализации и "самоубийства" в С++ при исключении в конструкторе (а особенно в одном из инициализированных по умолчанию полей) — удовольствие ниже среднего. Перегрузка функций и методов по типу аргумента — дело скорее вкуса. Может быть удобна, но может быть и использована криво.
Обычно если "хочется деструкторов", то это вы ещё не отвыкли от предыдущих языков
…
Defer совершенно другую задачу решает.
Таки да, я привык, что если я открою какой-то ресурс и никуда его не дену — он сам помрёт без моего участия, в конце области видимости. По поводу же defer — а какую в таком случае задачу он решает? Разве не очистку в конце блока? Впрочем, меня вполне устроило бы предупреждение от компилятора, если я забываю почистить ресурс.
в Го любая не-встроенная структура данных будет построена на interface{}.
Это не так. Есть масса примеров библиотек с generic-структурами, в которых используются специализированные интерфейсы (вроде Item, у которого есть один метод Less — для сравнения значений). Вы просто определеяете для своего типа этот метод и передаёте его в функции для этой структуры данных как интерфейс (не interface{})
Опять тайп-касты. В Java такая ситуация была до 1.5, после чего дженерики, хоть и урезанные, таки ввели.
В Джаве 1.5 тайп-касты же всё равно происходят, но автоматически, неявно, насколько я понимаю. Тоесть, понятно, что в паре с type erasure это выглядит безопасно, но если мы говорим о скорости исполнения, то ситуация такая же. Я не знаю насколько просто в Java померять потерю в скорости от этих кастов, но в Go это легко сделать и на небольших интерфейсах (большая часть интерфейсов в Go содержит 1-3 метода) это около 2 наносекунд.
если я забуду обработать ошибку, компилятор мне слова не скажет. И у меня будет NPE паника или сегфолт.
меня вполне устроило бы предупреждение от компилятора, если я забываю почистить ресурс.
Мне кажется, вы сильно переоцениваете роль компилятора :) С какой стати у вас должен быть сегфолт, если вы не проверили ошибку? Для компилятора это валидная ситуация — например вы конвертируете строку в число, и эти строки вы захардкодили, и вообще это одноразовый скрипт за 5 минут — в такой ситуации вполне допустимо не проверить ошибку, никакой паники или сегфолта тут не будет. Тоже самое с "почистить ресурс" — смотря что вы под этим подразумеваете. Память сборщик мусора почистит, понятное дело, но defer используется для того, чтобы выполнить какие-то действия при выходе из скопа (это не обязательно означает "конец жизни ресурса" — это конец скопа функции). Вы можете в дефере вывести что-то в лог, или запустить горутину, разлочить мьютекс или вообще что угодно сделать.
вроде Item, у которого есть один метод Less — для сравнения значений
Но чтобы получить реальный тип, который лежит в структуре — извольте тайп каст. Я говорю именно об этой ситуации.
В Джаве 1.5 тайп-касты же всё равно происходят, но автоматически, неявно, насколько я понимаю.
Да, происходят. На это ругается множество людей с самого выхода 1.5
Мне кажется, вы сильно переоцениваете роль компилятора :) С какой стати у вас должен быть сегфолт, если вы не проверили ошибку?
Как бы
val, err := someOp()
// здесь забыли вставить if err != nil { ... }, компилятор промолчит
val.field.fieldOp() // привет паника - или сегфолт, уже не помню
Память сборщик мусора почистит, понятное дело, но defer используется для того, чтобы выполнить какие-то действия при выходе из скопа
Кроме памяти есть ещё множество типов ресурсов. Самые простые — файлы и сокеты. И для многих из них хотелось бы точно знать, что они будут закрыты при выходе из скопа. Проблема даже не в необходимости явной инструкции для такого закрытия. Проблема в том, что компилятор в этом не желает помогать.
val, err := someOp()
// здесь забыли вставить if err != nil {… }, компилятор промолчит
Нет, в этом случае компилятор скажет, что "ты определил переменную err, но нигде не использовал" и остановится. Чтобы проигнорить ошибку, вам придется сознательно написать вместо err
— _
что означает "я сознательно игнорирую это значение, потому что так надо".
Самые простые — файлы и сокеты. И для многих из них хотелось бы точно знать, что они будут закрыты при выходе из скопа.
И чем при выходе из скопа не устраивают defer тогда? Если нужно какие-то дескрипторы железно позакрывать, не зная скопа жизни переменной, то для этого пользуют финалайзеры.
Чтобы проигнорить ошибку, вам придется сознательно написать вместо err — _ что означает "я сознательно игнорирую это значение, потому что так надо".
Окей, тут согласен. Давно не писал, возможно так и было. Впрочем, вопрос boilerplate остаётся, хоть и не так остро.
И чем при выходе из скопа не устраивают defer тогда?
Всем устраивает. Кроме того, что последний раз, когда я имел дело с Go, компилятор никак не предупреждал, что я его пропустил. Или ситуация поменялась, и я напрасно ною?
Кроме того, что последний раз, когда я имел дело с Go, компилятор никак не предупреждал, что я его пропустил. Или ситуация поменялась, и я напрасно ною?
Поймите, компилятор не может знать, обязателен ли код, который вы выполняете в defer или нет. Точнее, он всегда не обязателен. Defer это лишь синтаксически сахар, для выполнения какого-либо кода при выходе из функции, ничего более.
К сожалению, вы не совсем понимаете мой вопрос. Я упоминаю defer только как механизм для гарантированного закрытия, не более. Я не говорю, что он обязателен для чего-либо. Мой вопрос хотя бы о минимальной ситуации, когда переменная, содержащая неуправляемый ресурс, была создана (не передана снаружи) внутри функции, не "ушла наружу" и при этом не была корректно закрыта. По моему опыту, такая ситуация довольно часто встречается в реальном коде.
Нет, в этом случае компилятор скажет, что "ты определил переменную err, но нигде не использовал" и остановится.
Даже вот в таком случае?
val1, err := foo()
if err != nil { return nil, err; }
val2, err := bar(val1)
val2.field.fieldOp()
В добавок, если я забуду обработать ошибку, компилятор мне слова не скажет.
Полно статических анализаторов, которые нереально дополняют компилятор. Включая и этот момент — посмотрите https://github.com/alecthomas/gometalinter
И что в этом случае происходит с хваленой скоростью сборки?
Ну, запуск 20-30 линтеров требует времени. Но никто не заставляет запускать их на каждый коммит, так что обычный цикл код-тесты-… работает в соответствии с хвалёной скоростью сборки. А вот когда открывается PR и тесты запускаются в CI — там можно и gometalinter прогнать. Кроме того, можно управлять тем, какие линтеры будут запускаться, и ограничиться теми, которые отрабатывают быстро (у gometalinter есть ключик --fast
специально для этого случая).
Деструкторы? Для каких задач? Если освобождение ресурсов, то финализаторы отлично справляются. Опробовано на конвертации изображений в highload.
То есть по другим трём пунктам вопросов нет? :)
Л — логика…
Я ответил только то, что я ответил.
Прочитав ваш комментарий, мне показалось, что вы немного используете язык, но возможно я ошибаюсь. Поэтому спросил, для каких задач вам захотелось деструкторов и использовали ли вы финализаторы.
Прочее не вижу смысла комментировать — много уже говорено на этот счет в разных дискуссиях.
Л — логика…
Я вам наступил на любимый мозоль, что вы мне аж по карме прошлись?
Поэтому спросил, для каких задач вам захотелось деструкторов и использовали ли вы финализаторы.
Боже, ну почему все так вцепились в слово "деструкторы"? Я уже в нескольких ветках написал, что было бы приятно иметь хотя бы ворнинг "здесь может утечь ресурс, поставь defer".
приятно иметь хотя бы ворнинг "здесь может утечь ресурс, поставь defer".
В Go нет ворнингов :) И с каким образом компилятор должен угадывать, что является "утечкой ресурса". а что нет? Теоретически для определенных кейсов такое могут делать статические анализаторы, но лишь для определенного круга.
У меня нет статей — я не могу пройтись вам по карме. Вы странный. Пожалуй, не буду кормить.
Ага, для вас любой, кто что-то пишет или переводит про Go — воинствующий фанатичный тролль.
Ну, не совсем так. Не знаю про ZurgInq, но Вам реально не хватает сдержанности (исключительно на мой вкус, безусловно), что вполне может отворачивать от Go часть интересующихся. И это при том, что мне как раз очень нравятся и Go и Ваши статьи. Так что в словах ZurgInq доля истины есть. Если хотите, попробуйте просто не отвечать на комментарии троллей и хейтеров, либо отвечать на них только по сути, чисто технически — это будет выглядеть намного более адекватно.
Вы, в целом, конечно, правы — на них не стоит вообще реагировать, но если уж мои редкие перепалки с отдельными неадекватами могут "отворачивать от Go" (что, конечно, сомнительно), то комментарии таких хейтеров, которые рассказывают другим абсолютную чушь, которую сами себе придумали, могут "отворачивать от Go" гораздо сильнее.
Ну и да, Хабр это уже практически единственный ресурс, где я пересекаюсь с русскоязычным сообществом, и эта пропасть между уровнем общения в англо- и испаноязычной среде и русскоязычной меня каждый раз по новой удивляет. На один и тот же материал на английском я получу 20 благодарностей, 5 вежливых вопросов и 2 аккуратных приватных замечаний об опечатке, на русском — кучу минусов от хейтеров, оскорбительных замечаний об опечатках, оскорбления Go, его создателей и всего сообщества и 0 благодарностей. Если кто и поблагодарит, то его минусуют. Веселая, вобщем, тут мезозойская тусовка. :)
Это может быть частично связано с тем, что у них в культуре вообще не особо принято честно говорить гадости. Там где у них "очень, очень хорошо, правда можно вот тут совсем немного улучшить" у нас "это полное говно, придётся всё переделывать чтобы оно хоть как-то заработало". И мне наш вариант нравится больше, включая жаркие перепалки на хабре.
не особо принято честно говорить гадости.
Смеюсь вголос. Там вообще не принято говорить гадости. Принято говорить приятности, благодарить и поддерживать других.
И мне наш вариант нравится больше, включая жаркие перепалки на хабре.
Понятно, как и большинству остальных тут. Мне нет. Опыт работы в русскоязычных коллективах, где одни девелоперы буквально гнобили других, рассказывали девушкам-девелоперам, что их место на кухне, и мат чуть ли не в код-ревью проскакивал, выработал у меня аллергию на такое "общение".
Ну, одно дело перегибы, а другое когда люди тупо не получают адекватной обратной связи. Суть ведь не в том, чтобы сказать грубо, а в том, чтобы сказать честно! Потому что если ты объективно плохо делаешь свою работу а тебе говорят "отлично, хотя можно сделать чуть лучше" — это не стимулирует развитие и не даёт понимания того, насколько всё на самом деле плохо. Потому что всегда "можно сделать чуть лучше", по определению, так что это ничего не говорит о том, сейчас было сделано хорошо или плохо, и насколько хорошо или плохо.
Мы сейчас о разном. Я говорил о том, что банальное отсутствие умения говорить спасибо, некоего baseline-а вежливости и культура подпитывающаяя "често говорить гадости" выглядит очень дико со стороны.
Спасибо — стрелка вверх. Не интересно — иди дальше. Бесполезный/вводящий в заблуждение материал/совсем велосипед — стрелка вниз.
В комментарии технические вопросы-ответы по делу без перехода на личности вообще.
Думаю если бы так было всегда — было бы идеально…
Смеюсь вголос. Там вообще не принято говорить гадости. Принято говорить приятности, благодарить и поддерживать других.А что насчет думать?
Ага, для вас любой, кто что-то пишет или переводит про Go — воинствующий фанатичный тролль.Всего-то навсего нужно честно признавать, что у Go есть ряд недостатков и серебрянной пули не существует. Так-то у языка есть сильные стороны, но постоянное бормотание «это не баг а фича» касательно всего подряд портит все впечатление…
Я понимаю, о чём вы. У Go есть ряд недостатков, но это не те недостатки, о которых пишут хейтеры.
Go стоит особняком от большинства современных языков, и многие решения были приняты сознательно, по совершенно конкретным причинам и с совершенно конкретной целью — решить проблему запутанного кода, который пишут программисты вне зависимости от квалификации. Google нанимает тысячи сильнейших программистов планеты, и всё таки языки, которые использовались до Go приводили к стандартным проблемам работы в больших кодовых базах.
Это не стандартный подход, но эти решения были приняты абсолютно сознательно. И это действительно "фича" (сознательно приняли решение), а не "баг" (недосмотрели, не хватило ума понять, упустили из внимания).
Поэтому извините, но когда набегают "специалисты", рассказывая, про "единственно правильное ООП — это классы", "единственно правильная обработка ошибок — это эксепшены" или "единственно правильное программирования — это дженерики", и начинают доказывать, что Go полон недостатков и написан тупыми людьми, которые столько всего пропустили — то это фанатичное хейтерство, без абсолютно никакой ценности.
И когда таким людям пытаются объяснить, что нет — это не "проглядели" и не "недостаток", — это сознательный уход в другое русло развития языков программирования, отличное от того, к чему вы привыкли и считаете абсолютным счастьем — начинаются вот эти стоны про "фанбоев", "стокгольмский синдром" и "честно признайтесь".
Вы можете принимать это или нет, но Go сделали таким. Если вам не подходит такой язык — просто не используйте его. Но воинственно доказывать гоферам, что они не понимают, какими должны быть "правильные" языки программирования — это глупость. Это как приехать в Мадрид и доказывать прохожим, как нужно правильно разговаривать на русском, а не на вот этом вот непонятном, полном багов языке.
Ну а про серебрянную пулю это вообще смешно, конечно. Обычно этот аргумент люди используют, когда совсем уже не осталось в чём обвинить собеседника.
Поэтому извините, но когда набегают «специалисты», рассказывая, про «единственно правильное ООП — это классы», «единственно правильная обработка ошибок — это эксепшены» или «единственно правильное программирования — это дженерики»Про классы я не слышал если честно :) Но ведь ООП в Go вообще нету :)
Про дженерики вы передергиваете. Сами по себе они никому не упали. А вот невозможность нормально написать реюзабельную, скажем, функцию для вставки элемента в начало слайса — это проблема. Я знаю как делается в Go, но чести языку это не делает (постоянная копипаста тривиальных кусков кода).
«единственно правильная обработка ошибок — это эксепшены»А почему в Go нету эксепшнов? Может…
google.github.io/styleguide/cppguide.html#Exceptions
Если кратенько:
On their face, the benefits of using exceptions outweigh the costs, especially in new projects. However, for existing code… because most existing C++ code at Google is not prepared to deal with exceptions...А теперь вспомним кого планировалось переманивать на Go… Совпадение? :)
Но ведь ООП в Go вообще нету :)
В каком месте его там нету? :) Или вы тоже считаете, что ООП это классы?
Про дженерики вы передергиваете.
Отнюдь.
Если кратенько:
Забавно, но я этот же документ использовал выше, чтобы показать, что в Google отказались от эксепшенов. Некоторые из минусов, перечисленные там, имели гораздо больший вес, чем в С++.
В каком месте его там нету? :) Или вы тоже считаете, что ООП это классы?Покажите мне в Go наследование? Желательно еще научите как делать виртуальные методы ;)
Так то я не пытаюсь сказать, что отсутсвие в Go ООП делает это язык плохим, скорее наоборот.
Забавно, но я этот же документ использовал выше, чтобы показать, что в Google отказались от эксепшенов.Отказались в C++ (судя по гайдлайнам), сказав при этом «ну с исключениями в целом получше… но legacy и все такое». У вас инфа оностительно остальных языков или вы просто придумываете находу?
Некоторые из минусов, перечисленные там, имели гораздо больший вес, чем в С++.Ну вот вы опять в своем духе. Можете укзать конкретные минусы, которые для Go гораздо критичнее, нежели для С++?
Покажите мне в Go наследование?
За что же мне это. С чего вы взяли, что "наследование" есть обязательным элементом ООП?
Так то я не пытаюсь сказать, что отсутсвие в Go ООП делает это язык плохим, скорее наоборот.
В Go ООП реализована другим способом, нежели в класс-ориентированных языках. Это не означает, что в Go ООП нет.
Ну вот вы опять в своем духе. Можете укзать конкретные минусы, которые для Go гораздо критичнее, нежели для С++?
А в чьем духе мне ещё быть? Могу конечно, в каждой такой ветке их указываю:
- в Go приоритет на локальность, простоту и понятность кода.
По вашей же ссылке в списке cons есть два пункта, который очень противоречат этой идеологии. В С++ это, понятное дело, не проблема, там простота и понятность кода никогда не стояла приоритетом, объективно.
Это не означает, что в Go ООП нет.Мне не охота вступать по этой теме в дискусси. Но судя по оффициальной FAQ:
Yes and no. Although Go has types and methods and allows an object-oriented style of programming, there is no type hierarchy…Тут можно долго дискутировать, но лично для меня «yes» никогда не будет равно «yes and no».
в Go приоритет на локальность, простоту и понятность кода.Наверное именно поэтому есть panic/recover? Они то добавляют и простоты, и локальности, и понятности.
Вообще гляньте на тот список еще раз, внимательнее. Там есть пара-тройка пунктов, которые для C++ гораздо критичнее, нежели для Go (или не применимы к Go вообще).
Тут можно долго дискутировать
Незачем. Термин ООП придумал Алан Кей, и у него есть вполне четкие определения.
Наверное именно поэтому есть panic/recover? Они то добавляют и простоты, и локальности, и понятности.
Вы не понимаете, как panic и panic/recover в Go используются, и используете это незнание, чтобы поиронизировать? Ну успехов.
Термин ООП придумал Алан Кей, и у него есть вполне четкие определения.Отлично. Сходу нашлось такое. Там вполне вариации на тему. Вы какого вариана определения придерживаетесь, там где есть «Every object is an instance of a class (which must be an object).» или «Classes are organized into a singly-rooted tree structure, called the inheritance hierarchy.». Возможно вы используете некое иное определение, тогда попрошу его привести, дабы не было разночнений.
Возможно вы используете некое иное определение,
Я рад, что вы начали открывать для себя новые грани ООП. Но рекомендую спорить по теме, когда вы её изучили, а не когда только начали узнавать и нагуглили первый результат. Возможно вам поможет вот эта моя статья понять глубже тему:
https://habrahabr.ru/post/243593/
Ну так поясните, вы же go-гуру, не я.
Что именно вам пояснить? Вы пытались сами разобраться в теме, прежде чем иронизировать? Не поймите меня не правильно, но вот этот паттерн — "сарказм — осознание того, что не понимает темы — требования устроить персональную лекцию в комментариях" мне уже на Хабре очень надоел.
Я рад, что вы начали открывать для себя новые грани ООП. Но рекомендую спорить по теме, когда вы её изучили, а не когда только начали узнавать и нагуглили первый результат.Так вы приведете «определение от Алана Кея» с которым вы согласны? Или цитата
OOP to me means only messaging, local retention and protection and hiding of state-process, and extreme late-binding of all things.и есть необходимое и достаточное определение ООП?
Так получается, когда выходишь за рамки бинарного определения вещей — «нужен/не нужен», «плохо/хорошо» и так далее. Надеюсь у вас с этим проблем нет?На личности переходите (уж который раз). Совсем-совсем кончились аргументы? (вопрос риторический, можете не отвечать).
Давайте по теме.
1) Вы сказал, что Go суть ООП язык.
2) Вы указали, что следуете определению от Кея.
2) Вы дали «определение» ООП по Кею (на самом деле нет).
4) Вы признали, что Go ему не соотвествует.
Тут явно кто-то лишний. Есть несколько вариантов выхода из противоречия (список не исчерпывающий):
1) Вы соглашаетесь что Go не ООП язык.
2) Вы утверждаете, что это не верное определение ООП (можно даже сказать что-то плохое про Кея) и приводите новое;
3) Молча ставите минус моему комментарию.
4) Еще сильнее пытаетесь перейти на личности и оскорбления.
Интересно, какой же вариант вы выберете.
Go: 10 лет и растём дальше