- комментарий 0:
From: kmeaw
Date: 2022-11-14 09:42:15Z
* no "this" in methods
Кажется, не получилось. Или я не понимаю, в чём принципиальное отличие
receiver от this.
* no const or other type annotations
* no exceptions
Очень спорные решения, не уверен, что от этого становится проще и лучше.
А вот всё остальное - очень правильные решения, которые резко снижают
когнитивную нагрузку при написании (и чтении!) кода без ущерба для
производительности и выразительности.
Есть ещё одна вещь, которой не хватает - context уровня языка, а не
стандартной библиотеки.
- комментарий 1:
From: Sergey Matveev
Date: 2022-11-16 09:13:55Z
*** kmeaw [2022-11-14 12:40]:
>Кажется, не получилось. Или я не понимаю, в чём принципиальное отличие
>receiver от this.
Согласен -- тоже не вижу отличия.
>* no const or other type annotations
Я вот в Си на практике конечно вижу и использую эти штуки, но в голове
думая про то, что это поможет компилятору. Но если бы не это, то пока не
особо понимаю чем мне бы это бы помогло. Просто константы, кроме как для
подсказки компилятору (для оптимизаций), я бы использовал аналогично как
в Go -- где-то на верхнем уровне, просто буквально чтобы заменить
константные строчки или числа. А это в Go только на таком уровне и имеется.
>* no exceptions
Ну тут я всеми руками за это. С exceptions знаком только по Python языку
по серьёзной практике, но я всецело за if err != nil код. Вчера говорил
с одним знакомым, который в своё время на C и C++ писал. И он спросил
про Go (что это и всё в таком духе). И именно на отсутствие exception-ов
он сказал что для него это прям огромный плюс.
>Есть ещё одна вещь, которой не хватает - context уровня языка, а не
>стандартной библиотеки.
Эх, мне стыдно, но я до сих пор так и нигде его не использовал. Ну кроме
как подставлял какую-то пустышку если библиотека требовала его использование.
Читал и в Go-блоге про use-case-ы, но что-то пока так и не возникали лично
у меня они.
- комментарий 2:
From: kmeaw
Date: 2022-11-16 12:39:00Z
> >* no const or other type annotations
> но в голове думая про то, что это поможет компилятору
Это ещё и защищает от ошибок программиста. memcpy(dst, src, n) не даст
перепутать src и dst в большинстве случаев, потому что src - указатель
на константу. А в C++ ещё и const-методы бывают.
По сигнатуре сразу видно, изменяет вызов данные или нет.
> я всецело за if err != nil код
Я вижу три проблемы.
Первая - замусоривание кода одинаковыми строчками вида if err != nil {
return nil, err }. От этого при чтении замыливается глаз, и легко
пропустить if err == nil в этом потоке.
Вторая - язык не делает ничего, чтобы вместо
if err != nil {
return nil, err
}
заставить меня написать
if err != nil {
return nil, fmt.Errorf("cannot save config: %w", err)
}
Поэтому почти все используют первый вариант. Ведь в большинстве случаев
нужно просто выполнить какую-то последовательность действий, а если
любое из них не удалось, то просто сломаться.
И если такой код становится библиотечным, то ко мне прилетает
необёрнутая ошибка типа io.ErrUnexpectedEOF, которая никак не подскажет
пользователю, что пошло не так. С исключениями такого не будет - если
вызываемый код не умеет обрабатывать какую-то исключительную ситуацию,
то исключение рано или поздно долетит до catch-all обработчика, у
которого будет вся контекстуальная информация, включая стек вызовов -
даже если просто его распечатать, это очень поможет пользователю понять,
что пошло не так.
Третья - механизмов работы с ошибками два, есть ещё panic. Когда первый
раз читаешь документацию, то кажется, что всё просто и очевидно. Но
когда программа развивается, становится всё сложнее, и, наконец,
превращается в библиотеку, которая используется другой программой, а
прежний main - в метод Init(), то приходится рефакторить: все log.Fatalf
заменять на return fmt.Errorf, добавлять второе return value для ошибок
и протаскивать их наверх по стеку.
Исключения также могут оказаться более эффективными по
производительности, чем десятки проверок на err != nil. Хотя и не любят
их по той же причине - они могут коварно скрыть увеличение времени
исполнения в плохом сценарии, из-за чего тяжело давать гарантии в
системах реального времени.
- комментарий 3:
From: Sergey Matveev
Date: 2022-11-16 13:05:24Z
*** kmeaw [2022-11-16 15:38]:
>По сигнатуре сразу видно, изменяет вызов данные или нет.
Да, про это забыл. Соглашусь.
>Первая - замусоривание кода одинаковыми строчками вида if err != nil {
>return nil, err }. От этого при чтении замыливается глаз, и легко
>пропустить if err == nil в этом потоке.
По моему это субъективно. Можно в редакторе сделать подсветку err != nil
каким-то другим цветом. Я не забуду как в SQL сложно бывает читать
конструкции, где куча одинаковых слов, иногда отличающихся на одну букву
(приходится подсветкой помогать (f4a041639f520162b246b48ef248e7116b4a5919).
>if err != nil {
> return nil, fmt.Errorf("cannot save config: %w", err)
>}
Но так ведь такая штука и далеко не в первой версии Go появилась
(9425ee6ed097e608340912647fa97b817d093cbd). Типа и так уже тьма кода
написана и уже обратную совместимость приходится блюсти -- поздно
подталкивать к более корректным ошибкам.
Всему должна быть мера. Вот в Go нельзя сказать что эта штука реализует
такой то интерфейс, хотя бы ради того, чтобы помочь читающему и
намекнуть что хотел сделать автор. А Java заставляет. И многие считают
её избыточность кода проблемой и неудобством. Это я к тому, что если
переборщить с "подсказками" (и вынужденными человекочитаемыми
сообщениями) для читающего программиста, то это создаст не стоящий
геморрой пишущему.
Но Go хотя бы подталкивает проверять ошибки. Хороший разработчик и
исключения будет проверять и ошибки. А плохой, уж что что, но на
исключения будет забивать, так как ничто не намекает на них, не
заставляет явно игнорировать их, если ему действительно они не
интересны. Исключения требуют уже более высокий уровень аккуратности,
квалификации, понимания, как мне кажется.
>превращается в библиотеку, которая используется другой программой, а
>прежний main - в метод Init(), то приходится рефакторить: все log.Fatalf
>заменять на return fmt.Errorf, добавлять второе return value для ошибок
>и протаскивать их наверх по стеку.
Всё так. Но я всё равно тут не вижу проблемы. Если программист опытный,
то или сразу будет писать с мыслью о том, что это может стать
библиотекой, или писать попроще, менее парясь, быстрее, если понимает
что не надо ничего усложнять, ибо библиотекой не станет. Неопытный --
получит опыт. Я сужу по своему опыту с Python и понимаю о чём тут речь и
что, безусловно, они много где могут упростить жизнь. Но они требуют
большей аккуратности/опытности/понимания/квалификации от разработчика,
что означает что (грамотное) использование языка усложняется.
>системах реального времени.
Но если речь про Go, то разве о них вообще стоит говорить, раз в нём
garbage collector? Мне кажется это просто не его вотчина чтобы об этом
вообще думать.
- комментарий 4:
From: kmeaw
Date: 2022-11-17 19:42:32Z
> Хороший разработчик и исключения будет проверять и ошибки.
Безусловно, на Go можно писать хорошо и избегать всех описанных мной
проблем. Но если я буду наугад брать чужие библиотеки, которые по
описанию кажутся мне полезными, то далеко не каждая из них будет
написана хорошо, особенно если есть транзитивные зависимости.
Фундаментальная разница с исключениями в том, что для того, чтобы
получить хороший результат с ошибками, надо сознательно написать код,
который внутрь ошибки затащит контекстуальную информацию. А для
аналогичного результата с исключениями не надо вообще никакого кода
писать.
func f() (*Result, error) {
r1, err := g1()
if err != nil {
return nil, fmt.Errorf("cannot g1: %w", err)
}
r2, err := g2(r1)
if err != nil {
return nil, fmt.Errorf("cannot g2: %w", err)
}
return r2, nil
}
func main() {
res, err := f()
if err != nil {
log.Fatalf("cannot f(): %s", err)
}
}
может написать "cannot f(): cannot g2: unexpected EOF"
против (на несуществующем диалекте go-with-exceptions)
func f() *Result {
return g2(g1())
}
func main() {
res := f()
}
может написать:
uncaught exception: unexpected EOF
goroutine 1 [running]:
main.g2(0xfee1dead)
/go/src/foo/g2.go:11
main.main()
/go/src/foo/main.go:22
И сразу понятна причина.
- комментарий 5:
From: Sergey Matveev
Date: 2022-11-17 19:56:31Z
*** kmeaw [2022-11-17 22:42]:
>Но если я буду наугад брать чужие библиотеки, которые по
>описанию кажутся мне полезными, то далеко не каждая из них будет
>написана хорошо, особенно если есть транзитивные зависимости.
Согласен.
>А для аналогичного результата с исключениями не надо вообще никакого кода писать.
Тоже верно. Но наличие исключений, в то же время, заставит всех
потребителей библиотеки лениться и не проверять ошибки вовсе. И
лично для меня это куда более серьёзная проблема.
Код библиотеки пишется, грубо говоря, один раз. А используют библиотеку,
как правило, множество других потребителей. Один раз хорошо написать
"обёрнутые" ошибки -- вот и автоматом у всех будет хороший понятный
вывод. А вот заставить всех потребителей библиотеки учитывать
исключения, возможно регулярно появляющиеся и изменяющиеся -- куда более
сложная задача. А форсированное наличие ошибок заставляет или явно их
избегать, или хотя бы не совсем игнорировать и хоть как-то обрабатывать.
Я просто не вижу как можно "совместить" два кардинально разных подхода
(возврат error или выброс исключения). Либо то, либо другое. На одной
чаше весов упрощение жизни писателю библиотеки (исключения). А на другой
дисциплинирование и принуждение проверять ошибки потребителям библиотеки.
На мой взгляд, последнее куда сильнее перевешивает. В будущем заменить
ошибки на более понятные можно и прозрачно и без потери обратной
совместимости.
- комментарий 6:
From: Sergey Matveev
Date: 2022-11-17 20:00:39Z
Плюс мне кажется что авторы библиотек априори всё же немного более
квалифицированы, чем те, кто просто на основе рецептов со stackoverflow
что-то клепают. Сделать "package main" программу -- это одно. А сделать
нечто что может использоваться как библиотека -- уже чуть более сложнее
сделать, автоматом означая и чуть-чуть более продвинутый уровень
разработчика. Поэтому помогать и подталкивать к нужным действиям
(проверять и проверять ошибки) нужно наименее "квалифицированных"/опытных.
--
Sergey Matveev (http://www.stargrave.org/)
OpenPGP: 12AD 3268 9C66 0D42 6967 FD75 CB82 0563 2107 AD8A