Comments 12
Потрясающая статья: человек в течении многих лет старательно убеждает себя в том, что выбор был правильным и полностью игнорирует проблемы.
Да, подход C++ и Java (довольно старые языки), вызывает проблемы, но и подход Go тоже с сюрпризами. Почему-то, в этих статьях от "больших" специалистов полностью игнорируются такие вещи как Option типы и связанные с этим макросы, которые распространены в других языках типа Haskell, Rust, Kotlin.
Причина такой "избирательности" в банальной старости авторов языка: они не знают ничего за пределами C/C++ похоже.
Точно. И их там таких легион. Им пишут, что они таким решением наоборот провоцируют игнорировать ошибки, т.к. в реальном коде все задолбались писать после каждой строчки ещё три if err != nil и так раз за разом, всего лишь для того, что бы эту ошибку передать на уровень выше, а они в ответ: а исключения зато все игнорируют.
В тех же Java, Kotlin, C# нужно так же чуть ли ни каждую строчку оборачивать в try-catch
Вот пример для Kotlin:
import java.io.IOException
import java.nio.file.Files
import java.nio.file.Paths
fun main() {
val fileName = "/home/data/file.txt"
try {
val lines = Files.readAllLines(Paths.get(fileName))
println(lines)
} catch (e: IOException) {
e.printStackTrace()
}
}
А то, что не обернуто в try-catch, нужно проверить на null. Пример:
companydb = getCustomer(name, inn);
if (companydb == null) {}
Получается в Go много "if err != nil", а в Java/C# много if ( p != null ).
В той же 1с очень редко используется перехват исключений, вся обработка ошибок построена на том, что функция возвращает "неопределенно" (null) в случае ошибки, а вызывающая сторона проверяет, что вернулось.
И проверки на null после каждого вызова функций никого не смущает.
Мне тоже не нравится, что в Go код "замусоривается" проверками ошибок, но прочитав статью, понял, что и механизм исключений не лучше. Получается лучший вариант, это вариант описанный в разделе "Устранение обработки ошибок за счёт устранения ошибок"
Не-не, вы не совсем правильно это поняли. Я не знаю Яву, но предполагаю, что если в примере не обернуть вызов в try, то выполнение функции будет прекращено и исключение пойдет на уровень выше. Мне очень часто именно это и нужно. Т.е. мне не нужно обрабатывать ошибку в этой функции. Мне нужно только сообщить об ошибке туда, откуда эту функцию вызвали, а там тоже нужно только сообщить в вызывающую функцию и так 10 уровней вверх. И только на самом верхнем уровне может быть будет какая-то обработка этой ошибки. А может и не будет. Может просто вернёт какую-нибудь 500 ошибку, да в лог запишет. Но в Go так не получится сделать. В результате, просто что бы прекратить выполнение текущей функции и вернуть управление выше, нужны бесконечные if err != nil.
При чем тут Option? Он не про работу с ошибками, это лишь индикатор того, что функция может вернуть или не вернуть значение. Ладно, предположим имелся в виду паттерн Either.
Наивно считать, что авторы языка «деды» и ничего не знают о ФП подходах. Подход к обработке ошибок в го - это и есть слегка изменённый вариант реализации Either.
Вы спросите: почему бы по умолчанию не использовать знакомый многим Either и не шатать мозги? Монады по-настоящему раскрываются только в случае если используются в связке с другими монадами. Го не может себе этого позволить, так как он не настолько функциональный. Иначе получится уродец, в которого насильно впихнули одну ФП фичу. Как итог авторы выбрали компромисс, на мой взгляд удачный. Отказаться от exception’ов и при этом не скатываться в трушную функциональщину.
Наивно считать, что авторы языка «деды» и ничего не знают о ФП подходах.
Да нет тут никакой наивности. Вспомните как они сделали go get и сравните это с rust, ruby или php. Ну видно же, что люди по-прежнему на autotools сидят. Или реализация Generic, которую выпрашивали миллион лет и которая осталась слегка недоделаной.
Вы спросите: почему бы по умолчанию не использовать знакомый многим Either и не шатать мозги? Монады по-настоящему раскрываются только в случае если используются в связке с другими монадами.
Не думаю, что надо весь ФП тянуть. Например Rust и без этого справляется. Точно нужны полноценные макросы и нормальная система типов. Не как в Haskell, конечно, но постараться можно было.
Вот только натыкивание if err != nil { return err }
- это по сути и есть размотка стека, как при исключениях, только вручную. То что раньше делал компилятор, теперь стало модно делать копипастным кодом.
Исключения нельзя игнорировать без того, что бы программа не упала. Ошибки в go – можно. Что и приходится иногда делать для читаемости.
То, что ошибки в го явные – это очень хорошо. Всё остальное плохо, и в этом проблема.
Пример с `errWriter` типичный паттерн в го, чтобы спасти основную логику от исчезновения в тумане из `if err != nil { return err }`. Но, мало того, что он делает обработку ошибок неявной, очень ограниченно применим, и часто всё равно малоэффективен, он ещё и приносит проблему, которая на первый взгляд не очень заметна. В процессе эволюции программы, подготовка данных для errWriter.Write
можеть стать дороже, и тогда мы хотим выходить по первой же ошибке, а не впустую жрать процессор. Надо не забыть это сделать и отказаться от errWriter
.
Не знаю, кто вам минус поставил в комментарий, т.к. пишите вы правильно. Нейтрализовал своим плюсом. Пример с errWriter - это даже не паттерн. Это костыль. И я к такому же костылю в конце концов пришел, ну потому, что а как ещё. Функции невозможно замусориваются этим дублированием обработки ошибок. Но я отказываюсь называть это паттерном. Хак это, грязный хак, от бедности.
Исключения нельзя игнорировать без того, что бы программа не упала.
panic
нельзя игнорировать без того, что бы программа не упала.
Практика Go — Обработка ошибок (2 часть)